167167
168168_VALIDATOR_TEMPLATE = '''
169169 @model_validator(mode="after")
170- def {marker}(self):
170+ def {marker}(self) -> Any :
171171 """JSON Schema minProperties: require at least {minimum}
172172 provided {properties_noun}."""
173173 provided = self.model_fields_set | set(self.model_extra or {{}})
@@ -183,7 +183,7 @@ def {marker}(self):
183183
184184_MAX_VALIDATOR_TEMPLATE = '''
185185 @model_validator(mode="after")
186- def {marker}(self):
186+ def {marker}(self) -> Any :
187187 """JSON Schema maxProperties: allow at most {maximum}
188188 provided {properties_noun}."""
189189 provided = self.model_fields_set | set(self.model_extra or {{}})
@@ -199,7 +199,7 @@ def {marker}(self):
199199
200200_PROPNAMES_VALIDATOR_TEMPLATE = '''
201201 @model_validator(mode="after")
202- def {marker}(self):
202+ def {marker}(self) -> Any :
203203 """JSON Schema propertyNames: every extra key must match the
204204 declared reverse-domain pattern (schema propertyNames)."""
205205 pattern = {pattern!r}
@@ -216,7 +216,7 @@ def {marker}(self):
216216
217217_DEPENDENT_REQUIRED_TEMPLATE = '''
218218 @model_validator(mode="after")
219- def {marker}(self):
219+ def {marker}(self) -> Any :
220220 """JSON Schema dependentRequired: enforce dependent fields."""
221221 rules = {rules!r}
222222 provided = self.model_fields_set | set(self.model_extra or {{}})
@@ -238,9 +238,9 @@ def {marker}(self):
238238
239239_CONDITIONAL_REQUIRED_TEMPLATE = '''
240240 @model_validator(mode="after")
241- def {marker}(self):
241+ def {marker}(self) -> Any :
242242 """JSON Schema if/then: enforce conditionally required fields."""
243- rules = {rules!r}
243+ rules: list[dict[str, Any]] = {rules!r}
244244 for rule in rules:
245245 if getattr(self, rule["discriminator"], None) not in rule["values"]:
246246 continue
@@ -274,9 +274,9 @@ def {marker}(self):
274274
275275_CONDITIONAL_BOUNDS_TEMPLATE = '''
276276 @model_validator(mode="after")
277- def {marker}(self):
277+ def {marker}(self) -> Any :
278278 """JSON Schema if/then: enforce conditional numeric bounds."""
279- rules = {rules!r}
279+ rules: list[dict[str, Any]] = {rules!r}
280280 checks = {checks!r}
281281 for rule in rules:
282282 actual = getattr(self, rule["discriminator"], None)
@@ -300,11 +300,11 @@ def {marker}(self):
300300
301301_RETYPE_TEMPLATE = '''
302302 @model_validator(mode="after")
303- def {marker}(self):
303+ def {marker}(self) -> Any :
304304 """JSON Schema if/then: approximate a discriminator's array-item
305305 retyping to a different referenced schema, via that schema's own
306306 required keys and const-pinned fields."""
307- rules = {rules!r}
307+ rules: list[dict[str, Any]] = {rules!r}
308308 for rule in rules:
309309 actual = getattr(self, rule["discriminator"], None)
310310 if actual not in rule["values"]:
@@ -339,11 +339,11 @@ def {marker}(self):
339339
340340_UNIQUE_VALIDATOR_TEMPLATE = '''
341341 @field_validator("{field}", mode="after")
342- def {marker}_{field}(cls, value) : # noqa: N805
342+ def {marker}_{field}(cls, value: Any) -> Any : # noqa: N805
343343 """JSON Schema uniqueItems: reject duplicate entries."""
344344 if value is None:
345345 return value
346- seen = []
346+ seen: list[Any] = []
347347 for item in value:
348348 if item in seen:
349349 raise ValueError(
@@ -362,7 +362,7 @@ def {marker}_{field}(cls, value): # noqa: N805
362362# date-time pattern UCP uses is anchored only at the end.
363363_DATETIME_PATTERN_TEMPLATE = '''
364364 @field_validator("{field}", mode="before")
365- def {marker}_{field}(cls, value) : # noqa: N805
365+ def {marker}_{field}(cls, value: Any) -> Any : # noqa: N805
366366 """JSON Schema pattern: match the raw date-time string, since
367367 pydantic cannot apply a regex to the parsed datetime."""
368368 pattern = {pattern!r}
@@ -843,7 +843,7 @@ def _predicate_expr(pairs):
843843def _build_contains_function (func_name , groups , item_condition = None ):
844844 """Render the module-level ``AfterValidator`` counting function."""
845845 lines = [
846- f"def { func_name } (value) :" ,
846+ f"def { func_name } (value: list[Any]) -> list[Any] :" ,
847847 ' """JSON Schema contains/minContains/maxContains (see #49)."""' ,
848848 ]
849849 for index , group in enumerate (groups ):
@@ -949,7 +949,9 @@ def inject_array_contains(source, alias_name, groups, item_condition=None):
949949 addition = f", AfterValidator({ func_name } )"
950950 out = source [:insert_at ] + addition + source [insert_at :]
951951 func_src = _build_contains_function (func_name , groups , item_condition )
952- insert_at = assign_re .search (out ).start ()
952+ assign_match = assign_re .search (out )
953+ assert assign_match is not None
954+ insert_at = assign_match .start ()
953955 out = out [:insert_at ] + func_src + "\n \n " + out [insert_at :]
954956 if item_condition :
955957 out = _inject_item_condition_on_item_class (
@@ -962,7 +964,7 @@ def inject_array_contains(source, alias_name, groups, item_condition=None):
962964
963965_ITEM_COND_TEMPLATE = '''
964966 @model_validator(mode="after")
965- def {marker}(self):
967+ def {marker}(self) -> Any :
966968 """JSON Schema if/then: require {required!r} when {field!r} is custom."""
967969 if getattr(self, {field!r}, None) not in {excluded!r}:
968970 _present = self.model_fields_set | set(self.model_extra or {{}})
@@ -1827,7 +1829,7 @@ def _patch_max_properties():
18271829
18281830_NOT_ENUM_TEMPLATE = '''
18291831 @model_validator(mode="after")
1830- def {marker}(self):
1832+ def {marker}(self) -> Any :
18311833 """JSON Schema not.enum: reject known variant discriminator values on open-union fallback."""
18321834 if getattr(self, {field!r}, None) in {excluded!r}:
18331835 raise ValueError(
@@ -2365,7 +2367,7 @@ def _patch_open_union_not_enum():
23652367
23662368_FORBIDDEN_KEYS_TEMPLATE = '''
23672369 @model_validator(mode="after")
2368- def {marker}(self):
2370+ def {marker}(self) -> Any :
23692371 """JSON Schema not: reject forbidden property keys."""
23702372 _present = self.model_fields_set | set(self.model_extra or {{}})
23712373 for _key in {forbidden!r}:
@@ -2663,14 +2665,18 @@ def find_datetime_pattern_fields(source):
26632665 items), which is left as generated and must be reported.
26642666 """
26652667 tree = ast .parse (source )
2666- fields = [
2667- (
2668- cls .name ,
2669- stmt .target .id ,
2670- _pattern_keyword (stmt .value ).value .value ,
2671- )
2672- for cls , stmt in _datetime_pattern_fields (tree )
2673- ]
2668+ fields = []
2669+ for cls , stmt in _datetime_pattern_fields (tree ):
2670+ if not isinstance (stmt .target , ast .Name ):
2671+ continue
2672+ keyword = _pattern_keyword (stmt .value )
2673+ if (
2674+ keyword is None
2675+ or not isinstance (keyword .value , ast .Constant )
2676+ or not isinstance (keyword .value .value , str )
2677+ ):
2678+ continue
2679+ fields .append ((cls .name , stmt .target .id , keyword .value .value ))
26742680 unsupported = [
26752681 node .lineno
26762682 for node in ast .walk (tree )
@@ -2706,10 +2712,13 @@ def inject_datetime_patterns(source):
27062712 cls , stmt = next (
27072713 _datetime_pattern_fields (ast .parse (source )), (None , None )
27082714 )
2709- if stmt is None :
2715+ if cls is None or stmt is None :
27102716 return source
2717+ assert isinstance (stmt .target , ast .Name )
27112718 call = stmt .value
2719+ assert isinstance (call , ast .Call )
27122720 keyword = _pattern_keyword (call )
2721+ assert keyword is not None and isinstance (keyword .value , ast .Constant )
27132722 call .keywords = [kw for kw in call .keywords if kw is not keyword ]
27142723 default = call .args [0 ] if len (call .args ) == 1 else None
27152724 if (
@@ -2726,13 +2735,19 @@ def inject_datetime_patterns(source):
27262735 value = " = None"
27272736 else :
27282737 value = f" = { ast .unparse (call )} "
2738+ assert (
2739+ stmt .annotation .end_lineno is not None
2740+ and stmt .annotation .end_col_offset is not None
2741+ )
2742+ assert call .end_lineno is not None and call .end_col_offset is not None
27292743 start = _source_offset (
27302744 source , stmt .annotation .end_lineno , stmt .annotation .end_col_offset
27312745 )
27322746 end = _source_offset (source , call .end_lineno , call .end_col_offset )
27332747 source = source [:start ] + value + source [end :]
27342748
27352749 match = re .search (rf"^class { re .escape (cls .name )} \(" , source , re .M )
2750+ assert match is not None
27362751 end_match = re .compile (r"^\S" , re .M ).search (source , match .end ())
27372752 body_end = end_match .start () if end_match else len (source )
27382753 method = _DATETIME_PATTERN_TEMPLATE .format (
@@ -2803,12 +2818,12 @@ def _write_schemas_reexport_init():
28032818 ):
28042819 exports .append (node .target .id )
28052820 continue
2806- if (
2807- isinstance (node , type_alias_cls )
2808- and isinstance (node . name , ast . Name )
2809- and not node . name . id . startswith ( "_" )
2810- ):
2811- exports .append (node . name .id )
2821+ if isinstance ( node , type_alias_cls ):
2822+ alias_name = getattr (node , "name" , None )
2823+ if isinstance (
2824+ alias_name , ast . Name
2825+ ) and not alias_name . id . startswith ( "_" ):
2826+ exports .append (alias_name .id )
28122827 exports = sorted (dict .fromkeys (exports ))
28132828 init_path = generated_path .parent / "__init__.py"
28142829 if exports :
0 commit comments