Skip to content

Commit

Permalink
Merge pull request #37 from jetavator/issue-20-Add_docstrings_to_all_…
Browse files Browse the repository at this point in the history
…classes_and_methods

Complete outstanding documentation
  • Loading branch information
jtv8 authored May 25, 2020
2 parents 0350c68 + 9b43b23 commit 3c8cc60
Show file tree
Hide file tree
Showing 32 changed files with 812 additions and 351 deletions.
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
327 changes: 8 additions & 319 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,330 +1,19 @@
Welcome to wysdom!
##################

.. toctree::
: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
"""
)

.. 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])
<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!'


.. autoclass:: wysdom.RegistersSubclasses
:members:


Internals
A-Z Index
=========

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`
22 changes: 22 additions & 0 deletions docs/source/api_reference.rst
Original file line number Diff line number Diff line change
@@ -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:
Loading

0 comments on commit 3c8cc60

Please sign in to comment.