|
11 | 11 | import collections |
12 | 12 | import collections.abc |
13 | 13 | import dataclasses |
14 | | -import os |
15 | 14 | import typing |
16 | 15 | from functools import partial |
17 | 16 | from typing import Any, Callable, Iterable, Tuple, TypeVar, cast |
18 | 17 |
|
19 | 18 | import typing_extensions |
20 | 19 | from pydantic_core import ( |
21 | 20 | CoreSchema, |
22 | | - PydanticCustomError, |
23 | 21 | core_schema, |
24 | 22 | ) |
25 | 23 | from typing_extensions import get_args, get_origin |
26 | 24 |
|
27 | 25 | from pydantic._internal._serializers import serialize_sequence_via_list |
28 | 26 | from pydantic.errors import PydanticSchemaGenerationError |
29 | | -from pydantic.types import Strict |
30 | 27 |
|
31 | | -from ..json_schema import JsonSchemaValue |
32 | 28 | from . import _known_annotated_metadata, _typing_extra |
33 | 29 | from ._import_utils import import_cached_field_info |
34 | 30 | from ._internal_dataclass import slots_true |
35 | | -from ._schema_generation_shared import GetCoreSchemaHandler, GetJsonSchemaHandler |
| 31 | +from ._schema_generation_shared import GetCoreSchemaHandler |
36 | 32 |
|
37 | 33 | FieldInfo = import_cached_field_info() |
38 | 34 |
|
|
42 | 38 | StdSchemaFunction = Callable[[GenerateSchema, type[Any]], core_schema.CoreSchema] |
43 | 39 |
|
44 | 40 |
|
45 | | -@dataclasses.dataclass(**slots_true) |
46 | | -class InnerSchemaValidator: |
47 | | - """Use a fixed CoreSchema, avoiding interference from outward annotations.""" |
48 | | - |
49 | | - core_schema: CoreSchema |
50 | | - js_schema: JsonSchemaValue | None = None |
51 | | - js_core_schema: CoreSchema | None = None |
52 | | - js_schema_update: JsonSchemaValue | None = None |
53 | | - |
54 | | - def __get_pydantic_json_schema__(self, _schema: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: |
55 | | - if self.js_schema is not None: |
56 | | - return self.js_schema |
57 | | - js_schema = handler(self.js_core_schema or self.core_schema) |
58 | | - if self.js_schema_update is not None: |
59 | | - js_schema.update(self.js_schema_update) |
60 | | - return js_schema |
61 | | - |
62 | | - def __get_pydantic_core_schema__(self, _source_type: Any, _handler: GetCoreSchemaHandler) -> CoreSchema: |
63 | | - return self.core_schema |
64 | | - |
65 | | - |
66 | | -def path_schema_prepare_pydantic_annotations( |
67 | | - source_type: Any, annotations: Iterable[Any] |
68 | | -) -> tuple[Any, list[Any]] | None: |
69 | | - import pathlib |
70 | | - |
71 | | - orig_source_type: Any = get_origin(source_type) or source_type |
72 | | - if ( |
73 | | - (source_type_args := get_args(source_type)) |
74 | | - and orig_source_type is os.PathLike |
75 | | - and source_type_args[0] not in {str, bytes, Any} |
76 | | - ): |
77 | | - return None |
78 | | - |
79 | | - if orig_source_type not in { |
80 | | - os.PathLike, |
81 | | - pathlib.Path, |
82 | | - pathlib.PurePath, |
83 | | - pathlib.PosixPath, |
84 | | - pathlib.PurePosixPath, |
85 | | - pathlib.PureWindowsPath, |
86 | | - }: |
87 | | - return None |
88 | | - |
89 | | - metadata, remaining_annotations = _known_annotated_metadata.collect_known_metadata(annotations) |
90 | | - _known_annotated_metadata.check_metadata(metadata, _known_annotated_metadata.STR_CONSTRAINTS, orig_source_type) |
91 | | - |
92 | | - is_first_arg_byte = source_type_args and source_type_args[0] is bytes |
93 | | - construct_path = pathlib.PurePath if orig_source_type is os.PathLike else orig_source_type |
94 | | - constrained_schema = ( |
95 | | - core_schema.bytes_schema(**metadata) if is_first_arg_byte else core_schema.str_schema(**metadata) |
96 | | - ) |
97 | | - |
98 | | - def path_validator(input_value: str | bytes) -> os.PathLike[Any]: # type: ignore |
99 | | - try: |
100 | | - if is_first_arg_byte: |
101 | | - if isinstance(input_value, bytes): |
102 | | - try: |
103 | | - input_value = input_value.decode() |
104 | | - except UnicodeDecodeError as e: |
105 | | - raise PydanticCustomError('bytes_type', 'Input must be valid bytes') from e |
106 | | - else: |
107 | | - raise PydanticCustomError('bytes_type', 'Input must be bytes') |
108 | | - elif not isinstance(input_value, str): |
109 | | - raise PydanticCustomError('path_type', 'Input is not a valid path') |
110 | | - |
111 | | - return construct_path(input_value) |
112 | | - except TypeError as e: |
113 | | - raise PydanticCustomError('path_type', 'Input is not a valid path') from e |
114 | | - |
115 | | - instance_schema = core_schema.json_or_python_schema( |
116 | | - json_schema=core_schema.no_info_after_validator_function(path_validator, constrained_schema), |
117 | | - python_schema=core_schema.is_instance_schema(orig_source_type), |
118 | | - ) |
119 | | - |
120 | | - strict: bool | None = None |
121 | | - for annotation in annotations: |
122 | | - if isinstance(annotation, Strict): |
123 | | - strict = annotation.strict |
124 | | - |
125 | | - schema = core_schema.lax_or_strict_schema( |
126 | | - lax_schema=core_schema.union_schema( |
127 | | - [ |
128 | | - instance_schema, |
129 | | - core_schema.no_info_after_validator_function(path_validator, constrained_schema), |
130 | | - ], |
131 | | - custom_error_type='path_type', |
132 | | - custom_error_message=f'Input is not a valid path for {orig_source_type}', |
133 | | - strict=True, |
134 | | - ), |
135 | | - strict_schema=instance_schema, |
136 | | - serialization=core_schema.to_string_ser_schema(), |
137 | | - strict=strict, |
138 | | - ) |
139 | | - |
140 | | - return ( |
141 | | - orig_source_type, |
142 | | - [ |
143 | | - InnerSchemaValidator(schema, js_core_schema=constrained_schema, js_schema_update={'format': 'path'}), |
144 | | - *remaining_annotations, |
145 | | - ], |
146 | | - ) |
147 | | - |
148 | | - |
149 | 41 | def deque_validator( |
150 | 42 | input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler, maxlen: None | int |
151 | 43 | ) -> collections.deque[Any]: |
|
0 commit comments