labthings_fastapi.thing_description =================================== .. py:module:: labthings_fastapi.thing_description .. autoapi-nested-parse:: Thing Description module. This module supports the generation of Thing Descriptions. Currently, the top level function lives in `~lt.Thing.thing_description`, but most of the supporting code is in this submodule. A Pydantic model implementing the Thing Description is in `.thing_description._model`, and this is used to generate our TDs - using a `pydantic.BaseModel` helps make sure any TD errors get caught when they are generated in Python, which makes them much easier to debug. We also use the JSONSchema provided by W3C to validate the TDs we generate, in `.thing_description.validation`, as a double-check that we are standards-compliant. Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/labthings_fastapi/thing_description/_model/index /autoapi/labthings_fastapi/thing_description/validation/index Attributes ---------- .. autoapisummary:: labthings_fastapi.thing_description.JSONSchema Classes ------- .. autoapisummary:: labthings_fastapi.thing_description.DataSchema Functions --------- .. autoapisummary:: labthings_fastapi.thing_description.is_a_reference labthings_fastapi.thing_description.look_up_reference labthings_fastapi.thing_description.convert_anyof labthings_fastapi.thing_description.convert_prefixitems labthings_fastapi.thing_description.check_recursion labthings_fastapi.thing_description.jsonschema_to_dataschema labthings_fastapi.thing_description.type_to_dataschema Package Contents ---------------- .. py:class:: DataSchema(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Base for several classes describing datatypes. See https://www.w3.org/TR/wot-thing-description11/#dataschema. Create a new model by parsing and validating input data from keyword arguments. Raises [`ValidationError`][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model. `self` is explicitly positional-only to allow `self` as a field name. .. py:attribute:: field_type :type: Optional[TypeDeclaration] :value: None .. py:attribute:: description :type: Optional[Description] :value: None .. py:attribute:: title :type: Optional[Title] :value: None .. py:attribute:: descriptions :type: Optional[Descriptions] :value: None .. py:attribute:: titles :type: Optional[Titles] :value: None .. py:attribute:: writeOnly :type: Optional[bool] :value: None .. py:attribute:: readOnly :type: Optional[bool] :value: None .. py:attribute:: oneOf :type: Optional[list[DataSchema]] :value: None .. py:attribute:: unit :type: Optional[str] :value: None .. py:attribute:: enum :type: Optional[list] :value: None .. py:attribute:: format :type: Optional[str] :value: None .. py:attribute:: const :type: Optional[Any] :value: None .. py:attribute:: default :type: Optional[Any] :value: None .. py:attribute:: type :type: Optional[Type] :value: None .. py:attribute:: items :type: Optional[Union[DataSchema, List[DataSchema]]] :value: None .. py:attribute:: maxItems :type: Optional[int] :value: None .. py:attribute:: minItems :type: Optional[int] :value: None .. py:attribute:: minimum :type: Optional[Union[int, float]] :value: None .. py:attribute:: maximum :type: Optional[Union[int, float]] :value: None .. py:attribute:: exclusiveMinimum :type: Optional[Union[int, float]] :value: None .. py:attribute:: exclusiveMaximum :type: Optional[Union[int, float]] :value: None .. py:attribute:: multipleOf :type: Optional[Union[int, float]] :value: None .. py:attribute:: properties :type: Optional[Mapping[str, DataSchema]] :value: None .. py:attribute:: required :type: Optional[list[str]] :value: None .. py:attribute:: minLength :type: Optional[int] :value: None .. py:attribute:: maxLength :type: Optional[int] :value: None .. py:attribute:: pattern :type: Optional[str] :value: None .. py:attribute:: contentEncoding :type: Optional[str] :value: None .. py:attribute:: contentMediaType :type: Optional[str] :value: None .. py:attribute:: model_config Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. .. py:data:: JSONSchema .. py:function:: is_a_reference(d: JSONSchema) -> bool Return True if a JSONSchema dict is a reference. JSON Schema references are one-element dictionaries with a single key, `$ref`. `pydantic` sometimes breaks this rule and so we don't check that it's a single key. :param d: A JSONSchema dictionary. :return: ``True`` if the dictionary contains ``$ref``. .. py:function:: look_up_reference(reference: str, d: JSONSchema) -> JSONSchema Look up a reference in a JSONSchema. JSONSchema allows references, where chunks of JSON may be reused. Thing Description does not allow references, so we need to resolve them and paste them in-line. This function can only deal with local references, i.e. they must start with ``#`` indicating they belong to the current file. This function first asserts the reference is local (i.e. starts with # so it's relative to the current file), then looks up each path component in turn and returns the resolved chunk of JSON. :param reference: the local reference (should start with ``#``). :param d: the JSONSchema document. :return: the chunk of JSONSchema referenced by ``reference`` in ``d``. :raise KeyError: if the reference is not found in the supplied JSONSchema. :raise NotImplementedError: if the reference does not start with ``"#/`` and thus is not a local reference. .. py:function:: convert_anyof(d: JSONSchema) -> JSONSchema Convert the anyof key to oneof. JSONSchema makes a distinction between "anyof" and "oneof", where the former means "any of these fields can be present" and the latter means "exactly one of these fields must be present". Thing Description does not have this distinction, so we convert ``anyof`` to ``oneof``. :param d: the JSONSchema object. :return: a copy of ``d``, with ``anyOf`` replaced with ``oneOf``. .. py:function:: convert_prefixitems(d: JSONSchema) -> JSONSchema Convert the prefixitems key to items. The ``items`` key in an ArraySchema_ may work in two ways: * If it is a `DataSchema` instance, then it specifies the schema for all items in the array. This would apply to ``list[int]`` for example. * If it is a sequence of `DataSchema` instances, then we are describing a `tuple` with a defined number of elements, each with a defined type, for example `tuple[int, str]`\ . JSONSchema 2020 uses `items` to mean the type of subsequent items (i.e. ``list[...]``), so it's only ever a single type. When describing a `tuple` it will use ``prefixItems`` to give the type of each element. JSONSchema 2019 (on which ArraySchema_ is based) has a different definition again, using ``items`` and ``additionalItems``\ . We are converting from JSONSchema 2020 (which is what `pydantic` generates) to ArraySchema_ so we will convert ``prefixItems`` into ``items``\ . If both keys are present, we will raise an error, as that's not currently supported. :param d: the JSONSchema object. :return: a copy of ``d``, converted to 2019 format as above. :raise KeyError: if we would overwrite an existing ``items`` key. .. _ArraySchema: https://www.w3.org/TR/wot-thing-description/#arrayschema .. py:function:: check_recursion(depth: int, limit: int) -> None Check the recursion count is less than the limit. :param depth: the current recursion depth. :param limit: the maximum recursion depth. :raise RecursionError: if we exceed the recursion depth. .. py:function:: jsonschema_to_dataschema(d: JSONSchema, root_schema: Optional[JSONSchema] = None, recursion_depth: int = 0, recursion_limit: int = 99) -> JSONSchema Convert a data type description from JSONSchema to Thing Description. :ref:`wot_td` represents datatypes with DataSchemas, which are almost but not quite JSONSchema format. There are two main tasks to convert them: Resolving references -------------------- JSONSchema allows schemas to be replaced with `{"$ref": "#/path/to/schema"}`. Thing Description does not allow this. `dereference_jsonschema_dict` takes a `dict` representation of a JSON Schema document, and replaces all the references with the appropriate chunk of the file. Converting union types ---------------------- JSONSchema can represent `Union` types using the `anyOf` keyword, which is called `oneOf` by Thing Description. It's possible to achieve the same thing in the specific case of array elements, by setting `items` to a list of `DataSchema` objects. This function does not yet do that conversion. This generates a copy of the document, to avoid messing up `pydantic`'s cache. This function runs recursively: to start with, only ``d`` should be provided (the input JSONSchema). We will use the other arguments to keep track of recursion as we convert the schema. :param d: a JSONSchema representation of a datatype. :param root_schema: the whole JSONSchema document, for resolving references. This will be set to ``d`` when the function is called initially. :param recursion_depth: how deeply this function has recursed (starts at zero). :param recursion_limit: how deeply this function is allowed to recurse. :return: the datatype in Thing Description format. This is not yet a `.DataSchema` instance, but may be trivially converted to one with ``DataSchema(**schema)``. .. py:function:: type_to_dataschema(t: type, **kwargs: Any) -> _model.DataSchema Convert a Python type to a Thing Description DataSchema. This makes use of pydantic's `schema_of` function to create a json schema, then applies some fixes to make a DataSchema as per the Thing Description (because Thing Description is almost but not quite compatible with JSONSchema). Additional keyword arguments are added to the DataSchema, and will override the fields generated from the type that is passed in. Typically you'll want to use this for the `title` field. :param t: the Python datatype or `pydantic.BaseModel` subclass. :param \**kwargs: Additional keyword arguments passed to the `.DataSchema` constructor, often including ``title``. :return: a `.DataSchema` representing the type. :raise ValidationError: if the datatype cannot be represented by a `.DataSchema`.