File: models.py

package info (click to toggle)
python-mashumaro 3.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,408 kB
  • sloc: python: 19,981; sh: 16; makefile: 5
file content (212 lines) | stat: -rw-r--r-- 6,519 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import datetime
import ipaddress
from collections.abc import Sequence
from dataclasses import MISSING, dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Union

from typing_extensions import TYPE_CHECKING, Self, TypeAlias

from mashumaro.config import BaseConfig
from mashumaro.core.meta.helpers import iter_all_subclasses
from mashumaro.helper import pass_through
from mashumaro.jsonschema.dialects import DRAFT_2020_12, JSONSchemaDialect

if TYPE_CHECKING:  # pragma: no cover
    from mashumaro.jsonschema.plugins import BasePlugin
else:
    BasePlugin = Any

try:
    from mashumaro.mixins.orjson import (
        DataClassORJSONMixin as DataClassJSONMixin,
    )
except ImportError:  # pragma: no cover
    from mashumaro.mixins.json import DataClassJSONMixin  # type: ignore


# https://github.com/python/mypy/issues/3186
Number: TypeAlias = Union[int, float]

Null = object()


class JSONSchemaInstanceType(Enum):
    NULL = "null"
    BOOLEAN = "boolean"
    OBJECT = "object"
    ARRAY = "array"
    NUMBER = "number"
    STRING = "string"
    INTEGER = "integer"


class JSONSchemaInstanceFormat(Enum):
    pass


class JSONSchemaStringFormat(JSONSchemaInstanceFormat):
    DATETIME = "date-time"
    DATE = "date"
    TIME = "time"
    DURATION = "duration"
    EMAIL = "email"
    IDN_EMAIL = "idn-email"
    HOSTNAME = "hostname"
    IDN_HOSTNAME = "idn-hostname"
    IPV4ADDRESS = "ipv4"
    IPV6ADDRESS = "ipv6"
    URI = "uri"
    URI_REFERENCE = "uri-reference"
    IRI = "iri"
    IRI_REFERENCE = "iri-reference"
    UUID = "uuid"
    URI_TEMPLATE = "uri-template"
    JSON_POINTER = "json-pointer"
    RELATIVE_JSON_POINTER = "relative-json-pointer"
    REGEX = "regex"


class JSONSchemaInstanceFormatExtension(JSONSchemaInstanceFormat):
    TIMEDELTA = "time-delta"
    TIME_ZONE = "time-zone"
    IPV4NETWORK = "ipv4network"
    IPV6NETWORK = "ipv6network"
    IPV4INTERFACE = "ipv4interface"
    IPV6INTERFACE = "ipv6interface"
    DECIMAL = "decimal"
    FRACTION = "fraction"
    BASE64 = "base64"
    PATH = "path"


DATETIME_FORMATS = {
    datetime.datetime: JSONSchemaStringFormat.DATETIME,
    datetime.date: JSONSchemaStringFormat.DATE,
    datetime.time: JSONSchemaStringFormat.TIME,
}


IPADDRESS_FORMATS = {
    ipaddress.IPv4Address: JSONSchemaStringFormat.IPV4ADDRESS,
    ipaddress.IPv6Address: JSONSchemaStringFormat.IPV6ADDRESS,
    ipaddress.IPv4Network: JSONSchemaInstanceFormatExtension.IPV4NETWORK,
    ipaddress.IPv6Network: JSONSchemaInstanceFormatExtension.IPV6NETWORK,
    ipaddress.IPv4Interface: JSONSchemaInstanceFormatExtension.IPV4INTERFACE,
    ipaddress.IPv6Interface: JSONSchemaInstanceFormatExtension.IPV6INTERFACE,
}


def _deserialize_json_schema_instance_format(
    value: Any,
) -> JSONSchemaInstanceFormat:
    for cls in iter_all_subclasses(JSONSchemaInstanceFormat):
        try:
            return cls(value)
        except (ValueError, TypeError):
            pass
    raise ValueError(value)


@dataclass(unsafe_hash=True)
class JSONSchema(DataClassJSONMixin):
    # Common keywords
    schema: Optional[str] = None
    type: Optional[JSONSchemaInstanceType] = None
    enum: Optional[list[Any]] = None
    const: Optional[Any] = field(default_factory=lambda: MISSING)
    format: Optional[JSONSchemaInstanceFormat] = None
    title: Optional[str] = None
    description: Optional[str] = None
    anyOf: Optional[List["JSONSchema"]] = None
    reference: Optional[str] = None
    definitions: Optional[Dict[str, "JSONSchema"]] = None
    default: Optional[Any] = field(default_factory=lambda: MISSING)
    deprecated: Optional[bool] = None
    examples: Optional[list[Any]] = None
    # Keywords for Objects
    properties: Optional[Dict[str, "JSONSchema"]] = None
    patternProperties: Optional[Dict[str, "JSONSchema"]] = None
    additionalProperties: Union["JSONSchema", bool, None] = None
    propertyNames: Optional["JSONSchema"] = None
    # Keywords for Arrays
    prefixItems: Optional[List["JSONSchema"]] = None
    items: Optional["JSONSchema"] = None
    contains: Optional["JSONSchema"] = None
    # Validation keywords for numeric instances
    multipleOf: Optional[Number] = None
    maximum: Optional[Number] = None
    exclusiveMaximum: Optional[Number] = None
    minimum: Optional[Number] = None
    exclusiveMinimum: Optional[Number] = None
    # Validation keywords for Strings
    maxLength: Optional[int] = None
    minLength: Optional[int] = None
    pattern: Optional[str] = None
    # Validation keywords for Arrays
    maxItems: Optional[int] = None
    minItems: Optional[int] = None
    uniqueItems: Optional[bool] = None
    maxContains: Optional[int] = None
    minContains: Optional[int] = None
    # Validation keywords for Objects
    maxProperties: Optional[int] = None
    minProperties: Optional[int] = None
    required: Optional[list[str]] = None
    dependentRequired: Optional[dict[str, set[str]]] = None

    class Config(BaseConfig):
        omit_none = True
        serialize_by_alias = True
        aliases = {
            "schema": "$schema",
            "reference": "$ref",
            "definitions": "$defs",
        }
        serialization_strategy = {
            int: pass_through,
            float: pass_through,
            Null: pass_through,
            JSONSchemaInstanceFormat: {
                "deserialize": _deserialize_json_schema_instance_format,
            },
        }

    def __pre_serialize__(self) -> Self:
        if self.const is None:
            self.const = Null
        if self.default is None:
            self.default = Null
        return self

    def __post_serialize__(self, d: dict[Any, Any]) -> dict[Any, Any]:
        const = d.get("const")
        if const is MISSING:
            d.pop("const")
        elif const is Null:
            d["const"] = None
        default = d.get("default")
        if default is MISSING:
            d.pop("default")
        elif default is Null:
            d["default"] = None
        return d


@dataclass
class JSONObjectSchema(JSONSchema):
    type: Optional[JSONSchemaInstanceType] = JSONSchemaInstanceType.OBJECT


@dataclass
class JSONArraySchema(JSONSchema):
    type: Optional[JSONSchemaInstanceType] = JSONSchemaInstanceType.ARRAY


@dataclass
class Context:
    dialect: JSONSchemaDialect = DRAFT_2020_12
    definitions: dict[str, JSONSchema] = field(default_factory=dict)
    all_refs: Optional[bool] = None
    ref_prefix: Optional[str] = None
    plugins: Sequence[BasePlugin] = ()