]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: utils: modeling: simple documentation
authorAleš Mrázek <ales.mrazek@nic.cz>
Mon, 11 Jul 2022 09:49:56 +0000 (11:49 +0200)
committerAleš Mrázek <ales.mrazek@nic.cz>
Mon, 11 Jul 2022 09:49:56 +0000 (11:49 +0200)
manager/knot_resolver_manager/utils/modeling/README.md [new file with mode: 0644]

diff --git a/manager/knot_resolver_manager/utils/modeling/README.md b/manager/knot_resolver_manager/utils/modeling/README.md
new file mode 100644 (file)
index 0000000..48e7c90
--- /dev/null
@@ -0,0 +1,147 @@
+# Modeling utils
+
+These utilities are used to model schemas for data stored in a python dictionary or YAML and JSON format.
+The utilities also take care of parsing, validating and creating JSON schemas and basic documentation.
+
+## Creating schema
+
+Schema is created using `SchemaNode` class. Schema structure is specified using annotations.
+
+```python
+from .modeling import SchemaNode
+
+class BasicSchema(SchemaNode):
+    integer: int = 5    # a default value can be specified
+    string: str
+    boolean: bool
+```
+Even more complex types can be used in a schema. Schemas can be also nested.
+
+```python
+from typing import Any, Dict, List, Optional, Union
+
+class ComplexSchema(SchemaNode):
+    optional: Optional[str]     # this field is optional
+    union: Union[int, str]      # integer and string are both valid
+    list: List[int]             # list of integers
+    dictionary: Dict[str, Any] = {"key": False}
+    basic_schema: BasicSchema   # nested schema
+```
+
+
+### Additianal validation
+
+If a some additional validation needs to be done, there is `_validate()` method for that.
+`ValueError` exception should be raised in case of validation error.
+
+```python
+class FieldsSchema(SchemaNode):
+    field1: int
+    field2: int
+
+    def _validate(self) -> None:
+        if self.field1 > self.field2:
+            raise ValueError("field1 is bigger than field2")
+```
+
+
+### Additional layer, transformation methods
+
+It is possible to add layers to schema and use a transformation method between layers to process the value.
+Transformation method must be named based on field (`value` in this example) with `_` underscore prefix.
+In this example, the `Layer2Schema` is structure for input data and `Layer1Schema` is for result data.
+
+```python
+class Layer1Schema(SchemaNode):
+    class Layer2Schema(SchemaNode):
+        value: Union[str, int]
+
+    _LAYER = Layer2Schema
+
+    value: int
+
+    def _value(self, obj: Layer2Schema) -> Any:
+        if isinstance(str, obj.value):
+            return len(obj.value)   # transform str values to int; this is just example
+        return obj.value
+```
+
+### Documentation and JSON schema
+
+Created schema can be documented using simple docstring. Json schema is created by calling `json_schema()` method on schema class. JSON schema includes description from docstring, defaults, etc.
+
+```python
+BasicSchema(SchemaNode):
+    """
+    This is description for BasicSchema itself.
+
+    ---
+    integer: description for integer field
+    string: description for string field
+    boolean: description for boolean field
+    """
+
+    integer: int = 5
+    string: str
+    boolean: bool
+
+json_schema = BasicSchema.json_schema()
+```
+
+
+## Creating custom type
+
+Custom types can be only using `CustomValueType` class which is integrated to parsing a validating process.
+Use `DataValidationError` to rase exception during validation. `object_path` is used to track node in more complex/nested schemas and create useful logging message.
+
+```python
+from .modeling import CustomValueType
+from .modeling.exceptions import DataValidationError
+
+class IntNonNegative(CustomValueType):
+    def __init__(self, source_value: Any, object_path: str = "/") -> None:
+        super().__init__(source_value)
+        if isinstance(source_value, int) and not isinstance(source_value, bool):
+            if source_value < 0:
+                raise DataValidationError(f"value {source_value} is negative number.", object_path)
+            self._value = source_value
+        else:
+            raise DataValidationError(
+                f"expected integer, got '{type(source_value)}'",
+                object_path,
+            )
+```
+
+For JSON schema you should implement `json_schema` method.
+It should return [JSON schema representation](https://json-schema.org/understanding-json-schema/index.html) of the custom type.
+
+```python
+    @classmethod
+    def json_schema(cls: Type["IntNonNegative"]) -> Dict[Any, Any]:
+        return {"type": "integer", "minimum": 0}
+```
+
+
+## Parsing JSON/YAML
+
+For example, YAML data for `BasicSchema` look like this.
+
+```yaml
+# data.yaml
+integer: 55
+string: this is string
+boolean: false
+```
+
+To parse data from YAML format just use `parse_yaml` function or `parse_json` for JSON format.
+
+```python
+from .modeling import parse_yaml
+
+# read data from file
+with open("data.yaml") as f:
+    str_data = f.read()
+
+dict_data = parse_yaml(str_data)
+validated_data = BasicSchema(dict_data)
+```
\ No newline at end of file