diff --git a/docs/index.rst b/docs/index.rst index b15eccd..f65dbba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -95,6 +95,23 @@ properties, you may use the `default_function` parameter:: 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 ====== diff --git a/features/dict.feature b/features/dict.feature new file mode 100644 index 0000000..f35af6b --- /dev/null +++ b/features/dict.feature @@ -0,0 +1,83 @@ +Feature: Test JSON DOM objects + + Scenario: Test good input string + + Given the Python module dict_module.py + When we execute the following python code: + """ + example_dict_input = { + "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" + } + } + } + example = dict_module.Person(example_dict_input) + example_dict_output = example.to_builtin() + """ + Then the following statements are true: + """ + example.first_name == "Marge" + example.last_name == "Simpson" + example.current_address.first_line == "123 Fake Street" + example.current_address.second_line == "" + example.current_address.city == "Springfield" + example.current_address.postal_code == 58008 + example["current_address"].first_line == "123 Fake Street" + example["current_address"].second_line == "" + example["current_address"].city == "Springfield" + example["current_address"].postal_code == 58008 + example.previous_addresses[0].first_line == "742 Evergreen Terrace" + example.previous_addresses[0].second_line == "" + example.previous_addresses[0].city == "Springfield" + example.previous_addresses[0].postal_code == 58008 + example.vehicles["eabf04"].color == "orange" + example.vehicles["eabf04"].description == "Station Wagon" + example.vehicles["eabf04"].license == "eabf04" + len(example) == 5 + len(example.current_address) == 4 + len(example.previous_addresses) == 1 + len(example.vehicles) == 1 + parent(example.current_address) is example + document(example.current_address) is example + key(example.current_address) == "current_address" + parent(example.vehicles["eabf04"]) is example.vehicles + document(example.vehicles["eabf04"]) is example + key(example.vehicles["eabf04"]) == "eabf04" + parent(example["vehicles"]["eabf04"]) is example.vehicles + document(example["vehicles"]["eabf04"]) is example + key(example["vehicles"]["eabf04"]) == "eabf04" + schema(example).is_valid(example_dict_input) + example_dict_output == example_dict_input + """ + + Scenario: Test bad input string + + Given the Python module dict_module.py + When we execute the following python code: + """ + example_dict_input = {"foo": "bar"} + """ + Then the following statements are true: + """ + not(schema(example).is_valid(example_dict_input)) + """ + And the following statement raises ValidationError + """ + dict_module.Person(example_dict_input) + """ \ No newline at end of file diff --git a/features/examples/modules/dict_module.py b/features/examples/modules/dict_module.py new file mode 100644 index 0000000..14739be --- /dev/null +++ b/features/examples/modules/dict_module.py @@ -0,0 +1,27 @@ +from typing import Dict, List + +from wysdom import UserObject, UserProperty, SchemaArray, SchemaDict, key + + +class Vehicle(UserObject): + color: str = UserProperty(str) + description: str = UserProperty(str) + + @property + def license(self): + return key(self) + + +class Address(UserObject): + first_line: str = UserProperty(str) + second_line: str = UserProperty(str) + city: str = UserProperty(str) + postal_code: str = UserProperty(int) + + +class Person(UserObject): + first_name: str = UserProperty(str) + last_name: str = UserProperty(str) + current_address: Address = UserProperty(Address) + previous_addresses: List[Address] = UserProperty(SchemaArray(Address)) + vehicles: Dict[str, Vehicle] = UserProperty(SchemaDict(Vehicle)) diff --git a/features/steps/steps.py b/features/steps/steps.py index 47912ad..a51ad38 100644 --- a/features/steps/steps.py +++ b/features/steps/steps.py @@ -1,6 +1,6 @@ from behave import * -from wysdom import document, parent, key +from wysdom import document, parent, key, schema import os import importlib.util @@ -38,6 +38,7 @@ def step_impl(context): assert callable(document) assert callable(parent) assert callable(key) + assert callable(schema) for line in context.text.splitlines(): result = eval(line) if not result: diff --git a/wysdom/__init__.py b/wysdom/__init__.py index caf11c4..6e38a73 100644 --- a/wysdom/__init__.py +++ b/wysdom/__init__.py @@ -1,7 +1,7 @@ from .__version__ import __version__ from . import mixins from .exceptions import ValidationError -from .dom.functions import dom, document, parent, key +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 diff --git a/wysdom/__version__.py b/wysdom/__version__.py index 7341562..1f1760f 100644 --- a/wysdom/__version__.py +++ b/wysdom/__version__.py @@ -1,3 +1,3 @@ -VERSION = (0, 1, 0) +VERSION = (0, 1, 1) __version__ = '.'.join(map(str, VERSION)) diff --git a/wysdom/dom/__init__.py b/wysdom/dom/__init__.py index 3b88aa9..4f9fcba 100644 --- a/wysdom/dom/__init__.py +++ b/wysdom/dom/__init__.py @@ -3,3 +3,4 @@ from .DOMObject import DOMObject from .DOMDict import DOMDict from .DOMList import DOMList +from .functions import document, parent, key, schema diff --git a/wysdom/dom/functions.py b/wysdom/dom/functions.py index ce08421..b6efab7 100644 --- a/wysdom/dom/functions.py +++ b/wysdom/dom/functions.py @@ -1,20 +1,57 @@ from typing import Optional +from ..base_schema import Schema from .DOMElement import DOMElement from . import DOMInfo def dom(element: DOMElement) -> DOMInfo: + """ + Retrieve a DOMInfo object for a 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 element.__json_dom_info__ def document(element: DOMElement) -> Optional[DOMElement]: + """ + Retrieve the owning document for a DOMElement, if it exists. + + :param element: A DOM element + :return: The owning document for that DOM element, or None if none exists + """ return dom(element).document def parent(element: DOMElement) -> Optional[DOMElement]: + """ + Retrieve the parent element of a DOMElement, if it exists. + + :param element: A DOM element + :return: The parent element of that DOM element, or None of none exists + """ return dom(element).parent 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). + + :param element: A DOM element + :return: The key of that DOM element in its parent, or None if it has no key + """ return dom(element).element_key + + +def schema(element: DOMElement) -> Schema: + """ + Retrieve the Schema object for a particular DOMElement. + + :param element: A DOM element + :return: The Schema object associated with that DOM element + """ + return element.__json_schema__()