Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(commons): deduplicate logic to keep behavior consistent [#14] #15

Merged
merged 4 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Changes for version 0.23.10
Major Changes
^^^^^^^^^^^^^

- None
- fix(commons): deduplicate logic to keep behavior consistent [#14]


Minor Changes
Expand Down
6 changes: 2 additions & 4 deletions aiootp/commons/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,8 @@ def __setitem__(self, name: str, value: t.Any, /) -> None:
"""
if name in self:
raise Issue.cant_reassign_attribute(name)
elif name.__class__ is str:
object.__setattr__(self, name, value)
else:
self.__dict__[name] = value

super().__setitem__(name, value)

def __delitem__(self, name: str, /) -> None:
"""
Expand Down
29 changes: 13 additions & 16 deletions aiootp/commons/slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ def __init__(
dictionary offers flexibility, though the interplay with slots
can cause problems. This initializer avoids setting the names
declared in `__slots__` within the a potential instance dict.

This is a general & flexible initializer. Subclasses can improve
their performance & documentation by being more specific, ie:

def __init__(self, a: int, b: int) -> None:
self.a = a
self.b = b
"""
# fmt: off
slots = self._slots_set # flexible init. not great
for name, value in {**dict(mapping), **kw}.items(): # performance. subclasses
if name in slots: # should prefer specificity
object.__setattr__(self, name, value) # ie. self.a = a
else: # self.b = b
self.__dict__[name] = value # ...
# fmt: on
self.update(mapping, **kw)
rmlibre marked this conversation as resolved.
Show resolved Hide resolved

def __dir__(self, /) -> t.List[t.Hashable]:
"""
Expand Down Expand Up @@ -138,10 +138,9 @@ def __contains__(self, name: t.Hashable, /) -> bool:
"""
Returns a bool of `name`'s membership in the instance.
"""
if name.__class__ is str:
return hasattr(self, name)
else:
return name in getattr(self, "__dict__", ())
in_slots = name in self._slots_set
in_dict = name in getattr(self, "__dict__", ())
return (in_slots and hasattr(self, name)) or in_dict

def __setattr__(self, name: str, value: t.Any, /) -> None:
"""
Expand Down Expand Up @@ -322,10 +321,8 @@ def __setitem__(self, name: str, value: t.Any, /) -> None:
"""
if name in self:
raise Issue.cant_reassign_attribute(name)
elif name.__class__ is str:
object.__setattr__(self, name, value)
else:
self.__dict__[name] = value

super().__setitem__(name, value)

def __delitem__(self, name: str, /) -> None:
"""
Expand Down
11 changes: 2 additions & 9 deletions aiootp/commons/typed_slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,7 @@ def __setitem__(self, name: str, value: t.Any, /) -> None:
Sets the attribute if the type is correct.
"""
self._validate_type(name, value)
if name.__class__ is str:
object.__setattr__(self, name, value)
else:
self.__dict__[name] = value
super().__setitem__(name, value)


class OpenTypedSlots(TypedSlots):
Expand Down Expand Up @@ -210,11 +207,7 @@ def __setitem__(self, name: str, value: t.Any, /) -> None:
if name in self:
raise Issue.cant_reassign_attribute(name)

self._validate_type(name, value)
if name.__class__ is str:
object.__setattr__(self, name, value)
else:
self.__dict__[name] = value
super().__setitem__(name, value)

def __delitem__(self, name: str, /) -> None:
"""
Expand Down
16 changes: 16 additions & 0 deletions tests/test_namespace_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,22 @@ async def test_false_contains_logic(self) -> None:
assert randoms.token_bits(32) not in obj
assert None not in obj

async def test_frozen_state_is_enforced(self) -> None:
problem = ( # fmt: skip
"A mutation was allowed on a frozen object."
)
if not self._frozen:
return

obj = self._type(self._items)
with Ignore(PermissionError, if_else=violation(problem)):
obj.__init__(self._items)

obj = self._type(self._items)
for name, value in self._items.items():
with Ignore(PermissionError, if_else=violation(problem)):
obj[name] = value


class BaseTypedSubclassDefinitionsTests(BaseVariableHoldingClassTests):
async def test_slots_contains_all_names_with_declared_types(
Expand Down
Loading