vtjson 2.1.1__py3-none-any.whl → 2.1.3__py3-none-any.whl
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.
- vtjson/vtjson.py +152 -102
- {vtjson-2.1.1.dist-info → vtjson-2.1.3.dist-info}/METADATA +18 -187
- vtjson-2.1.3.dist-info/RECORD +8 -0
- vtjson-2.1.1.dist-info/RECORD +0 -8
- {vtjson-2.1.1.dist-info → vtjson-2.1.3.dist-info}/AUTHORS +0 -0
- {vtjson-2.1.1.dist-info → vtjson-2.1.3.dist-info}/LICENSE +0 -0
- {vtjson-2.1.1.dist-info → vtjson-2.1.3.dist-info}/WHEEL +0 -0
- {vtjson-2.1.1.dist-info → vtjson-2.1.3.dist-info}/top_level.txt +0 -0
vtjson/vtjson.py
CHANGED
|
@@ -12,7 +12,7 @@ import urllib.parse
|
|
|
12
12
|
import warnings
|
|
13
13
|
from collections.abc import Sequence, Sized
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
-
from typing import Any, Callable,
|
|
15
|
+
from typing import Any, Callable, Type, TypeVar, Union, cast
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
from typing import Literal
|
|
@@ -41,15 +41,15 @@ except ImportError:
|
|
|
41
41
|
supports_Annotated = False
|
|
42
42
|
|
|
43
43
|
if hasattr(typing, "get_origin"):
|
|
44
|
-
|
|
44
|
+
supports_Generics = True
|
|
45
45
|
else:
|
|
46
|
-
|
|
46
|
+
supports_Generics = False
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
49
|
typing.get_type_hints(int, include_extras=True)
|
|
50
|
-
|
|
50
|
+
supports_structural = True
|
|
51
51
|
except Exception:
|
|
52
|
-
|
|
52
|
+
supports_structural = False
|
|
53
53
|
|
|
54
54
|
try:
|
|
55
55
|
from types import UnionType
|
|
@@ -115,7 +115,7 @@ class SchemaError(Exception):
|
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
__version__ = "2.1.
|
|
118
|
+
__version__ = "2.1.3"
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
@dataclass
|
|
@@ -147,6 +147,35 @@ skip_first = Apply(skip_first=True)
|
|
|
147
147
|
_dns_resolver: dns.resolver.Resolver | None = None
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
def _get_type_hints(schema: object) -> dict[str, object]:
|
|
151
|
+
if not supports_structural:
|
|
152
|
+
raise SchemaError(
|
|
153
|
+
"Structural subtyping in not supported in this " "Python version"
|
|
154
|
+
)
|
|
155
|
+
type_hints = {}
|
|
156
|
+
if isinstance(schema, type) and hasattr(schema, "__annotations__"):
|
|
157
|
+
type_hints = typing.get_type_hints(schema, include_extras=True)
|
|
158
|
+
return type_hints
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _to_dict(type_hints: dict[str, object], total: bool = True) -> dict[object, object]:
|
|
162
|
+
d: dict[object, object] = {}
|
|
163
|
+
if not supports_Generics:
|
|
164
|
+
raise SchemaError("Generic types are not supported")
|
|
165
|
+
for k, v in type_hints.items():
|
|
166
|
+
v_ = v
|
|
167
|
+
k_: str | optional_key = k
|
|
168
|
+
value_type = typing.get_origin(v)
|
|
169
|
+
if supports_NotRequired and value_type in (Required, NotRequired):
|
|
170
|
+
v_ = typing.get_args(v)[0]
|
|
171
|
+
if total and supports_NotRequired and value_type == NotRequired:
|
|
172
|
+
k_ = optional_key(k)
|
|
173
|
+
elif not total and (not supports_NotRequired or value_type != Required):
|
|
174
|
+
k_ = optional_key(k)
|
|
175
|
+
d[k_] = v_
|
|
176
|
+
return d
|
|
177
|
+
|
|
178
|
+
|
|
150
179
|
def _get_dns_resolver() -> dns.resolver.Resolver:
|
|
151
180
|
global _dns_resolver
|
|
152
181
|
if _dns_resolver is not None:
|
|
@@ -177,9 +206,16 @@ def _c(s: object) -> str:
|
|
|
177
206
|
|
|
178
207
|
|
|
179
208
|
def _wrong_type_message(
|
|
180
|
-
object_: object,
|
|
209
|
+
object_: object,
|
|
210
|
+
name: str,
|
|
211
|
+
type_name: str,
|
|
212
|
+
explanation: str | None = None,
|
|
213
|
+
skip_value: bool = False,
|
|
181
214
|
) -> str:
|
|
182
|
-
|
|
215
|
+
if not skip_value:
|
|
216
|
+
message = f"{name} (value:{_c(object_)}) is not of type '{type_name}'"
|
|
217
|
+
else:
|
|
218
|
+
message = f"{name} is not of type '{type_name}'"
|
|
183
219
|
if explanation is not None:
|
|
184
220
|
message += f": {explanation}"
|
|
185
221
|
return message
|
|
@@ -480,14 +516,20 @@ class quote(compiled_schema):
|
|
|
480
516
|
|
|
481
517
|
|
|
482
518
|
class _set_name(compiled_schema):
|
|
519
|
+
reason: bool
|
|
483
520
|
schema: compiled_schema
|
|
484
521
|
__name__: str
|
|
485
522
|
|
|
486
523
|
def __init__(
|
|
487
|
-
self,
|
|
524
|
+
self,
|
|
525
|
+
schema: object,
|
|
526
|
+
name: str,
|
|
527
|
+
reason: bool = False,
|
|
528
|
+
_deferred_compiles: _mapping | None = None,
|
|
488
529
|
) -> None:
|
|
489
530
|
self.schema = _compile(schema, _deferred_compiles=_deferred_compiles)
|
|
490
531
|
self.__name__ = name
|
|
532
|
+
self.reason = reason
|
|
491
533
|
|
|
492
534
|
def __validate__(
|
|
493
535
|
self,
|
|
@@ -498,22 +540,34 @@ class _set_name(compiled_schema):
|
|
|
498
540
|
) -> str:
|
|
499
541
|
message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
|
|
500
542
|
if message != "":
|
|
501
|
-
|
|
543
|
+
if not self.reason:
|
|
544
|
+
return _wrong_type_message(object_, name, self.__name__)
|
|
545
|
+
else:
|
|
546
|
+
return _wrong_type_message(
|
|
547
|
+
object_, name, self.__name__, explanation=message, skip_value=True
|
|
548
|
+
)
|
|
502
549
|
return ""
|
|
503
550
|
|
|
504
551
|
|
|
505
552
|
class set_name:
|
|
553
|
+
reason: bool
|
|
506
554
|
schema: object
|
|
507
555
|
name: str
|
|
508
556
|
|
|
509
|
-
def __init__(self, schema: object, name: str) -> None:
|
|
557
|
+
def __init__(self, schema: object, name: str, reason: bool = False) -> None:
|
|
510
558
|
if not isinstance(name, str):
|
|
511
559
|
raise SchemaError(f"The name {_c(name)} is not a string")
|
|
512
560
|
self.schema = schema
|
|
513
561
|
self.name = name
|
|
562
|
+
self.reason = reason
|
|
514
563
|
|
|
515
564
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _set_name:
|
|
516
|
-
return _set_name(
|
|
565
|
+
return _set_name(
|
|
566
|
+
self.schema,
|
|
567
|
+
self.name,
|
|
568
|
+
reason=self.reason,
|
|
569
|
+
_deferred_compiles=_deferred_compiles,
|
|
570
|
+
)
|
|
517
571
|
|
|
518
572
|
|
|
519
573
|
class regex(compiled_schema):
|
|
@@ -1035,6 +1089,11 @@ def _compile(
|
|
|
1035
1089
|
_deferred_compiles[schema] = _deferred(_deferred_compiles, schema)
|
|
1036
1090
|
|
|
1037
1091
|
# real work starts here
|
|
1092
|
+
if supports_Generics:
|
|
1093
|
+
origin = typing.get_origin(schema)
|
|
1094
|
+
else:
|
|
1095
|
+
origin = object()
|
|
1096
|
+
|
|
1038
1097
|
ret: compiled_schema
|
|
1039
1098
|
if isinstance(schema, type) and issubclass(schema, compiled_schema):
|
|
1040
1099
|
try:
|
|
@@ -1049,69 +1108,46 @@ def _compile(
|
|
|
1049
1108
|
ret = schema.__compile__(_deferred_compiles=_deferred_compiles)
|
|
1050
1109
|
elif isinstance(schema, compiled_schema):
|
|
1051
1110
|
ret = schema
|
|
1111
|
+
elif supports_TypedDict and typing.is_typeddict(schema):
|
|
1112
|
+
ret = _compile(
|
|
1113
|
+
structural(schema, dict=True), _deferred_compiles=_deferred_compiles
|
|
1114
|
+
)
|
|
1115
|
+
elif isinstance(schema, type) and hasattr(schema, "_is_protocol"):
|
|
1116
|
+
assert hasattr(schema, "__name__") and isinstance(schema.__name__, str)
|
|
1117
|
+
ret = _compile(structural(schema), _deferred_compiles=_deferred_compiles)
|
|
1118
|
+
elif schema == Any:
|
|
1119
|
+
ret = anything()
|
|
1120
|
+
elif hasattr(schema, "__name__") and hasattr(schema, "__supertype__"):
|
|
1121
|
+
ret = _NewType(
|
|
1122
|
+
schema,
|
|
1123
|
+
_deferred_compiles=_deferred_compiles,
|
|
1124
|
+
)
|
|
1125
|
+
elif origin == list:
|
|
1126
|
+
ret = _List(typing.get_args(schema)[0], _deferred_compiles=_deferred_compiles)
|
|
1127
|
+
elif origin == tuple:
|
|
1128
|
+
ret = _Tuple(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1129
|
+
elif origin == dict:
|
|
1130
|
+
ret = _Dict(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1131
|
+
elif origin == Union:
|
|
1132
|
+
ret = _Union(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1133
|
+
elif supports_Literal and origin == Literal:
|
|
1134
|
+
ret = _Literal(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1135
|
+
elif supports_Annotated and origin == Annotated:
|
|
1136
|
+
ret = _Annotated(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1137
|
+
elif supports_UnionType and isinstance(schema, UnionType):
|
|
1138
|
+
ret = _Union(schema.__args__, _deferred_compiles=_deferred_compiles)
|
|
1139
|
+
elif isinstance(schema, type):
|
|
1140
|
+
ret = _type(schema)
|
|
1141
|
+
elif callable(schema):
|
|
1142
|
+
ret = _callable(schema)
|
|
1143
|
+
elif isinstance(schema, tuple) or isinstance(schema, list):
|
|
1144
|
+
ret = _sequence(schema, _deferred_compiles=_deferred_compiles)
|
|
1145
|
+
elif isinstance(schema, dict):
|
|
1146
|
+
ret = _dict(schema, _deferred_compiles=_deferred_compiles)
|
|
1147
|
+
elif isinstance(schema, set):
|
|
1148
|
+
ret = _set(schema, _deferred_compiles=_deferred_compiles)
|
|
1052
1149
|
else:
|
|
1053
|
-
|
|
1054
|
-
origin = typing.get_origin(schema)
|
|
1055
|
-
|
|
1056
|
-
type_hints = {}
|
|
1057
|
-
if (
|
|
1058
|
-
supports_extra_type_hints
|
|
1059
|
-
and isinstance(schema, type)
|
|
1060
|
-
and hasattr(schema, "__annotations__")
|
|
1061
|
-
):
|
|
1062
|
-
type_hints = typing.get_type_hints(schema, include_extras=True)
|
|
1063
|
-
if supports_TypedDict and typing.is_typeddict(schema):
|
|
1064
|
-
assert hasattr(schema, "__total__") and isinstance(schema.__total__, bool)
|
|
1065
|
-
ret = _TypedDict(
|
|
1066
|
-
type_hints,
|
|
1067
|
-
schema.__total__,
|
|
1068
|
-
_deferred_compiles=_deferred_compiles,
|
|
1069
|
-
)
|
|
1070
|
-
elif isinstance(schema, type) and hasattr(schema, "_is_protocol"):
|
|
1071
|
-
ret = _fields(type_hints, _deferred_compiles=_deferred_compiles)
|
|
1072
|
-
elif schema == Any:
|
|
1073
|
-
ret = anything()
|
|
1074
|
-
elif (sys.version_info < (3, 10) and hasattr(schema, "__supertype__")) or (
|
|
1075
|
-
sys.version_info >= (3, 10) and isinstance(schema, NewType)
|
|
1076
|
-
):
|
|
1077
|
-
assert hasattr(schema, "__name__") and hasattr(schema, "__supertype__")
|
|
1078
|
-
ret = _NewType(
|
|
1079
|
-
schema.__supertype__,
|
|
1080
|
-
schema.__name__,
|
|
1081
|
-
_deferred_compiles=_deferred_compiles,
|
|
1082
|
-
)
|
|
1083
|
-
elif supports_GenericAlias and origin == list:
|
|
1084
|
-
ret = _List(
|
|
1085
|
-
typing.get_args(schema)[0], _deferred_compiles=_deferred_compiles
|
|
1086
|
-
)
|
|
1087
|
-
elif supports_GenericAlias and origin == tuple:
|
|
1088
|
-
ret = _Tuple(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1089
|
-
elif supports_GenericAlias and origin == dict:
|
|
1090
|
-
ret = _Dict(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1091
|
-
elif supports_GenericAlias and origin == Union:
|
|
1092
|
-
ret = _Union(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
|
|
1093
|
-
elif supports_Literal and origin == Literal:
|
|
1094
|
-
ret = _Literal(
|
|
1095
|
-
typing.get_args(schema), _deferred_compiles=_deferred_compiles
|
|
1096
|
-
)
|
|
1097
|
-
elif supports_Annotated and origin == Annotated:
|
|
1098
|
-
ret = _Annotated(
|
|
1099
|
-
typing.get_args(schema), _deferred_compiles=_deferred_compiles
|
|
1100
|
-
)
|
|
1101
|
-
elif supports_UnionType and isinstance(schema, UnionType):
|
|
1102
|
-
ret = _Union(schema.__args__, _deferred_compiles=_deferred_compiles)
|
|
1103
|
-
elif isinstance(schema, type):
|
|
1104
|
-
ret = _type(schema)
|
|
1105
|
-
elif callable(schema):
|
|
1106
|
-
ret = _callable(schema)
|
|
1107
|
-
elif isinstance(schema, tuple) or isinstance(schema, list):
|
|
1108
|
-
ret = _sequence(schema, _deferred_compiles=_deferred_compiles)
|
|
1109
|
-
elif isinstance(schema, dict):
|
|
1110
|
-
ret = _dict(schema, _deferred_compiles=_deferred_compiles)
|
|
1111
|
-
elif isinstance(schema, set):
|
|
1112
|
-
ret = _set(schema, _deferred_compiles=_deferred_compiles)
|
|
1113
|
-
else:
|
|
1114
|
-
ret = _const(schema)
|
|
1150
|
+
ret = _const(schema)
|
|
1115
1151
|
|
|
1116
1152
|
# back to updating the cache
|
|
1117
1153
|
if _deferred_compiles.in_use(schema):
|
|
@@ -2051,6 +2087,43 @@ class _set(compiled_schema):
|
|
|
2051
2087
|
return str(self.schema_)
|
|
2052
2088
|
|
|
2053
2089
|
|
|
2090
|
+
class structural:
|
|
2091
|
+
type_dict: dict[object, object]
|
|
2092
|
+
dict: bool
|
|
2093
|
+
__name__: str
|
|
2094
|
+
|
|
2095
|
+
def __init__(self, schema: object, dict: bool = False):
|
|
2096
|
+
if not isinstance(dict, bool):
|
|
2097
|
+
raise SchemaError("bool flag is not a bool")
|
|
2098
|
+
type_hints = _get_type_hints(schema)
|
|
2099
|
+
self.dict = dict
|
|
2100
|
+
total = True
|
|
2101
|
+
if hasattr(schema, "__total__") and isinstance(schema.__total__, bool):
|
|
2102
|
+
total = schema.__total__
|
|
2103
|
+
self.type_dict = _to_dict(type_hints, total=total)
|
|
2104
|
+
assert hasattr(schema, "__name__") and isinstance(schema.__name__, str)
|
|
2105
|
+
self.__name__ = schema.__name__
|
|
2106
|
+
|
|
2107
|
+
def __compile__(
|
|
2108
|
+
self, _deferred_compiles: _mapping | None = None
|
|
2109
|
+
) -> compiled_schema:
|
|
2110
|
+
if not self.dict:
|
|
2111
|
+
type_dict_ = cast(dict[str, object], self.type_dict)
|
|
2112
|
+
return _set_name(
|
|
2113
|
+
fields(type_dict_),
|
|
2114
|
+
self.__name__,
|
|
2115
|
+
reason=True,
|
|
2116
|
+
_deferred_compiles=_deferred_compiles,
|
|
2117
|
+
)
|
|
2118
|
+
else:
|
|
2119
|
+
return _set_name(
|
|
2120
|
+
dict(self.type_dict),
|
|
2121
|
+
self.__name__,
|
|
2122
|
+
reason=True,
|
|
2123
|
+
_deferred_compiles=_deferred_compiles,
|
|
2124
|
+
)
|
|
2125
|
+
|
|
2126
|
+
|
|
2054
2127
|
class _Literal(compiled_schema):
|
|
2055
2128
|
def __init__(
|
|
2056
2129
|
self, schema: tuple[object, ...], _deferred_compiles: _mapping | None = None
|
|
@@ -2111,9 +2184,12 @@ class _Dict(compiled_schema):
|
|
|
2111
2184
|
|
|
2112
2185
|
class _NewType(compiled_schema):
|
|
2113
2186
|
def __init__(
|
|
2114
|
-
self, schema: object,
|
|
2187
|
+
self, schema: object, _deferred_compiles: _mapping | None = None
|
|
2115
2188
|
) -> None:
|
|
2116
|
-
|
|
2189
|
+
assert hasattr(schema, "__name__") and hasattr(schema, "__supertype__")
|
|
2190
|
+
c = _set_name(
|
|
2191
|
+
schema.__supertype__, schema.__name__, _deferred_compiles=_deferred_compiles
|
|
2192
|
+
)
|
|
2117
2193
|
setattr(
|
|
2118
2194
|
self,
|
|
2119
2195
|
"__validate__",
|
|
@@ -2144,29 +2220,3 @@ class _Annotated(compiled_schema):
|
|
|
2144
2220
|
"__validate__",
|
|
2145
2221
|
c.__validate__,
|
|
2146
2222
|
)
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
class _TypedDict(compiled_schema):
|
|
2150
|
-
def __init__(
|
|
2151
|
-
self,
|
|
2152
|
-
schema: dict[str, object],
|
|
2153
|
-
total: bool,
|
|
2154
|
-
_deferred_compiles: _mapping | None = None,
|
|
2155
|
-
) -> None:
|
|
2156
|
-
d: dict[object, object] = {}
|
|
2157
|
-
for k, v in schema.items():
|
|
2158
|
-
v_ = v
|
|
2159
|
-
k_: str | optional_key = k
|
|
2160
|
-
value_type = typing.get_origin(v)
|
|
2161
|
-
if supports_NotRequired and value_type in (Required, NotRequired):
|
|
2162
|
-
v_ = typing.get_args(v)[0]
|
|
2163
|
-
if total and supports_NotRequired and value_type == NotRequired:
|
|
2164
|
-
k_ = optional_key(k)
|
|
2165
|
-
elif not total and (not supports_NotRequired or value_type != Required):
|
|
2166
|
-
k_ = optional_key(k)
|
|
2167
|
-
d[k_] = v_
|
|
2168
|
-
setattr(
|
|
2169
|
-
self,
|
|
2170
|
-
"__validate__",
|
|
2171
|
-
_dict(d, _deferred_compiles=_deferred_compiles).__validate__,
|
|
2172
|
-
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vtjson
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: A lightweight package for validating JSON like Python objects
|
|
5
5
|
Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
|
|
6
6
|
Project-URL: Homepage, https://github.com/vdbergh/vtjson
|
|
@@ -24,193 +24,24 @@ A lightweight package for validating JSON like Python objects.
|
|
|
24
24
|
|
|
25
25
|
## Schemas
|
|
26
26
|
|
|
27
|
-
Validation of JSON like Python objects is done according to a `schema` which is somewhat inspired by a typescript type. The format of a schema is more or less self explanatory
|
|
27
|
+
Validation of JSON like Python objects is done according to a `schema` which is somewhat inspired by a typescript type. The format of a schema is more or less self explanatory. As an [example](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example1.md) one may consult the schema of the run object in the mongodb database underlying the Fishtest web application <https://tests.stockfishchess.org/tests>.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
The following conventions are used:
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
- As in typescript, a (string) key ending in `?` represents an optional key. The corresponding schema (the item the key points to) will only be used for validation when the key is present in the object that should be validated. A key can also be made optional by wrapping it as `optional_key(key)`.
|
|
32
|
+
- If in a list/tuple the last entry is `...` (ellipsis) it means that the next to last entry will be repeated zero or more times. In this way generic types can be created. For example the schema `[str, ...]` represents a list of strings.
|
|
33
|
+
|
|
34
|
+
As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
|
|
32
35
|
|
|
33
36
|
```python
|
|
34
|
-
import
|
|
35
|
-
from datetime import datetime
|
|
36
|
-
from bson.objectid import ObjectId
|
|
37
|
-
from vtjson import glob, ip_address, regex, url
|
|
38
|
-
|
|
39
|
-
net_name = regex("nn-[a-z0-9]{12}.nnue", name="net_name")
|
|
40
|
-
tc = regex(r"([1-9]\d*/)?\d+(\.\d+)?(\+\d+(\.\d+)?)?", name="tc")
|
|
41
|
-
str_int = regex(r"[1-9]\d*", name="str_int")
|
|
42
|
-
sha = regex(r"[a-f0-9]{40}", name="sha")
|
|
43
|
-
country_code = regex(r"[A-Z][A-Z]", name="country_code")
|
|
44
|
-
run_id = regex(r"[a-f0-9]{24}", name="run_id")
|
|
45
|
-
uuid = regex(r"[0-9a-zA-Z]{2,}(-[a-f0-9]{4}){3}-[a-f0-9]{12}", name="uuid")
|
|
46
|
-
epd_file = glob("*.epd", name="epd_file")
|
|
47
|
-
pgn_file = glob("*.pgn", name="pgn_file")
|
|
48
|
-
|
|
49
|
-
worker_info_schema = {
|
|
50
|
-
"uname": str,
|
|
51
|
-
"architecture": [str, str],
|
|
52
|
-
"concurrency": int,
|
|
53
|
-
"max_memory": int,
|
|
54
|
-
"min_threads": int,
|
|
55
|
-
"username": str,
|
|
56
|
-
"version": int,
|
|
57
|
-
"python_version": [int, int, int],
|
|
58
|
-
"gcc_version": [int, int, int],
|
|
59
|
-
"compiler": union("clang++", "g++"),
|
|
60
|
-
"unique_key": uuid,
|
|
61
|
-
"modified": bool,
|
|
62
|
-
"ARCH": str,
|
|
63
|
-
"nps": float,
|
|
64
|
-
"near_github_api_limit": bool,
|
|
65
|
-
"remote_addr": ip_address,
|
|
66
|
-
"country_code": union(country_code, "?"),
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
results_schema = {
|
|
70
|
-
"wins": int,
|
|
71
|
-
"losses": int,
|
|
72
|
-
"draws": int,
|
|
73
|
-
"crashes": int,
|
|
74
|
-
"time_losses": int,
|
|
75
|
-
"pentanomial": [int, int, int, int, int],
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
schema = {
|
|
79
|
-
"_id?": ObjectId,
|
|
80
|
-
"start_time": datetime,
|
|
81
|
-
"last_updated": datetime,
|
|
82
|
-
"tc_base": float,
|
|
83
|
-
"base_same_as_master": bool,
|
|
84
|
-
"rescheduled_from?": run_id,
|
|
85
|
-
"approved": bool,
|
|
86
|
-
"approver": str,
|
|
87
|
-
"finished": bool,
|
|
88
|
-
"deleted": bool,
|
|
89
|
-
"failed": bool,
|
|
90
|
-
"is_green": bool,
|
|
91
|
-
"is_yellow": bool,
|
|
92
|
-
"workers": int,
|
|
93
|
-
"cores": int,
|
|
94
|
-
"results": results_schema,
|
|
95
|
-
"results_info?": {
|
|
96
|
-
"style": str,
|
|
97
|
-
"info": [str, ...],
|
|
98
|
-
},
|
|
99
|
-
"args": {
|
|
100
|
-
"base_tag": str,
|
|
101
|
-
"new_tag": str,
|
|
102
|
-
"base_nets": [net_name, ...],
|
|
103
|
-
"new_nets": [net_name, ...],
|
|
104
|
-
"num_games": int,
|
|
105
|
-
"tc": tc,
|
|
106
|
-
"new_tc": tc,
|
|
107
|
-
"book": union(epd_file, pgn_file),
|
|
108
|
-
"book_depth": str_int,
|
|
109
|
-
"threads": int,
|
|
110
|
-
"resolved_base": sha,
|
|
111
|
-
"resolved_new": sha,
|
|
112
|
-
"msg_base": str,
|
|
113
|
-
"msg_new": str,
|
|
114
|
-
"base_options": str,
|
|
115
|
-
"new_options": str,
|
|
116
|
-
"info": str,
|
|
117
|
-
"base_signature": str_int,
|
|
118
|
-
"new_signature": str_int,
|
|
119
|
-
"username": str,
|
|
120
|
-
"tests_repo": url,
|
|
121
|
-
"auto_purge": bool,
|
|
122
|
-
"throughput": float,
|
|
123
|
-
"itp": float,
|
|
124
|
-
"priority": float,
|
|
125
|
-
"adjudication": bool,
|
|
126
|
-
"sprt?": {
|
|
127
|
-
"alpha": 0.05,
|
|
128
|
-
"beta": 0.05,
|
|
129
|
-
"elo0": float,
|
|
130
|
-
"elo1": float,
|
|
131
|
-
"elo_model": "normalized",
|
|
132
|
-
"state": union("", "accepted", "rejected"),
|
|
133
|
-
"llr": float,
|
|
134
|
-
"batch_size": int,
|
|
135
|
-
"lower_bound": -math.log(19),
|
|
136
|
-
"upper_bound": math.log(19),
|
|
137
|
-
"lost_samples?": int,
|
|
138
|
-
"illegal_update?": int,
|
|
139
|
-
"overshoot?": {
|
|
140
|
-
"last_update": int,
|
|
141
|
-
"skipped_updates": int,
|
|
142
|
-
"ref0": float,
|
|
143
|
-
"m0": float,
|
|
144
|
-
"sq0": float,
|
|
145
|
-
"ref1": float,
|
|
146
|
-
"m1": float,
|
|
147
|
-
"sq1": float,
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
"spsa?": {
|
|
151
|
-
"A": float,
|
|
152
|
-
"alpha": float,
|
|
153
|
-
"gamma": float,
|
|
154
|
-
"raw_params": str,
|
|
155
|
-
"iter": int,
|
|
156
|
-
"num_iter": int,
|
|
157
|
-
"params": [
|
|
158
|
-
{
|
|
159
|
-
"name": str,
|
|
160
|
-
"start": float,
|
|
161
|
-
"min": float,
|
|
162
|
-
"max": float,
|
|
163
|
-
"c_end": float,
|
|
164
|
-
"r_end": float,
|
|
165
|
-
"c": float,
|
|
166
|
-
"a_end": float,
|
|
167
|
-
"a": float,
|
|
168
|
-
"theta": float,
|
|
169
|
-
},
|
|
170
|
-
...,
|
|
171
|
-
],
|
|
172
|
-
"param_history?": [
|
|
173
|
-
[{"theta": float, "R": float, "c": float}, ...],
|
|
174
|
-
...,
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
"tasks": [
|
|
179
|
-
{
|
|
180
|
-
"num_games": int,
|
|
181
|
-
"active": bool,
|
|
182
|
-
"last_updated": datetime,
|
|
183
|
-
"start": int,
|
|
184
|
-
"residual?": float,
|
|
185
|
-
"residual_color?": str,
|
|
186
|
-
"bad?": True,
|
|
187
|
-
"stats": results_schema,
|
|
188
|
-
"worker_info": worker_info_schema,
|
|
189
|
-
},
|
|
190
|
-
...,
|
|
191
|
-
],
|
|
192
|
-
"bad_tasks?": [
|
|
193
|
-
{
|
|
194
|
-
"num_games": int,
|
|
195
|
-
"active": False,
|
|
196
|
-
"last_updated": datetime,
|
|
197
|
-
"start": int,
|
|
198
|
-
"residual": float,
|
|
199
|
-
"residual_color": str,
|
|
200
|
-
"bad": True,
|
|
201
|
-
"task_id": int,
|
|
202
|
-
"stats": results_schema,
|
|
203
|
-
"worker_info": worker_info_schema,
|
|
204
|
-
},
|
|
205
|
-
...,
|
|
206
|
-
],
|
|
207
|
-
}
|
|
208
|
-
```
|
|
37
|
+
from typing import assert_type
|
|
209
38
|
|
|
210
|
-
|
|
39
|
+
def f(run_from_api: object, ...) -> ...:
|
|
40
|
+
run = safe_cast(runs_schema, run_from_api)
|
|
41
|
+
assert_type(run, runs_schema) # Confirm that run has indeed the correct type now
|
|
42
|
+
```
|
|
211
43
|
|
|
212
|
-
|
|
213
|
-
- If in a list/tuple the last entry is `...` (ellipsis) it means that the next to last entry will be repeated zero or more times. In this way generic types can be created. For example the schema `[str, ...]` represents a list of strings.
|
|
44
|
+
If the cast succeeds then it means that the `run_from_api` object has been validated against the `runs_schema` and its type has been changed accordingly.
|
|
214
45
|
|
|
215
46
|
## Usage
|
|
216
47
|
|
|
@@ -358,15 +189,15 @@ A consequence of this algorithm is that non-const keys are automatically optiona
|
|
|
358
189
|
`vtjson` recognizes the following type hints as schemas.
|
|
359
190
|
|
|
360
191
|
```python
|
|
361
|
-
Annotated, dict[...], Dict[...], list[...], List[...], tuple[...],
|
|
362
|
-
|
|
192
|
+
Annotated, dict[...], Dict[...], list[...], List[...], tuple[...], Tuple[...],
|
|
193
|
+
Protocol, Literal, NewType, TypedDict, Union (or the equivalent operator |).
|
|
363
194
|
```
|
|
364
195
|
|
|
365
196
|
For example `dict[str, str]` is translated internally into the schema `{str: str}`. See below for more information.
|
|
366
197
|
|
|
367
198
|
### Annotated
|
|
368
199
|
|
|
369
|
-
- More general vtjson schemas can work along Python type hints by using `typing.Annotated` contruct. The most naive way to do this is via
|
|
200
|
+
- More general vtjson schemas can work along Python type hints by using the `typing.Annotated` contruct. The most naive way to do this is via
|
|
370
201
|
|
|
371
202
|
```python
|
|
372
203
|
Annotated[type_hint, vtjson_schema, skip_first]
|
|
@@ -405,7 +236,7 @@ For example `dict[str, str]` is translated internally into the schema `{str: str
|
|
|
405
236
|
|
|
406
237
|
Note that Python imposes strong restrictions on what constitutes a valid type hint but `vtjson` is much more lax about this. Enforcing the restrictions is left to the type checkers or the Python interpreter.
|
|
407
238
|
|
|
408
|
-
- `TypedDict
|
|
239
|
+
- `TypedDict`. A TypedDict type hint is translated into a `dict` schema. E.g.
|
|
409
240
|
|
|
410
241
|
```python
|
|
411
242
|
class Movie(TypedDict):
|
|
@@ -415,7 +246,7 @@ Note that Python imposes strong restrictions on what constitutes a valid type hi
|
|
|
415
246
|
|
|
416
247
|
internally becomes `{"title": str, "price": float}`. `vtjson` supports the `total` option to `TypedDict` as well as the `Required` and `NotRequired` annotations of fields, if they are compatible with the Python version being used.
|
|
417
248
|
|
|
418
|
-
- `Protocol`. A class implementing a protocol is translated into a fields
|
|
249
|
+
- `Protocol`. A class implementing a protocol is translated into a `fields` schema. E.g.
|
|
419
250
|
|
|
420
251
|
```python
|
|
421
252
|
class Movie(Protocol):
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
vtjson/__init__.py,sha256=oLX4JH6_R7dYtTiGfBG3pQGR21IArspifdmZilbuGOw,68
|
|
2
|
+
vtjson/vtjson.py,sha256=bCndIajECAc65M4aeNcjDJRZ8cLv4Jz4H7ryt7XHwio,65935
|
|
3
|
+
vtjson-2.1.3.dist-info/AUTHORS,sha256=qmxaXxaIO-YPNHJAZ0dcCrnPCs1x9ocbtMksiy4i80M,21
|
|
4
|
+
vtjson-2.1.3.dist-info/LICENSE,sha256=n7xW-zX8xBLHzCdqWIMRuMzBD_ACLcNCwio0LEkKt1o,1077
|
|
5
|
+
vtjson-2.1.3.dist-info/METADATA,sha256=hoLkMLs5Kh6GVA-Xw8QX1TlCJDB7gSrzRSYrjzj9OqM,23506
|
|
6
|
+
vtjson-2.1.3.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
7
|
+
vtjson-2.1.3.dist-info/top_level.txt,sha256=9DlSF3l63igcvnYPcj117F2hzOW4Nx0N-JBoW3jjBZM,7
|
|
8
|
+
vtjson-2.1.3.dist-info/RECORD,,
|
vtjson-2.1.1.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
vtjson/__init__.py,sha256=oLX4JH6_R7dYtTiGfBG3pQGR21IArspifdmZilbuGOw,68
|
|
2
|
-
vtjson/vtjson.py,sha256=yUcIkc2qOxBr9ThaESForCkIR91bxvp_MS0UCl-OTfM,64659
|
|
3
|
-
vtjson-2.1.1.dist-info/AUTHORS,sha256=qmxaXxaIO-YPNHJAZ0dcCrnPCs1x9ocbtMksiy4i80M,21
|
|
4
|
-
vtjson-2.1.1.dist-info/LICENSE,sha256=n7xW-zX8xBLHzCdqWIMRuMzBD_ACLcNCwio0LEkKt1o,1077
|
|
5
|
-
vtjson-2.1.1.dist-info/METADATA,sha256=3jfSDOvDaKFRPhJGO0p0tvKJYKf0Jg16JWoMQvN1usk,27476
|
|
6
|
-
vtjson-2.1.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
7
|
-
vtjson-2.1.1.dist-info/top_level.txt,sha256=9DlSF3l63igcvnYPcj117F2hzOW4Nx0N-JBoW3jjBZM,7
|
|
8
|
-
vtjson-2.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|