Skip to content

Commit

Permalink
Remove item_class attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
M1troll committed Nov 21, 2024
1 parent e13c733 commit fe31e2b
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 138 deletions.
79 changes: 10 additions & 69 deletions pomcorn/component.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import warnings
from inspect import isclass
from typing import (
Any,
Expand Down Expand Up @@ -212,79 +211,21 @@ def __init_subclass__(cls) -> None:
"""Run logic for getting/overriding item_class attr for subclasses."""
super().__init_subclass__()

# Try to get `item_class` from overridden `item_class` attribute
# If `item_class` is a property, it means that it hasn't been
# overridden as class attribute
item_class_attr = getattr(cls, "item_class", None)
item_class_attr = (
None if isinstance(item_class_attr, property) else item_class_attr
)

# If class has valid `_item_class` attribute from a parent class and
# doesn't have `item_class` class attribute
cls_item_class = cls._item_class
if cls.is_valid_item_class(cls_item_class) and not item_class_attr:
# If class has valid `_item_class` attribute from a parent class
if cls.is_valid_item_class(cls._item_class):
# We leave using of parent `item_class`
return

# Try to get `item_class` from first generic variable
list_item_class = cls.get_list_item_class()

if not (item_class_attr or list_item_class):
# If `item_class` is not specified in generic and class attribute
# we leave it empty because it maybe not specified in base class
# but will be specified in child
if not list_item_class:
# If `item_class` is not specified in generic we leave it empty
# because it maybe not specified in base class but will be
# specified in child
return

# Overridden `item_class` attribute have priority
if item_class_attr:
cls._item_class = item_class_attr
elif list_item_class:
cls._item_class = list_item_class

# If component has an `item_class` attribute that matches first generic
# variable
if item_class_attr and item_class_attr == list_item_class:
msg = (
f"The `item_class` you specify ({item_class_attr.__name__}) "
f"in `{cls.__name__}.item_class` is the same as type "
f"specified in Generic[ListItemType] "
f"({list_item_class.__name__}). You may not specify it for "
"this class because it will be done automatically based on "
"first generic variable."
)
warnings.warn(Warning(msg), stacklevel=2)

@property
def item_class(self) -> type[ListItemType]:
"""Class which will be used for list items.
By default this attr is automatically filled by first generic variable.
But if you need - you can override this property as class attribute.
Raises:
ValueError: If item class is not specified for class.
"""
if not self._item_class:
raise ValueError(
"`item_class` is None. Specify it via first generic variable "
f"of `{self.__class__.__name__}` or `item_class` attribute.",
)
return self._item_class

@item_class.setter
def item_class(self, value: type[ListItemType]) -> None:
if value == self.item_class:
warnings.warn(
Warning(
"You don't need to override `item_class` with "
f"`{value.__name__}` because it already stores "
"same value.",
),
stacklevel=2,
)
self._item_class = value
cls._item_class = list_item_class

@property
def base_item_locator(self) -> locators.XPathLocator:
Expand Down Expand Up @@ -331,7 +272,7 @@ def all(self) -> list[ListItemType]:
items: list[ListItemType] = []
for locator in self.iter_locators(self.base_item_locator):
items.append(
self.item_class(page=self.page, base_locator=locator),
self._item_class(page=self.page, base_locator=locator),
)
return items

Expand Down Expand Up @@ -366,13 +307,13 @@ def get_item_by_text(self, text: str) -> ListItemType:
locator = self.base_item_locator.extend_query(
extra_query=f"[contains(.,'{text}')]",
)
return self.item_class(page=self.page, base_locator=locator)
return self._item_class(page=self.page, base_locator=locator)

def __repr__(self) -> str:
return (
"ListComponent("
f"component={self.__class__}, "
f"item_class={self.item_class}, "
f"item_class={self._item_class}, "
f"base_item_locator={self.base_item_locator}, "
f"count={self.count}, "
f"items={self.all}, "
Expand Down
77 changes: 8 additions & 69 deletions tests/list_component/test_item_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class InheritedList(BaseList[Page]):

# Ensure that `InheritedList.item_class` has correct type
list_cls = InheritedList(fake_page)
assert list_cls.item_class is ItemClass
assert list_cls._item_class is ItemClass


def test_no_set_item_class(fake_page: Page) -> None:
Expand All @@ -42,6 +42,7 @@ class BaseList(Generic[TItem, TPage], ListComponent[TItem, TPage]):
"""Base list component without specified Generic variables."""

base_locator = locators.XPathLocator("html") # required
relative_item_locator = locators.XPathLocator("body") # required
wait_until_visible = lambda _: True # to not wait anything

# Inherit from `BaseList` and specify only page generic variable
Expand All @@ -50,13 +51,9 @@ class InheritedList(Generic[TItem], BaseList[TItem, Page]):

list_cls = InheritedList(fake_page) # type: ignore

# Expect an error
msg = (
"`item_class` is None. Specify it via first generic variable "
"of `InheritedList` or `item_class` attribute."
)
with pytest.raises(ValueError, match=msg):
list_cls.item_class
assert not list_cls._item_class
with pytest.raises(TypeError, match=r"object is not callable"):
list_cls.get_item_by_text("item")


def test_set_item_class_in_child_via_generic(fake_page: Page) -> None:
Expand All @@ -74,7 +71,7 @@ class InheritedList(BaseList[ItemClass, Page]):

# Ensure that `InheritedList.item_class` has correct type
list_cls = InheritedList(fake_page)
assert list_cls.item_class is ItemClass
assert list_cls._item_class is ItemClass


def test_specify_all_generic_variables(fake_page: Page) -> None:
Expand All @@ -85,7 +82,7 @@ class List(ListComponent[ItemClass, Page]):
wait_until_visible = lambda _: True # to not wait anything

list_cls = List(fake_page)
assert list_cls.item_class is ItemClass
assert list_cls._item_class is ItemClass


def test_set_item_class_with_extra_generic_variable(fake_page: Page) -> None:
Expand All @@ -107,62 +104,4 @@ class List(BaseList[Param]):

# Ensure that `List.item_class` has correct type
list_cls = List(fake_page)
assert list_cls.item_class is ItemClass


def test_override_item_class(fake_page: Page) -> None:
"""Check that it possible to override item_class via class attribute."""

class BaseList(ListComponent[ItemClass, Page]):
"""Base list component with specified generic variables."""

base_locator = locators.XPathLocator("html") # required
wait_until_visible = lambda _: True # to not wait anything

# Override `item_class` via class attribute
class OverriddenItem(ItemClass):
...

class OverriddenList(BaseList):
item_class = OverriddenItem

# Ensure that `OverriddenList.item_class` has correct type
list_cls = OverriddenList(fake_page)
assert list_cls.item_class is OverriddenItem

# Check that no warnings are issued during the test
captured = pytest.warns()
assert len(captured) == 0, f"Unexpected warnings: {captured}"


def test_useless_item_class_attribute_warning() -> None:
"""Check warning if item_class attr is same as in first generic var."""
msg = (
r"The `item_class` you specify \(ItemClass\) in `_.item_class` is "
r"the same as type specified in Generic\[ListItemType\] "
r"\(ItemClass\). You may not specify it for this class because it "
r"will be done automatically based on first generic variable."
)
with pytest.warns(Warning, match=msg):

class _(ListComponent[ItemClass, Page]):
item_class = ItemClass
base_locator = locators.XPathLocator("html") # required
wait_until_visible = lambda _: True # to not wait anything


def test_useless_item_class_overriding_warning(fake_page: Page) -> None:
"""Check warning if you override item_class attribute to the same value."""

class List(ListComponent[ItemClass, Page]):
base_locator = locators.XPathLocator("html") # required
wait_until_visible = lambda _: True # to not wait anything

list_cls = List(fake_page)

msg = (
"You don't need to override `item_class` with "
"`ItemClass` because it already stores same value."
)
with pytest.warns(Warning, match=msg):
list_cls.item_class = ItemClass
assert list_cls._item_class is ItemClass

0 comments on commit fe31e2b

Please sign in to comment.