pomcorn 0.8.0__tar.gz → 0.8.2__tar.gz
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-0.8.0 → pomcorn-0.8.2}/PKG-INFO +2 -2
- {pomcorn-0.8.0 → pomcorn-0.8.2}/README.md +0 -1
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/component.py +109 -21
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pyproject.toml +7 -2
- {pomcorn-0.8.0 → pomcorn-0.8.2}/LICENSE +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/__init__.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/descriptors/__init__.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/descriptors/element.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/element.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/exceptions.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/locators/__init__.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/locators/base_locators.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/locators/xpath_locators.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/page.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/py.typed +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/waits_conditions.py +0 -0
- {pomcorn-0.8.0 → pomcorn-0.8.2}/pomcorn/web_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pomcorn
|
|
3
|
-
Version: 0.8.
|
|
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,4 +1,14 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
[tool.poetry]
|
|
2
2
|
name = "pomcorn"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.2"
|
|
4
4
|
description = "Base implementation of Page Object Model"
|
|
5
5
|
authors = [
|
|
6
6
|
"Saritasa <pypi@saritasa.com>",
|
|
@@ -64,7 +64,7 @@ sphinx = ">= 7.2.6"
|
|
|
64
64
|
sphinx-rtd-theme = ">= 1.3.0rc1"
|
|
65
65
|
# Support mermaid diagrams
|
|
66
66
|
# https://github.com/mgaitan/sphinxcontrib-mermaid/tree/master
|
|
67
|
-
sphinxcontrib-mermaid = "
|
|
67
|
+
sphinxcontrib-mermaid = ">=0.9.2,<1.1.0"
|
|
68
68
|
|
|
69
69
|
[tool.poetry.group.demo.dependencies]
|
|
70
70
|
pytest = ">= 7.4.2"
|
|
@@ -171,6 +171,11 @@ pytest-parametrize-names-type = "list"
|
|
|
171
171
|
pytest-parametrize-values-type = "list"
|
|
172
172
|
pytest-parametrize-values-row-type = "list"
|
|
173
173
|
|
|
174
|
+
per-file-ignores = [
|
|
175
|
+
# Ignore using lambda in `item_class` tests
|
|
176
|
+
"tests/list_component/test_item_class.py: E731",
|
|
177
|
+
]
|
|
178
|
+
|
|
174
179
|
[tool.mypy]
|
|
175
180
|
# mypy configurations: https://mypy.readthedocs.io/en/latest/config_file.html
|
|
176
181
|
# https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|