Skip to content

Commit

Permalink
create new export() method in PageObjectRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
BurnzZ committed Mar 28, 2022
1 parent 8e805a7 commit 5eed2dc
Show file tree
Hide file tree
Showing 21 changed files with 203 additions and 6 deletions.
43 changes: 37 additions & 6 deletions docs/intro/pop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ by simply importing them:
page = FurnitureProductPage(response)
item = page.to_item()
.. _`pop-recommended-requirements`:

Recommended Requirements
~~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -143,13 +145,15 @@ inside of ``ecommerce-page-objects/ecommerce_page_objects/__init__.py``:

.. code-block:: python
from web_poet import default_registry, consume_modules
from web_poet import default_registry
# This allows all of the OverrideRules declared inside the package
# using @handle_urls to be properly discovered and loaded.
consume_modules(__package__)
REGISTRY = default_registry.export(__package__)
REGISTRY = default_registry
The :meth:`~.PageObjectRegistry.export` method returns a new instance of
:class:`~.PageObjectRegistry` which contains only the :class:`~.OverrideRule`
from the given package. This means that if there are other **POPs** using the
recommended ``default_registry``, any :class:`~.OverrideRule` that are not part
of the packages are not included.

This allows any developer using a **POP** to easily access all of the
:class:`~.OverrideRule` using the convention of accessing it via the
Expand All @@ -173,8 +177,35 @@ This allows any developer using a **POP** to easily access all of the

However, it is **recommended** to use the instances of
:class:`~.PageObjectRegistry` to leverage the validation logic for its
contents.
contents, as well as its other functionalities.

Lastly, when trying to repackage multiple **POPs** into a single unifying **POP**
which contains all of the :class:`~.OverrideRule`, it can easily be packaged
as:

.. code-block:: python
from web_poet import PageObjectRegistry
import base_A_package
import base_B_package
# If on Python 3.9+
combined_rules = base_A_package.REGISTRY | base_B_package.REGISTRY
# If on lower Python versions
combined_rules = {**base_A_package.REGISTRY, **base_B_package.REGISTRY}
REGISTRY = PageObjectRegistry(combined_rules)
Note that you can also opt to use only a subset of the :class:`~.OverrideRule`
by selecting the specific ones in ``combined_rules`` before creating a new
:class:`~.PageObjectRegistry` instance. An **inclusion** rule is preferred than
an **exclusion** rule (see **Tip #4** in the :ref:`conventions-and-best-practices`).
You can use :meth:`~.PageObjectRegistry.search_overrides` when selecting the
rules.

.. _`conventions-and-best-practices`:

Conventions and Best Practices
------------------------------
Expand Down
60 changes: 60 additions & 0 deletions tests/test_pop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""This tests the expected behavior of importing multiple different POPs such
that they don't leak the OverrideRules from other POPs that use the same
``default_registry``.
Packacking and exporting a given POP should be resilient from such cases.
In particular, this tests the :meth:`PageObjectRegistry.export` functionality.
"""

def test_base_A():
from tests_pop import base_A_package

reg = base_A_package.REGISTRY

assert len(reg) == 2
assert base_A_package.site_1.A_Site1 in reg
assert base_A_package.site_2.A_Site2 in reg


def test_base_B():
from tests_pop import base_B_package

reg = base_B_package.REGISTRY

assert len(reg) == 2
assert base_B_package.site_2.B_Site2 in reg
assert base_B_package.site_3.B_Site3 in reg


def test_improved_A():
from tests_pop import improved_A_package, base_A_package

reg = improved_A_package.REGISTRY

assert len(reg) == 3
assert improved_A_package.site_1.A_Improved_Site1 in reg
assert improved_A_package.base_A_package.site_1.A_Site1 in reg
assert improved_A_package.base_A_package.site_2.A_Site2 in reg


def test_combine_A_B():
from tests_pop import combine_A_B_package, base_A_package, base_B_package

reg = combine_A_B_package.REGISTRY

assert len(reg) == 4
assert combine_A_B_package.base_A_package.site_1.A_Site1 in reg
assert combine_A_B_package.base_A_package.site_2.A_Site2 in reg
assert combine_A_B_package.base_B_package.site_2.B_Site2 in reg
assert combine_A_B_package.base_B_package.site_3.B_Site3 in reg


def test_combine_A_B_subset():
from tests_pop import combine_A_B_subset_package, improved_A_package, base_B_package

reg = combine_A_B_subset_package.REGISTRY

assert len(reg) == 2
assert combine_A_B_subset_package.improved_A_package.site_1.A_Improved_Site1 in reg
assert combine_A_B_subset_package.base_B_package.site_3.B_Site3 in reg
Empty file added tests_pop/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions tests_pop/base_A_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from web_poet import default_registry

REGISTRY = default_registry.export(__package__)
2 changes: 2 additions & 0 deletions tests_pop/base_A_package/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class BasePage:
...
8 changes: 8 additions & 0 deletions tests_pop/base_A_package/site_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from web_poet import handle_urls

from .base import BasePage


@handle_urls("site_1.com", overrides=BasePage)
class A_Site1:
...
8 changes: 8 additions & 0 deletions tests_pop/base_A_package/site_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from web_poet import handle_urls

from .base import BasePage


@handle_urls("site_2.com", overrides=BasePage)
class A_Site2:
...
3 changes: 3 additions & 0 deletions tests_pop/base_B_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from web_poet import default_registry

REGISTRY = default_registry.export(__package__)
2 changes: 2 additions & 0 deletions tests_pop/base_B_package/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class BasePage:
...
8 changes: 8 additions & 0 deletions tests_pop/base_B_package/site_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from web_poet import handle_urls

from .base import BasePage


@handle_urls("site_2.com", overrides=BasePage)
class B_Site2:
...
8 changes: 8 additions & 0 deletions tests_pop/base_B_package/site_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from web_poet import handle_urls

from .base import BasePage


@handle_urls("site_3.com", overrides=BasePage)
class B_Site3:
...
9 changes: 9 additions & 0 deletions tests_pop/combine_A_B_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""This POP simply wants to repackage POP "A" and "B" into one unifying package."""

from web_poet import PageObjectRegistry

from . import base_A_package
from . import base_B_package

combined = {**base_A_package.REGISTRY, **base_B_package.REGISTRY}
REGISTRY = PageObjectRegistry(combined)
1 change: 1 addition & 0 deletions tests_pop/combine_A_B_package/base_A_package
1 change: 1 addition & 0 deletions tests_pop/combine_A_B_package/base_B_package
16 changes: 16 additions & 0 deletions tests_pop/combine_A_B_subset_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""This POP simply wants to repackage POP "A" and "B" into one unifying package."""

from web_poet import PageObjectRegistry

from . import improved_A_package
from . import base_B_package

rules_A_improved = improved_A_package.REGISTRY.search_overrides(
use=improved_A_package.site_1.A_Improved_Site1 # type:ignore
)
rules_B = base_B_package.REGISTRY.search_overrides(
use=base_B_package.site_3.B_Site3 # type: ignore
)

combined_rules = rules_A_improved + rules_B
REGISTRY = PageObjectRegistry.from_override_rules(combined_rules)
1 change: 1 addition & 0 deletions tests_pop/combine_A_B_subset_package/base_B_package
1 change: 1 addition & 0 deletions tests_pop/combine_A_B_subset_package/improved_A_package
3 changes: 3 additions & 0 deletions tests_pop/improved_A_package/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from web_poet import default_registry

REGISTRY = default_registry.export(__package__)
1 change: 1 addition & 0 deletions tests_pop/improved_A_package/base_A_package
9 changes: 9 additions & 0 deletions tests_pop/improved_A_package/site_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from web_poet import handle_urls

from .base_A_package.base import BasePage
from .base_A_package.site_1 import A_Site1


@handle_urls("site_1.com", overrides=BasePage)
class A_Improved_Site1(A_Site1):
... # some improvements here after subclassing the original one.
22 changes: 22 additions & 0 deletions web_poet/overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import importlib.util
import warnings
import pkgutil
from copy import deepcopy
from collections import deque
from dataclasses import dataclass, field
from operator import attrgetter
Expand Down Expand Up @@ -121,6 +122,27 @@ def from_override_rules(
"""
return cls({rule.use: rule for rule in rules})

def export(self, package: str) -> PageObjectRegistry:
"""Returns a new :class:`~.PageObjectRegistry` instance containing
:class:`~.OverrideRule` which are only found in the provided **package**.
This is used in cases wherein all of the :class:`~.OverrideRule` in a
given **Page Object Project (POP)** should be placed inside a dedicated
registry for packaging. See :ref:`POP Recommended Requirements
<pop-recommended-requirements>` for more info about this.
Note that the :func:`~.consume_modules` will be called on the said
**package** which adds any undiscovered :class:`~.OverrideRule` to the
original :class:`~.PageObjectRegistry` instance. There's no need to worry
about unrelated rules from being added since it wouldn't happen if the
given registry's ``@handle_urls`` annotation wasn't the one used.
"""
backup_state = deepcopy(self)
self.clear()
rules = self.get_overrides(consume=package)
self = backup_state
return self.from_override_rules(rules)

def handle_urls(
self,
include: Strings,
Expand Down

0 comments on commit 5eed2dc

Please sign in to comment.