Skip to content

Commit

Permalink
Merge ea87605 into 562dddc
Browse files Browse the repository at this point in the history
  • Loading branch information
PythonFZ committed Dec 14, 2022
2 parents 562dddc + ea87605 commit 1cceed4
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 9 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "zninit"
version = "0.1.6"
version = "0.1.7"
description = "Descriptor based dataclass implementation"
authors = ["zincwarecode <zincwarecode@gmail.com>"]
license = "Apache-2.0"
Expand Down
33 changes: 33 additions & 0 deletions tests/test_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,36 @@ def test_get_desc_values():

assert desc1_1.__get__(self) == "Hello"
assert desc1_2.__get__(self) == "World"


class FrozenExample:
"""ZnInit Frozen Descriptor."""

value = Descriptor(frozen=True)


def test_frozen_descriptor():
"""Test a frozen descriptor."""
example = FrozenExample()
example.value = 42
with pytest.raises(TypeError):
example.value = 25
assert example.value == 42

# Running twice is a test.
example = FrozenExample()
example.value = 42
with pytest.raises(TypeError):
example.value = 25
assert example.value == 42


class WithMetadata:
"""ZnInit Descriptor with metadata."""

value = Descriptor(metadata={"foo": "bar"})


def test_descriptor_metadata():
"""Test a descriptor with metadata."""
assert WithMetadata.value.metadata["foo"] == "bar"
29 changes: 29 additions & 0 deletions tests/test_i_zninit.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""General 'ZnInit' integration tests."""
import pytest

import zninit


Expand All @@ -14,10 +16,37 @@ class ExampleCls(zninit.ZnInit, metaclass=GetItemMeta):
"""Example 'ZnInit' with metaclass."""

parameter = zninit.Descriptor()
frozen_parameter = zninit.Descriptor(None, frozen=True)


def test_ExampleCls():
"""Test 'ZnInit' with a metaclass."""
example = ExampleCls(parameter=25)
assert example.parameter == 25
assert ExampleCls[42] == 42


def test_frozen_parameter():
"""Test frozen parameter."""
example = ExampleCls(parameter=18, frozen_parameter=42)
assert example.frozen_parameter == 42
with pytest.raises(TypeError):
example.frozen_parameter = 43

example = ExampleCls(parameter=18, frozen_parameter=42)
assert example.frozen_parameter == 42
with pytest.raises(TypeError):
example.frozen_parameter = 43


def test_frozen_parameter_default():
"""Test frozen_parameter with default value."""
example = ExampleCls(parameter=18)
assert example.frozen_parameter is None
with pytest.raises(TypeError):
example.frozen_parameter = 43

example = ExampleCls(parameter=18, frozen_parameter=42)
assert example.frozen_parameter == 42
with pytest.raises(TypeError):
example.frozen_parameter = 43
2 changes: 1 addition & 1 deletion tests/test_zninit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

def test_version():
"""Test the installed version."""
assert zninit.__version__ == "0.1.6"
assert zninit.__version__ == "0.1.7"
27 changes: 20 additions & 7 deletions zninit/descriptor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import functools
import sys
import typing
import weakref

with contextlib.suppress(ImportError):
import typeguard
Expand All @@ -18,7 +19,7 @@ class Empty: # pylint: disable=too-few-public-methods
"""


class Descriptor:
class Descriptor: # pylint: disable=too-many-instance-attributes
"""Simple Python Descriptor that allows adding.
This class allows to add metadata to arbitrary class arguments:
Expand Down Expand Up @@ -53,6 +54,8 @@ def __init__(
name="",
use_repr: bool = True,
check_types: bool = False,
metadata: dict = None,
frozen: bool = False,
): # pylint: disable=too-many-arguments
"""Define a Descriptor object.
Expand All @@ -71,13 +74,20 @@ def __init__(
descriptor should be used in the __repr__ string.
check_types: bool, default=False
Check the type when using __set__ against the type annotation.
frozen: bool, default=False
Freeze the attribute after the first __set__ call.
metadata: dict, default=None
additional metadata for the descriptor.
"""
self._default = default
self._owner = owner
self._instance = instance
self._name = name
self.use_repr = use_repr
self.check_types = check_types
self.metadata = metadata or {}
self.frozen = frozen
self._frozen = weakref.WeakKeyDictionary()
if check_types and ("typeguard" not in sys.modules):
raise ImportError(
"Need to install 'pip install zninit[typeguard]' for type checking."
Expand Down Expand Up @@ -117,12 +127,11 @@ def annotation(self):
except AttributeError:
annotations = {}

if self.check_types:
if self.name not in annotations:
raise KeyError(
f"Could not find 'annotation' for {self.name} in '{self.owner}' with"
" 'check_types=True'"
)
if self.check_types and self.name not in annotations:
raise KeyError(
f"Could not find 'annotation' for {self.name} in '{self.owner}' with"
" 'check_types=True'"
)
return annotations.get(self.name)

def __set_name__(self, owner, name):
Expand All @@ -149,12 +158,16 @@ def __get__(self, instance, owner=None):

def __set__(self, instance, value):
"""Save value to instance.__dict__."""
if self._frozen.get(instance, False):
raise TypeError(f"Frozen attribute '{self.name}' can not be changed.")
if self.check_types:
typeguard.check_type(
argname=self.name, value=value, expected_type=self.annotation
)
self._instance = instance
instance.__dict__[self.name] = value
if self.frozen:
self._frozen[instance] = True


DescriptorTypeT = typing.TypeVar("DescriptorTypeT", bound=Descriptor)
Expand Down

0 comments on commit 1cceed4

Please sign in to comment.