pytastic 0.0.6__py3-none-any.whl → 0.0.7__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.
pytastic/codegen.py CHANGED
@@ -6,7 +6,6 @@ from pytastic.utils import parse_constraints
6
6
 
7
7
  class CodegenCompiler:
8
8
  """Generates optimized Python validation functions from type schemas."""
9
-
10
9
  def __init__(self):
11
10
  self._cache: Dict[Type, Any] = {}
12
11
  self._counter = 0
@@ -66,14 +65,32 @@ class CodegenCompiler:
66
65
  elif base_origin is list or base_origin is List:
67
66
  inner_type = get_args(base_type)[0] if get_args(base_type) else Any
68
67
  return self._gen_list(var_name, path_var, inner_type, constraints, indent)
68
+ elif base_origin is tuple or base_origin is Tuple:
69
+ inner_types = get_args(base_type)
70
+ return self._gen_tuple(var_name, path_var, inner_types, constraints, indent)
69
71
 
70
72
  if self._is_typeddict(schema):
71
- return self._gen_object(schema, var_name, path_var, {}, indent)
73
+ constraints = {}
74
+ # Handle metadata from `_: Annotated[...]` pattern
75
+ if hasattr(schema, '__annotations__') and '_' in schema.__annotations__:
76
+ meta_annotation = schema.__annotations__.get('_', None)
77
+ if meta_annotation and get_origin(meta_annotation) is Annotated:
78
+ meta_args = get_args(meta_annotation)
79
+ constraint_str = ""
80
+ for arg in meta_args[1:]:
81
+ if isinstance(arg, str):
82
+ constraint_str += arg + ";"
83
+ meta_constraints = parse_constraints(constraint_str)
84
+ constraints.update(meta_constraints)
85
+ return self._gen_object(schema, var_name, path_var, constraints, indent)
72
86
 
73
87
  if origin is list or origin is List:
74
88
  inner_type = args[0] if args else Any
75
89
  return self._gen_list(var_name, path_var, inner_type, {}, indent)
76
90
 
91
+ if origin is tuple or origin is Tuple:
92
+ return self._gen_tuple(var_name, path_var, args, {}, indent)
93
+
77
94
  if origin is Union:
78
95
  return self._gen_union(var_name, path_var, args, indent)
79
96
 
@@ -178,6 +195,19 @@ class CodegenCompiler:
178
195
  if not is_required:
179
196
  indent -= 1
180
197
 
198
+ # Additional Properties Check
199
+ if constraints.get('strict') or constraints.get('additional_properties') is False:
200
+ lines.append(f"{ind}known_keys = set({repr(list(type_hints.keys()))}) - {{'_'}}")
201
+ lines.append(f"{ind}extra_keys = set({var}.keys()) - known_keys")
202
+ lines.append(f"{ind}if extra_keys:")
203
+ lines.append(f"{ind} raise ValidationError(f'Extra fields not allowed at {{{path}}}: {{extra_keys}}', [{{'path': {path}, 'message': 'Extra fields not allowed'}}])")
204
+
205
+ # Min Properties
206
+ min_props = constraints.get('min_properties') or constraints.get('min_props')
207
+ if min_props:
208
+ lines.append(f"{ind}if len({var}) < {min_props}:")
209
+ lines.append(f"{ind} raise ValidationError(f'Too few properties at {{{path}}}', [{{'path': {path}, 'message': 'Min properties {min_props}'}}])")
210
+
181
211
  return lines
182
212
 
183
213
  def _gen_list(self, var: str, path: str, item_type: Type, constraints: Dict, indent: int) -> List[str]:
@@ -228,6 +258,30 @@ class CodegenCompiler:
228
258
 
229
259
  return lines
230
260
 
261
+ def _gen_tuple(self, var: str, path: str, item_types: Tuple[Type, ...], constraints: Dict, indent: int) -> List[str]:
262
+ """Generate tuple validation code."""
263
+ ind = ' ' * indent
264
+ lines = []
265
+
266
+ lines.append(f"{ind}if not isinstance({var}, (list, tuple)):")
267
+ lines.append(f"{ind} raise ValidationError(f'Expected tuple at {{{path}}}', [{{'path': {path}, 'message': 'Invalid type'}}])")
268
+
269
+ expected_len = len(item_types)
270
+ lines.append(f"{ind}if len({var}) != {expected_len}:")
271
+ lines.append(f"{ind} raise ValidationError(f'Expected {expected_len} items at {{{path}}}', [{{'path': {path}, 'message': 'Expected {expected_len} items'}}])")
272
+
273
+ for i, item_type in enumerate(item_types):
274
+ item_path = f"{path} + f'[{i}]'"
275
+ # Tuple items are accessed by index
276
+ # We need to assign to a temp var to be safe for recursive validation
277
+ temp_var = f"_tuple_item_{indent}_{i}"
278
+ lines.append(f"{ind}{temp_var} = {var}[{i}]")
279
+
280
+ item_lines = self._generate_validator(item_type, temp_var, item_path, indent)
281
+ lines.extend(item_lines)
282
+
283
+ return lines
284
+
231
285
  def _gen_literal(self, var: str, path: str, values: Tuple, indent: int) -> List[str]:
232
286
  """Generate literal validation code."""
233
287
  ind = ' ' * indent
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytastic
3
- Version: 0.0.6
3
+ Version: 0.0.7
4
4
  Summary: A dependency-free JSON validation library using TypedDict and Annotated
5
5
  License-File: LICENSE
6
6
  Author: Tersoo
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
+ Project-URL: Repository, https://github.com/tersoo/pytastic
16
17
  Description-Content-Type: text/markdown
17
18
 
18
19
  # Pytastic
@@ -37,7 +38,7 @@ Benchmark results (100,000 validation iterations):
37
38
  | **Pytastic** | **0.1794** | **557,277** | **3.37x** |
38
39
  | Pydantic | 0.2002 | 499,381 | 3.76x |
39
40
 
40
- **Pytastic is faster than Pydantic** Pure Python with zero dependencies!
41
+ **Pytastic is faster than Pydantic** i.e. Pure Python with zero dependencies!
41
42
 
42
43
  ## Installation
43
44
 
@@ -1,12 +1,12 @@
1
1
  pytastic/__init__.py,sha256=V8VDtL5XF_UMQ1rbR_s6XTffHD5INdZA3G37X4pHbm4,158
2
- pytastic/codegen.py,sha256=V7rqBTzK_Bm27kN6-pnmfjfBpu-LPFiVRSSqrY4x6gw,11387
2
+ pytastic/codegen.py,sha256=Wr_s_jmaVMLagenH_qkzpkEyx6Rm-_ISXYiA5MKyt0w,14571
3
3
  pytastic/compiler.py,sha256=sddyIsVxzEM6dKo-Zre52d6q3YiLlwGequKFdeKPwPA,5384
4
4
  pytastic/core.py,sha256=uVSzLaG11ys1ivow57J869uo3KLXS3GtT4El0n-edgI,1982
5
5
  pytastic/exceptions.py,sha256=Jp8i00Qo0dqbxO_mwQRO14qQFdzeBqqDsDK7h7dvWz0,721
6
6
  pytastic/schema.py,sha256=N37aZBFsNIoWoH9wFAAbzR1DCET0UYg_eAChFl96xRw,4465
7
7
  pytastic/utils.py,sha256=_HLPlVL3in2dm2lvGNRDYvmweQ93Fpgf6e4aRlS11Q0,1012
8
8
  pytastic/validators.py,sha256=LmoQFzcYiElActlQZIb-1eSxKqvjRQOf1vXN4NCN9jM,11605
9
- pytastic-0.0.6.dist-info/METADATA,sha256=fmsqOuUimTl55HzxRPM_iO1S_wSCDiBNR8UpAv_V8J8,2641
10
- pytastic-0.0.6.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
11
- pytastic-0.0.6.dist-info/licenses/LICENSE,sha256=IYQPqrAtRIRqyLsFwHdH9W-VdVfKOnZ2WZu1hrG_nLU,1063
12
- pytastic-0.0.6.dist-info/RECORD,,
9
+ pytastic-0.0.7.dist-info/METADATA,sha256=7OXjvyhEHBQH97ppdeH_1G7m8XVJI2IfUJoGrmXc3vk,2706
10
+ pytastic-0.0.7.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
11
+ pytastic-0.0.7.dist-info/licenses/LICENSE,sha256=IYQPqrAtRIRqyLsFwHdH9W-VdVfKOnZ2WZu1hrG_nLU,1063
12
+ pytastic-0.0.7.dist-info/RECORD,,