Coverage for core / src / sensorkit / webapi / schema.py: 100%
34 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-02 00:03 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-02 00:03 +0000
1# SPDX-License-Identifier: Apache-2.0
2import itertools
3from typing import Any
5from pydantic import BaseModel, TypeAdapter
7from sensorkit import api as sk
8from sensorkit.common.keyword import _keyword_index
11def _sensorkit_models():
12 # Return all models registered in the sensorkit package.
13 # FIXME: This is top-level and should probably live in `sensorkit.api`
14 return itertools.chain(
15 _keyword_index,
16 sk.DeviceCommand.registry.entries,
17 sk.Task.registry.entries,
18 )
21def _update_refs(obj):
22 """Recursively update $ref from #/$defs/Name to #/components/schemas/Name."""
23 if isinstance(obj, dict):
24 if "$ref" in obj and isinstance(obj["$ref"], str):
25 if obj["$ref"].startswith("#/$defs/"):
26 obj["$ref"] = obj["$ref"].replace("#/$defs/", "#/components/schemas/")
27 for v in obj.values():
28 _update_refs(v)
29 elif isinstance(obj, list):
30 for item in obj:
31 _update_refs(item)
34def _add_model_schema(model: type[BaseModel], schemas: dict[str, Any]):
35 adapter = TypeAdapter(model)
36 schema = adapter.json_schema()
38 # Extract internal definitions and move them to global schemas.
39 if "$defs" in schema:
40 defs = schema.pop("$defs")
42 for name, def_schema in defs.items():
43 if name not in schemas:
44 # Recursively update refs in the definition itself.
45 _update_refs(def_schema)
46 schemas[name] = def_schema
48 # Update refs in the main schema.
49 _update_refs(schema)
51 # Add the class schema to components/schemas.
52 schema_name = model.__name__
54 if schema_name not in schemas:
55 schemas[schema_name] = schema
58def add_sensorkit_schema(schemas: dict[str, Any]):
59 # Generate JSON schema for registered models.
60 for cls in _sensorkit_models():
61 if issubclass(cls, BaseModel):
62 _add_model_schema(cls, schemas)