labthings_fastapi.thing_description

Thing Description module.

This module supports the generation of Thing Descriptions. Currently, the top level function lives in 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

Attributes

JSONSchema

Classes

DataSchema

Base for several classes describing datatypes.

Functions

is_a_reference(→ bool)

Return True if a JSONSchema dict is a reference.

look_up_reference(→ JSONSchema)

Look up a reference in a JSONSchema.

convert_anyof(→ JSONSchema)

Convert the anyof key to oneof.

convert_prefixitems(→ JSONSchema)

Convert the prefixitems key to items.

check_recursion(→ None)

Check the recursion count is less than the limit.

jsonschema_to_dataschema(→ JSONSchema)

Convert a data type description from JSONSchema to Thing Description.

type_to_dataschema(→ _model.DataSchema)

Convert a Python type to a Thing Description DataSchema.

Package Contents

class labthings_fastapi.thing_description.DataSchema(/, **data: Any)

Bases: 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.

field_type: TypeDeclaration | None = None
description: Description | None = None
title: Title | None = None
descriptions: Descriptions | None = None
titles: Titles | None = None
writeOnly: bool | None = None
readOnly: bool | None = None
oneOf: list[DataSchema] | None = None
unit: str | None = None
enum: list | None = None
format: str | None = None
const: Any | None = None
default: Any | None = None
type: Type | None = None
items: DataSchema | List[DataSchema] | None = None
maxItems: int | None = None
minItems: int | None = None
minimum: int | float | None = None
maximum: int | float | None = None
exclusiveMinimum: int | float | None = None
exclusiveMaximum: int | float | None = None
multipleOf: int | float | None = None
properties: Mapping[str, DataSchema] | None = None
required: list[str] | None = None
minLength: int | None = None
maxLength: int | None = None
pattern: str | None = None
contentEncoding: str | None = None
contentMediaType: str | None = None
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

labthings_fastapi.thing_description.JSONSchema
labthings_fastapi.thing_description.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.

Parameters:

d – A JSONSchema dictionary.

Returns:

True if the dictionary contains $ref.

labthings_fastapi.thing_description.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.

Parameters:
  • reference – the local reference (should start with #).

  • d – the JSONSchema document.

Returns:

the chunk of JSONSchema referenced by reference in d.

Raises:
  • KeyError – if the reference is not found in the supplied JSONSchema.

  • NotImplementedError – if the reference does not start with "#/ and thus is not a local reference.

labthings_fastapi.thing_description.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.

Parameters:

d – the JSONSchema object.

Returns:

a copy of d, with anyOf replaced with oneOf.

labthings_fastapi.thing_description.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.

Parameters:

d – the JSONSchema object.

Returns:

a copy of d, converted to 2019 format as above.

Raises:

KeyError – if we would overwrite an existing items key.

labthings_fastapi.thing_description.check_recursion(depth: int, limit: int) None

Check the recursion count is less than the limit.

Parameters:
  • depth – the current recursion depth.

  • limit – the maximum recursion depth.

Raises:

RecursionError – if we exceed the recursion depth.

labthings_fastapi.thing_description.jsonschema_to_dataschema(d: JSONSchema, root_schema: JSONSchema | None = None, recursion_depth: int = 0, recursion_limit: int = 99) JSONSchema

Convert a data type description from JSONSchema to Thing Description.

Thing Description 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).

labthings_fastapi.thing_description.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.

Parameters:
  • t – the Python datatype or pydantic.BaseModel subclass.

  • **kwargs – Additional keyword arguments passed to the DataSchema constructor, often including title.

Returns:

a DataSchema representing the type.

Raises:

ValidationError – if the datatype cannot be represented by a DataSchema.