From 6d72421638ad25a209ca210c58dc81d2f569b95c Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Sat, 23 May 2020 22:21:14 +0200 Subject: [PATCH 1/6] Update docstrings for base_schema and user_object_schema --- wysdom/base_schema/Schema.py | 4 ++-- wysdom/base_schema/SchemaAnything.py | 3 +++ wysdom/base_schema/SchemaConst.py | 3 +++ wysdom/base_schema/SchemaNone.py | 3 +++ wysdom/base_schema/SchemaPrimitive.py | 6 ++++++ wysdom/base_schema/SchemaType.py | 6 ++++++ wysdom/object_schema/SchemaAnyOf.py | 8 ++++++++ wysdom/object_schema/SchemaArray.py | 9 +++++++++ wysdom/object_schema/SchemaDict.py | 9 +++++++++ wysdom/object_schema/SchemaObject.py | 14 ++++++++++++++ wysdom/object_schema/resolve_arg_to_type.py | 9 +++++++++ wysdom/user_objects/UserProperty.py | 4 ++-- 12 files changed, 74 insertions(+), 4 deletions(-) diff --git a/wysdom/base_schema/Schema.py b/wysdom/base_schema/Schema.py index 1b71e96..04a27cb 100644 --- a/wysdom/base_schema/Schema.py +++ b/wysdom/base_schema/Schema.py @@ -40,8 +40,8 @@ def __call__( @property def schema_ref_name(self) -> Optional[str]: """ - A unique reference name to use when this scheme is referred to by other schemas. - If this returns a string, references to this scheme will use the $ref keyword without + A unique reference name to use when this schema is referred to by other schemas. + If this returns a string, references to this schema will use the $ref keyword without replicating the full schema. If this property returns None, the full contents of the schema will be used. diff --git a/wysdom/base_schema/SchemaAnything.py b/wysdom/base_schema/SchemaAnything.py index 0931af9..6f0bcf1 100644 --- a/wysdom/base_schema/SchemaAnything.py +++ b/wysdom/base_schema/SchemaAnything.py @@ -4,6 +4,9 @@ class SchemaAnything(Schema): + """ + A schema where any valid JSON will be accepted. + """ @property def jsonschema_definition(self) -> Dict[str, Any]: diff --git a/wysdom/base_schema/SchemaConst.py b/wysdom/base_schema/SchemaConst.py index c00fb8e..8f4a17e 100644 --- a/wysdom/base_schema/SchemaConst.py +++ b/wysdom/base_schema/SchemaConst.py @@ -4,6 +4,9 @@ class SchemaConst(Schema): + """ + A schema requiring a string constant. + """ value: str = None diff --git a/wysdom/base_schema/SchemaNone.py b/wysdom/base_schema/SchemaNone.py index d570f98..feb0e4d 100644 --- a/wysdom/base_schema/SchemaNone.py +++ b/wysdom/base_schema/SchemaNone.py @@ -2,5 +2,8 @@ class SchemaNone(SchemaType): + """ + A schema requiring a null value. + """ type_name: str = 'null' diff --git a/wysdom/base_schema/SchemaPrimitive.py b/wysdom/base_schema/SchemaPrimitive.py index 1d9caa1..13f4179 100644 --- a/wysdom/base_schema/SchemaPrimitive.py +++ b/wysdom/base_schema/SchemaPrimitive.py @@ -4,6 +4,12 @@ class SchemaPrimitive(SchemaType): + """ + A schema requiring a primitive variable type + + :param python_type: The primitive Python type expected by this schema + """ + JSON_TYPES: Dict[Type, str] = { str: 'string', bool: 'boolean', diff --git a/wysdom/base_schema/SchemaType.py b/wysdom/base_schema/SchemaType.py index 8e294e8..2db8862 100644 --- a/wysdom/base_schema/SchemaType.py +++ b/wysdom/base_schema/SchemaType.py @@ -8,10 +8,16 @@ class SchemaType(Schema, ABC): + """ + Abstract base class for any schema with the "type" keyword + """ @property @abstractmethod def type_name(self) -> str: + """ + :return: Value for the JSON schema "type" keyword + """ pass @property diff --git a/wysdom/object_schema/SchemaAnyOf.py b/wysdom/object_schema/SchemaAnyOf.py index 465da6d..0338363 100644 --- a/wysdom/object_schema/SchemaAnyOf.py +++ b/wysdom/object_schema/SchemaAnyOf.py @@ -6,6 +6,14 @@ class SchemaAnyOf(Schema): + """ + A schema requiring a match with any of the permitted schemas supplied. + + :param allowed_schemas: A list (or other Iterable) containing the permitted + `Schema` objects. + :param schema_ref_name: An optional unique reference name to use when this schema + is referred to by other schemas. + """ allowed_schemas: Tuple[Schema] = None schema_ref_name: Optional[str] = None diff --git a/wysdom/object_schema/SchemaArray.py b/wysdom/object_schema/SchemaArray.py index b3b71b7..5589b25 100644 --- a/wysdom/object_schema/SchemaArray.py +++ b/wysdom/object_schema/SchemaArray.py @@ -7,6 +7,15 @@ class SchemaArray(Schema): + """ + A schema specifying an array(corresponding to a Python list) + + :param items: The permitted data type or schema for the items of this array. Must + be one of: + A primitive Python type (str, int, bool, float) + A subclass of `UserObject` + An instance of `Schema` + """ items: Schema = None diff --git a/wysdom/object_schema/SchemaDict.py b/wysdom/object_schema/SchemaDict.py index 8c9a691..a60113e 100644 --- a/wysdom/object_schema/SchemaDict.py +++ b/wysdom/object_schema/SchemaDict.py @@ -8,6 +8,15 @@ class SchemaDict(SchemaObject): + """ + A schema specifying an object with dynamic properties (corresponding to a Python dict) + + :param items: The permitted data type or schema for the properties of this object. Must + be one of: + A primitive Python type (str, int, bool, float) + A subclass of `UserObject` + An instance of `Schema` + """ def __init__( self, diff --git a/wysdom/object_schema/SchemaObject.py b/wysdom/object_schema/SchemaObject.py index 72ad5a7..2d0fb69 100644 --- a/wysdom/object_schema/SchemaObject.py +++ b/wysdom/object_schema/SchemaObject.py @@ -6,6 +6,20 @@ class SchemaObject(SchemaType): + """ + A schema specifying an object with named properties. + + :param properties: A dictionary of `Schema` object defining the expected + names and types of this object's properties. + :param additional_properties: Defines whether this object permits additional + dynamically-named properties. Can be True or False, or + can be set to a specific `Schema` to restrict the permitted + types of any additional properties. + :param object_type: A custom object type to use when creating object instances + from this schema. + :param schema_ref_name: An optional unique reference name to use when this schema + is referred to by other schemas. + """ type_name: str = "object" properties: Optional[Dict[str, Schema]] = None diff --git a/wysdom/object_schema/resolve_arg_to_type.py b/wysdom/object_schema/resolve_arg_to_type.py index 56b1915..75e97e1 100644 --- a/wysdom/object_schema/resolve_arg_to_type.py +++ b/wysdom/object_schema/resolve_arg_to_type.py @@ -13,6 +13,15 @@ def resolve_arg_to_schema( arg: Union[Type, Schema] ) -> Schema: + """ + Resolve an argument of heterogeneous type to a `Schema` instance. + + :param arg: Argument to resolve to a Schema. Must be one of: + A primitive Python type (str, int, bool, float) + A subclass of `UserObject` + An instance of `Schema`. + :return: A `Schema` instance corresponding to the supplied argument. + """ if inspect.isclass(arg): if issubclass(arg, DOMElement): return arg.__json_schema__() diff --git a/wysdom/user_objects/UserProperty.py b/wysdom/user_objects/UserProperty.py index b2f202c..6580370 100644 --- a/wysdom/user_objects/UserProperty.py +++ b/wysdom/user_objects/UserProperty.py @@ -15,7 +15,7 @@ class UserProperty(object): be one of: A primitive Python type (str, int, bool, float) A subclass of `UserObject` - An instance of `JSONSchema` + An instance of `Schema` :param name: The name of this property in the underlying data object. If not provided, this defaults to @@ -55,7 +55,7 @@ def __get__( ) -> Any: if instance is None: raise AttributeError( - "JSONSchemaProperty is not valid as a class descriptor") + "UserProperty is not valid as a class data descriptor") if self.name not in instance: if self.default_function: instance[self.name] = self.default_function(instance) From 0cebd41e3c0be50c17daeb3d0deb3b0601271fe7 Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Mon, 25 May 2020 12:30:32 +0200 Subject: [PATCH 2/6] Integrate output from sphinx-autodoc --- docs/index.rst | 326 +-------------------------- docs/source/api_reference.rst | 22 ++ docs/source/user_docs.rst | 323 ++++++++++++++++++++++++++ docs/source/wysdom.base_schema.rst | 62 +++++ docs/source/wysdom.dom.rst | 62 +++++ docs/source/wysdom.mixins.rst | 38 ++++ docs/source/wysdom.object_schema.rst | 54 +++++ docs/source/wysdom.user_objects.rst | 30 +++ 8 files changed, 597 insertions(+), 320 deletions(-) create mode 100644 docs/source/api_reference.rst create mode 100644 docs/source/user_docs.rst create mode 100644 docs/source/wysdom.base_schema.rst create mode 100644 docs/source/wysdom.dom.rst create mode 100644 docs/source/wysdom.mixins.rst create mode 100644 docs/source/wysdom.object_schema.rst create mode 100644 docs/source/wysdom.user_objects.rst diff --git a/docs/index.rst b/docs/index.rst index 71af0b2..3d99848 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,329 +2,15 @@ :maxdepth: 2 :caption: Contents: -.. include:: ../README.rst - - -User objects API -================= - -To build custom user object definitions in a declarative style, -you do so by creating subclasses of `wysdom.UserObject`. -Instances of your subclass will behave as a `MutableMapping`, -so any code that works on the underlying dict that you use to -populate it should also work on the object instance. - -.. autoclass:: wysdom.UserObject - :members: - :inherited-members: - -There are two ways to add properties to a UserObject. The first -is to add named properties, by using the `wysdom.UserProperty` -data descriptor:: - - class Person(UserObject): - first_name = UserProperty(str) - -The second is to allow dynamically named additional properties:: - - class Person(UserObject, additional_properties=True): - ... - -Any additional properties that are not explicitly defined as named -attributes using the UserProperty descriptor must be accessed -using the subscript style, `object_instance[property_name]`. - -You may also restrict the data types of the additional properties -that you will allow. The type parameter that you pass in to -`additional_properties` can be a primitive Python type, a subclass -of `UserObject`, or an instance of `wysdom.Schema`:: - - class Person(UserObject, additional_properties=str): - ... - - class Person(UserObject, additional_properties=Address): - ... - - class Person(UserObject, additional_properties=SchemaDict[Vehicle]): - ... - -.. autoclass:: wysdom.UserProperty - :members: - -Property Types --------------- - -The type parameter that you pass in to `UserProperty` can be a primitive -Python type, a subclass of `UserObject`, or an instance of `wysdom.Schema`:: - - class Person(UserObject): - first_name = UserProperty(str) - last_name = UserProperty(str) - current_address = UserProperty(Address) - previous_addresses = UserProperty(SchemaArray(Address)) - vehicles = UserProperty(SchemaDict(Vehicle)) - -Property Naming ---------------- - -If a UserProperty is not explicitly given a name, it is populated using -the attribute name that it is given on the parent class. If you want -the name of the attribute in the class to be different from the -key in the underlying data that is supplied to the object, you -may specify it explicitly using the `name` parameter:: - - class Person(UserObject): - last_name = UserProperty(str, name="surname") - -Defaults --------- - -If you need a UserProperty to have a default value, you may give it -a static value using the `default` parameter:: - - class Person(UserObject): - first_name = UserProperty(str, default="") - -Or if you need the default value to have a dynamic value based on other -properties, you may use the `default_function` parameter:: - - class Person(UserObject): - ... - known_as = UserProperty( - str, - default_function=lambda person: person.first_name - ) - -DOM functions -============= - -While the DOM and schema information can be retrieved from a DOMElement -using the `__json_dom_info__` property and `__json_schema__()` method -respectively, the following convenience functions are provided -for code readability. - -.. autofunction:: wysdom.document - -.. autofunction:: wysdom.parent - -.. autofunction:: wysdom.key - -.. autofunction:: wysdom.schema - - -Mixins -====== - -The interface for UserObject has been kept as minimal as possible to -avoid cluttering the interfaces of user subclasses with unnecessary -methods. However, there is some common functionality, such as reading -and writing JSON and YAML - -ReadsJSON ---------- - -Usage: As in the first usage example, but add ReadsJSON to the -bases of Person:: - - class Person(UserObject, ReadsJSON): - first_name = UserProperty(str) - last_name = UserProperty(str) - current_address = UserProperty(Address) - previous_addresses = UserProperty(SchemaArray(Address)) - - person_instance = Person.from_json( - """ - { - "first_name": "Marge", - "last_name": "Simpson", - "current_address": { - "first_line": "123 Fake Street", - "second_line": "", - "city": "Springfield", - "postal_code": 58008 - }, - "previous_addresses": [{ - "first_line": "742 Evergreen Terrace", - "second_line": "", - "city": "Springfield", - "postal_code": 58008 - }], - "vehicles": { - "eabf04": { - "color": "orange", - "description": "Station Wagon" - } - } - } - """ - ) - -.. autoclass:: wysdom.ReadsJSON - :members: - -ReadsYAML ---------- - -Usage: As in the first usage example, but add ReadsYAML to the -bases of Person:: + source/user_docs + source/api_reference - class Person(UserObject, ReadsYAML): - first_name = UserProperty(str) - last_name = UserProperty(str) - current_address = UserProperty(Address) - previous_addresses = UserProperty(SchemaArray(Address)) - person_instance = Person.from_yaml( - """ - first_name: Marge - last_name: Simpson - current_address: - first_line: 123 Fake Street - second_line: '' - city: Springfield - postal_code: 58008 - previous_addresses: - - first_line: 742 Evergreen Terrace - second_line: '' - city: Springfield - postal_code: 58008 - vehicles: - eabf04: - color: orange - description: Station Wagon - """ - ) +A-Z Index +######### -.. autoclass:: wysdom.ReadsYAML - :members: - - -RegistersSubclasses -------------------- - -Use `RegistersSubclasses` as a mixin if you want an abstract base class to -have several more specific subclasses:: - - class Pet(UserObject, RegistersSubclasses, ABC): - pet_type: str = UserProperty(str) - name: str = UserProperty(str) - - @abstractmethod - def speak(self): - pass - - - class Dog(Pet): - pet_type: str = UserProperty(SchemaConst("dog")) - - def speak(self): - return f"{self.name} says Woof!" - - - class Cat(Pet): - pet_type: str = UserProperty(SchemaConst("cat")) - - def speak(self): - return f"{self.name} says Miaow!" - - -If you use RegistersSubclasses, you may refer to the abstract -base class when defining properties and schemas in wysdom. When -the DOM is populated with data, the subclass which matches the -supplied data's schema will automatically be chosen:: - - class Person(UserObject): - pets = UserProperty(SchemaArray(Pet)) - - - person_instance = Person({ - "pets": [{ - "pet_type": "dog", - "name": "Santa's Little Helper" - }] - }) - ->>> type(person_instance.pets[0]) - - - -If you include an abstract base class in an object definition, it will -be represented in the JSON schema using the `SchemaAnyOf` with all of -the defined subclasses as allowed options. - - -Registering classes by name -........................... - -If your application needs to look up registered subclasses by a key, -you may supply the register_as keyword when declaring a subclass:: - - class Elephant(Pet, register_as="elephant"): - pet_type: str = UserProperty(SchemaConst("elephant")) - - def speak(self): - return f"{self.name} says Trumpet!" - -You may then use the class's registered name to look up the class or -create an instance from its parent class:: - - >>> Pet.registered_subclass("elephant") - - - >>> Pet.registered_subclass_instance("elephant", - ... {"pet_type": "elephant", "name": "Stampy"}).speak() - 'Stampy says Trumpet!' - - -.. autoclass:: wysdom.RegistersSubclasses - :members: - - -Internals -========= - -Schema objects --------------- - -.. autoclass:: wysdom.Schema - :members: - -Base schemas -............ - -The following schemas define simple atomic schemas -(defined in the subpackage `wysdom.base_schema`): - -=============== ================================================================== -Name Description -=============== ================================================================== -Schema abstract base class -SchemaType abstract base class for any schema with the "type" directive -SchemaAnything any valid JSON will be accepted -SchemaConst a string constant -SchemaNone a null value -SchemaPrimitive a primitive variable -=============== ================================================================== - -Object schemas -.............. - -The following schemas define complex schemas which reference other schemas -(defined in the subpackage `wysdom.object_schema`): - -=============== ================================================================== -Name Description -=============== ================================================================== -SchemaAnyOf Any of the permitted schemas supplied -SchemaArray An array (corresponding to a Python list) -SchemaObject An object with named properties -SchemaDict An object with dynamic properties (corresponding to a Python dict) -=============== ================================================================== +* :ref:`genindex` -Indices and tables -================== +.. include:: ../README.rst -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/source/api_reference.rst b/docs/source/api_reference.rst new file mode 100644 index 0000000..d6cd2f0 --- /dev/null +++ b/docs/source/api_reference.rst @@ -0,0 +1,22 @@ +API reference +============= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + wysdom.base_schema + wysdom.dom + wysdom.mixins + wysdom.object_schema + wysdom.user_objects + +Exceptions +---------- + +.. automodule:: wysdom.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/user_docs.rst b/docs/source/user_docs.rst new file mode 100644 index 0000000..c6862b0 --- /dev/null +++ b/docs/source/user_docs.rst @@ -0,0 +1,323 @@ +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +User Documentation +################## + + +User objects API +================= + +To build custom user object definitions in a declarative style, +you do so by creating subclasses of `wysdom.UserObject`. +Instances of your subclass will behave as a `MutableMapping`, +so any code that works on the underlying dict that you use to +populate it should also work on the object instance. + +.. autoclass:: wysdom.UserObject + :members: + :inherited-members: + +There are two ways to add properties to a UserObject. The first +is to add named properties, by using the `wysdom.UserProperty` +data descriptor:: + + class Person(UserObject): + first_name = UserProperty(str) + +The second is to allow dynamically named additional properties:: + + class Person(UserObject, additional_properties=True): + ... + +Any additional properties that are not explicitly defined as named +attributes using the UserProperty descriptor must be accessed +using the subscript style, `object_instance[property_name]`. + +You may also restrict the data types of the additional properties +that you will allow. The type parameter that you pass in to +`additional_properties` can be a primitive Python type, a subclass +of `UserObject`, or an instance of `wysdom.Schema`:: + + class Person(UserObject, additional_properties=str): + ... + + class Person(UserObject, additional_properties=Address): + ... + + class Person(UserObject, additional_properties=SchemaDict[Vehicle]): + ... + +.. autoclass:: wysdom.UserProperty + :members: + +Property Types +-------------- + +The type parameter that you pass in to `UserProperty` can be a primitive +Python type, a subclass of `UserObject`, or an instance of `wysdom.Schema`:: + + class Person(UserObject): + first_name = UserProperty(str) + last_name = UserProperty(str) + current_address = UserProperty(Address) + previous_addresses = UserProperty(SchemaArray(Address)) + vehicles = UserProperty(SchemaDict(Vehicle)) + +Property Naming +--------------- + +If a UserProperty is not explicitly given a name, it is populated using +the attribute name that it is given on the parent class. If you want +the name of the attribute in the class to be different from the +key in the underlying data that is supplied to the object, you +may specify it explicitly using the `name` parameter:: + + class Person(UserObject): + last_name = UserProperty(str, name="surname") + +Defaults +-------- + +If you need a UserProperty to have a default value, you may give it +a static value using the `default` parameter:: + + class Person(UserObject): + first_name = UserProperty(str, default="") + +Or if you need the default value to have a dynamic value based on other +properties, you may use the `default_function` parameter:: + + class Person(UserObject): + ... + known_as = UserProperty( + str, + default_function=lambda person: person.first_name + ) + +DOM functions +============= + +While the DOM and schema information can be retrieved from a DOMElement +using the `__json_dom_info__` property and `__json_schema__()` method +respectively, the following convenience functions are provided +for code readability. + +.. autofunction:: wysdom.document + +.. autofunction:: wysdom.parent + +.. autofunction:: wysdom.key + +.. autofunction:: wysdom.schema + + +Mixins +====== + +The interface for UserObject has been kept as minimal as possible to +avoid cluttering the interfaces of user subclasses with unnecessary +methods. However, there is some common functionality, such as reading +and writing JSON and YAML + +ReadsJSON +--------- + +Usage: As in the first usage example, but add ReadsJSON to the +bases of Person:: + + class Person(UserObject, ReadsJSON): + first_name = UserProperty(str) + last_name = UserProperty(str) + current_address = UserProperty(Address) + previous_addresses = UserProperty(SchemaArray(Address)) + + person_instance = Person.from_json( + """ + { + "first_name": "Marge", + "last_name": "Simpson", + "current_address": { + "first_line": "123 Fake Street", + "second_line": "", + "city": "Springfield", + "postal_code": 58008 + }, + "previous_addresses": [{ + "first_line": "742 Evergreen Terrace", + "second_line": "", + "city": "Springfield", + "postal_code": 58008 + }], + "vehicles": { + "eabf04": { + "color": "orange", + "description": "Station Wagon" + } + } + } + """ + ) + +.. autoclass:: wysdom.ReadsJSON + :members: + +ReadsYAML +--------- + +Usage: As in the first usage example, but add ReadsYAML to the +bases of Person:: + + class Person(UserObject, ReadsYAML): + first_name = UserProperty(str) + last_name = UserProperty(str) + current_address = UserProperty(Address) + previous_addresses = UserProperty(SchemaArray(Address)) + + person_instance = Person.from_yaml( + """ + first_name: Marge + last_name: Simpson + current_address: + first_line: 123 Fake Street + second_line: '' + city: Springfield + postal_code: 58008 + previous_addresses: + - first_line: 742 Evergreen Terrace + second_line: '' + city: Springfield + postal_code: 58008 + vehicles: + eabf04: + color: orange + description: Station Wagon + """ + ) + +.. autoclass:: wysdom.ReadsYAML + :members: + + +RegistersSubclasses +------------------- + +Use `RegistersSubclasses` as a mixin if you want an abstract base class to +have several more specific subclasses:: + + class Pet(UserObject, RegistersSubclasses, ABC): + pet_type: str = UserProperty(str) + name: str = UserProperty(str) + + @abstractmethod + def speak(self): + pass + + + class Dog(Pet): + pet_type: str = UserProperty(SchemaConst("dog")) + + def speak(self): + return f"{self.name} says Woof!" + + + class Cat(Pet): + pet_type: str = UserProperty(SchemaConst("cat")) + + def speak(self): + return f"{self.name} says Miaow!" + + +If you use RegistersSubclasses, you may refer to the abstract +base class when defining properties and schemas in wysdom. When +the DOM is populated with data, the subclass which matches the +supplied data's schema will automatically be chosen:: + + class Person(UserObject): + pets = UserProperty(SchemaArray(Pet)) + + + person_instance = Person({ + "pets": [{ + "pet_type": "dog", + "name": "Santa's Little Helper" + }] + }) + +>>> type(person_instance.pets[0]) + + + +If you include an abstract base class in an object definition, it will +be represented in the JSON schema using the `SchemaAnyOf` with all of +the defined subclasses as allowed options. + + +Registering classes by name +........................... + +If your application needs to look up registered subclasses by a key, +you may supply the register_as keyword when declaring a subclass:: + + class Elephant(Pet, register_as="elephant"): + pet_type: str = UserProperty(SchemaConst("elephant")) + + def speak(self): + return f"{self.name} says Trumpet!" + +You may then use the class's registered name to look up the class or +create an instance from its parent class:: + + >>> Pet.registered_subclass("elephant") + + + >>> Pet.registered_subclass_instance("elephant", + ... {"pet_type": "elephant", "name": "Stampy"}).speak() + 'Stampy says Trumpet!' + + +.. autoclass:: wysdom.RegistersSubclasses + :members: + + +Internals +========= + +Schema objects +-------------- + +.. autoclass:: wysdom.Schema + :members: + +Base schemas +............ + +The following schemas define simple atomic schemas +(defined in the subpackage `wysdom.base_schema`): + +=============== ================================================================== +Name Description +=============== ================================================================== +Schema abstract base class +SchemaType abstract base class for any schema with the "type" directive +SchemaAnything any valid JSON will be accepted +SchemaConst a string constant +SchemaNone a null value +SchemaPrimitive a primitive variable +=============== ================================================================== + +Object schemas +.............. + +The following schemas define complex schemas which reference other schemas +(defined in the subpackage `wysdom.object_schema`): + +=============== ================================================================== +Name Description +=============== ================================================================== +SchemaAnyOf Any of the permitted schemas supplied +SchemaArray An array (corresponding to a Python list) +SchemaObject An object with named properties +SchemaDict An object with dynamic properties (corresponding to a Python dict) +=============== ================================================================== diff --git a/docs/source/wysdom.base_schema.rst b/docs/source/wysdom.base_schema.rst new file mode 100644 index 0000000..f083acb --- /dev/null +++ b/docs/source/wysdom.base_schema.rst @@ -0,0 +1,62 @@ +wysdom.base\_schema package +=========================== + +Submodules +---------- + +wysdom.base\_schema.Schema module +--------------------------------- + +.. automodule:: wysdom.base_schema.Schema + :members: + :undoc-members: + :show-inheritance: + +wysdom.base\_schema.SchemaAnything module +----------------------------------------- + +.. automodule:: wysdom.base_schema.SchemaAnything + :members: + :undoc-members: + :show-inheritance: + +wysdom.base\_schema.SchemaConst module +-------------------------------------- + +.. automodule:: wysdom.base_schema.SchemaConst + :members: + :undoc-members: + :show-inheritance: + +wysdom.base\_schema.SchemaNone module +------------------------------------- + +.. automodule:: wysdom.base_schema.SchemaNone + :members: + :undoc-members: + :show-inheritance: + +wysdom.base\_schema.SchemaPrimitive module +------------------------------------------ + +.. automodule:: wysdom.base_schema.SchemaPrimitive + :members: + :undoc-members: + :show-inheritance: + +wysdom.base\_schema.SchemaType module +------------------------------------- + +.. automodule:: wysdom.base_schema.SchemaType + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: wysdom.base_schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/wysdom.dom.rst b/docs/source/wysdom.dom.rst new file mode 100644 index 0000000..226fdb9 --- /dev/null +++ b/docs/source/wysdom.dom.rst @@ -0,0 +1,62 @@ +wysdom.dom package +================== + +Submodules +---------- + +wysdom.dom.DOMDict module +------------------------- + +.. automodule:: wysdom.dom.DOMDict + :members: + :undoc-members: + :show-inheritance: + +wysdom.dom.DOMElement module +---------------------------- + +.. automodule:: wysdom.dom.DOMElement + :members: + :undoc-members: + :show-inheritance: + +wysdom.dom.DOMList module +------------------------- + +.. automodule:: wysdom.dom.DOMList + :members: + :undoc-members: + :show-inheritance: + +wysdom.dom.DOMObject module +--------------------------- + +.. automodule:: wysdom.dom.DOMObject + :members: + :undoc-members: + :show-inheritance: + +wysdom.dom.DOMProperties module +------------------------------- + +.. automodule:: wysdom.dom.DOMProperties + :members: + :undoc-members: + :show-inheritance: + +wysdom.dom.functions module +--------------------------- + +.. automodule:: wysdom.dom.functions + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: wysdom.dom + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/wysdom.mixins.rst b/docs/source/wysdom.mixins.rst new file mode 100644 index 0000000..90af696 --- /dev/null +++ b/docs/source/wysdom.mixins.rst @@ -0,0 +1,38 @@ +wysdom.mixins package +===================== + +Submodules +---------- + +wysdom.mixins.ReadsJSON module +------------------------------ + +.. automodule:: wysdom.mixins.ReadsJSON + :members: + :undoc-members: + :show-inheritance: + +wysdom.mixins.ReadsYAML module +------------------------------ + +.. automodule:: wysdom.mixins.ReadsYAML + :members: + :undoc-members: + :show-inheritance: + +wysdom.mixins.RegistersSubclasses module +---------------------------------------- + +.. automodule:: wysdom.mixins.RegistersSubclasses + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: wysdom.mixins + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/wysdom.object_schema.rst b/docs/source/wysdom.object_schema.rst new file mode 100644 index 0000000..65149d4 --- /dev/null +++ b/docs/source/wysdom.object_schema.rst @@ -0,0 +1,54 @@ +wysdom.object\_schema package +============================= + +Submodules +---------- + +wysdom.object\_schema.SchemaAnyOf module +---------------------------------------- + +.. automodule:: wysdom.object_schema.SchemaAnyOf + :members: + :undoc-members: + :show-inheritance: + +wysdom.object\_schema.SchemaArray module +---------------------------------------- + +.. automodule:: wysdom.object_schema.SchemaArray + :members: + :undoc-members: + :show-inheritance: + +wysdom.object\_schema.SchemaDict module +--------------------------------------- + +.. automodule:: wysdom.object_schema.SchemaDict + :members: + :undoc-members: + :show-inheritance: + +wysdom.object\_schema.SchemaObject module +----------------------------------------- + +.. automodule:: wysdom.object_schema.SchemaObject + :members: + :undoc-members: + :show-inheritance: + +wysdom.object\_schema.resolve\_arg\_to\_type module +--------------------------------------------------- + +.. automodule:: wysdom.object_schema.resolve_arg_to_type + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: wysdom.object_schema + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/wysdom.user_objects.rst b/docs/source/wysdom.user_objects.rst new file mode 100644 index 0000000..f906314 --- /dev/null +++ b/docs/source/wysdom.user_objects.rst @@ -0,0 +1,30 @@ +wysdom.user\_objects package +============================ + +Submodules +---------- + +wysdom.user\_objects.UserObject module +-------------------------------------- + +.. automodule:: wysdom.user_objects.UserObject + :members: + :undoc-members: + :show-inheritance: + +wysdom.user\_objects.UserProperty module +---------------------------------------- + +.. automodule:: wysdom.user_objects.UserProperty + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: wysdom.user_objects + :members: + :undoc-members: + :show-inheritance: From 0611db6e5ec076ecdcfc86bf8304daa8567f5d5c Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Mon, 25 May 2020 13:30:42 +0200 Subject: [PATCH 3/6] Add docstrings to dom subpackage --- wysdom/dom/DOMDict.py | 17 +++++++++++++---- wysdom/dom/DOMElement.py | 30 ++++++++++++++++++++++++++++++ wysdom/dom/DOMList.py | 16 +++++++++++++--- wysdom/dom/DOMObject.py | 9 +++++++++ wysdom/dom/DOMProperties.py | 12 ++++++++++++ wysdom/dom/functions.py | 20 +++++++++++--------- 6 files changed, 88 insertions(+), 16 deletions(-) diff --git a/wysdom/dom/DOMDict.py b/wysdom/dom/DOMDict.py index 2ff76b5..2dc39ff 100644 --- a/wysdom/dom/DOMDict.py +++ b/wysdom/dom/DOMDict.py @@ -14,17 +14,26 @@ class DOMDict(DOMObject, Generic[T_co]): - - _additional_properties: Schema = None + """ + An object with dynamic properties (corresponding to a Python dict). + """ def __init__( self, value: Optional[Mapping[str, Any]] = None, json_dom_info: Optional[DOMInfo] = None, - _item_type: Optional[Schema] = None + item_type: Optional[Schema] = None ) -> None: + """ + :param value: A dict (or any `Mapping`) containing the data to populate this + object's properties. + :param json_dom_info: A `DOMInfo` named tuple containing information about this object's + position in the DOM. + :param item_type: A `Schema` object specifying what constitutes a valid property + of this object. + """ self.__json_schema_properties__ = DOMProperties( - additional_properties=(_item_type or SchemaAnything()) + additional_properties=(item_type or SchemaAnything()) ) super().__init__( value or {}, diff --git a/wysdom/dom/DOMElement.py b/wysdom/dom/DOMElement.py index d62cb53..110b4ae 100644 --- a/wysdom/dom/DOMElement.py +++ b/wysdom/dom/DOMElement.py @@ -8,6 +8,16 @@ class DOMInfo(NamedTuple): + """ + Named tuple containing information about a DOM element's position within the DOM. + + :param element: The :class:`DOMElement` that this DOMInfo tuple provides information for. + :param document: The owning document for a :class:`DOMElement`, if it exists. + :param parent: The parent element of a :class:`DOMElement`, if it exists. + :param element_key: The key of a particular :class:`DOMElement` in its parent element, + if it can be referred to by a key (i.e. if it its parent element + is a Mapping). + """ element: Optional[DOMElement] = None document: Optional[DOMElement] = None @@ -16,6 +26,9 @@ class DOMInfo(NamedTuple): class DOMElement(ABC): + """ + Abstract base class for any DOM element. + """ __json_dom_info__: DOMInfo = None @@ -26,6 +39,12 @@ def __init__( json_dom_info: DOMInfo = None, **kwargs: Any ) -> None: + """ + :param value: A data structure containing the data to populate this element. + :param json_dom_info: A :class:`DOMInfo` named tuple containing information about this object's + position in the DOM. + :param kwargs: Keyword arguments. + """ if value is not None: raise ValueError( "The parameter 'value' must be handled by a non-abstract subclass." @@ -50,7 +69,18 @@ def __json_schema__(cls) -> Schema: @abstractmethod def to_builtin(self) -> Any: + """ + Returns the contents of this DOM object as a Python builtin. Return type + varies depending on the specific object type. + """ pass def walk_elements(self) -> Iterator[DOMInfo]: + """ + Walk through the full tree structure within this DOM element. + Returns an iterator of :class:`DOMInfo` tuples in the form + (element, document, parent element_key). + + :return: An iterator of :class:`DOMInfo` tuples. + """ yield self.__json_dom_info__ diff --git a/wysdom/dom/DOMList.py b/wysdom/dom/DOMList.py index 04ab88e..5e2ce00 100644 --- a/wysdom/dom/DOMList.py +++ b/wysdom/dom/DOMList.py @@ -20,6 +20,9 @@ class DOMList(DOMElement, MutableSequence, Generic[T_co]): + """ + An array element (corresponding to a Python list). + """ __json_element_data__: List[DOMElement] = None @@ -27,16 +30,23 @@ def __init__( self, value: Iterable, json_dom_info: Optional[DOMInfo] = None, - _item_type: Optional[Schema] = None + item_type: Optional[Schema] = None ) -> None: + """ + :param value: A list (or any :class:`Typing.Iterable`) containing the data to populate this + object's items. + :param json_dom_info: A `DOMInfo` named tuple containing information about this object's + position in the DOM. + :param item_type: A `Schema` object specifying what constitutes a valid item in this array. + """ if value and not isinstance(value, Iterable): raise ValidationError( f"Cannot validate input. Object is not iterable: {value}" ) super().__init__(None, json_dom_info) self.__json_element_data__ = [] - if _item_type is not None: - self.item_type = _item_type + if item_type is not None: + self.item_type = item_type self[:] = value @overload diff --git a/wysdom/dom/DOMObject.py b/wysdom/dom/DOMObject.py index 1fd8136..c727e1d 100644 --- a/wysdom/dom/DOMObject.py +++ b/wysdom/dom/DOMObject.py @@ -14,6 +14,9 @@ class DOMObject(DOMElement, MutableMapping): + """ + An object with named properties. + """ __json_schema_properties__: DOMProperties = None __json_element_data__: Dict[str, DOMElement] = None @@ -23,6 +26,12 @@ def __init__( value: Mapping[str, Any] = None, json_dom_info: DOMInfo = None ) -> None: + """ + :param value: A dict (or any `Mapping`) containing the data to populate this + object's properties. + :param json_dom_info: A `DOMInfo` named tuple containing information about this object's + position in the DOM. + """ if value and not isinstance(value, Mapping): raise ValidationError( f"Cannot validate input. Object is not a mapping: {value}" diff --git a/wysdom/dom/DOMProperties.py b/wysdom/dom/DOMProperties.py index f067761..b5592e4 100644 --- a/wysdom/dom/DOMProperties.py +++ b/wysdom/dom/DOMProperties.py @@ -4,6 +4,9 @@ class DOMProperties(object): + """ + A container for property information for a :class:`.DOMObject`. + """ properties: Dict[str, Schema] = None additional_properties: Union[bool, Schema] = False @@ -13,5 +16,14 @@ def __init__( properties: Dict[str, Schema] = None, additional_properties: Union[bool, Schema] = False ) -> None: + """ + :param properties: A dictionary of :class:`~wysdom.base_schema.Schema.Schema` objects + defining the expected names and types of a :class:`.DOMObject`'s + properties. + :param additional_properties: Defines whether a :class:`.DOMObject` permits additional + dynamically-named properties. Can be True or False, or + can be set to a specific `Schema` to restrict the permitted + types of any additional properties. + """ self.properties = properties or {} self.additional_properties = additional_properties diff --git a/wysdom/dom/functions.py b/wysdom/dom/functions.py index b6efab7..9abd65c 100644 --- a/wysdom/dom/functions.py +++ b/wysdom/dom/functions.py @@ -7,18 +7,18 @@ def dom(element: DOMElement) -> DOMInfo: """ - Retrieve a DOMInfo object for a DOMElement containing information about that - element's position in the DOM. + Retrieve a :class:`.DOMInfo` object for a :class:`.DOMElement` containing information + about that element's position in the DOM. :param element: A DOM element - :return: The DOMInfo object for that DOM element + :return: The :class:`.DOMInfo` object for that DOM element """ return element.__json_dom_info__ def document(element: DOMElement) -> Optional[DOMElement]: """ - Retrieve the owning document for a DOMElement, if it exists. + Retrieve the owning document for a :class:`.DOMElement`, if it exists. :param element: A DOM element :return: The owning document for that DOM element, or None if none exists @@ -28,7 +28,7 @@ def document(element: DOMElement) -> Optional[DOMElement]: def parent(element: DOMElement) -> Optional[DOMElement]: """ - Retrieve the parent element of a DOMElement, if it exists. + Retrieve the parent element of a :class:`.DOMElement`, if it exists. :param element: A DOM element :return: The parent element of that DOM element, or None of none exists @@ -38,8 +38,8 @@ def parent(element: DOMElement) -> Optional[DOMElement]: def key(element: DOMElement) -> Optional[str]: """ - Retrieve the key of a particular DOMElement in its parent element, if it can be - referred to by a key (i.e. if it its parent element is a Mapping). + Retrieve the key of a particular :class:`.DOMElement` in its parent element, if it can be + referred to by a key (i.e. if it its parent element is a :class:`Mapping`). :param element: A DOM element :return: The key of that DOM element in its parent, or None if it has no key @@ -49,9 +49,11 @@ def key(element: DOMElement) -> Optional[str]: def schema(element: DOMElement) -> Schema: """ - Retrieve the Schema object for a particular DOMElement. + Retrieve the :class:`~wysdom.base_schema.Schema.Schema` object for + a particular :class:`.DOMElement`. :param element: A DOM element - :return: The Schema object associated with that DOM element + :return: The :class:`~wysdom.base_schema.Schema.Schema` object associated + with that DOM element """ return element.__json_schema__() From 19394d4ecc594f72d179fcbef9f5941b74f78f3c Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Mon, 25 May 2020 14:27:15 +0200 Subject: [PATCH 4/6] Update docstring references and reorganise API docs --- docs/conf.py | 2 + docs/index.rst | 5 +- docs/source/user_docs.rst | 78 +++++++++++----------------- docs/source/wysdom.base_schema.rst | 38 +++++--------- docs/source/wysdom.dom.rst | 45 ++++++++-------- docs/source/wysdom.mixins.rst | 26 +++------- docs/source/wysdom.object_schema.rst | 32 ++++-------- docs/source/wysdom.user_objects.rst | 22 ++++---- wysdom/__init__.py | 7 ++- wysdom/dom/DOMDict.py | 6 +-- wysdom/dom/DOMElement.py | 6 +-- wysdom/dom/DOMList.py | 9 +++- wysdom/dom/DOMObject.py | 4 +- wysdom/dom/DOMProperties.py | 4 +- wysdom/dom/functions.py | 6 +-- wysdom/object_schema/SchemaArray.py | 4 +- wysdom/object_schema/SchemaDict.py | 2 +- wysdom/object_schema/SchemaObject.py | 2 +- wysdom/user_objects/UserObject.py | 12 +++++ wysdom/user_objects/UserProperty.py | 14 ++--- wysdom/user_objects/__init__.py | 2 +- 21 files changed, 144 insertions(+), 182 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 398cb07..8439805 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,6 +34,8 @@ # -- General configuration --------------------------------------------------- +autoclass_content = 'both' + # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. diff --git a/docs/index.rst b/docs/index.rst index 3d99848..8043656 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,3 +1,6 @@ +Welcome to wysdom! +################## + .. toctree:: :maxdepth: 2 :caption: Contents: @@ -7,7 +10,7 @@ A-Z Index -######### +========= * :ref:`genindex` diff --git a/docs/source/user_docs.rst b/docs/source/user_docs.rst index c6862b0..db78a9a 100644 --- a/docs/source/user_docs.rst +++ b/docs/source/user_docs.rst @@ -10,17 +10,13 @@ User objects API ================= To build custom user object definitions in a declarative style, -you do so by creating subclasses of `wysdom.UserObject`. +you do so by creating subclasses of :class:`wysdom.UserObject`. Instances of your subclass will behave as a `MutableMapping`, so any code that works on the underlying dict that you use to populate it should also work on the object instance. -.. autoclass:: wysdom.UserObject - :members: - :inherited-members: - There are two ways to add properties to a UserObject. The first -is to add named properties, by using the `wysdom.UserProperty` +is to add named properties, by using the :class:`wysdom.UserProperty` data descriptor:: class Person(UserObject): @@ -38,7 +34,7 @@ using the subscript style, `object_instance[property_name]`. You may also restrict the data types of the additional properties that you will allow. The type parameter that you pass in to `additional_properties` can be a primitive Python type, a subclass -of `UserObject`, or an instance of `wysdom.Schema`:: +of :class:`~wysdom.UserProperty`, or an instance of :class:`~wysdom.Schema`:: class Person(UserObject, additional_properties=str): ... @@ -49,14 +45,12 @@ of `UserObject`, or an instance of `wysdom.Schema`:: class Person(UserObject, additional_properties=SchemaDict[Vehicle]): ... -.. autoclass:: wysdom.UserProperty - :members: Property Types -------------- -The type parameter that you pass in to `UserProperty` can be a primitive -Python type, a subclass of `UserObject`, or an instance of `wysdom.Schema`:: +The type parameter that you pass in to :class:`~wysdom.UserProperty` can be a primitive +Python type, a subclass of :class:`~wysdom.UserProperty`, or an instance of :class:`~wysdom.Schema`:: class Person(UserObject): first_name = UserProperty(str) @@ -124,8 +118,8 @@ and writing JSON and YAML ReadsJSON --------- -Usage: As in the first usage example, but add ReadsJSON to the -bases of Person:: +Usage: As in the first usage example, but add :class:`wysdom.mixins.ReadsJSON` +to the bases of Person:: class Person(UserObject, ReadsJSON): first_name = UserProperty(str) @@ -160,14 +154,12 @@ bases of Person:: """ ) -.. autoclass:: wysdom.ReadsJSON - :members: ReadsYAML --------- -Usage: As in the first usage example, but add ReadsYAML to the -bases of Person:: +Usage: As in the first usage example, but add :class:`wysdom.mixins.ReadsYAML` +to the bases of Person:: class Person(UserObject, ReadsYAML): first_name = UserProperty(str) @@ -196,14 +188,11 @@ bases of Person:: """ ) -.. autoclass:: wysdom.ReadsYAML - :members: - RegistersSubclasses ------------------- -Use `RegistersSubclasses` as a mixin if you want an abstract base class to +Use :class:`wysdom.mixins.RegistersSubclasses` as a mixin if you want an abstract base class to have several more specific subclasses:: class Pet(UserObject, RegistersSubclasses, ABC): @@ -277,18 +266,11 @@ create an instance from its parent class:: 'Stampy says Trumpet!' -.. autoclass:: wysdom.RegistersSubclasses - :members: - - Internals ========= -Schema objects --------------- - -.. autoclass:: wysdom.Schema - :members: +Schemas +------- Base schemas ............ @@ -296,16 +278,16 @@ Base schemas The following schemas define simple atomic schemas (defined in the subpackage `wysdom.base_schema`): -=============== ================================================================== -Name Description -=============== ================================================================== -Schema abstract base class -SchemaType abstract base class for any schema with the "type" directive -SchemaAnything any valid JSON will be accepted -SchemaConst a string constant -SchemaNone a null value -SchemaPrimitive a primitive variable -=============== ================================================================== +================================ ================================================================== +Name Description +================================ ================================================================== +:class:`~wysdom.Schema` abstract base class +:class:`~wysdom.SchemaType` abstract base class for any schema with the "type" directive +:class:`~wysdom.SchemaAnything` any valid JSON will be accepted +:class:`~wysdom.SchemaConst` a string constant +:class:`~wysdom.SchemaNone` a null value +:class:`~wysdom.SchemaPrimitive` a primitive variable +================================ ================================================================== Object schemas .............. @@ -313,11 +295,11 @@ Object schemas The following schemas define complex schemas which reference other schemas (defined in the subpackage `wysdom.object_schema`): -=============== ================================================================== -Name Description -=============== ================================================================== -SchemaAnyOf Any of the permitted schemas supplied -SchemaArray An array (corresponding to a Python list) -SchemaObject An object with named properties -SchemaDict An object with dynamic properties (corresponding to a Python dict) -=============== ================================================================== +================================ ================================================================== +Name Description +================================ ================================================================== +:class:`~wysdom.SchemaAnyOf` Any of the permitted schemas supplied +:class:`~wysdom.SchemaArray` An array (corresponding to a Python list) +:class:`~wysdom.SchemaObject` An object with named properties +:class:`~wysdom.SchemaDict` An object with dynamic properties (corresponding to a Python dict) +================================ ================================================================== diff --git a/docs/source/wysdom.base_schema.rst b/docs/source/wysdom.base_schema.rst index f083acb..354a9d8 100644 --- a/docs/source/wysdom.base_schema.rst +++ b/docs/source/wysdom.base_schema.rst @@ -1,62 +1,50 @@ -wysdom.base\_schema package +base\_schema =========================== -Submodules ----------- - -wysdom.base\_schema.Schema module +Schema --------------------------------- -.. automodule:: wysdom.base_schema.Schema +.. autoclass:: wysdom.Schema :members: :undoc-members: :show-inheritance: -wysdom.base\_schema.SchemaAnything module +SchemaAnything ----------------------------------------- -.. automodule:: wysdom.base_schema.SchemaAnything +.. autoclass:: wysdom.SchemaAnything :members: :undoc-members: :show-inheritance: -wysdom.base\_schema.SchemaConst module +SchemaConst -------------------------------------- -.. automodule:: wysdom.base_schema.SchemaConst +.. autoclass:: wysdom.SchemaConst :members: :undoc-members: :show-inheritance: -wysdom.base\_schema.SchemaNone module +SchemaNone ------------------------------------- -.. automodule:: wysdom.base_schema.SchemaNone +.. autoclass:: wysdom.SchemaNone :members: :undoc-members: :show-inheritance: -wysdom.base\_schema.SchemaPrimitive module +SchemaPrimitive ------------------------------------------ -.. automodule:: wysdom.base_schema.SchemaPrimitive +.. autoclass:: wysdom.SchemaPrimitive :members: :undoc-members: :show-inheritance: -wysdom.base\_schema.SchemaType module +SchemaType ------------------------------------- -.. automodule:: wysdom.base_schema.SchemaType - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: wysdom.base_schema +.. autoclass:: wysdom.SchemaType :members: :undoc-members: :show-inheritance: diff --git a/docs/source/wysdom.dom.rst b/docs/source/wysdom.dom.rst index 226fdb9..9a1bec7 100644 --- a/docs/source/wysdom.dom.rst +++ b/docs/source/wysdom.dom.rst @@ -1,62 +1,59 @@ -wysdom.dom package +dom ================== -Submodules ----------- - -wysdom.dom.DOMDict module +DOMDict ------------------------- -.. automodule:: wysdom.dom.DOMDict +.. autoclass:: wysdom.dom.DOMDict :members: :undoc-members: :show-inheritance: -wysdom.dom.DOMElement module +DOMElement ---------------------------- -.. automodule:: wysdom.dom.DOMElement +.. autoclass:: wysdom.dom.DOMElement :members: :undoc-members: :show-inheritance: -wysdom.dom.DOMList module -------------------------- +DOMInfo +---------------------------- -.. automodule:: wysdom.dom.DOMList +.. autoclass:: wysdom.dom.DOMInfo :members: :undoc-members: :show-inheritance: -wysdom.dom.DOMObject module ---------------------------- -.. automodule:: wysdom.dom.DOMObject +DOMList +------------------------- + +.. autoclass:: wysdom.dom.DOMList :members: :undoc-members: :show-inheritance: -wysdom.dom.DOMProperties module -------------------------------- +DOMObject +--------------------------- -.. automodule:: wysdom.dom.DOMProperties +.. autoclass:: wysdom.dom.DOMObject :members: :undoc-members: :show-inheritance: -wysdom.dom.functions module ---------------------------- +DOMProperties +------------------------------- -.. automodule:: wysdom.dom.functions +.. autoclass:: wysdom.dom.DOMProperties :members: :undoc-members: :show-inheritance: +functions +--------------------------- -Module contents ---------------- - -.. automodule:: wysdom.dom +.. automodule:: wysdom.dom.functions :members: :undoc-members: :show-inheritance: diff --git a/docs/source/wysdom.mixins.rst b/docs/source/wysdom.mixins.rst index 90af696..8bf2694 100644 --- a/docs/source/wysdom.mixins.rst +++ b/docs/source/wysdom.mixins.rst @@ -1,38 +1,26 @@ -wysdom.mixins package +mixins ===================== -Submodules ----------- - -wysdom.mixins.ReadsJSON module +ReadsJSON ------------------------------ -.. automodule:: wysdom.mixins.ReadsJSON +.. autoclass:: wysdom.mixins.ReadsJSON :members: :undoc-members: :show-inheritance: -wysdom.mixins.ReadsYAML module +ReadsYAML ------------------------------ -.. automodule:: wysdom.mixins.ReadsYAML +.. autoclass:: wysdom.mixins.ReadsYAML :members: :undoc-members: :show-inheritance: -wysdom.mixins.RegistersSubclasses module +RegistersSubclasses ---------------------------------------- -.. automodule:: wysdom.mixins.RegistersSubclasses - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: wysdom.mixins +.. autoclass:: wysdom.mixins.RegistersSubclasses :members: :undoc-members: :show-inheritance: diff --git a/docs/source/wysdom.object_schema.rst b/docs/source/wysdom.object_schema.rst index 65149d4..ed6999d 100644 --- a/docs/source/wysdom.object_schema.rst +++ b/docs/source/wysdom.object_schema.rst @@ -1,54 +1,42 @@ -wysdom.object\_schema package +object\_schema ============================= -Submodules ----------- - -wysdom.object\_schema.SchemaAnyOf module +SchemaAnyOf ---------------------------------------- -.. automodule:: wysdom.object_schema.SchemaAnyOf +.. autoclass:: wysdom.SchemaAnyOf :members: :undoc-members: :show-inheritance: -wysdom.object\_schema.SchemaArray module +SchemaArray ---------------------------------------- -.. automodule:: wysdom.object_schema.SchemaArray +.. autoclass:: wysdom.SchemaArray :members: :undoc-members: :show-inheritance: -wysdom.object\_schema.SchemaDict module +SchemaDict --------------------------------------- -.. automodule:: wysdom.object_schema.SchemaDict +.. autoclass:: wysdom.SchemaDict :members: :undoc-members: :show-inheritance: -wysdom.object\_schema.SchemaObject module +SchemaObject ----------------------------------------- -.. automodule:: wysdom.object_schema.SchemaObject +.. autoclass:: wysdom.SchemaObject :members: :undoc-members: :show-inheritance: -wysdom.object\_schema.resolve\_arg\_to\_type module +resolve\_arg\_to\_type --------------------------------------------------- .. automodule:: wysdom.object_schema.resolve_arg_to_type :members: :undoc-members: :show-inheritance: - - -Module contents ---------------- - -.. automodule:: wysdom.object_schema - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/wysdom.user_objects.rst b/docs/source/wysdom.user_objects.rst index f906314..4af709c 100644 --- a/docs/source/wysdom.user_objects.rst +++ b/docs/source/wysdom.user_objects.rst @@ -1,30 +1,26 @@ -wysdom.user\_objects package +user\_objects ============================ -Submodules ----------- - -wysdom.user\_objects.UserObject module +UserObject -------------------------------------- -.. automodule:: wysdom.user_objects.UserObject +.. autoclass:: wysdom.UserObject :members: :undoc-members: :show-inheritance: -wysdom.user\_objects.UserProperty module ----------------------------------------- +UserProperties +-------------------------------------- -.. automodule:: wysdom.user_objects.UserProperty +.. autoclass:: wysdom.user_objects.UserProperties :members: :undoc-members: :show-inheritance: +UserProperty +---------------------------------------- -Module contents ---------------- - -.. automodule:: wysdom.user_objects +.. autoclass:: wysdom.UserProperty :members: :undoc-members: :show-inheritance: diff --git a/wysdom/__init__.py b/wysdom/__init__.py index 08925be..33f92a6 100644 --- a/wysdom/__init__.py +++ b/wysdom/__init__.py @@ -1,10 +1,9 @@ from .__version__ import __version__ from .exceptions import ValidationError from .dom import document, parent, key, schema -from .dom import DOMInfo as DOMInfo -from .dom import DOMElement as Element -from .base_schema import Schema, SchemaAnything, SchemaConst -from .object_schema import SchemaArray, SchemaDict +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 from .mixins import ReadsJSON, ReadsYAML, RegistersSubclasses diff --git a/wysdom/dom/DOMDict.py b/wysdom/dom/DOMDict.py index 2dc39ff..7970ba6 100644 --- a/wysdom/dom/DOMDict.py +++ b/wysdom/dom/DOMDict.py @@ -25,11 +25,11 @@ def __init__( item_type: Optional[Schema] = None ) -> None: """ - :param value: A dict (or any `Mapping`) containing the data to populate this + :param value: A dict (or any :class:`collections.abc.Mapping`) containing the data to populate this object's properties. - :param json_dom_info: A `DOMInfo` named tuple containing information about this object's + :param json_dom_info: A :class:`~wysdom.dom.DOMInfo` named tuple containing information about this object's position in the DOM. - :param item_type: A `Schema` object specifying what constitutes a valid property + :param item_type: A :class:`~wysdom.Schema` object specifying what constitutes a valid property of this object. """ self.__json_schema_properties__ = DOMProperties( diff --git a/wysdom/dom/DOMElement.py b/wysdom/dom/DOMElement.py index 110b4ae..f03e202 100644 --- a/wysdom/dom/DOMElement.py +++ b/wysdom/dom/DOMElement.py @@ -41,7 +41,7 @@ def __init__( ) -> None: """ :param value: A data structure containing the data to populate this element. - :param json_dom_info: A :class:`DOMInfo` named tuple containing information about this object's + :param json_dom_info: A :class:`~wysdom.dom.DOMInfo` named tuple containing information about this object's position in the DOM. :param kwargs: Keyword arguments. """ @@ -78,9 +78,9 @@ def to_builtin(self) -> Any: def walk_elements(self) -> Iterator[DOMInfo]: """ Walk through the full tree structure within this DOM element. - Returns an iterator of :class:`DOMInfo` tuples in the form + Returns an iterator of :class:`~wysdom.dom.DOMInfo` tuples in the form (element, document, parent element_key). - :return: An iterator of :class:`DOMInfo` tuples. + :return: An iterator of :class:`~wysdom.dom.DOMInfo` tuples. """ yield self.__json_dom_info__ diff --git a/wysdom/dom/DOMList.py b/wysdom/dom/DOMList.py index 5e2ce00..bd740b4 100644 --- a/wysdom/dom/DOMList.py +++ b/wysdom/dom/DOMList.py @@ -35,9 +35,9 @@ def __init__( """ :param value: A list (or any :class:`Typing.Iterable`) containing the data to populate this object's items. - :param json_dom_info: A `DOMInfo` named tuple containing information about this object's + :param json_dom_info: A :class:`~wysdom.dom.DOMInfo` named tuple containing information about this object's position in the DOM. - :param item_type: A `Schema` object specifying what constitutes a valid item in this array. + :param item_type: A :class:`~wysdom.Schema` object specifying what constitutes a valid item in this array. """ if value and not isinstance(value, Iterable): raise ValidationError( @@ -107,6 +107,11 @@ def insert(self, index: int, item: Any) -> None: self.__json_element_data__.insert(index, self._new_child_item(item)) def to_builtin(self) -> List[Any]: + """ + Returns the contents of this DOM object as a Python builtin. + + :return: A Python list containing this object's data + """ return [ ( v.to_builtin() diff --git a/wysdom/dom/DOMObject.py b/wysdom/dom/DOMObject.py index c727e1d..38883c0 100644 --- a/wysdom/dom/DOMObject.py +++ b/wysdom/dom/DOMObject.py @@ -27,9 +27,9 @@ def __init__( json_dom_info: DOMInfo = None ) -> None: """ - :param value: A dict (or any `Mapping`) containing the data to populate this + :param value: A dict (or any :class:`collections.abc.Mapping`) containing the data to populate this object's properties. - :param json_dom_info: A `DOMInfo` named tuple containing information about this object's + :param json_dom_info: A :class:`~wysdom.dom.DOMInfo` named tuple containing information about this object's position in the DOM. """ if value and not isinstance(value, Mapping): diff --git a/wysdom/dom/DOMProperties.py b/wysdom/dom/DOMProperties.py index b5592e4..8273609 100644 --- a/wysdom/dom/DOMProperties.py +++ b/wysdom/dom/DOMProperties.py @@ -17,12 +17,12 @@ def __init__( additional_properties: Union[bool, Schema] = False ) -> None: """ - :param properties: A dictionary of :class:`~wysdom.base_schema.Schema.Schema` objects + :param properties: A dictionary of :class:`~wysdom.base_schema.Schema` objects defining the expected names and types of a :class:`.DOMObject`'s properties. :param additional_properties: Defines whether a :class:`.DOMObject` permits additional dynamically-named properties. Can be True or False, or - can be set to a specific `Schema` to restrict the permitted + can be set to a specific :class:`~wysdom.Schema` to restrict the permitted types of any additional properties. """ self.properties = properties or {} diff --git a/wysdom/dom/functions.py b/wysdom/dom/functions.py index 9abd65c..f450e79 100644 --- a/wysdom/dom/functions.py +++ b/wysdom/dom/functions.py @@ -39,7 +39,7 @@ def parent(element: DOMElement) -> Optional[DOMElement]: def key(element: DOMElement) -> Optional[str]: """ Retrieve the key of a particular :class:`.DOMElement` in its parent element, if it can be - referred to by a key (i.e. if it its parent element is a :class:`Mapping`). + referred to by a key (i.e. if it its parent element is a :class:`collections.abc.Mapping`). :param element: A DOM element :return: The key of that DOM element in its parent, or None if it has no key @@ -49,11 +49,11 @@ def key(element: DOMElement) -> Optional[str]: def schema(element: DOMElement) -> Schema: """ - Retrieve the :class:`~wysdom.base_schema.Schema.Schema` object for + Retrieve the :class:`~wysdom.base_schema.Schema` object for a particular :class:`.DOMElement`. :param element: A DOM element - :return: The :class:`~wysdom.base_schema.Schema.Schema` object associated + :return: The :class:`~wysdom.base_schema.Schema` object associated with that DOM element """ return element.__json_schema__() diff --git a/wysdom/object_schema/SchemaArray.py b/wysdom/object_schema/SchemaArray.py index 5589b25..fea6f82 100644 --- a/wysdom/object_schema/SchemaArray.py +++ b/wysdom/object_schema/SchemaArray.py @@ -8,7 +8,7 @@ class SchemaArray(Schema): """ - A schema specifying an array(corresponding to a Python list) + A schema specifying an array (corresponding to a Python list) :param items: The permitted data type or schema for the items of this array. Must be one of: @@ -33,7 +33,7 @@ def __call__( return DOMList( value, dom_info, - _item_type=self.items + item_type=self.items ) @property diff --git a/wysdom/object_schema/SchemaDict.py b/wysdom/object_schema/SchemaDict.py index a60113e..4036261 100644 --- a/wysdom/object_schema/SchemaDict.py +++ b/wysdom/object_schema/SchemaDict.py @@ -34,5 +34,5 @@ def __call__( return DOMDict( value, dom_info, - _item_type=self.additional_properties + item_type=self.additional_properties ) diff --git a/wysdom/object_schema/SchemaObject.py b/wysdom/object_schema/SchemaObject.py index 2d0fb69..f32665c 100644 --- a/wysdom/object_schema/SchemaObject.py +++ b/wysdom/object_schema/SchemaObject.py @@ -9,7 +9,7 @@ class SchemaObject(SchemaType): """ A schema specifying an object with named properties. - :param properties: A dictionary of `Schema` object defining the expected + :param properties: A dictionary of `Schema` objects defining the expected names and types of this object's properties. :param additional_properties: Defines whether this object permits additional dynamically-named properties. Can be True or False, or diff --git a/wysdom/user_objects/UserObject.py b/wysdom/user_objects/UserObject.py index 1c7c175..422ef53 100644 --- a/wysdom/user_objects/UserObject.py +++ b/wysdom/user_objects/UserObject.py @@ -17,12 +17,24 @@ class UserProperties(DOMProperties): + """ + A container for property information for a :class:`.UserObject` subclass. + """ def __init__( self, user_class: Type[UserObject], additional_properties: Union[bool, Schema] = False ): + """ + :param user_class: The subclass of :class:`.UserObject` to extract properties from. + Properties will be extracted from the class's + :class:`~wysdom.user_objects.UserProperty` descriptors. + :param additional_properties: Defines whether a :class:`.DOMObject` permits additional + dynamically-named properties. Can be True or False, or + can be set to a specific :class:`~wysdom.base_schema.Schema` + to restrict the permitted types of any additional properties. + """ self._user_class = user_class properties = {} for superclass in reversed(list(self._schema_superclasses())): diff --git a/wysdom/user_objects/UserProperty.py b/wysdom/user_objects/UserProperty.py index 6580370..f12a26d 100644 --- a/wysdom/user_objects/UserProperty.py +++ b/wysdom/user_objects/UserProperty.py @@ -8,18 +8,20 @@ class UserProperty(object): """ A data descriptor for creating attributes in user-defined subclasses - of `UserObject` which are mapped to keys in the underlying + of :class:`~.wysdom.user_objects.UserObject` which are mapped to keys in the underlying data object and to the `properties` key in the object's JSON schema. :param property_type: The data type or schema for this property. Must be one of: - A primitive Python type (str, int, bool, float) - A subclass of `UserObject` - An instance of `Schema` + + * A primitive Python type (:class:`str`, :class:`int`, + :class:`bool`, :class:`float`) + * A subclass of :class:`~.wysdom.user_objects.UserObject` + * An instance of :class:`~wysdom.base_schema.Schema` :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 `UserObject` + 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 @@ -29,7 +31,7 @@ class UserProperty(object): :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 `UserObject` instance that + passed the :class:`~.wysdom.user_objects.UserObject` instance that owns the property. Cannot be set in conjunction with `default`. """ diff --git a/wysdom/user_objects/__init__.py b/wysdom/user_objects/__init__.py index 1a0fc39..f38c7e4 100644 --- a/wysdom/user_objects/__init__.py +++ b/wysdom/user_objects/__init__.py @@ -1,2 +1,2 @@ -from .UserObject import UserObject +from .UserObject import UserObject, UserProperties from .UserProperty import UserProperty From 78a46e36c7f821188b252a4d010b92ac5de54510 Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Mon, 25 May 2020 15:45:26 +0200 Subject: [PATCH 5/6] Implement and test walk_elements --- features/dict.feature | 21 +++++++++++++++++++++ features/steps/steps.py | 19 +++++++++++++++++++ wysdom/dom/DOMList.py | 1 + wysdom/dom/DOMObject.py | 1 + 4 files changed, 42 insertions(+) diff --git a/features/dict.feature b/features/dict.feature index afed65e..d8b2ceb 100644 --- a/features/dict.feature +++ b/features/dict.feature @@ -72,6 +72,7 @@ Feature: Test dictionary DOM objects } } } + walk_elements = list(example.walk_elements()) """ Then the following statements are true: """ @@ -111,6 +112,26 @@ Feature: Test dictionary DOM objects copy.copy(example).to_builtin() == example_dict_input copy.deepcopy(example).to_builtin() == example_dict_input """ + And the list walk_elements contains the following tuples: + | element | document | parent | element_key | + | example | example | None | None | + | "Marge" | example | example | "first_name" | + | "Simpson" | example | example | "last_name" | + | example.current_address | example | example | "current_address" | + | "123 Fake Street" | example | example.current_address | "first_line" | + | "" | example | example.current_address | "second_line" | + | "Springfield" | example | example.current_address | "city" | + | 58008 | example | example.current_address | "postal_code" | + | example.previous_addresses | example | example | "previous_addresses" | + | example.previous_addresses[0] | example | example.previous_addresses | None | + | "742 Evergreen Terrace" | example | example.previous_addresses[0] | "first_line" | + | "" | example | example.previous_addresses[0] | "second_line" | + | "Springfield" | example | example.previous_addresses[0] | "city" | + | 58008 | example | example.previous_addresses[0] | "postal_code" | + | example.vehicles | example | example | "vehicles" | + | example.vehicles["eabf04"] | example | example.vehicles | "eabf04" | + | "orange" | example | example.vehicles["eabf04"] | "color" | + | "Station Wagon" | example | example.vehicles["eabf04"] | "description" | Scenario: Test bad input string diff --git a/features/steps/steps.py b/features/steps/steps.py index 4aade48..53cf832 100644 --- a/features/steps/steps.py +++ b/features/steps/steps.py @@ -54,3 +54,22 @@ def step_impl(context, exception_type): except Exception as e: if e.__class__.__name__ != exception_type: raise + +@then("the list {variable_name} contains the following tuples") +def step_impl(context, variable_name): + tuple_list = eval(variable_name) + + def matches(a, b): + return a is b or a == b + + for x in context.table: + if not any( + matches(y.element, eval(x["element"])) + and matches(y.document, eval(x["document"])) + and matches(y.parent, eval(x["parent"])) + and matches(y.element_key, eval(x["element_key"])) + for y in tuple_list + ): + raise Exception(f"Could not find {x}") + if len(list(context.table)) != len(tuple_list): + raise Exception("Lengths of lists do not match") diff --git a/wysdom/dom/DOMList.py b/wysdom/dom/DOMList.py index bd740b4..1595330 100644 --- a/wysdom/dom/DOMList.py +++ b/wysdom/dom/DOMList.py @@ -122,6 +122,7 @@ def to_builtin(self) -> List[Any]: ] def walk_elements(self) -> Iterator[DOMInfo]: + yield self.__json_dom_info__ for value in self: if isinstance(value, DOMElement): yield from value.walk_elements() diff --git a/wysdom/dom/DOMObject.py b/wysdom/dom/DOMObject.py index 38883c0..3fc94eb 100644 --- a/wysdom/dom/DOMObject.py +++ b/wysdom/dom/DOMObject.py @@ -82,6 +82,7 @@ def __str__(self): return str(self.__json_element_data__) def walk_elements(self) -> Iterator[DOMInfo]: + yield self.__json_dom_info__ for key, value in self.items(): if isinstance(value, DOMElement): yield from value.walk_elements() From 9b43b23519a82214c2aba7e3ae68f26ed668eabc Mon Sep 17 00:00:00 2001 From: Joe Taylor Date: Mon, 25 May 2020 21:39:15 +0200 Subject: [PATCH 6/6] Document SchemaConst, SchemaArray and SchemaDict --- docs/source/user_docs.rst | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/source/user_docs.rst b/docs/source/user_docs.rst index db78a9a..0fb2530 100644 --- a/docs/source/user_docs.rst +++ b/docs/source/user_docs.rst @@ -90,6 +90,44 @@ properties, you may use the `default_function` parameter:: default_function=lambda person: person.first_name ) +Constants +--------- + +Sometimes a property should always have one constant value for a given +schema. A common use case is for properties that identify an object as a +particular object type. + +In this case, use the :class:`wysdom.SchemaConst` class:: + + pet_type = UserProperty(SchemaConst("cat")) + + +Arrays and Dicts +---------------- + +For complex schemas, it is often necessary to declare a property as +being an array or a dictionary or other objects. + +For an array, use the :class:`wysdom.SchemaArray`. Properties of this type +function identically to a Python list (specifically a +:class:`collections.abc.MutableSequence`):: + + related_people = UserProperty(SchemaArray(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)) + +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. + +For both SchemaArray and SchemaDict you may pass in any type definition that +you would pass to a UserProperty. + + DOM functions =============