From 5686357c0ec4bc0edf7b7c1b9ee95c32db1df096 Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Fri, 12 Mar 2021 23:09:29 +0100 Subject: [PATCH] Implement and document ListProperty and DictProperty (#67) --- docs/source/user_docs.rst | 16 +++++- docs/source/wysdom.user_objects.rst | 16 ++++++ features/examples/modules/dict_module.py | 9 ++-- wysdom/__init__.py | 2 +- wysdom/user_objects/DictProperty.py | 69 ++++++++++++++++++++++++ wysdom/user_objects/ListProperty.py | 66 +++++++++++++++++++++++ wysdom/user_objects/__init__.py | 2 + 7 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 wysdom/user_objects/DictProperty.py create mode 100644 wysdom/user_objects/ListProperty.py diff --git a/docs/source/user_docs.rst b/docs/source/user_docs.rst index 05ac0cd..21ce46f 100644 --- a/docs/source/user_docs.rst +++ b/docs/source/user_docs.rst @@ -42,7 +42,7 @@ of :class:`~wysdom.UserProperty`, or an instance of :class:`~wysdom.Schema`:: class Person(UserObject, additional_properties=Address): ... - class Person(UserObject, additional_properties=SchemaDict[Vehicle]): + class Person(UserObject, additional_properties=SchemaDict(Vehicle)): ... @@ -183,12 +183,26 @@ function identically to a Python list (specifically a related_people = UserProperty(SchemaArray(Person)) + +From 0.3.0, you can use the data descriptor :class:`wysdom.ListProperty` +to aid readability by equivalently writing:: + + related_people = ListProperty(Person) + + For an dictionary, use the :class:`wysdom.SchemaDict`. Properties of this type function identically to a Python dict (specifically a :class:`collections.abc.MutableMapping` with keys of type :class:`str`):: related_people = UserProperty(SchemaDict(Person)) + +From 0.3.0, you can use the data descriptor :class:`wysdom.DictProperty` +to aid readability by equivalently writing:: + + related_people = DictProperty(Person) + + A `SchemaDict` is a special case of a :class:`wysdom.SchemaObject` with no named properties and with additional_properties set to the type specification that you supply. diff --git a/docs/source/wysdom.user_objects.rst b/docs/source/wysdom.user_objects.rst index 4af709c..0afed53 100644 --- a/docs/source/wysdom.user_objects.rst +++ b/docs/source/wysdom.user_objects.rst @@ -24,3 +24,19 @@ UserProperty :members: :undoc-members: :show-inheritance: + +ListProperty +---------------------------------------- + +.. autoclass:: wysdom.ListProperty + :members: + :undoc-members: + :show-inheritance: + +DictProperty +---------------------------------------- + +.. autoclass:: wysdom.DictProperty + :members: + :undoc-members: + :show-inheritance: diff --git a/features/examples/modules/dict_module.py b/features/examples/modules/dict_module.py index a97072c..a24b9dd 100644 --- a/features/examples/modules/dict_module.py +++ b/features/examples/modules/dict_module.py @@ -1,7 +1,7 @@ from typing import Dict, List from enum import Enum -from wysdom import UserObject, UserProperty, SchemaArray, SchemaDict, key +from wysdom import UserObject, UserProperty, ListProperty, DictProperty, key class Color(Enum): @@ -31,8 +31,9 @@ class Person(UserObject): current_address: Address = UserProperty( Address, default_function=lambda person: person.previous_addresses[0]) - previous_addresses: List[Address] = UserProperty(SchemaArray(Address)) - vehicles: Dict[str, Vehicle] = UserProperty( - SchemaDict(Vehicle, key_pattern=r"^[a-f0-9]{6}$"), + previous_addresses: List[Address] = ListProperty(Address) + vehicles: Dict[str, Vehicle] = DictProperty( + Vehicle, + key_pattern=r"^[a-f0-9]{6}$", default={}, persist_defaults=True) diff --git a/wysdom/__init__.py b/wysdom/__init__.py index ebca75e..eab2f9e 100644 --- a/wysdom/__init__.py +++ b/wysdom/__init__.py @@ -4,6 +4,6 @@ from . import dom from .base_schema import Schema, SchemaType, SchemaNone, SchemaPrimitive, SchemaAnything, SchemaConst from .object_schema import SchemaArray, SchemaDict, SchemaAnyOf, SchemaObject -from .user_objects import UserProperty, UserObject, properties +from .user_objects import UserObject, UserProperty, ListProperty, DictProperty, properties from .mixins import ReadsJSON, ReadsYAML, RegistersSubclasses diff --git a/wysdom/user_objects/DictProperty.py b/wysdom/user_objects/DictProperty.py new file mode 100644 index 0000000..369c82b --- /dev/null +++ b/wysdom/user_objects/DictProperty.py @@ -0,0 +1,69 @@ +from typing import Type, Any, Union, Optional, Callable + +from ..base_schema import Schema +from ..object_schema import SchemaDict + +from .UserProperty import UserProperty + + +class DictProperty(UserProperty): + """ + A data descriptor for creating attributes in user-defined subclasses + of :class:`~.wysdom.user_objects.UserObject` with a property_type + of :class:`~.wysdom.object_schema.SchemaDict`. + + :param items: The permitted data type or schema for the properties of the underlying + :class:`~.wysdom.object_schema.SchemaDict`. Must be one of: + + A primitive Python type (str, int, bool, float) + A subclass of :class:`~.wysdom.user_objects.UserObject`. + An instance of :class:`~.wysdom.base_schema.Schema`. + + :param optional: Determines whether this property is optional in the underlying + data object. If default or default_function are set, this + will default to True, otherwise False. + + :param name: The name of this property in the underlying + data object. If not provided, this defaults to + the name of the attribute on the :class:`~.wysdom.user_objects.UserObject` + instance that owns the property. + + :param default: A static value which provides a default value + for this property. Cannot be set in conjunction + with `default_function`. + + :param default_function: A function which provides a default value + for this property. The function must have a + single positional argument, `self`, which is + passed the :class:`~.wysdom.user_objects.UserObject` instance that + owns the property. Cannot be set in conjunction + with `default`. + + :param persist_defaults: If this property is set to True and a UserProperty has either the + `default` or `default_function` property, when the UserProperty returns + a default value that value will also be explicitly stored in the underlying + data object. This is often desirable behavior if the UserProperty + returns another object and your code expects it to return the same + object instance each time it is accessed. + + :param key_pattern: A regex pattern to validate the keys of the dictionary against. + """ + + def __init__( + self, + items: Union[Type, Schema], + optional: Optional[bool] = None, + name: Optional[str] = None, + default: Optional[Any] = None, + default_function: Optional[Callable] = None, + persist_defaults: Optional[bool] = None, + key_pattern: Optional[str] = None + ) -> None: + super().__init__( + property_type=SchemaDict(items, key_pattern=key_pattern), + optional=optional, + name=name, + default=default, + default_function=default_function, + persist_defaults=persist_defaults + ) diff --git a/wysdom/user_objects/ListProperty.py b/wysdom/user_objects/ListProperty.py new file mode 100644 index 0000000..8b767d0 --- /dev/null +++ b/wysdom/user_objects/ListProperty.py @@ -0,0 +1,66 @@ +from typing import Type, Any, Union, Optional, Callable + +from ..base_schema import Schema +from ..object_schema import SchemaArray + +from .UserProperty import UserProperty + + +class ListProperty(UserProperty): + """ + A data descriptor for creating attributes in user-defined subclasses + of :class:`~.wysdom.user_objects.UserObject` with a property_type + of :class:`~.wysdom.object_schema.SchemaArray`. + + :param items: The permitted data type or schema for the items of the underlying + :class:`~.wysdom.object_schema.SchemaArray`. Must be one of: + + A primitive Python type (str, int, bool, float) + A subclass of :class:`~.wysdom.user_objects.UserObject`. + An instance of :class:`~.wysdom.base_schema.Schema`. + + :param optional: Determines whether this property is optional in the underlying + data object. If default or default_function are set, this + will default to True, otherwise False. + + :param name: The name of this property in the underlying + data object. If not provided, this defaults to + the name of the attribute on the :class:`~.wysdom.user_objects.UserObject` + instance that owns the property. + + :param default: A static value which provides a default value + for this property. Cannot be set in conjunction + with `default_function`. + + :param default_function: A function which provides a default value + for this property. The function must have a + single positional argument, `self`, which is + passed the :class:`~.wysdom.user_objects.UserObject` instance that + owns the property. Cannot be set in conjunction + with `default`. + + :param persist_defaults: If this property is set to True and a UserProperty has either the + `default` or `default_function` property, when the UserProperty returns + a default value that value will also be explicitly stored in the underlying + data object. This is often desirable behavior if the UserProperty + returns another object and your code expects it to return the same + object instance each time it is accessed. + """ + + def __init__( + self, + items: Union[Type, Schema], + optional: Optional[bool] = None, + name: Optional[str] = None, + default: Optional[Any] = None, + default_function: Optional[Callable] = None, + persist_defaults: Optional[bool] = None + ) -> None: + super().__init__( + property_type=SchemaArray(items), + optional=optional, + name=name, + default=default, + default_function=default_function, + persist_defaults=persist_defaults + ) diff --git a/wysdom/user_objects/__init__.py b/wysdom/user_objects/__init__.py index 701ebda..3a103ac 100644 --- a/wysdom/user_objects/__init__.py +++ b/wysdom/user_objects/__init__.py @@ -1,3 +1,5 @@ from .UserObject import UserObject, UserProperties from .UserProperty import UserProperty +from .ListProperty import ListProperty +from .DictProperty import DictProperty from .functions import properties