tigrbl_spec 0.4.2.dev4__tar.gz → 0.4.3.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tigrbl_spec-0.4.2.dev4 → tigrbl_spec-0.4.3.dev4}/PKG-INFO +3 -1
- {tigrbl_spec-0.4.2.dev4 → tigrbl_spec-0.4.3.dev4}/pyproject.toml +15 -2
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/__init__.py +44 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/codegen.py +27 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/migrations/__init__.py +51 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/models/__init__.py +3 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/models/v0_3_20/__init__.py +45 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schema.py +202 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/AppSpec.json +146 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/BindingRegistrySpec.json +57 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/BindingSpec.json +51 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/ColumnSpec.json +137 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/DataTypeSpec.json +72 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/EngineProviderSpec.json +54 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/EngineSpec.json +169 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/FieldSpec.json +70 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/ForeignKeySpec.json +84 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/HookSpec.json +122 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/HttpJsonRpcBindingSpec.json +67 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/HttpRestBindingSpec.json +67 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/HttpStreamBindingSpec.json +67 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/IOSpec.json +174 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/OpSpec.json +282 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/RequestSpec.json +82 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/ResponseSpec.json +166 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/RouterSpec.json +108 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/SchemaRef.json +55 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/SchemaSpec.json +77 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/SessionSpec.json +363 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/SseBindingSpec.json +67 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/StorageSpec.json +201 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/StorageTransformSpec.json +65 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/StorageTypeRef.json +58 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/TableProfileSpec.json +134 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/TableRegistrySpec.json +47 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/TableSpec.json +158 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/TemplateSpec.json +84 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/WebTransportBindingSpec.json +78 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/WsBindingSpec.json +67 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/bundle.json +3565 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/manifest.json +42 -0
- tigrbl_spec-0.4.3.dev4/tigrbl_spec/schemas/0.3.20/shared.json +224 -0
- tigrbl_spec-0.4.2.dev4/tigrbl_spec/__init__.py +0 -5
- {tigrbl_spec-0.4.2.dev4 → tigrbl_spec-0.4.3.dev4}/LICENSE +0 -0
- {tigrbl_spec-0.4.2.dev4 → tigrbl_spec-0.4.3.dev4}/NOTICE +0 -0
- {tigrbl_spec-0.4.2.dev4 → tigrbl_spec-0.4.3.dev4}/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl_spec
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3.dev4
|
|
4
4
|
Summary: Shared Tigrbl interfaces, protocol definitions, compatibility targets, and specification artifacts for framework integration.
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,6 +225,8 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
225
225
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
226
226
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
227
227
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
228
|
+
Requires-Dist: jsonschema (>=4.0)
|
|
229
|
+
Requires-Dist: typing-extensions (>=4.0)
|
|
228
230
|
Project-URL: Discord, https://discord.gg/K4YTAPapjR
|
|
229
231
|
Project-URL: Homepage, https://github.com/tigrbl/tigrbl
|
|
230
232
|
Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tigrbl_spec"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3.dev4"
|
|
4
4
|
description = "Shared Tigrbl interfaces, protocol definitions, compatibility targets, and specification artifacts for framework integration."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -41,7 +41,10 @@ classifiers = [
|
|
|
41
41
|
requires-python = ">=3.10,<3.15"
|
|
42
42
|
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
43
43
|
|
|
44
|
-
dependencies = [
|
|
44
|
+
dependencies = [
|
|
45
|
+
"jsonschema>=4.0",
|
|
46
|
+
"typing-extensions>=4.0",
|
|
47
|
+
]
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
[project.urls]
|
|
@@ -55,6 +58,16 @@ Issues = "https://github.com/tigrbl/tigrbl/issues"
|
|
|
55
58
|
requires = ["poetry-core>=1.0.0"]
|
|
56
59
|
build-backend = "poetry.core.masonry.api"
|
|
57
60
|
|
|
61
|
+
[tool.poetry]
|
|
62
|
+
packages = [
|
|
63
|
+
{ include = "tigrbl_spec" },
|
|
64
|
+
]
|
|
65
|
+
include = [
|
|
66
|
+
"tigrbl_spec/schemas/**/*.json",
|
|
67
|
+
"tigrbl_spec/migrations/**/*.py",
|
|
68
|
+
"tigrbl_spec/models/**/*.py",
|
|
69
|
+
]
|
|
70
|
+
|
|
58
71
|
[dependency-groups]
|
|
59
72
|
dev = [
|
|
60
73
|
"pytest>=8.0",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Tigrbl specification schema catalog and bindings."""
|
|
2
|
+
|
|
3
|
+
from .schema import (
|
|
4
|
+
CURRENT_SCHEMA_VERSION,
|
|
5
|
+
SCHEMA_VERSION,
|
|
6
|
+
SpecSchemaError,
|
|
7
|
+
SpecValidationError,
|
|
8
|
+
UnknownSpecKindError,
|
|
9
|
+
UnsupportedSchemaVersionError,
|
|
10
|
+
identity_fields,
|
|
11
|
+
load_bundle,
|
|
12
|
+
load_manifest,
|
|
13
|
+
load_payload,
|
|
14
|
+
load_schema,
|
|
15
|
+
schema_path,
|
|
16
|
+
schema_versions,
|
|
17
|
+
spec_kinds,
|
|
18
|
+
spec_type,
|
|
19
|
+
validate_payload,
|
|
20
|
+
with_identity,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"__version__",
|
|
25
|
+
"CURRENT_SCHEMA_VERSION",
|
|
26
|
+
"SCHEMA_VERSION",
|
|
27
|
+
"SpecSchemaError",
|
|
28
|
+
"SpecValidationError",
|
|
29
|
+
"UnknownSpecKindError",
|
|
30
|
+
"UnsupportedSchemaVersionError",
|
|
31
|
+
"identity_fields",
|
|
32
|
+
"load_bundle",
|
|
33
|
+
"load_manifest",
|
|
34
|
+
"load_payload",
|
|
35
|
+
"load_schema",
|
|
36
|
+
"schema_path",
|
|
37
|
+
"schema_versions",
|
|
38
|
+
"spec_kinds",
|
|
39
|
+
"spec_type",
|
|
40
|
+
"validate_payload",
|
|
41
|
+
"with_identity",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
__version__ = "0.4.0.dev1"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
from tigrbl_spec.schema import CURRENT_SCHEMA_VERSION, spec_kinds
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CodegenFreshnessError(AssertionError):
|
|
9
|
+
"""Raised when checked-in generated models do not match packaged schemas."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generated_module_name(version: str = CURRENT_SCHEMA_VERSION) -> str:
|
|
13
|
+
normalized = version.replace(".", "_")
|
|
14
|
+
return f"tigrbl_spec.models.v{normalized}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_generated_model_freshness(version: str = CURRENT_SCHEMA_VERSION) -> None:
|
|
18
|
+
module = import_module(generated_module_name(version))
|
|
19
|
+
expected = set(spec_kinds(version))
|
|
20
|
+
actual = {name for name in getattr(module, "__all__", ()) if name != "SpecDataclass"}
|
|
21
|
+
missing = sorted(expected - actual)
|
|
22
|
+
extra = sorted(actual - expected)
|
|
23
|
+
if missing or extra:
|
|
24
|
+
raise CodegenFreshnessError(
|
|
25
|
+
f"Generated tigrbl_spec dataclasses are stale for {version}: "
|
|
26
|
+
f"missing={missing}, extra={extra}"
|
|
27
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tigrbl_spec.schema import CURRENT_SCHEMA_VERSION, UnsupportedSchemaVersionError, schema_versions
|
|
7
|
+
|
|
8
|
+
SUPPORTED_SCHEMA_VERSIONS = schema_versions()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SchemaMigrationError(ValueError):
|
|
12
|
+
"""Raised when no valid schema catalog migration path exists."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def migration_path(
|
|
16
|
+
from_version: str,
|
|
17
|
+
to_version: str = CURRENT_SCHEMA_VERSION,
|
|
18
|
+
) -> tuple[str, ...]:
|
|
19
|
+
if from_version not in SUPPORTED_SCHEMA_VERSIONS:
|
|
20
|
+
raise UnsupportedSchemaVersionError(f"Unsupported source schema version {from_version!r}")
|
|
21
|
+
if to_version not in SUPPORTED_SCHEMA_VERSIONS:
|
|
22
|
+
raise UnsupportedSchemaVersionError(f"Unsupported target schema version {to_version!r}")
|
|
23
|
+
if from_version == to_version:
|
|
24
|
+
return (from_version,)
|
|
25
|
+
versions = list(SUPPORTED_SCHEMA_VERSIONS)
|
|
26
|
+
start = versions.index(from_version)
|
|
27
|
+
end = versions.index(to_version)
|
|
28
|
+
if end < start:
|
|
29
|
+
raise SchemaMigrationError(
|
|
30
|
+
f"Downgrade migrations are not supported: {from_version!r} -> {to_version!r}"
|
|
31
|
+
)
|
|
32
|
+
path = tuple(versions[start : end + 1])
|
|
33
|
+
if len(path) < 2:
|
|
34
|
+
raise SchemaMigrationError(f"No migration path from {from_version!r} to {to_version!r}")
|
|
35
|
+
return path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def migrate_payload(
|
|
39
|
+
spec_kind: str,
|
|
40
|
+
payload: dict[str, Any],
|
|
41
|
+
from_version: str,
|
|
42
|
+
to_version: str = CURRENT_SCHEMA_VERSION,
|
|
43
|
+
) -> dict[str, Any]:
|
|
44
|
+
path = migration_path(from_version, to_version)
|
|
45
|
+
migrated = copy.deepcopy(payload)
|
|
46
|
+
if len(path) == 1:
|
|
47
|
+
return migrated
|
|
48
|
+
raise SchemaMigrationError(
|
|
49
|
+
f"No adjacent migration implementation registered for {spec_kind!r}: "
|
|
50
|
+
f"{from_version!r} -> {to_version!r}"
|
|
51
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, ClassVar
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
from tigrbl_spec.schema import CURRENT_SCHEMA_VERSION, identity_fields, spec_kinds, validate_payload
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class SpecDataclass:
|
|
12
|
+
spec_kind: str
|
|
13
|
+
spec_schema_version: str
|
|
14
|
+
spec_type: str
|
|
15
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
16
|
+
|
|
17
|
+
KIND: ClassVar[str]
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_dict(cls, payload: dict[str, Any]) -> Self:
|
|
21
|
+
validated = validate_payload(cls.KIND, payload, CURRENT_SCHEMA_VERSION)
|
|
22
|
+
identity = identity_fields(cls.KIND, CURRENT_SCHEMA_VERSION)
|
|
23
|
+
data = {key: value for key, value in validated.items() if key not in identity}
|
|
24
|
+
return cls(data=data, **identity)
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, Any]:
|
|
27
|
+
return {
|
|
28
|
+
"spec_kind": self.spec_kind,
|
|
29
|
+
"spec_schema_version": self.spec_schema_version,
|
|
30
|
+
"spec_type": self.spec_type,
|
|
31
|
+
**self.data,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _model_class(kind: str) -> type[SpecDataclass]:
|
|
36
|
+
return dataclass(slots=True)(
|
|
37
|
+
type(kind, (SpecDataclass,), {"KIND": kind, "__annotations__": {"KIND": ClassVar[str]}})
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
for _kind in spec_kinds(CURRENT_SCHEMA_VERSION):
|
|
42
|
+
globals()[_kind] = _model_class(_kind)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ["SpecDataclass", *spec_kinds(CURRENT_SCHEMA_VERSION)]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from jsonschema import Draft202012Validator, exceptions
|
|
11
|
+
from referencing import Registry, Resource
|
|
12
|
+
|
|
13
|
+
CURRENT_SCHEMA_VERSION = "0.3.20"
|
|
14
|
+
SCHEMA_VERSION = CURRENT_SCHEMA_VERSION
|
|
15
|
+
SPEC_TYPE_PREFIX = "urn:tigrbl:spec"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SpecSchemaError(ValueError):
|
|
19
|
+
"""Base error for tigrbl_spec schema catalog failures."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnknownSpecKindError(SpecSchemaError):
|
|
23
|
+
"""Raised when a payload references an unknown spec kind."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UnsupportedSchemaVersionError(SpecSchemaError):
|
|
27
|
+
"""Raised when a catalog version is not packaged or supported."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SpecValidationError(SpecSchemaError):
|
|
31
|
+
"""Raised when a spec payload does not satisfy its schema."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def spec_type(spec_kind: str, version: str = CURRENT_SCHEMA_VERSION) -> str:
|
|
35
|
+
return f"{SPEC_TYPE_PREFIX}:{spec_kind}:{version}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def identity_fields(spec_kind: str, version: str = CURRENT_SCHEMA_VERSION) -> dict[str, str]:
|
|
39
|
+
return {
|
|
40
|
+
"spec_kind": spec_kind,
|
|
41
|
+
"spec_schema_version": version,
|
|
42
|
+
"spec_type": spec_type(spec_kind, version),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def schemas_root() -> resources.abc.Traversable:
|
|
47
|
+
return resources.files("tigrbl_spec").joinpath("schemas")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def schema_versions() -> tuple[str, ...]:
|
|
51
|
+
versions = [
|
|
52
|
+
item.name
|
|
53
|
+
for item in schemas_root().iterdir()
|
|
54
|
+
if item.is_dir() and item.joinpath("manifest.json").is_file()
|
|
55
|
+
]
|
|
56
|
+
return tuple(sorted(versions))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _require_version(version: str) -> None:
|
|
60
|
+
if version not in schema_versions():
|
|
61
|
+
raise UnsupportedSchemaVersionError(
|
|
62
|
+
f"Unsupported tigrbl_spec schema catalog version {version!r}; "
|
|
63
|
+
f"available versions: {', '.join(schema_versions()) or '<none>'}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@lru_cache(maxsize=None)
|
|
68
|
+
def _load_json_file(version: str, filename: str) -> dict[str, Any]:
|
|
69
|
+
_require_version(version)
|
|
70
|
+
path = schemas_root().joinpath(version, filename)
|
|
71
|
+
if not path.is_file():
|
|
72
|
+
raise FileNotFoundError(f"Schema catalog file not found: {version}/{filename}")
|
|
73
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
74
|
+
return json.load(handle)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def load_manifest(version: str = CURRENT_SCHEMA_VERSION) -> dict[str, Any]:
|
|
78
|
+
return copy.deepcopy(_load_json_file(version, "manifest.json"))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_bundle(version: str = CURRENT_SCHEMA_VERSION) -> dict[str, Any]:
|
|
82
|
+
return copy.deepcopy(_load_json_file(version, "bundle.json"))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def spec_kinds(version: str = CURRENT_SCHEMA_VERSION) -> tuple[str, ...]:
|
|
86
|
+
manifest = load_manifest(version)
|
|
87
|
+
return tuple(sorted((manifest.get("schemas") or {}).keys()))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def schema_path(spec_kind: str, version: str = CURRENT_SCHEMA_VERSION) -> Path:
|
|
91
|
+
if spec_kind not in spec_kinds(version):
|
|
92
|
+
raise UnknownSpecKindError(f"Unknown tigrbl_spec kind {spec_kind!r}")
|
|
93
|
+
return Path(str(schemas_root().joinpath(version, f"{spec_kind}.json")))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def load_schema(spec_kind: str, version: str = CURRENT_SCHEMA_VERSION) -> dict[str, Any]:
|
|
97
|
+
if spec_kind not in spec_kinds(version):
|
|
98
|
+
raise UnknownSpecKindError(f"Unknown tigrbl_spec kind {spec_kind!r}")
|
|
99
|
+
return copy.deepcopy(_load_json_file(version, f"{spec_kind}.json"))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def with_identity(
|
|
103
|
+
spec_kind: str,
|
|
104
|
+
payload: dict[str, Any],
|
|
105
|
+
version: str = CURRENT_SCHEMA_VERSION,
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
identified = dict(payload)
|
|
108
|
+
identified.update(identity_fields(spec_kind, version))
|
|
109
|
+
return identified
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _identity_version(payload: dict[str, Any]) -> str:
|
|
113
|
+
value = payload.get("spec_schema_version")
|
|
114
|
+
if not isinstance(value, str):
|
|
115
|
+
raise SpecValidationError("Spec payload must include string spec_schema_version")
|
|
116
|
+
return value
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _validate_identity(spec_kind: str, payload: dict[str, Any], version: str) -> None:
|
|
120
|
+
expected = identity_fields(spec_kind, version)
|
|
121
|
+
for field, expected_value in expected.items():
|
|
122
|
+
actual = payload.get(field)
|
|
123
|
+
if actual != expected_value:
|
|
124
|
+
raise SpecValidationError(
|
|
125
|
+
f"Spec payload identity field {field!r} must be {expected_value!r}; got {actual!r}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@lru_cache(maxsize=None)
|
|
130
|
+
def _registry(version: str) -> Registry:
|
|
131
|
+
_require_version(version)
|
|
132
|
+
base_uri = f"urn:tigrbl:spec:schema-catalog:{version}/"
|
|
133
|
+
pairs = []
|
|
134
|
+
manifest = load_manifest(version)
|
|
135
|
+
filenames = ["shared.json", *(manifest.get("schemas") or {}).values()]
|
|
136
|
+
for filename in filenames:
|
|
137
|
+
schema = _load_json_file(version, filename)
|
|
138
|
+
pairs.append((base_uri + filename, Resource.from_contents(schema)))
|
|
139
|
+
return Registry().with_resources(pairs)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@lru_cache(maxsize=None)
|
|
143
|
+
def _validator(spec_kind: str, version: str) -> Draft202012Validator:
|
|
144
|
+
schema = load_schema(spec_kind, version)
|
|
145
|
+
base_uri = f"urn:tigrbl:spec:schema-catalog:{version}/"
|
|
146
|
+
return Draft202012Validator(schema, registry=_registry(version), _resolver=None).evolve(
|
|
147
|
+
schema={**schema, "$id": base_uri + f"{spec_kind}.json"}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def validate_payload(
|
|
152
|
+
spec_kind: str,
|
|
153
|
+
payload: dict[str, Any],
|
|
154
|
+
version: str | None = None,
|
|
155
|
+
) -> dict[str, Any]:
|
|
156
|
+
if not isinstance(payload, dict):
|
|
157
|
+
raise SpecValidationError("Spec payload must be a JSON object")
|
|
158
|
+
resolved_version = version or _identity_version(payload)
|
|
159
|
+
_require_version(resolved_version)
|
|
160
|
+
if spec_kind not in spec_kinds(resolved_version):
|
|
161
|
+
raise UnknownSpecKindError(f"Unknown tigrbl_spec kind {spec_kind!r}")
|
|
162
|
+
_validate_identity(spec_kind, payload, resolved_version)
|
|
163
|
+
try:
|
|
164
|
+
_validator(spec_kind, resolved_version).validate(payload)
|
|
165
|
+
except exceptions.ValidationError as exc:
|
|
166
|
+
location = ".".join(str(part) for part in exc.absolute_path)
|
|
167
|
+
prefix = f"{spec_kind} payload"
|
|
168
|
+
if location:
|
|
169
|
+
prefix = f"{prefix} at {location}"
|
|
170
|
+
raise SpecValidationError(f"{prefix} failed schema validation: {exc.message}") from exc
|
|
171
|
+
return copy.deepcopy(payload)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def load_payload(
|
|
175
|
+
spec_kind: str,
|
|
176
|
+
payload: dict[str, Any],
|
|
177
|
+
*,
|
|
178
|
+
migrate: bool = False,
|
|
179
|
+
to_version: str = CURRENT_SCHEMA_VERSION,
|
|
180
|
+
) -> dict[str, Any]:
|
|
181
|
+
from tigrbl_spec.migrations import migrate_payload
|
|
182
|
+
|
|
183
|
+
version = _identity_version(payload)
|
|
184
|
+
loaded = migrate_payload(spec_kind, payload, version, to_version) if migrate else payload
|
|
185
|
+
return validate_payload(spec_kind, loaded, to_version if migrate else version)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
INDIVIDUAL_SPEC_NAMES = spec_kinds(CURRENT_SCHEMA_VERSION)
|
|
189
|
+
JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema"
|
|
190
|
+
SHARED_SCHEMA_NAME = "shared.json"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def build_shared_json_schema() -> dict[str, Any]:
|
|
194
|
+
return _load_json_file(CURRENT_SCHEMA_VERSION, SHARED_SCHEMA_NAME)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build_individual_spec_json_schemas() -> dict[str, dict[str, Any]]:
|
|
198
|
+
return {kind: load_schema(kind) for kind in INDIVIDUAL_SPEC_NAMES}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def build_spec_json_schema_bundle() -> dict[str, Any]:
|
|
202
|
+
return load_manifest()
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$defs": {
|
|
3
|
+
"AppSpecEnvelope": {
|
|
4
|
+
"additionalProperties": {
|
|
5
|
+
"$ref": "./shared.json#/$defs/SerdeValue"
|
|
6
|
+
},
|
|
7
|
+
"properties": {
|
|
8
|
+
"__dataclass__": {
|
|
9
|
+
"const": "tigrbl_core._spec.app_spec:AppSpec"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"required": [
|
|
13
|
+
"__dataclass__"
|
|
14
|
+
],
|
|
15
|
+
"title": "AppSpecEnvelope",
|
|
16
|
+
"type": "object"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"$id": "AppSpec.json",
|
|
20
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"deps": {
|
|
24
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
25
|
+
},
|
|
26
|
+
"description": {
|
|
27
|
+
"anyOf": [
|
|
28
|
+
{
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "null"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"engine": {
|
|
37
|
+
"anyOf": [
|
|
38
|
+
{
|
|
39
|
+
"$ref": "./shared.json#/$defs/SerdeValue"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "null"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"execution_backend": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
"hooks": {
|
|
50
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
51
|
+
},
|
|
52
|
+
"jsonrpc_prefix": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
"lifespan": {
|
|
56
|
+
"anyOf": [
|
|
57
|
+
{
|
|
58
|
+
"$ref": "./shared.json#/$defs/SerdeValue"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "null"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"middlewares": {
|
|
66
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
67
|
+
},
|
|
68
|
+
"ops": {
|
|
69
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
70
|
+
},
|
|
71
|
+
"response": {
|
|
72
|
+
"anyOf": [
|
|
73
|
+
{
|
|
74
|
+
"anyOf": [
|
|
75
|
+
{
|
|
76
|
+
"$ref": "./ResponseSpec.json"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"$ref": "./ResponseSpec.json#/$defs/ResponseSpecEnvelope"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type": "null"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"routers": {
|
|
89
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
90
|
+
},
|
|
91
|
+
"schemas": {
|
|
92
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
93
|
+
},
|
|
94
|
+
"security_deps": {
|
|
95
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
96
|
+
},
|
|
97
|
+
"spec_kind": {
|
|
98
|
+
"const": "AppSpec",
|
|
99
|
+
"description": "Tigrbl spec payload kind."
|
|
100
|
+
},
|
|
101
|
+
"spec_schema_version": {
|
|
102
|
+
"const": "0.3.20",
|
|
103
|
+
"description": "Tigrbl spec schema catalog version."
|
|
104
|
+
},
|
|
105
|
+
"spec_type": {
|
|
106
|
+
"const": "urn:tigrbl:spec:AppSpec:0.3.20",
|
|
107
|
+
"description": "Versioned Tigrbl spec type URN."
|
|
108
|
+
},
|
|
109
|
+
"system_prefix": {
|
|
110
|
+
"type": "string"
|
|
111
|
+
},
|
|
112
|
+
"tables": {
|
|
113
|
+
"$ref": "./shared.json#/$defs/StepFnTuple"
|
|
114
|
+
},
|
|
115
|
+
"title": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
118
|
+
"version": {
|
|
119
|
+
"type": "string"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"required": [
|
|
123
|
+
"spec_kind",
|
|
124
|
+
"spec_schema_version",
|
|
125
|
+
"spec_type",
|
|
126
|
+
"title",
|
|
127
|
+
"description",
|
|
128
|
+
"version",
|
|
129
|
+
"execution_backend",
|
|
130
|
+
"engine",
|
|
131
|
+
"routers",
|
|
132
|
+
"ops",
|
|
133
|
+
"tables",
|
|
134
|
+
"schemas",
|
|
135
|
+
"hooks",
|
|
136
|
+
"security_deps",
|
|
137
|
+
"deps",
|
|
138
|
+
"response",
|
|
139
|
+
"jsonrpc_prefix",
|
|
140
|
+
"system_prefix",
|
|
141
|
+
"middlewares",
|
|
142
|
+
"lifespan"
|
|
143
|
+
],
|
|
144
|
+
"title": "AppSpec",
|
|
145
|
+
"type": "object"
|
|
146
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$defs": {
|
|
3
|
+
"BindingRegistrySpecEnvelope": {
|
|
4
|
+
"additionalProperties": {
|
|
5
|
+
"$ref": "./shared.json#/$defs/SerdeValue"
|
|
6
|
+
},
|
|
7
|
+
"properties": {
|
|
8
|
+
"__dataclass__": {
|
|
9
|
+
"const": "tigrbl_core._spec.binding_spec:BindingRegistrySpec"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"required": [
|
|
13
|
+
"__dataclass__"
|
|
14
|
+
],
|
|
15
|
+
"title": "BindingRegistrySpecEnvelope",
|
|
16
|
+
"type": "object"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"$id": "BindingRegistrySpec.json",
|
|
20
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"_bindings": {
|
|
24
|
+
"additionalProperties": {
|
|
25
|
+
"anyOf": [
|
|
26
|
+
{
|
|
27
|
+
"$ref": "./BindingSpec.json"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"$ref": "./BindingSpec.json#/$defs/BindingSpecEnvelope"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"type": "object"
|
|
35
|
+
},
|
|
36
|
+
"spec_kind": {
|
|
37
|
+
"const": "BindingRegistrySpec",
|
|
38
|
+
"description": "Tigrbl spec payload kind."
|
|
39
|
+
},
|
|
40
|
+
"spec_schema_version": {
|
|
41
|
+
"const": "0.3.20",
|
|
42
|
+
"description": "Tigrbl spec schema catalog version."
|
|
43
|
+
},
|
|
44
|
+
"spec_type": {
|
|
45
|
+
"const": "urn:tigrbl:spec:BindingRegistrySpec:0.3.20",
|
|
46
|
+
"description": "Versioned Tigrbl spec type URN."
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"required": [
|
|
50
|
+
"spec_kind",
|
|
51
|
+
"spec_schema_version",
|
|
52
|
+
"spec_type",
|
|
53
|
+
"_bindings"
|
|
54
|
+
],
|
|
55
|
+
"title": "BindingRegistrySpec",
|
|
56
|
+
"type": "object"
|
|
57
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$defs": {
|
|
3
|
+
"BindingSpecEnvelope": {
|
|
4
|
+
"additionalProperties": {
|
|
5
|
+
"$ref": "./shared.json#/$defs/SerdeValue"
|
|
6
|
+
},
|
|
7
|
+
"properties": {
|
|
8
|
+
"__dataclass__": {
|
|
9
|
+
"const": "tigrbl_core._spec.binding_spec:BindingSpec"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"required": [
|
|
13
|
+
"__dataclass__"
|
|
14
|
+
],
|
|
15
|
+
"title": "BindingSpecEnvelope",
|
|
16
|
+
"type": "object"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"$id": "BindingSpec.json",
|
|
20
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"name": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
"spec": {
|
|
27
|
+
"$ref": "./shared.json#/$defs/TransportBindingSpec"
|
|
28
|
+
},
|
|
29
|
+
"spec_kind": {
|
|
30
|
+
"const": "BindingSpec",
|
|
31
|
+
"description": "Tigrbl spec payload kind."
|
|
32
|
+
},
|
|
33
|
+
"spec_schema_version": {
|
|
34
|
+
"const": "0.3.20",
|
|
35
|
+
"description": "Tigrbl spec schema catalog version."
|
|
36
|
+
},
|
|
37
|
+
"spec_type": {
|
|
38
|
+
"const": "urn:tigrbl:spec:BindingSpec:0.3.20",
|
|
39
|
+
"description": "Versioned Tigrbl spec type URN."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": [
|
|
43
|
+
"spec_kind",
|
|
44
|
+
"spec_schema_version",
|
|
45
|
+
"spec_type",
|
|
46
|
+
"name",
|
|
47
|
+
"spec"
|
|
48
|
+
],
|
|
49
|
+
"title": "BindingSpec",
|
|
50
|
+
"type": "object"
|
|
51
|
+
}
|