pytastic 0.0.2__tar.gz → 0.0.4__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.
- {pytastic-0.0.2 → pytastic-0.0.4}/PKG-INFO +13 -1
- {pytastic-0.0.2 → pytastic-0.0.4}/README.md +12 -0
- {pytastic-0.0.2 → pytastic-0.0.4}/pyproject.toml +1 -1
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/compiler.py +11 -75
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/core.py +1 -3
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/schema.py +5 -11
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/utils.py +6 -21
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/validators.py +4 -14
- {pytastic-0.0.2 → pytastic-0.0.4}/LICENSE +0 -0
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/__init__.py +0 -0
- {pytastic-0.0.2 → pytastic-0.0.4}/pytastic/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytastic
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A dependency-free JSON validation library using TypedDict and Annotated
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Tersoo
|
|
@@ -45,6 +45,7 @@ class User(TypedDict):
|
|
|
45
45
|
username: Annotated[str, "min_len=3; regex=^[a-z_]+$"]
|
|
46
46
|
age: Annotated[int, "min=18"]
|
|
47
47
|
role: Literal["admin", "user"]
|
|
48
|
+
```
|
|
48
49
|
|
|
49
50
|
# 2. Usage Patterns
|
|
50
51
|
|
|
@@ -68,6 +69,17 @@ user = vx.User({"username": "tersoo", "age": 25, "role": "admin"})
|
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
## 3. JSON Schema
|
|
72
|
+
```python
|
|
73
|
+
# Export standard JSON Schema
|
|
74
|
+
print(vx.schema(User))
|
|
75
|
+
# Output:
|
|
76
|
+
# {
|
|
77
|
+
# "type": "object",
|
|
78
|
+
# "properties": {
|
|
79
|
+
# "username": { "type": "string", "minLength": 3 ... },
|
|
80
|
+
# ...
|
|
81
|
+
# }
|
|
82
|
+
# }
|
|
71
83
|
```
|
|
72
84
|
|
|
73
85
|
## License
|
|
@@ -28,6 +28,7 @@ class User(TypedDict):
|
|
|
28
28
|
username: Annotated[str, "min_len=3; regex=^[a-z_]+$"]
|
|
29
29
|
age: Annotated[int, "min=18"]
|
|
30
30
|
role: Literal["admin", "user"]
|
|
31
|
+
```
|
|
31
32
|
|
|
32
33
|
# 2. Usage Patterns
|
|
33
34
|
|
|
@@ -51,6 +52,17 @@ user = vx.User({"username": "tersoo", "age": 25, "role": "admin"})
|
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## 3. JSON Schema
|
|
55
|
+
```python
|
|
56
|
+
# Export standard JSON Schema
|
|
57
|
+
print(vx.schema(User))
|
|
58
|
+
# Output:
|
|
59
|
+
# {
|
|
60
|
+
# "type": "object",
|
|
61
|
+
# "properties": {
|
|
62
|
+
# "username": { "type": "string", "minLength": 3 ... },
|
|
63
|
+
# ...
|
|
64
|
+
# }
|
|
65
|
+
# }
|
|
54
66
|
```
|
|
55
67
|
|
|
56
68
|
## License
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Any, Type, Dict, get_origin, get_args, Union, List, Tuple, Annotated, _TypedDictMeta, Literal, cast, Generic, TypeVar, Set # type: ignore
|
|
3
|
-
from pytastic.validators import
|
|
3
|
+
from pytastic.validators import (
|
|
4
|
+
Validator, NumberValidator, StringValidator, CollectionValidator,
|
|
5
|
+
UnionValidator, ObjectValidator, LiteralValidator, AnyValidator
|
|
6
|
+
)
|
|
4
7
|
from pytastic.exceptions import SchemaDefinitionError
|
|
5
8
|
from pytastic.utils import parse_constraints, normalize_key
|
|
9
|
+
from typing import get_type_hints, NotRequired, Required
|
|
6
10
|
|
|
7
11
|
class SchemaCompiler:
|
|
8
12
|
"""Compiles Python types (TypedDict, Annotated, etc.) into Validators."""
|
|
@@ -14,9 +18,7 @@ class SchemaCompiler:
|
|
|
14
18
|
"""
|
|
15
19
|
Compiles a schema type into a Validator instance.
|
|
16
20
|
"""
|
|
17
|
-
if schema in self._cache:
|
|
18
|
-
return self._cache[schema]
|
|
19
|
-
|
|
21
|
+
if schema in self._cache: return self._cache[schema]
|
|
20
22
|
validator = self._build_validator(schema)
|
|
21
23
|
self._cache[schema] = validator
|
|
22
24
|
return validator
|
|
@@ -25,21 +27,14 @@ class SchemaCompiler:
|
|
|
25
27
|
origin = get_origin(schema)
|
|
26
28
|
args = get_args(schema)
|
|
27
29
|
|
|
28
|
-
# 1. Annotated[T, "constraints"]
|
|
29
30
|
if origin is Annotated:
|
|
30
31
|
base_type = args[0]
|
|
31
32
|
constraint_str = ""
|
|
32
|
-
# Combine all string annotations
|
|
33
33
|
for arg in args[1:]:
|
|
34
34
|
if isinstance(arg, str):
|
|
35
35
|
constraint_str += arg + ";"
|
|
36
36
|
|
|
37
37
|
constraints = parse_constraints(constraint_str)
|
|
38
|
-
|
|
39
|
-
# Recurse on base type but PASS DOWN constraints?
|
|
40
|
-
# Actually, most validators take constraints in __init__.
|
|
41
|
-
# We need to peek at base type to know which Validator to create.
|
|
42
|
-
|
|
43
38
|
base_origin = get_origin(base_type)
|
|
44
39
|
|
|
45
40
|
if base_type is int or base_type is float:
|
|
@@ -49,7 +44,6 @@ class SchemaCompiler:
|
|
|
49
44
|
return StringValidator(constraints)
|
|
50
45
|
|
|
51
46
|
if base_origin is list or base_origin is List:
|
|
52
|
-
# Annotated[list[int], "min_items=3"]
|
|
53
47
|
inner_type = get_args(base_type)[0] if get_args(base_type) else Any
|
|
54
48
|
item_validator = self.compile(inner_type)
|
|
55
49
|
return CollectionValidator(constraints, item_validator=item_validator)
|
|
@@ -61,109 +55,52 @@ class SchemaCompiler:
|
|
|
61
55
|
return CollectionValidator(constraints, item_validator=item_validators)
|
|
62
56
|
|
|
63
57
|
if base_origin is Union:
|
|
64
|
-
# Annotated[Union[A, B], "one_of"]
|
|
65
58
|
union_mode = "one_of" if constraints.get("one_of") else "any_of"
|
|
66
59
|
validators = [self.compile(t) for t in get_args(base_type)]
|
|
67
60
|
return UnionValidator(validators, mode=union_mode)
|
|
68
61
|
|
|
69
|
-
# Annotated[TypedDict, "..."]
|
|
70
62
|
if self._is_typeddict(base_type):
|
|
71
|
-
# Constraints on the object itself (e.g. min_properties)
|
|
72
|
-
# We need to compile the TypedDict logic first, then inject constraints?
|
|
73
|
-
# Or pass constraints to ObjectValidator?
|
|
74
63
|
return self._compile_typeddict(base_type, constraints)
|
|
75
64
|
|
|
76
|
-
# Fallback: Just return compiled base type (ignoring constraints if unknown?)
|
|
77
|
-
# Or raise error?
|
|
78
65
|
return self.compile(base_type)
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
if self._is_typeddict(schema):
|
|
82
|
-
return self._compile_typeddict(schema, {})
|
|
83
|
-
|
|
84
|
-
# 3. List[T]
|
|
67
|
+
if self._is_typeddict(schema): return self._compile_typeddict(schema, {})
|
|
85
68
|
if origin is list or origin is List:
|
|
86
69
|
inner_type = args[0] if args else Any
|
|
87
70
|
return CollectionValidator({}, item_validator=self.compile(inner_type))
|
|
88
71
|
|
|
89
|
-
# 4. Tuple[T, ...]
|
|
90
72
|
if origin is tuple or origin is Tuple:
|
|
91
73
|
return CollectionValidator({}, item_validator=[self.compile(t) for t in args])
|
|
92
|
-
|
|
93
|
-
# 5. Union[A, B]
|
|
94
74
|
if origin is Union:
|
|
95
75
|
return UnionValidator([self.compile(t) for t in args], mode="any_of")
|
|
96
|
-
|
|
97
|
-
# 6. Literal[A, B]
|
|
98
76
|
if origin is Literal:
|
|
99
77
|
return LiteralValidator(args)
|
|
100
|
-
|
|
101
|
-
# 7. Basic Types (No Annotations)
|
|
102
78
|
if schema is int or schema is float:
|
|
103
79
|
return NumberValidator({}, number_type=schema)
|
|
104
|
-
|
|
105
80
|
if schema is str:
|
|
106
81
|
return StringValidator({})
|
|
107
|
-
|
|
108
82
|
if schema is Any:
|
|
109
|
-
# Pass-through validator
|
|
110
|
-
from pytastic.validators import Validator
|
|
111
|
-
class AnyValidator(Validator):
|
|
112
|
-
def validate(self, data, path=""): return data
|
|
113
83
|
return AnyValidator()
|
|
114
|
-
|
|
115
|
-
# Raise unknown type error
|
|
116
|
-
# Check for NotRequired? NotRequired is stripped by get_type_hints usually,
|
|
117
|
-
# or handled at TypedDict level.
|
|
118
|
-
|
|
119
84
|
raise SchemaDefinitionError(f"Unsupported type: {schema}")
|
|
120
85
|
|
|
121
86
|
def _is_typeddict(self, t: Type) -> bool:
|
|
122
87
|
return isinstance(t, _TypedDictMeta) or (isinstance(t, type) and issubclass(t, dict) and hasattr(t, '__annotations__'))
|
|
123
88
|
|
|
124
89
|
def _compile_typeddict(self, td_cls: Type, constraints: Dict[str, Any]) -> ObjectValidator:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
# Python 3.9+ get_type_hints(include_extras=True)
|
|
128
|
-
# But for TypedDict, we check key presence.
|
|
129
|
-
|
|
90
|
+
# Python 3.9+ get_type_hints(include_extras=True) includes Annotated
|
|
130
91
|
type_hints = get_type_hints(td_cls, include_extras=True)
|
|
131
92
|
fields = {}
|
|
132
93
|
required_keys = set()
|
|
133
|
-
|
|
134
|
-
# Inspect for Required/NotRequired
|
|
135
|
-
# Note: In TypedDict, strict means only defined keys.
|
|
136
|
-
# Checking required status depends on 'total=True/False' and individual generic modifiers.
|
|
137
|
-
|
|
138
94
|
is_total = getattr(td_cls, '__total__', True)
|
|
139
95
|
|
|
140
96
|
for key, value in type_hints.items():
|
|
141
|
-
# Check if value is wrapped in NotRequired or Required
|
|
142
|
-
# This is tricky because get_type_hints might strip it or parse it.
|
|
143
|
-
# get_type_hints with include_extras=True preserves Annotated, but NotRequired handling varies.
|
|
144
|
-
|
|
145
|
-
# Simple heuristic:
|
|
146
97
|
is_required = is_total
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# Actually get_type_hints usually resolves it.
|
|
150
|
-
# Using basic TypedDict inspection:
|
|
151
|
-
if hasattr(td_cls, '__required_keys__'):
|
|
152
|
-
is_required = key in td_cls.__required_keys__
|
|
153
|
-
|
|
154
|
-
if is_required:
|
|
155
|
-
required_keys.add(key)
|
|
156
|
-
|
|
98
|
+
if hasattr(td_cls, '__required_keys__'): is_required = key in td_cls.__required_keys__
|
|
99
|
+
if is_required: required_keys.add(key)
|
|
157
100
|
fields[key] = self.compile(value)
|
|
158
101
|
|
|
159
|
-
#
|
|
160
|
-
# User example: _: Annotated[None, "strict; min_properties=2"]
|
|
161
|
-
# This key is meant for metadata, not a real field. We should check if it exists in type_hints.
|
|
162
|
-
|
|
102
|
+
# Handle metadata from `_: Annotated[...]` pattern
|
|
163
103
|
if '_' in fields:
|
|
164
|
-
# Extract constraints from wildcard field
|
|
165
|
-
# It's compiled as a AnyValidator or NoneValidator, but we need the Annotated constraints.
|
|
166
|
-
# Easier to check __annotations__ directly for '_'
|
|
167
104
|
meta_annotation = td_cls.__annotations__.get('_', None)
|
|
168
105
|
if meta_annotation and get_origin(meta_annotation) is Annotated:
|
|
169
106
|
args = get_args(meta_annotation)
|
|
@@ -177,5 +114,4 @@ class SchemaCompiler:
|
|
|
177
114
|
# Remove _ from fields and required keys checks
|
|
178
115
|
fields.pop('_', None)
|
|
179
116
|
required_keys.discard('_')
|
|
180
|
-
|
|
181
117
|
return ObjectValidator(fields, constraints, required_keys)
|
|
@@ -12,7 +12,7 @@ class Pytastic:
|
|
|
12
12
|
"""
|
|
13
13
|
def __init__(self):
|
|
14
14
|
self.compiler = SchemaCompiler()
|
|
15
|
-
self._registry: Dict[str, Any] = {}
|
|
15
|
+
self._registry: Dict[str, Any] = {}
|
|
16
16
|
|
|
17
17
|
def register(self, schema: Type[T]) -> None:
|
|
18
18
|
"""
|
|
@@ -27,7 +27,6 @@ class Pytastic:
|
|
|
27
27
|
Validates data against a schema.
|
|
28
28
|
If the schema is not registered, it is compiled on the fly.
|
|
29
29
|
"""
|
|
30
|
-
# Auto-compile if passed a class directly
|
|
31
30
|
validator = self.compiler.compile(schema)
|
|
32
31
|
return validator.validate(data)
|
|
33
32
|
|
|
@@ -37,7 +36,6 @@ class Pytastic:
|
|
|
37
36
|
"""
|
|
38
37
|
if name in self._registry:
|
|
39
38
|
validator = self._registry[name]
|
|
40
|
-
# Return a callable that runs validation
|
|
41
39
|
def validate_wrapper(data: Any) -> Any:
|
|
42
40
|
return validator.validate(data)
|
|
43
41
|
return validate_wrapper
|
|
@@ -41,7 +41,7 @@ class JsonSchemaGenerator:
|
|
|
41
41
|
return {} # Fallback
|
|
42
42
|
|
|
43
43
|
def _visit_number(self, v: NumberValidator) -> Dict[str, Any]:
|
|
44
|
-
schema = {"type": "integer" if v.number_type is int else "number"}
|
|
44
|
+
schema: Dict[str, Any] = {"type": "integer" if v.number_type is int else "number"}
|
|
45
45
|
c = v.constraints
|
|
46
46
|
|
|
47
47
|
if 'min' in c: schema['minimum'] = float(c['min'])
|
|
@@ -55,7 +55,7 @@ class JsonSchemaGenerator:
|
|
|
55
55
|
return schema
|
|
56
56
|
|
|
57
57
|
def _visit_string(self, v: StringValidator) -> Dict[str, Any]:
|
|
58
|
-
schema = {"type": "string"}
|
|
58
|
+
schema: Dict[str, Any] = {"type": "string"}
|
|
59
59
|
c = v.constraints
|
|
60
60
|
|
|
61
61
|
min_l = c.get('min_length') or c.get('min_len')
|
|
@@ -73,7 +73,7 @@ class JsonSchemaGenerator:
|
|
|
73
73
|
return schema
|
|
74
74
|
|
|
75
75
|
def _visit_collection(self, v: CollectionValidator) -> Dict[str, Any]:
|
|
76
|
-
schema = {"type": "array"}
|
|
76
|
+
schema: Dict[str, Any] = {"type": "array"}
|
|
77
77
|
c = v.constraints
|
|
78
78
|
|
|
79
79
|
if 'min_items' in c: schema['minItems'] = int(c['min_items'])
|
|
@@ -81,13 +81,7 @@ class JsonSchemaGenerator:
|
|
|
81
81
|
if c.get('unique') or c.get('unique_items'): schema['uniqueItems'] = True
|
|
82
82
|
|
|
83
83
|
if isinstance(v.item_validator, list):
|
|
84
|
-
# Tuple
|
|
85
|
-
# We'll use prefixItems style logic but map to 'items' array + 'minItems' match for simple tuples?
|
|
86
|
-
# Actually standard JSON schema uses "prefixItems" for positionals now.
|
|
87
|
-
# Let's use generic "items": [...] if supported or use prefixItems.
|
|
88
|
-
# To be safe for older parsers, tuple validation usually maps to:
|
|
89
|
-
# "type": "array", "items": [s1, s2], "minItems": X, "maxItems": X
|
|
90
|
-
schema['type'] = 'array'
|
|
84
|
+
# Tuple validation using 'prefixItems' (JSON Schema 2020-12+)
|
|
91
85
|
schema['prefixItems'] = [self._visit(iv) for iv in v.item_validator]
|
|
92
86
|
schema['minItems'] = len(v.item_validator)
|
|
93
87
|
schema['maxItems'] = len(v.item_validator)
|
|
@@ -98,7 +92,7 @@ class JsonSchemaGenerator:
|
|
|
98
92
|
return schema
|
|
99
93
|
|
|
100
94
|
def _visit_object(self, v: ObjectValidator) -> Dict[str, Any]:
|
|
101
|
-
schema = {"type": "object", "properties": {}, "required": [], "additionalProperties": True}
|
|
95
|
+
schema: Dict[str, Any] = {"type": "object", "properties": {}, "required": [], "additionalProperties": True}
|
|
102
96
|
|
|
103
97
|
for name, field_validator in v.fields.items():
|
|
104
98
|
schema["properties"][name] = self._visit(field_validator)
|
|
@@ -5,36 +5,21 @@ def parse_constraints(annotation_str: str) -> Dict[str, Any]:
|
|
|
5
5
|
Parses a constraint string into a dictionary.
|
|
6
6
|
Example: "min=10; max=20; unique" -> {'min': '10', 'max': '20', 'unique': True}
|
|
7
7
|
"""
|
|
8
|
-
if not annotation_str:
|
|
9
|
-
return {}
|
|
10
|
-
|
|
8
|
+
if not annotation_str: return {}
|
|
11
9
|
constraints = {}
|
|
12
|
-
# Split by semicolon
|
|
13
10
|
parts = annotation_str.split(';')
|
|
14
11
|
|
|
15
12
|
for part in parts:
|
|
16
13
|
part = part.strip()
|
|
17
|
-
if not part:
|
|
18
|
-
continue
|
|
19
|
-
|
|
14
|
+
if not part: continue
|
|
20
15
|
if '=' in part:
|
|
21
16
|
key, value = part.split('=', 1)
|
|
22
|
-
key = key.strip()
|
|
23
|
-
value = value.strip()
|
|
24
|
-
|
|
25
|
-
# Basic type inference
|
|
26
|
-
if value.lower() == 'true':
|
|
27
|
-
value = True
|
|
28
|
-
elif value.lower() == 'false':
|
|
29
|
-
value = False
|
|
30
|
-
# We treat numbers as strings here, they will be cast by the specific validators
|
|
31
|
-
# because we don't know if 'min=10' is for an int or a float yet.
|
|
17
|
+
key, value = key.strip(), value.strip()
|
|
32
18
|
|
|
19
|
+
if value.lower() == 'true': value = True
|
|
20
|
+
elif value.lower() == 'false': value = False
|
|
33
21
|
constraints[key] = value
|
|
34
|
-
else:
|
|
35
|
-
# Boolean flag (e.g., "unique")
|
|
36
|
-
constraints[part] = True
|
|
37
|
-
|
|
22
|
+
else: constraints[part] = True
|
|
38
23
|
return constraints
|
|
39
24
|
|
|
40
25
|
def normalize_key(key: str) -> str:
|
|
@@ -11,13 +11,12 @@ class Validator(ABC, Generic[T]):
|
|
|
11
11
|
|
|
12
12
|
@abstractmethod
|
|
13
13
|
def validate(self, data: Any, path: str = "") -> T:
|
|
14
|
-
"""
|
|
15
|
-
Validate data against the schema.
|
|
16
|
-
Raises ValidationError if validation fails.
|
|
17
|
-
Returns the validated (and possibly coerced) data.
|
|
18
|
-
"""
|
|
19
14
|
pass
|
|
20
15
|
|
|
16
|
+
class AnyValidator(Validator[Any]):
|
|
17
|
+
def validate(self, data: Any, path: str = "") -> Any:
|
|
18
|
+
return data
|
|
19
|
+
|
|
21
20
|
class NumberValidator(Validator[Union[int, float]]):
|
|
22
21
|
def __init__(self, constraints: Dict[str, Any], number_type: type = int):
|
|
23
22
|
self.constraints = constraints
|
|
@@ -27,8 +26,6 @@ class NumberValidator(Validator[Union[int, float]]):
|
|
|
27
26
|
if not isinstance(data, (int, float)):
|
|
28
27
|
raise ValidationError(f"Expected number, got {type(data).__name__}", [{"path": path, "message": "Invalid type"}])
|
|
29
28
|
|
|
30
|
-
# Type check (int vs float strictly?)
|
|
31
|
-
# If strict int is needed:
|
|
32
29
|
if self.number_type is int and isinstance(data, float) and not data.is_integer():
|
|
33
30
|
raise ValidationError(f"Expected integer, got float", [{"path": path, "message": "Expected integer"}])
|
|
34
31
|
|
|
@@ -56,12 +53,9 @@ class NumberValidator(Validator[Union[int, float]]):
|
|
|
56
53
|
if val >= limit:
|
|
57
54
|
raise ValidationError(f"Value {val} must be less than {limit}", [{"path": path, "message": f"Must be < {limit}"}])
|
|
58
55
|
|
|
59
|
-
# Step / Multiple Of
|
|
60
56
|
step = self.constraints.get('step') or self.constraints.get('multiple_of')
|
|
61
57
|
if step:
|
|
62
58
|
step_val = float(step)
|
|
63
|
-
# Use a small epsilon for float comparison logic if needed, but simple mod usually works for basic cases
|
|
64
|
-
# For floats, direct modulo can be problematic due to precision. math.isclose is better.
|
|
65
59
|
if not math.isclose(val % step_val, 0, abs_tol=1e-9) and not math.isclose(val % step_val, step_val, abs_tol=1e-9):
|
|
66
60
|
raise ValidationError(f"Value {val} is not a multiple of {step_val}", [{"path": path, "message": f"Must be multiple of {step_val}"}])
|
|
67
61
|
|
|
@@ -133,12 +127,10 @@ class CollectionValidator(Validator[list]):
|
|
|
133
127
|
|
|
134
128
|
# Unique Items
|
|
135
129
|
if self.constraints.get('unique') or self.constraints.get('unique_items'):
|
|
136
|
-
# This is complex for unhashable types (dicts/lists), but simplistic set check for primitives
|
|
137
130
|
try:
|
|
138
131
|
if len(set(data)) != len(data):
|
|
139
132
|
raise ValidationError("List items must be unique", [{"path": path, "message": "Duplicate items found"}])
|
|
140
133
|
except TypeError:
|
|
141
|
-
# Fallback for unhashables (O(N^2))
|
|
142
134
|
for i in range(len(data)):
|
|
143
135
|
for j in range(i + 1, len(data)):
|
|
144
136
|
if data[i] == data[j]:
|
|
@@ -209,8 +201,6 @@ class ObjectValidator(Validator[Dict]):
|
|
|
209
201
|
# Check required keys
|
|
210
202
|
missing = self.required_keys - data.keys()
|
|
211
203
|
if missing:
|
|
212
|
-
# Check if missing keys have defaults? TypedDict doesn't support defaults easily at runtime without extra inspecting.
|
|
213
|
-
# For now, strict required check.
|
|
214
204
|
for k in missing:
|
|
215
205
|
errors.append({"path": f"{path}.{k}" if path else k, "message": "Field is required"})
|
|
216
206
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|