Skip to content

Commit

Permalink
Integrate typing into source
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartin16 committed Oct 9, 2023
1 parent 5a988e6 commit b84d0e1
Show file tree
Hide file tree
Showing 39 changed files with 2,105 additions and 3,429 deletions.
27 changes: 6 additions & 21 deletions .pre-commit-config.yaml
Expand Up @@ -52,32 +52,17 @@ repos:
rev: v1.5.1
hooks:
- id: mypy
files: '.*\.pyi'
files: ^src/
additional_dependencies:
- types-requests
- types-six
- packaging
args:
- --strict
- --disallow-any-unimported
- --disallow-any-expr
- --disallow-any-decorated
# - --disallow-any-unimported
# - --disallow-any-expr
# - --disallow-any-decorated
- --warn-unreachable
- --warn-unused-ignores
- --warn-redundant-casts
- --strict-optional
- --show-traceback

- repo: local
hooks:
- id: stubtest
name: mypy.stubtest
language: system
entry: stubtest
args:
- qbittorrentapi
- --allowlist=tests/_resources/mypy_stubtest_allowlist.txt
pass_filenames: false
types_or:
- python
- text
files: '.*\.pyi?'
- --implicit-reexport
98 changes: 51 additions & 47 deletions src/qbittorrentapi/_attrdict.py
Expand Up @@ -27,16 +27,26 @@
Since AttrDict is abandoned, I've consolidated the code here for future use.
AttrMap and AttrDefault are left for posterity but commented out.
"""
from __future__ import annotations

from abc import ABCMeta
from abc import abstractmethod
from collections.abc import Mapping
from collections.abc import MutableMapping
from collections.abc import Sequence
from re import match as re_match
from typing import Any
from typing import Dict
from typing import Mapping
from typing import MutableMapping
from typing import Sequence
from typing import TypeVar

K = TypeVar("K")
V = TypeVar("V")
T = TypeVar("T")
KOther = TypeVar("KOther")
VOther = TypeVar("VOther")

def merge(left, right):

def merge(left: Mapping[K, V], right: Mapping[K, V]) -> dict[K, V]:
"""
Merge two mappings objects together, combining overlapping Mappings, and
favoring right-values.
Expand Down Expand Up @@ -64,17 +74,17 @@ def merge(left, right):
left_value = left[key]
right_value = right[key]

if isinstance(left_value, Mapping) and isinstance(
right_value, Mapping
): # recursive merge
merged[key] = merge(left_value, right_value)
else: # overwrite with right value
# recursive merge
if isinstance(left_value, Mapping) and isinstance(right_value, Mapping):
merged[key] = merge(left_value, right_value) # type: ignore
# overwrite with right value
else:
merged[key] = right_value

return merged


class Attr(Mapping, metaclass=ABCMeta):
class Attr(Mapping[K, V], metaclass=ABCMeta):
"""
A ``mixin`` class for a mapping that allows for attribute-style access of
values.
Expand All @@ -98,12 +108,13 @@ class Attr(Mapping, metaclass=ABCMeta):
"""

@abstractmethod
def _configuration(self):
def _configuration(self) -> Any:
"""All required state for building a new instance with the same
settings as the current object."""

@classmethod
def _constructor(cls, mapping, configuration):
@abstractmethod
def _constructor(cls, mapping: Mapping[K, V], configuration: Any) -> Attr[K, V]:
"""
A standardized constructor used internally by Attr.
Expand All @@ -112,9 +123,8 @@ def _constructor(cls, mapping, configuration):
that will allow nested assignment (e.g., attr.foo.bar = baz)
configuration: The return value of Attr._configuration
"""
raise NotImplementedError("You need to implement this")

def __call__(self, key):
def __call__(self, key: K) -> Attr[K, V]:
"""
Dynamically access a key-value pair.
Expand All @@ -125,25 +135,21 @@ def __call__(self, key):
"""
if key not in self:
raise AttributeError(
"'{cls} instance has no attribute '{name}'".format(
cls=self.__class__.__name__, name=key
)
f"'{self.__class__.__name__} instance has no attribute '{key}'"
)

return self._build(self[key])

def __getattr__(self, key):
def __getattr__(self, key: Any) -> Any:
"""Access an item as an attribute."""
if key not in self or not self._valid_name(key):
raise AttributeError(
"'{cls}' instance has no attribute '{name}'".format(
cls=self.__class__.__name__, name=key
)
f"'{self.__class__.__name__}' instance has no attribute '{key}'"
)

return self._build(self[key])

def __add__(self, other):
def __add__(self, other: Mapping[K, V]) -> Attr[K, V]:
"""
Add a mapping to this Attr, creating a new, merged Attr.
Expand All @@ -152,11 +158,11 @@ def __add__(self, other):
NOTE: Addition is not commutative. a + b != b + a.
"""
if not isinstance(other, Mapping):
return NotImplemented
return NotImplemented # type: ignore

return self._constructor(merge(self, other), self._configuration())

def __radd__(self, other):
def __radd__(self, other: Mapping[K, V]) -> Attr[K, V]:
"""
Add this Attr to a mapping, creating a new, merged Attr.
Expand All @@ -165,11 +171,11 @@ def __radd__(self, other):
NOTE: Addition is not commutative. a + b != b + a.
"""
if not isinstance(other, Mapping):
return NotImplemented
return NotImplemented # type: ignore

return self._constructor(merge(other, self), self._configuration())

def _build(self, obj):
def _build(self, obj: Any) -> AttrDict[K, V]:
"""
Conditionally convert an object to allow for recursive mapping access.
Expand All @@ -184,14 +190,13 @@ def _build(self, obj):
obj = self._constructor(obj, self._configuration())
elif isinstance(obj, Sequence) and not isinstance(obj, (str, bytes)):
sequence_type = getattr(self, "_sequence_type", None)

if sequence_type:
obj = sequence_type(self._build(element) for element in obj)

return obj
return obj # type: ignore

@classmethod
def _valid_name(cls, key):
def _valid_name(cls, key: Any) -> bool:
"""
Check whether a key is a valid attribute name.
Expand All @@ -202,31 +207,30 @@ def _valid_name(cls, key):
those would be 'get', 'items', 'keys', 'values', 'mro', and
'register').
"""
return (
return bool(
isinstance(key, str)
and re_match("^[A-Za-z][A-Za-z0-9_]*$", key)
and not hasattr(cls, key)
)


class MutableAttr(Attr, MutableMapping, metaclass=ABCMeta):
"""A ``mixin`` class for a mapping that allows for attribute-style access of
values."""
class MutableAttr(Attr[K, V], MutableMapping[K, V], metaclass=ABCMeta):
"""A ``mixin`` mapping class that allows for attribute-style access of values."""

def _setattr(self, key, value):
def _setattr(self, key: str, value: Any) -> None:
"""Add an attribute to the object, without attempting to add it as a
key to the mapping."""
super().__setattr__(key, value)

def __setattr__(self, key, value):
def __setattr__(self, key: str, value: V) -> None:
"""
Add an attribute.
key: The name of the attribute
value: The attributes contents
"""
if self._valid_name(key):
self[key] = value
self[key] = value # type: ignore
elif getattr(self, "_allow_invalid_attributes", True):
super().__setattr__(key, value)
else:
Expand All @@ -236,19 +240,19 @@ def __setattr__(self, key, value):
)
)

def _delattr(self, key):
def _delattr(self, key: str) -> None:
"""Delete an attribute from the object, without attempting to remove it
from the mapping."""
super().__delattr__(key)

def __delattr__(self, key, force=False):
def __delattr__(self, key: str, force: bool = False) -> None: # type: ignore
"""
Delete an attribute.
key: The name of the attribute
"""
if self._valid_name(key):
del self[key]
del self[key] # type: ignore
elif getattr(self, "_allow_invalid_attributes", True):
super().__delattr__(key)
else:
Expand All @@ -259,35 +263,35 @@ def __delattr__(self, key, force=False):
)


class AttrDict(dict, MutableAttr):
class AttrDict(Dict[K, V], MutableAttr[K, V]):
"""A dict that implements MutableAttr."""

def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)

self._setattr("_sequence_type", tuple)
self._setattr("_allow_invalid_attributes", False)

def _configuration(self):
def _configuration(self) -> Any:
"""The configuration for an attrmap instance."""
return self._sequence_type

def __getstate__(self):
def __getstate__(self) -> Any:
"""Serialize the object."""
return self.copy(), self._sequence_type, self._allow_invalid_attributes

def __setstate__(self, state):
def __setstate__(self, state: Any) -> None:
"""Deserialize the object."""
mapping, sequence_type, allow_invalid_attributes = state
self.update(mapping)
self._setattr("_sequence_type", sequence_type)
self._setattr("_allow_invalid_attributes", allow_invalid_attributes)

def __repr__(self):
return f"AttrDict({super().__repr__()})"
def __repr__(self) -> str:
return f"{self.__class__.__name__}({super().__repr__()})"

@classmethod
def _constructor(cls, mapping, configuration):
def _constructor(cls, mapping: Mapping[K, V], configuration: Any) -> AttrDict[K, V]:
"""A standardized constructor."""
attr = cls(mapping)
attr._setattr("_sequence_type", configuration)
Expand Down
37 changes: 0 additions & 37 deletions src/qbittorrentapi/_attrdict.pyi

This file was deleted.

29 changes: 0 additions & 29 deletions src/qbittorrentapi/_types.pyi

This file was deleted.

0 comments on commit b84d0e1

Please sign in to comment.