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.

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 UserProperty, or an instance of Schema:

class Person(UserObject, additional_properties=str):
    ...

class Person(UserObject, additional_properties=Address):
    ...

class Person(UserObject, additional_properties=SchemaDict[Vehicle]):
    ...

Property Types

The type parameter that you pass in to UserProperty can be a primitive Python type, a subclass of UserProperty, or an instance of 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")

Optional Properties

If a UserProperty is intended to be an optional property in the underlying data object, it can be specified as such using the optional parameter:

class Person(UserObject):
    ...
    middle_name = UserProperty(str, optional=True)

If a parameter is not specified as optional, it will appear in the required list in the generated JSON schema and will therefore throw a ValidationError if it is missing from any underlying data object that is loaded.

If optional properties do not have a default or default_function, they will default to None if not set.

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
    )

A UserProperty may not have both a default and a default_function, and if either default or default_function is set then optional defaults to True (and cannot be explicitly set to False).

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 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 wysdom.SchemaArray. Properties of this type function identically to a Python list (specifically a collections.abc.MutableSequence):

related_people = UserProperty(SchemaArray(Person))

For an dictionary, use the wysdom.SchemaDict. Properties of this type function identically to a Python dict (specifically a collections.abc.MutableMapping with keys of type str):

related_people = UserProperty(SchemaDict(Person))

A SchemaDict is a special case of a 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

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.

wysdom.document(element: wysdom.dom.DOMElement.DOMElement) → Optional[wysdom.dom.DOMElement.DOMElement]

Retrieve the owning document for a DOMElement, if it exists.

Parameters:element – A DOM element
Returns:The owning document for that DOM element, or None if none exists
wysdom.parent(element: wysdom.dom.DOMElement.DOMElement) → Optional[wysdom.dom.DOMElement.DOMElement]

Retrieve the parent element of a DOMElement, if it exists.

Parameters:element – A DOM element
Returns:The parent element of that DOM element, or None of none exists
wysdom.key(element: wysdom.dom.DOMElement.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 collections.abc.Mapping).

Parameters:element – A DOM element
Returns:The key of that DOM element in its parent, or None if it has no key
wysdom.schema(element: wysdom.dom.DOMElement.DOMElement) → wysdom.base_schema.Schema.Schema

Retrieve the Schema object for a particular DOMElement.

Parameters:element – A DOM element
Returns:The Schema object associated with that DOM element

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 wysdom.mixins.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"
        }
      }
    }
    """
)

ReadsYAML

Usage: As in the first usage example, but add wysdom.mixins.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
    """
)

RegistersSubclasses

Use wysdom.mixins.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])
<class '__main__.Dog'>

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")
<class '__main__.Elephant'>

>>> Pet.registered_subclass_instance("elephant",
...     {"pet_type": "elephant", "name": "Stampy"}).speak()
'Stampy says Trumpet!'

Internals

Schemas

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)