diff --git a/pomcorn/component.py b/pomcorn/component.py index d0cdb44..b1376ad 100644 --- a/pomcorn/component.py +++ b/pomcorn/component.py @@ -1,4 +1,3 @@ -import warnings from inspect import isclass from typing import ( Any, @@ -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: @@ -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 @@ -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}, " diff --git a/tests/list_component/test_item_class.py b/tests/list_component/test_item_class.py index c7b8d38..d7bde11 100644 --- a/tests/list_component/test_item_class.py +++ b/tests/list_component/test_item_class.py @@ -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: @@ -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 @@ -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: @@ -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: @@ -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: @@ -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