pomcorn 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pomcorn might be problematic. Click here for more details.

pomcorn/component.py CHANGED
@@ -1,4 +1,14 @@
1
- from typing import Generic, TypeVar, get_args, overload
1
+ import typing
2
+ from inspect import isclass
3
+ from typing import (
4
+ Any,
5
+ Generic,
6
+ Literal,
7
+ TypeVar,
8
+ get_args,
9
+ get_origin,
10
+ overload,
11
+ )
2
12
 
3
13
  from . import locators
4
14
  from .element import XPathElement
@@ -8,6 +18,17 @@ from .web_view import WebView
8
18
  TPage = TypeVar("TPage", bound=Page)
9
19
 
10
20
 
21
+ class _EmptyValue:
22
+ """Singleton to use as default value for empty class attribute."""
23
+
24
+ def __bool__(self) -> Literal[False]:
25
+ """Allow `EmptyValue` to be used in bool expressions."""
26
+ return False
27
+
28
+
29
+ EmptyValue: Any = _EmptyValue()
30
+
31
+
11
32
  class Component(Generic[TPage], WebView):
12
33
  """The class to represent a page component that depends on base locator.
13
34
 
@@ -182,31 +203,67 @@ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
182
203
 
183
204
  """
184
205
 
206
+ _item_class: type[ListItemType] = EmptyValue
207
+
185
208
  item_locator: locators.XPathLocator | None = None
186
209
  relative_item_locator: locators.XPathLocator | None = None
187
210
 
211
+ def __class_getitem__(cls, item: tuple[type, ...]) -> Any:
212
+ """Create parameterized versions of generic classes.
213
+
214
+ This method is called when the class is used as a parameterized type,
215
+ such as MyGeneric[int] or MyGeneric[List[str]].
216
+
217
+ We override this method to store values passed in generic parameters.
218
+
219
+ Args:
220
+ cls - The generic class itself.
221
+ item - The type used for parameterization.
222
+
223
+ Returns:
224
+ type: A parameterized version of the class with the specified type.
225
+
226
+ """
227
+ list_cls = super().__class_getitem__(item) # type: ignore
228
+ cls.__parameters__ = item # type: ignore
229
+ return list_cls
230
+
188
231
  def __init__(
189
232
  self,
190
233
  page: TPage,
191
234
  base_locator: locators.XPathLocator | None = None,
192
235
  wait_until_visible: bool = True,
193
- ):
236
+ ) -> None:
237
+ # If `_item_class` was not specified in `__init_subclass__`, this means
238
+ # that `ListComponent` is used as a parameterized type
239
+ # (e.g., `List[ItemClass, Page]`).
240
+ if isinstance(self._item_class, _EmptyValue):
241
+ # In this way we check the stored generic parameters and, if first
242
+ # from them is valid, set it as `_item_class`
243
+ first_generic_param = self.__parameters__[0]
244
+ if self.is_valid_item_class(first_generic_param):
245
+ self._item_class = first_generic_param
194
246
  super().__init__(page, base_locator, wait_until_visible)
195
- if item_class := getattr(self, "item_class", None):
196
- import warnings
197
-
198
- warnings.warn(
199
- DeprecationWarning(
200
- "\nSpecifying `item_class` attribute in `ListComponent` "
201
- f"({self.__class__}) is DEPRECATED. It is now "
202
- "automatically substituted from Generic[ListItemType]. "
203
- "Ability to specify this attribute will be removed soon.",
204
- ),
205
- stacklevel=2,
206
- )
207
- self._item_class = item_class
208
- else:
209
- self._item_class = self._get_list_item_class()
247
+
248
+ def __init_subclass__(cls) -> None:
249
+ """Run logic for getting/overriding item_class attr for subclasses."""
250
+ super().__init_subclass__()
251
+
252
+ # If class has valid `_item_class` attribute from a parent class
253
+ if cls.is_valid_item_class(cls._item_class):
254
+ # We leave using of parent `item_class`
255
+ return
256
+
257
+ # Try to get `item_class` from first generic variable
258
+ list_item_class = cls.get_list_item_class()
259
+
260
+ if not list_item_class:
261
+ # If `item_class` is not specified in generic we leave it empty
262
+ # because it maybe not specified in base class but will be
263
+ # specified in child
264
+ return
265
+
266
+ cls._item_class = list_item_class
210
267
 
211
268
  @property
212
269
  def base_item_locator(self) -> locators.XPathLocator:
@@ -257,6 +314,41 @@ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
257
314
  )
258
315
  return items
259
316
 
317
+ @classmethod
318
+ def get_list_item_class(cls) -> type[ListItemType] | None:
319
+ """Return class passed in `Generic[ListItemType]`."""
320
+ base_class = next(
321
+ _class
322
+ for _class in cls.__orig_bases__ # type: ignore
323
+ if isclass(get_origin(_class))
324
+ and issubclass(get_origin(_class), ListComponent)
325
+ )
326
+
327
+ # Get first generic variable and return it if it is valid item class
328
+ item_class = get_args(base_class)[0]
329
+ if cls.is_valid_item_class(item_class):
330
+ return item_class
331
+
332
+ return None
333
+
334
+ @classmethod
335
+ def is_valid_item_class(cls, item_class: Any) -> bool:
336
+ """Check that specified ``item_class`` is valid.
337
+
338
+ Valid ``item_class`` should be
339
+ * a class and subclass of ``Component``
340
+ * or TypeAlias based on ``Component``
341
+
342
+ """
343
+ if isclass(item_class) and issubclass(item_class, Component):
344
+ return True
345
+
346
+ if isinstance(item_class, typing._GenericAlias): # type: ignore
347
+ type_alias = item_class.__origin__ # type: ignore
348
+ return isclass(type_alias) and issubclass(type_alias, Component)
349
+
350
+ return False
351
+
260
352
  def get_item_by_text(self, text: str) -> ListItemType:
261
353
  """Get list item by text."""
262
354
  locator = self.base_item_locator.extend_query(
@@ -264,10 +356,6 @@ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
264
356
  )
265
357
  return self._item_class(page=self.page, base_locator=locator)
266
358
 
267
- def _get_list_item_class(self) -> type[ListItemType]:
268
- """Return class passed in `Generic[ListItemType]`."""
269
- return get_args(self.__orig_bases__[0])[0] # type: ignore
270
-
271
359
  def __repr__(self) -> str:
272
360
  return (
273
361
  "ListComponent("
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pomcorn
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Base implementation of Page Object Model
5
5
  Home-page: https://pypi.org/project/pomcorn/
6
6
  License: MIT
@@ -18,6 +18,7 @@ Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
23
  Requires-Dist: selenium (>=4.12)
23
24
  Project-URL: Documentation, http://pomcorn.rtfd.io/
@@ -119,7 +120,6 @@ Below is the code that opens ``PyPI.org``, searches for packages by name and pri
119
120
 
120
121
  class PackageList(ListComponent[Package, PyPIPage]):
121
122
 
122
- item_class = Package
123
123
  relative_item_locator = locators.ClassLocator("snippet__name")
124
124
 
125
125
  @property
@@ -1,5 +1,5 @@
1
1
  pomcorn/__init__.py,sha256=V8v_V3wgKbao1KAQkcVTMnpGwyslw0nHfuTp-DwWTXg,318
2
- pomcorn/component.py,sha256=-QoF5BnCKGqugUKHO4SPMKZJtmX6zLCVoZBQm9QzhIA,9286
2
+ pomcorn/component.py,sha256=Z0WAcHjWDj67YpQeuU66zrjexmcYLuFStgTPzUqcy9Y,12078
3
3
  pomcorn/descriptors/__init__.py,sha256=5q_d5GtcaFlIIyHdsUnE9UNbz3g40qJCHKNaWGvcC6s,72
4
4
  pomcorn/descriptors/element.py,sha256=4g_4ay5f00AXzCT4xHN1i7mTrlsECoUHh1Ph_TFPBDs,4502
5
5
  pomcorn/element.py,sha256=6VLLSPlc7961qa4rRXdno7cEAAeY8GEP2d4wtM5R9a0,12216
@@ -11,7 +11,7 @@ pomcorn/page.py,sha256=wuLoGUn1n0e7wSXLGYyUVnoPlz_gMfT8Jtg6yA09IeY,5755
11
11
  pomcorn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  pomcorn/waits_conditions.py,sha256=E8sLfBvIV2EO91Mm5KyKXB5IGEB9gUWza3XPE4_aCQE,3528
13
13
  pomcorn/web_view.py,sha256=PlwEVgdOwFZXJY2WuUT2C2wyK1xOBJne5-A_FOuwsQI,14209
14
- pomcorn-0.8.0.dist-info/LICENSE,sha256=1vmhQNp1dDH8ksJ199LuG4oKz5D4ZvNccPcFckiG2S4,1091
15
- pomcorn-0.8.0.dist-info/METADATA,sha256=YuVbebm1X8leLyFIAxmYbS4UqAUwT1iEaqmy70QhBkA,5949
16
- pomcorn-0.8.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
17
- pomcorn-0.8.0.dist-info/RECORD,,
14
+ pomcorn-0.8.2.dist-info/LICENSE,sha256=1vmhQNp1dDH8ksJ199LuG4oKz5D4ZvNccPcFckiG2S4,1091
15
+ pomcorn-0.8.2.dist-info/METADATA,sha256=ZiCtGGbvbQuzLFzxbdLXtqCN6VReZ8lTc2QXOtzosV4,5973
16
+ pomcorn-0.8.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ pomcorn-0.8.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any