Skip to content

Commit 4dff15a

Browse files
committed
create new export() method in PageObjectRegistry
1 parent e256b96 commit 4dff15a

File tree

21 files changed

+224
-18
lines changed

21 files changed

+224
-18
lines changed

docs/intro/pop.rst

+58-18
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ by simply importing them:
118118
page = FurnitureProductPage(response)
119119
item = page.to_item()
120120
121+
.. _`pop-recommended-requirements`:
122+
121123
Recommended Requirements
122124
~~~~~~~~~~~~~~~~~~~~~~~~
123125

@@ -141,45 +143,83 @@ all :class:`~.OverrideRule` by writing the following code inside of
141143

142144
.. code-block:: python
143145
144-
from web_poet import consume_modules
146+
from web_poet import default_registry
147+
148+
REGISTRY = default_registry.export(__package__)
145149
146-
# This allows all of the OverrideRules declared inside the package
147-
# using @handle_urls to be properly discovered and loaded.
148-
consume_modules(__package__)
150+
This does two things:
149151

150-
.. note::
152+
1. The :meth:`~.PageObjectRegistry.export` method returns a new instance of
153+
:class:`~.PageObjectRegistry` which contains only the :class:`~.OverrideRule`
154+
from the given package. This means that if there are other **POPs** using the
155+
recommended ``default_registry``, any :class:`~.OverrideRule` that are not part
156+
of the package are not included.
151157

152-
Remember, code in Python like annotations are only read and executed
158+
2. Remember that code in Python like annotations are only read and executed
153159
when the module it belongs to is imported. Thus, in order for all the
154160
``@handle_urls`` annotation to properly reflect its data, they need to
155-
be imported recursively via :func:`~.consume_modules`.
161+
be imported recursively via :func:`~.consume_modules`. Fortunately,
162+
:meth:`~.PageObjectRegistry.export` already consumes the ``__package__``
163+
for us.
156164

157165
This allows developers to properly access all of the :class:`~.OverrideRule`
158-
declared using the ``@handle_urls`` annotation inside the **POP**. In turn,
159-
this also allows **POPs** which use ``web_poet.default_registry`` to have all
160-
their rules discovered if they are adhering to using Convention **#3**
161-
(see :ref:`best-practices`).
166+
declared using the ``@handle_urls`` annotation inside the **POP**:
162167

163-
In other words, importing the ``ecommerce_page_objects`` **POP** to a
164-
project immediately loads all of the rules in **web-poet's**
165-
``default_registry``:
168+
.. code-block:: python
169+
170+
import ecommerce_page_objects
171+
172+
ecommerce_rules = ecommerce_page_objects.get_overrides()
173+
174+
At the same time, this also allows **POPs** which use ``web_poet.default_registry``
175+
to have all their rules discovered if they are adhering to using Convention **#3**
176+
(see :ref:`conventions-and-best-practices`). In other words, importing the
177+
``ecommerce_page_objects`` **POP** to a project immediately loads all of the rules
178+
in **web-poet's** ``default_registry``:
166179

167180
.. code-block:: python
168181
169182
from web_poet import default_registry
170183
184+
ecommerce_rules = ecommerce_page_objects.get_overrides()
185+
171186
import ecommerce_page_objects
172187
173-
# All the rules are now available.
174-
rules = default_registry.get_overrides()
188+
# All the rules are also available once ecommerce_page_objects is imported.
189+
all_rules = default_registry.get_overrides()
175190
176191
If this recommended requirement is followed properly, there's no need to
177192
call ``consume_modules("ecommerce_page_objects")`` before performing the
178193
:meth:`~.PageObjectRegistry.get_overrides`, since all the :class:`~.OverrideRule`
179-
were already discovered upon **POP** importation.
194+
were already discovered upon **POP** importation.
195+
196+
Lastly, when trying to repackage multiple **POPs** into a single unifying **POP**
197+
which contains all of the :class:`~.OverrideRule`, it can easily be packaged
198+
as:
199+
200+
.. code-block:: python
201+
202+
from web_poet import PageObjectRegistry
203+
204+
import base_A_package
205+
import base_B_package
206+
207+
# If on Python 3.9+
208+
combined_reg = base_A_package.REGISTRY | base_B_package.REGISTRY
209+
210+
# If on lower Python versions
211+
combined_reg = {**base_A_package.REGISTRY, **base_B_package.REGISTRY}
212+
213+
REGISTRY = PageObjectRegistry(combined_reg)
180214
181-
.. _`best-practices`:
215+
Note that you can also opt to use only a subset of the :class:`~.OverrideRule`
216+
by selecting the specific ones in ``combined_reg`` before creating a new
217+
:class:`~.PageObjectRegistry` instance. An **inclusion** rule is preferred than
218+
an **exclusion** rule (see **Tip #4** in the :ref:`conventions-and-best-practices`).
219+
You can use :meth:`~.PageObjectRegistry.search_overrides` when selecting the
220+
rules.
182221

222+
.. _`conventions-and-best-practices`:
183223

184224
Conventions and Best Practices
185225
------------------------------

tests/test_pop.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""This tests the expected behavior of importing multiple different POPs such
2+
that they don't leak the OverrideRules from other POPs that use the same
3+
``default_registry``.
4+
5+
Packacking and exporting a given POP should be resilient from such cases.
6+
7+
In particular, this tests the :meth:`PageObjectRegistry.export` functionality.
8+
"""
9+
10+
def test_base_A():
11+
from tests_pop import base_A_package
12+
13+
reg = base_A_package.REGISTRY
14+
15+
assert len(reg) == 2
16+
assert base_A_package.site_1.A_Site1 in reg
17+
assert base_A_package.site_2.A_Site2 in reg
18+
19+
20+
def test_base_B():
21+
from tests_pop import base_B_package
22+
23+
reg = base_B_package.REGISTRY
24+
25+
assert len(reg) == 2
26+
assert base_B_package.site_2.B_Site2 in reg
27+
assert base_B_package.site_3.B_Site3 in reg
28+
29+
30+
def test_improved_A():
31+
from tests_pop import improved_A_package, base_A_package
32+
33+
reg = improved_A_package.REGISTRY
34+
35+
assert len(reg) == 3
36+
assert improved_A_package.site_1.A_Improved_Site1 in reg
37+
assert improved_A_package.base_A_package.site_1.A_Site1 in reg
38+
assert improved_A_package.base_A_package.site_2.A_Site2 in reg
39+
40+
41+
def test_combine_A_B():
42+
from tests_pop import combine_A_B_package, base_A_package, base_B_package
43+
44+
reg = combine_A_B_package.REGISTRY
45+
46+
assert len(reg) == 4
47+
assert combine_A_B_package.base_A_package.site_1.A_Site1 in reg
48+
assert combine_A_B_package.base_A_package.site_2.A_Site2 in reg
49+
assert combine_A_B_package.base_B_package.site_2.B_Site2 in reg
50+
assert combine_A_B_package.base_B_package.site_3.B_Site3 in reg
51+
52+
53+
def test_combine_A_B_subset():
54+
from tests_pop import combine_A_B_subset_package, improved_A_package, base_B_package
55+
56+
reg = combine_A_B_subset_package.REGISTRY
57+
58+
assert len(reg) == 2
59+
assert combine_A_B_subset_package.improved_A_package.site_1.A_Improved_Site1 in reg
60+
assert combine_A_B_subset_package.base_B_package.site_3.B_Site3 in reg

tests_pop/__init__.py

Whitespace-only changes.

tests_pop/base_A_package/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from web_poet import default_registry
2+
3+
REGISTRY = default_registry.export(__package__)

tests_pop/base_A_package/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class BasePage:
2+
...

tests_pop/base_A_package/site_1.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from web_poet import handle_urls
2+
3+
from .base import BasePage
4+
5+
6+
@handle_urls("site_1.com", overrides=BasePage)
7+
class A_Site1:
8+
...

tests_pop/base_A_package/site_2.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from web_poet import handle_urls
2+
3+
from .base import BasePage
4+
5+
6+
@handle_urls("site_2.com", overrides=BasePage)
7+
class A_Site2:
8+
...

tests_pop/base_B_package/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from web_poet import default_registry
2+
3+
REGISTRY = default_registry.export(__package__)

tests_pop/base_B_package/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class BasePage:
2+
...

tests_pop/base_B_package/site_2.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from web_poet import handle_urls
2+
3+
from .base import BasePage
4+
5+
6+
@handle_urls("site_2.com", overrides=BasePage)
7+
class B_Site2:
8+
...

tests_pop/base_B_package/site_3.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from web_poet import handle_urls
2+
3+
from .base import BasePage
4+
5+
6+
@handle_urls("site_3.com", overrides=BasePage)
7+
class B_Site3:
8+
...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""This POP simply wants to repackage POP "A" and "B" into one unifying package."""
2+
3+
from web_poet import PageObjectRegistry
4+
5+
from . import base_A_package
6+
from . import base_B_package
7+
8+
combined = {**base_A_package.REGISTRY, **base_B_package.REGISTRY}
9+
REGISTRY = PageObjectRegistry(combined)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../base_A_package
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../base_B_package
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""This POP simply wants to repackage POP "A" and "B" into one unifying package."""
2+
3+
from web_poet import PageObjectRegistry
4+
5+
from . import improved_A_package
6+
from . import base_B_package
7+
8+
rules_A_improved = improved_A_package.REGISTRY.search_overrides(
9+
use=improved_A_package.site_1.A_Improved_Site1 # type:ignore
10+
)
11+
rules_B = base_B_package.REGISTRY.search_overrides(
12+
use=base_B_package.site_3.B_Site3 # type: ignore
13+
)
14+
15+
combined_rules = rules_A_improved + rules_B
16+
REGISTRY = PageObjectRegistry.from_override_rules(combined_rules)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../base_B_package
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../improved_A_package
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from web_poet import default_registry
2+
3+
REGISTRY = default_registry.export(__package__)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../base_A_package
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from web_poet import handle_urls
2+
3+
from .base_A_package.base import BasePage
4+
from .base_A_package.site_1 import A_Site1
5+
6+
7+
@handle_urls("site_1.com", overrides=BasePage)
8+
class A_Improved_Site1(A_Site1):
9+
... # some improvements here after subclassing the original one.

web_poet/overrides.py

+22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import importlib.util
55
import warnings
66
import pkgutil
7+
from copy import deepcopy
78
from collections import deque
89
from dataclasses import dataclass, field
910
from operator import attrgetter
@@ -121,6 +122,27 @@ def from_override_rules(
121122
"""
122123
return cls({rule.use: rule for rule in rules})
123124

125+
def export(self, package: str) -> PageObjectRegistry:
126+
"""Returns a new :class:`~.PageObjectRegistry` instance containing
127+
:class:`~.OverrideRule` which are only found in the provided **package**.
128+
129+
This is used in cases wherein all of the :class:`~.OverrideRule` in a
130+
given **Page Object Project (POP)** should be placed inside a dedicated
131+
registry for packaging. See :ref:`POP Recommended Requirements
132+
<pop-recommended-requirements>` for more info about this.
133+
134+
Note that the :func:`~.consume_modules` will be called on the said
135+
**package** which adds any undiscovered :class:`~.OverrideRule` to the
136+
original :class:`~.PageObjectRegistry` instance. There's no need to worry
137+
about unrelated rules from being added since it wouldn't happen if the
138+
given registry's ``@handle_urls`` annotation wasn't the one used.
139+
"""
140+
backup_state = deepcopy(self)
141+
self.clear()
142+
rules = self.get_overrides(consume=package)
143+
self = backup_state
144+
return self.from_override_rules(rules)
145+
124146
def handle_urls(
125147
self,
126148
include: Strings,

0 commit comments

Comments
 (0)