tmux-python / libtmux

⚙️ Python API / wrapper for tmux
https://libtmux.git-pull.com
MIT License
1.01k stars 104 forks source link

fix!(QueryList): Generic fixes #515

Closed tony closed 7 months ago

tony commented 7 months ago

ChatGPT helped!

Part 1

Prompt Assume this: ``` """Utilities for filtering or searching :class:`list` of objects / list data. Note ---- This is an internal API not covered by versioning policy. """ import re import traceback import typing as t from collections.abc import Mapping, Sequence if t.TYPE_CHECKING: class LookupProtocol(t.Protocol): """Protocol for :class:`QueryList` filtering operators.""" def __call__( self, data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: """Return callback for :class:`QueryList` filtering operators.""" ... T = t.TypeVar("T", t.Any, t.Any) no_arg = object() class MultipleObjectsReturned(Exception): """The requested object does not exist.""" class ObjectDoesNotExist(Exception): """The query returned multiple objects when only one was expected.""" def keygetter( obj: "Mapping[str, t.Any]", path: str, ) -> t.Union[None, t.Any, str, t.List[str], "Mapping[str, str]"]: """obj, "foods__breakfast", obj['foods']['breakfast']. >>> keygetter({ "foods": { "breakfast": "cereal" } }, "foods__breakfast") 'cereal' >>> keygetter({ "foods": { "breakfast": "cereal" } }, "foods") {'breakfast': 'cereal'} """ try: sub_fields = path.split("__") dct = obj for sub_field in sub_fields: if isinstance(dct, dict): dct = dct[sub_field] elif hasattr(dct, sub_field): dct = getattr(dct, sub_field) except Exception as e: traceback.print_stack() print(f"Above error was {e}") return None return dct def parse_lookup( obj: "Mapping[str, t.Any]", path: str, lookup: str, ) -> t.Optional[t.Any]: """Check if field lookup key, e.g. "my__path__contains" has comparator, return val. If comparator not used or value not found, return None. mykey__endswith("mykey") -> "mykey" else None >>> parse_lookup({ "food": "red apple" }, "food__istartswith", "__istartswith") 'red apple' """ try: if isinstance(path, str) and isinstance(lookup, str) and path.endswith(lookup): field_name = path.rsplit(lookup)[0] if field_name is not None: return keygetter(obj, field_name) except Exception: traceback.print_stack() return None def lookup_exact( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: return rhs == data def lookup_iexact( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return rhs.lower() == data.lower() def lookup_contains( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)): return False return rhs in data def lookup_icontains( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)): return False if isinstance(data, str): return rhs.lower() in data.lower() if isinstance(data, Mapping): return rhs.lower() in [k.lower() for k in data] return False def lookup_startswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.startswith(rhs) def lookup_istartswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.lower().startswith(rhs.lower()) def lookup_endswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.endswith(rhs) def lookup_iendswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.lower().endswith(rhs.lower()) def lookup_in( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(rhs, list): return data in rhs try: if isinstance(rhs, str) and isinstance(data, Mapping): return rhs in data if isinstance(rhs, str) and isinstance(data, (str, list)): return rhs in data if isinstance(rhs, str) and isinstance(data, Mapping): return rhs in data # TODO: Add a deep Mappingionary matcher # if isinstance(rhs, Mapping) and isinstance(data, Mapping): # return rhs.items() not in data.items() except Exception: return False return False def lookup_nin( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(rhs, list): return data not in rhs try: if isinstance(rhs, str) and isinstance(data, Mapping): return rhs not in data if isinstance(rhs, str) and isinstance(data, (str, list)): return rhs not in data if isinstance(rhs, str) and isinstance(data, Mapping): return rhs not in data # TODO: Add a deep Mappingionary matcher # if isinstance(rhs, Mapping) and isinstance(data, Mapping): # return rhs.items() not in data.items() except Exception: return False return False def lookup_regex( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)): return bool(re.search(rhs, data)) return False def lookup_iregex( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)): return bool(re.search(rhs, data, re.IGNORECASE)) return False LOOKUP_NAME_MAP: 'Mapping[str, "LookupProtocol"]' = { "eq": lookup_exact, "exact": lookup_exact, "iexact": lookup_iexact, "contains": lookup_contains, "icontains": lookup_icontains, "startswith": lookup_startswith, "istartswith": lookup_istartswith, "endswith": lookup_endswith, "iendswith": lookup_iendswith, "in": lookup_in, "nin": lookup_nin, "regex": lookup_regex, "iregex": lookup_iregex, } class PKRequiredException(Exception): def __init__(self, *args: object): return super().__init__("items() require a pk_key exists") class OpNotFound(ValueError): def __init__(self, op: str, *args: object): return super().__init__(f"{op} not in LOOKUP_NAME_MAP") class QueryList(t.List[T]): """Filter list of object/dictionaries. For small, local datasets. *Experimental, unstable*. >>> query = QueryList( ... [ ... { ... "place": "Largo", ... "city": "Tampa", ... "state": "Florida", ... "foods": {"fruit": ["banana", "orange"], "breakfast": "cereal"}, ... }, ... { ... "place": "Chicago suburbs", ... "city": "Elmhurst", ... "state": "Illinois", ... "foods": {"fruit": ["apple", "cantelope"], "breakfast": "waffles"}, ... }, ... ] ... ) >>> query.filter(place="Chicago suburbs")[0]['city'] 'Elmhurst' >>> query.filter(place__icontains="chicago")[0]['city'] 'Elmhurst' >>> query.filter(foods__breakfast="waffles")[0]['city'] 'Elmhurst' >>> query.filter(foods__fruit__in="cantelope")[0]['city'] 'Elmhurst' >>> query.filter(foods__fruit__in="orange")[0]['city'] 'Tampa' >>> query.get(foods__fruit__in="orange")['city'] 'Tampa' """ data: "Sequence[T]" pk_key: t.Optional[str] def items(self) -> t.List[T]: if self.pk_key is None: raise PKRequiredException() return [(getattr(item, self.pk_key), item) for item in self] def __eq__( self, other: object, # other: t.Union[ # "QueryList[T]", # t.List[Mapping[str, str]], # t.List[Mapping[str, int]], # t.List[Mapping[str, t.Union[str, Mapping[str, t.Union[List[str], str]]]]], # ], ) -> bool: data = other if not isinstance(self, list) or not isinstance(data, list): return False if len(self) == len(data): for a, b in zip(self, data): if isinstance(a, Mapping): a_keys = a.keys() if a.keys == b.keys(): for key in a_keys: if abs(a[key] - b[key]) > 1: return False else: if a != b: return False return True return False def filter( self, matcher: t.Optional[t.Union[t.Callable[[T], bool], T]] = None, **kwargs: t.Any, ) -> "QueryList[T]": """Filter list of objects.""" def filter_lookup(obj: t.Any) -> bool: for path, v in kwargs.items(): try: lhs, op = path.rsplit("__", 1) if op not in LOOKUP_NAME_MAP: raise OpNotFound(op=op) except ValueError: lhs = path op = "exact" assert op in LOOKUP_NAME_MAP path = lhs data = keygetter(obj, path) if data is None or not LOOKUP_NAME_MAP[op](data, v): return False return True if callable(matcher): _filter = matcher elif matcher is not None: def val_match(obj: t.Union[str, t.List[t.Any]]) -> bool: if isinstance(matcher, list): return obj in matcher else: return bool(obj == matcher) _filter = val_match else: _filter = filter_lookup return self.__class__(k for k in self if _filter(k)) def get( self, matcher: t.Optional[t.Union[t.Callable[[T], bool], T]] = None, default: t.Optional[t.Any] = no_arg, **kwargs: t.Any, ) -> t.Optional[T]: """Retrieve one object. Raises :exc:`MultipleObjectsReturned` if multiple objects found. Raises :exc:`ObjectDoesNotExist` if no object found, unless ``default`` stated. """ objs = self.filter(matcher=matcher, **kwargs) if len(objs) > 1: raise MultipleObjectsReturned() elif len(objs) == 0: if default == no_arg: raise ObjectDoesNotExist() return default return objs[0] ``` What would it take to make `QueryList` behave as a generic list type? Right now (as-is) `QueryList[CustomObjectType]` is not a valid annotation according to mypy.

Response:

Response To make `QueryList` behave as a generic list type and ensure it's recognized as such by static type checkers like mypy, you need to ensure that it correctly inherits from a generic type and uses type annotations properly throughout its definition. The modifications needed involve leveraging the typing system to annotate `QueryList` as a generic collection properly. Here are the steps to make `QueryList` a properly typed, generic list in Python: 1. **Ensure Proper Inheritance**: The `QueryList` should inherit from `typing.Generic` and `collections.abc.Sequence` (or another appropriate base class) to get list-like behavior while also being generic. You've already imported `Generic` and are using `TypeVar`, which is great. However, your `QueryList` definition should explicitly indicate it's generic over some type `T`. 2. **Use the TypeVar Correctly**: You declared a `TypeVar` named `T` but didn't use it effectively within `QueryList` to ensure it's recognized as a generic class by type checkers. You should use `T` as a placeholder for the type of items contained in the `QueryList`. 3. **Annotate `data` Attribute**: The `data` attribute in `QueryList` should be annotated to indicate it holds a sequence of type `T` elements. However, this attribute seems redundant if `QueryList` itself behaves like a list; you might be intending to use `self` to store the data directly. 4. **Correct Method Annotations**: Ensure all methods that operate on elements of the list use the `T` type variable appropriately. This includes methods that add, remove, retrieve, or operate on elements in any way, ensuring they are compatible with the generic type `T`. 5. **Update `__init__` Method**: Your `QueryList` doesn't explicitly define an `__init__` method to initialize the list. It's important to properly initialize the internal list structure that will hold the data. Below is an updated example that incorporates these suggestions: ```python from typing import TypeVar, Generic, List, Optional, Callable, Any, Union, Sequence from collections.abc import Iterable import re T = TypeVar('T') class QueryList(Generic[T], List[T]): def __init__(self, items: Optional[Iterable[T]] = None) -> None: super().__init__(items if items is not None else []) # Example method updated to use type variable T def filter(self, matcher: Optional[Union[Callable[[T], bool], T]] = None, **kwargs: Any) -> 'QueryList[T]': filtered_list = [item for item in self if self._match(item, matcher, **kwargs)] return QueryList(filtered_list) def _match(self, item: T, matcher: Optional[Union[Callable[[T], bool], T]], **kwargs: Any) -> bool: # Implement matching logic here return True # Ensure other methods also use the T type variable where appropriate ``` This example assumes `QueryList` directly inherits list-like behavior from `List[T]` and adds typing information to make it a generic class capable of holding items of any specified type. It demonstrates how to initialize it with items of type `T` and includes a placeholder for a `filter` method that should be implemented according to your specific needs. Remember, when you're using `QueryList[CustomObjectType]`, the `CustomObjectType` needs to be a class or type alias that mypy can recognize. Also, make sure your runtime logic correctly handles the types you expect `QueryList` to contain, as Python's type annotations are not enforced at runtime.

Part 2

Prompt Consider the following: ``` """Utilities for filtering or searching :class:`list` of objects / list data. Note ---- This is an internal API not covered by versioning policy. """ import re import traceback import typing as t from collections.abc import Iterable, Mapping, Sequence if t.TYPE_CHECKING: class LookupProtocol(t.Protocol): """Protocol for :class:`QueryList` filtering operators.""" def __call__( self, data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: """Return callback for :class:`QueryList` filtering operators.""" ... T = t.TypeVar("T") # T = t.TypeVar("T", t.Any, t.Any) no_arg = object() class MultipleObjectsReturned(Exception): """The requested object does not exist.""" class ObjectDoesNotExist(Exception): """The query returned multiple objects when only one was expected.""" def keygetter( obj: "Mapping[str, t.Any]", path: str, ) -> t.Union[None, t.Any, str, t.List[str], "Mapping[str, str]"]: """obj, "foods__breakfast", obj['foods']['breakfast']. >>> keygetter({ "foods": { "breakfast": "cereal" } }, "foods__breakfast") 'cereal' >>> keygetter({ "foods": { "breakfast": "cereal" } }, "foods") {'breakfast': 'cereal'} """ try: sub_fields = path.split("__") dct = obj for sub_field in sub_fields: if isinstance(dct, dict): dct = dct[sub_field] elif hasattr(dct, sub_field): dct = getattr(dct, sub_field) except Exception as e: traceback.print_stack() print(f"Above error was {e}") return None return dct def parse_lookup( obj: "Mapping[str, t.Any]", path: str, lookup: str, ) -> t.Optional[t.Any]: """Check if field lookup key, e.g. "my__path__contains" has comparator, return val. If comparator not used or value not found, return None. mykey__endswith("mykey") -> "mykey" else None >>> parse_lookup({ "food": "red apple" }, "food__istartswith", "__istartswith") 'red apple' """ try: if isinstance(path, str) and isinstance(lookup, str) and path.endswith(lookup): field_name = path.rsplit(lookup)[0] if field_name is not None: return keygetter(obj, field_name) except Exception: traceback.print_stack() return None def lookup_exact( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: return rhs == data def lookup_iexact( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return rhs.lower() == data.lower() def lookup_contains( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)): return False return rhs in data def lookup_icontains( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)): return False if isinstance(data, str): return rhs.lower() in data.lower() if isinstance(data, Mapping): return rhs.lower() in [k.lower() for k in data] return False def lookup_startswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.startswith(rhs) def lookup_istartswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.lower().startswith(rhs.lower()) def lookup_endswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.endswith(rhs) def lookup_iendswith( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False return data.lower().endswith(rhs.lower()) def lookup_in( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(rhs, list): return data in rhs try: if isinstance(rhs, str) and isinstance(data, Mapping): return rhs in data if isinstance(rhs, str) and isinstance(data, (str, list)): return rhs in data if isinstance(rhs, str) and isinstance(data, Mapping): return rhs in data # TODO: Add a deep Mappingionary matcher # if isinstance(rhs, Mapping) and isinstance(data, Mapping): # return rhs.items() not in data.items() except Exception: return False return False def lookup_nin( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(rhs, list): return data not in rhs try: if isinstance(rhs, str) and isinstance(data, Mapping): return rhs not in data if isinstance(rhs, str) and isinstance(data, (str, list)): return rhs not in data if isinstance(rhs, str) and isinstance(data, Mapping): return rhs not in data # TODO: Add a deep Mappingionary matcher # if isinstance(rhs, Mapping) and isinstance(data, Mapping): # return rhs.items() not in data.items() except Exception: return False return False def lookup_regex( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)): return bool(re.search(rhs, data)) return False def lookup_iregex( data: t.Union[str, t.List[str], "Mapping[str, str]"], rhs: t.Union[str, t.List[str], "Mapping[str, str]", "re.Pattern[str]"], ) -> bool: if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)): return bool(re.search(rhs, data, re.IGNORECASE)) return False LOOKUP_NAME_MAP: 'Mapping[str, "LookupProtocol"]' = { "eq": lookup_exact, "exact": lookup_exact, "iexact": lookup_iexact, "contains": lookup_contains, "icontains": lookup_icontains, "startswith": lookup_startswith, "istartswith": lookup_istartswith, "endswith": lookup_endswith, "iendswith": lookup_iendswith, "in": lookup_in, "nin": lookup_nin, "regex": lookup_regex, "iregex": lookup_iregex, } class PKRequiredException(Exception): def __init__(self, *args: object): return super().__init__("items() require a pk_key exists") class OpNotFound(ValueError): def __init__(self, op: str, *args: object): return super().__init__(f"{op} not in LOOKUP_NAME_MAP") class QueryList(t.Generic[T], t.List[T]): """Filter list of object/dictionaries. For small, local datasets. *Experimental, unstable*. >>> query = QueryList( ... [ ... { ... "place": "Largo", ... "city": "Tampa", ... "state": "Florida", ... "foods": {"fruit": ["banana", "orange"], "breakfast": "cereal"}, ... }, ... { ... "place": "Chicago suburbs", ... "city": "Elmhurst", ... "state": "Illinois", ... "foods": {"fruit": ["apple", "cantelope"], "breakfast": "waffles"}, ... }, ... ] ... ) >>> query.filter(place="Chicago suburbs")[0]['city'] 'Elmhurst' >>> query.filter(place__icontains="chicago")[0]['city'] 'Elmhurst' >>> query.filter(foods__breakfast="waffles")[0]['city'] 'Elmhurst' >>> query.filter(foods__fruit__in="cantelope")[0]['city'] 'Elmhurst' >>> query.filter(foods__fruit__in="orange")[0]['city'] 'Tampa' >>> query.get(foods__fruit__in="orange")['city'] 'Tampa' """ data: "Sequence[T]" pk_key: t.Optional[str] def __init__(self, items: t.Optional[Iterable[T]] = None) -> None: super().__init__(items if items is not None else []) def items(self) -> t.List[T]: if self.pk_key is None: raise PKRequiredException() return [(getattr(item, self.pk_key), item) for item in self] def __eq__( self, other: object, # other: t.Union[ # "QueryList[T]", # t.List[Mapping[str, str]], # t.List[Mapping[str, int]], # t.List[Mapping[str, t.Union[str, Mapping[str, t.Union[List[str], str]]]]], # ], ) -> bool: data = other if not isinstance(self, list) or not isinstance(data, list): return False if len(self) == len(data): for a, b in zip(self, data): if isinstance(a, Mapping): a_keys = a.keys() if a.keys == b.keys(): for key in a_keys: if abs(a[key] - b[key]) > 1: return False else: if a != b: return False return True return False def filter( self, matcher: t.Optional[t.Union[t.Callable[[T], bool], T]] = None, **kwargs: t.Any, ) -> "QueryList[T]": """Filter list of objects.""" def filter_lookup(obj: t.Any) -> bool: for path, v in kwargs.items(): try: lhs, op = path.rsplit("__", 1) if op not in LOOKUP_NAME_MAP: raise OpNotFound(op=op) except ValueError: lhs = path op = "exact" assert op in LOOKUP_NAME_MAP path = lhs data = keygetter(obj, path) if data is None or not LOOKUP_NAME_MAP[op](data, v): return False return True if callable(matcher): _filter = matcher elif matcher is not None: def val_match(obj: t.Union[str, t.List[t.Any]]) -> bool: if isinstance(matcher, list): return obj in matcher else: return bool(obj == matcher) _filter = val_match else: _filter = filter_lookup return self.__class__(k for k in self if _filter(k)) def get( self, matcher: t.Optional[t.Union[t.Callable[[T], bool], T]] = None, default: t.Optional[t.Any] = no_arg, **kwargs: t.Any, ) -> t.Optional[T]: """Retrieve one object. Raises :exc:`MultipleObjectsReturned` if multiple objects found. Raises :exc:`ObjectDoesNotExist` if no object found, unless ``default`` stated. """ objs = self.filter(matcher=matcher, **kwargs) if len(objs) > 1: raise MultipleObjectsReturned() elif len(objs) == 0: if default == no_arg: raise ObjectDoesNotExist() return default return objs[0] ``` What do I need to do to solve these mypy warnings: ``` src/libtmux/_internal/query_list.py:307: error: List comprehension has incompatible type List[tuple[Any, T]]; expected List[T] [misc] src/libtmux/_internal/query_list.py:376: error: Incompatible types in assignment (expression has type "Callable[[str | list[Any]], bool]", variable has type "Callable[[T], bool]") [assignment] ```

Response:

Response To resolve the mypy warnings you're encountering, you need to address type compatibility and the correct use of generics in your `QueryList` class. Let's go through each error and discuss the solutions: ### Error 1: List comprehension has incompatible type List[tuple[Any, T]]; expected List[T] This error occurs in the `items` method. The method signature implies it returns a list of type `T`, but the actual implementation returns a list of tuples. To fix this, you should adjust either the method's return type to accurately reflect the returned data or change the method's functionality to match the expected return type. Since the method's purpose seems to be returning items along with their primary keys, adjusting the return type annotation is the appropriate solution. Solution: ```python def items(self) -> t.List[t.Tuple[Any, T]]: if self.pk_key is None: raise PKRequiredException() return [(getattr(item, self.pk_key), item) for item in self] ``` ### Error 2: Incompatible types in assignment This error indicates a mismatch in the expected callable type in your `filter` method. The `_filter` variable is expected to be a callable that takes an item of type `T` and returns a boolean. However, the assignment to `val_match` doesn't match this signature because `val_match` is defined to take either a string or a list of any type. This discrepancy arises due to the flexible nature of `matcher`, which can be a list, affecting the expected type of objects passed to `_filter`. Solution: To resolve this, you should ensure that the lambda or function assigned to `_filter` always matches the expected signature, `(T) -> bool`. If `matcher` can be of different types, handle the type checking inside the callable, ensuring it always accepts an argument of type `T` and returns a boolean. ```python if callable(matcher): _filter = matcher elif matcher is not None: def val_match(obj: T) -> bool: if isinstance(matcher, list): return obj in matcher # Assuming `obj` can be directly compared to items in `matcher` else: # You might need additional type checks here to ensure `obj` and `matcher` are compatible return obj == matcher _filter = val_match else: _filter = filter_lookup ``` In this solution, `val_match` assumes that `obj` can be compared directly to the items in `matcher` or to `matcher` itself. You might need to adjust this logic depending on the types `T` can represent, especially if `T` is not guaranteed to be comparable with the types in `matcher`. ### General Note on Type Checking and Generics When working with generics and type checking in Python, especially with complex class behaviors like `QueryList`, it's crucial to ensure that all type annotations accurately reflect the intended use and behaviors of your class methods. This includes being precise about the types of arguments, return values, and the handling of generic type variables throughout the class. Remember that mypy's type checking is based on the static analysis of these annotations, so discrepancies between the annotations and the actual code logic will lead to warnings or errors.
codecov[bot] commented 7 months ago

Codecov Report

Attention: 10 lines in your changes are missing coverage. Please review.

Comparison is base (32fff3e) 88.67% compared to head (cb9f465) 89.06%.

:exclamation: Current head cb9f465 differs from pull request most recent head 76bcfc8. Consider uploading reports for the commit 76bcfc8 to get more accurate results

Files Patch % Lines
tests/_internal/test_query_list.py 69.69% 8 Missing and 2 partials :warning:
Additional details and impacted files ```diff @@ Coverage Diff @@ ## master #515 +/- ## ========================================== + Coverage 88.67% 89.06% +0.39% ========================================== Files 35 36 +1 Lines 3522 3557 +35 Branches 488 502 +14 ========================================== + Hits 3123 3168 +45 + Misses 288 268 -20 - Partials 111 121 +10 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.