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.

is_an_object(→ bool)

Determine whether a JSON schema dict is an object.

convert_object(→ JSONSchema)

Convert an object from JSONSchema to Thing Description.

convert_anyof(→ JSONSchema)

Convert the anyof key to oneof.

convert_prefixitems(→ JSONSchema)

Convert the prefixitems key to items.

convert_additionalproperties(→ JSONSchema)

Move additionalProperties into properties, or remove it.

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.is_an_object(d: JSONSchema) bool

Determine whether a JSON schema dict is an object.

Parameters:

d – a chunk of JSONSchema describing a datatype.

Returns:

True if the type is object.

labthings_fastapi.thing_description.convert_object(d: JSONSchema) JSONSchema

Convert an object from JSONSchema to Thing Description.

Convert JSONSchema objects to Thing Description datatypes.

Currently, this deletes the additionalProperties keyword, which is not supported by Thing Description.

Parameters:

d – the JSONSchema object.

Returns:

a copy of d, with additionalProperties deleted.

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.

JSONSchema 2019 (as used by thing description) used items with a list of values in the same way that JSONSchema now uses prefixitems.

JSONSchema 2020 uses items to mean the same as additionalItems in JSONSchema 2019 - but Thing Description doesn’t support the additionalItems keyword. This will result in us overwriting additional items, and we raise a ValueError if that happens.

This behaviour may be relaxed in the future.

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.convert_additionalproperties(d: JSONSchema) JSONSchema

Move additionalProperties into properties, or remove it.

JSONSchema uses additionalProperties to define optional properties of objects. For Thing Descriptions, this should be moved inside the properties object.

Parameters:

d – the JSONSchema object.

Returns:

a copy of d, with additionalProperties moved into properties or deleted if properties is not present.

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:

ValueError – 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.