Skip to content

Commit

Permalink
Enhance setter typing and documentation.
Browse files Browse the repository at this point in the history
1. Use TypeVar also with value passed to the setter methods. I don't
   like one letter type names T, V, A too much, but that seems to be
   a convention and it also keeps signature lengths reasonable.

2. Add docstrings.

Related to #4570.
  • Loading branch information
pekkaklarck committed Mar 22, 2023
1 parent 810afc8 commit 7fb508c
Showing 1 changed file with 41 additions and 7 deletions.
48 changes: 41 additions & 7 deletions src/robot/utils/setter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,76 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Callable, Generic, overload, TypeVar
from typing import Callable, Generic, overload, TypeVar, Type, Union


T = TypeVar('T')
V = TypeVar('V')
A = TypeVar('A')


class setter(Generic[V]):
class setter(Generic[T, V, A]):
"""Modify instance attributes only when they are set, not when they are get.
def __init__(self, method: Callable[[T, Any], V]):
Usage::
@setter
def source(self, source: str|Path) -> Path:
return source if isinstance(source, Path) else Path(source)
The setter method is called when the attribute is assigned like::
instance.source = 'example.txt'
and the returned value is stored in the instance in an attribute like
``_setter__source``. When the attribute is accessed, the stored value is
returned.
The above example is equivalent to using the standard ``property`` as
follows. The main benefit of using ``setter`` is that it avoids a dummy
getter method::
@property
def source(self) -> Path:
return self._source
@source.setter
def source(self, source: src|Path):
self._source = source if isinstance(source, Path) else Path(source)
When using ``setter`` with ``__slots__``, the special ``_setter__xxx``
attributes needs to be added to ``__slots__`` as well. The provided
:class:`SetterAwareType` metaclass can take care of that automatically.
"""

def __init__(self, method: Callable[[T, V], A]):
self.method = method
self.attr_name = '_setter__' + method.__name__
self.__doc__ = method.__doc__

@overload
def __get__(self, instance: None, owner: 'type[T]') -> 'setter':
def __get__(self, instance: None, owner: Type[T]) -> 'setter':
...

@overload
def __get__(self, instance: T, owner: 'type[T]') -> V:
def __get__(self, instance: T, owner: Type[T]) -> A:
...

def __get__(self, instance: 'T|None', owner: 'type[T]') -> 'V|setter':
def __get__(self, instance: Union[T, None], owner: Type[T]) -> Union[A, 'setter']:
if instance is None:
return self
try:
return getattr(instance, self.attr_name)
except AttributeError:
raise AttributeError(self.method.__name__)

def __set__(self, instance: T, value: Any):
def __set__(self, instance: T, value: V):
if instance is not None:
setattr(instance, self.attr_name, self.method(instance, value))


class SetterAwareType(type):
"""Metaclass for adding attributes used by :class:`setter` to ``__slots__``."""

def __new__(cls, name, bases, dct):
if '__slots__' in dct:
Expand Down

0 comments on commit 7fb508c

Please sign in to comment.