mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-07-27 06:28:50 +00:00
feat: use XDG directory standards
Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
This commit is contained in:
parent
9736f096f6
commit
407c3e3bad
50 changed files with 5611 additions and 508 deletions
|
@ -12,23 +12,12 @@ Type-safe data interchange for Python data classes.
|
|||
|
||||
import dataclasses
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from dataclasses import is_dataclass
|
||||
from typing import Callable, Dict, Optional, Type, TypeVar, Union, overload
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from typing import Annotated as Annotated
|
||||
else:
|
||||
from typing_extensions import Annotated as Annotated
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeAlias as TypeAlias
|
||||
else:
|
||||
from typing_extensions import TypeAlias as TypeAlias
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import dataclass_transform as dataclass_transform
|
||||
else:
|
||||
from typing_extensions import dataclass_transform as dataclass_transform
|
||||
from typing import Annotated as Annotated
|
||||
from typing import TypeAlias as TypeAlias
|
||||
from typing import TypeVar, overload
|
||||
from typing import dataclass_transform as dataclass_transform
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
@ -56,17 +45,17 @@ class CompactDataClass:
|
|||
|
||||
|
||||
@overload
|
||||
def typeannotation(cls: Type[T], /) -> Type[T]: ...
|
||||
def typeannotation(cls: type[T], /) -> type[T]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def typeannotation(cls: None, *, eq: bool = True, order: bool = False) -> Callable[[Type[T]], Type[T]]: ...
|
||||
def typeannotation(cls: None, *, eq: bool = True, order: bool = False) -> Callable[[type[T]], type[T]]: ...
|
||||
|
||||
|
||||
@dataclass_transform(eq_default=True, order_default=False)
|
||||
def typeannotation(
|
||||
cls: Optional[Type[T]] = None, *, eq: bool = True, order: bool = False
|
||||
) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
|
||||
cls: type[T] | None = None, *, eq: bool = True, order: bool = False
|
||||
) -> type[T] | Callable[[type[T]], type[T]]:
|
||||
"""
|
||||
Returns the same class as was passed in, with dunder methods added based on the fields defined in the class.
|
||||
|
||||
|
@ -76,7 +65,7 @@ def typeannotation(
|
|||
:returns: A data-class type, or a wrapper for data-class types.
|
||||
"""
|
||||
|
||||
def wrap(cls: Type[T]) -> Type[T]:
|
||||
def wrap(cls: type[T]) -> type[T]:
|
||||
# mypy fails to equate bound-y functions (first argument interpreted as
|
||||
# the bound object) with class methods, hence the `ignore` directive.
|
||||
cls.__repr__ = _compact_dataclass_repr # type: ignore[method-assign]
|
||||
|
@ -179,41 +168,41 @@ class SpecialConversion:
|
|||
"Indicates that the annotated type is subject to custom conversion rules."
|
||||
|
||||
|
||||
int8: TypeAlias = Annotated[int, Signed(True), Storage(1), IntegerRange(-128, 127)]
|
||||
int16: TypeAlias = Annotated[int, Signed(True), Storage(2), IntegerRange(-32768, 32767)]
|
||||
int32: TypeAlias = Annotated[
|
||||
type int8 = Annotated[int, Signed(True), Storage(1), IntegerRange(-128, 127)]
|
||||
type int16 = Annotated[int, Signed(True), Storage(2), IntegerRange(-32768, 32767)]
|
||||
type int32 = Annotated[
|
||||
int,
|
||||
Signed(True),
|
||||
Storage(4),
|
||||
IntegerRange(-2147483648, 2147483647),
|
||||
]
|
||||
int64: TypeAlias = Annotated[
|
||||
type int64 = Annotated[
|
||||
int,
|
||||
Signed(True),
|
||||
Storage(8),
|
||||
IntegerRange(-9223372036854775808, 9223372036854775807),
|
||||
]
|
||||
|
||||
uint8: TypeAlias = Annotated[int, Signed(False), Storage(1), IntegerRange(0, 255)]
|
||||
uint16: TypeAlias = Annotated[int, Signed(False), Storage(2), IntegerRange(0, 65535)]
|
||||
uint32: TypeAlias = Annotated[
|
||||
type uint8 = Annotated[int, Signed(False), Storage(1), IntegerRange(0, 255)]
|
||||
type uint16 = Annotated[int, Signed(False), Storage(2), IntegerRange(0, 65535)]
|
||||
type uint32 = Annotated[
|
||||
int,
|
||||
Signed(False),
|
||||
Storage(4),
|
||||
IntegerRange(0, 4294967295),
|
||||
]
|
||||
uint64: TypeAlias = Annotated[
|
||||
type uint64 = Annotated[
|
||||
int,
|
||||
Signed(False),
|
||||
Storage(8),
|
||||
IntegerRange(0, 18446744073709551615),
|
||||
]
|
||||
|
||||
float32: TypeAlias = Annotated[float, Storage(4)]
|
||||
float64: TypeAlias = Annotated[float, Storage(8)]
|
||||
type float32 = Annotated[float, Storage(4)]
|
||||
type float64 = Annotated[float, Storage(8)]
|
||||
|
||||
# maps globals of type Annotated[T, ...] defined in this module to their string names
|
||||
_auxiliary_types: Dict[object, str] = {}
|
||||
_auxiliary_types: dict[object, str] = {}
|
||||
module = sys.modules[__name__]
|
||||
for var in dir(module):
|
||||
typ = getattr(module, var)
|
||||
|
@ -222,7 +211,7 @@ for var in dir(module):
|
|||
_auxiliary_types[typ] = var
|
||||
|
||||
|
||||
def get_auxiliary_format(data_type: object) -> Optional[str]:
|
||||
def get_auxiliary_format(data_type: object) -> str | None:
|
||||
"Returns the JSON format string corresponding to an auxiliary type."
|
||||
|
||||
return _auxiliary_types.get(data_type)
|
||||
|
|
|
@ -12,12 +12,11 @@ import enum
|
|||
import ipaddress
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import typing
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Type, TypeVar, Union
|
||||
from typing import Any, Literal, TypeVar, Union
|
||||
|
||||
from .auxiliary import (
|
||||
Alias,
|
||||
|
@ -40,57 +39,57 @@ T = TypeVar("T")
|
|||
|
||||
@dataclass
|
||||
class JsonSchemaNode:
|
||||
title: Optional[str]
|
||||
description: Optional[str]
|
||||
title: str | None
|
||||
description: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaType(JsonSchemaNode):
|
||||
type: str
|
||||
format: Optional[str]
|
||||
format: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaBoolean(JsonSchemaType):
|
||||
type: Literal["boolean"]
|
||||
const: Optional[bool]
|
||||
default: Optional[bool]
|
||||
examples: Optional[List[bool]]
|
||||
const: bool | None
|
||||
default: bool | None
|
||||
examples: list[bool] | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaInteger(JsonSchemaType):
|
||||
type: Literal["integer"]
|
||||
const: Optional[int]
|
||||
default: Optional[int]
|
||||
examples: Optional[List[int]]
|
||||
enum: Optional[List[int]]
|
||||
minimum: Optional[int]
|
||||
maximum: Optional[int]
|
||||
const: int | None
|
||||
default: int | None
|
||||
examples: list[int] | None
|
||||
enum: list[int] | None
|
||||
minimum: int | None
|
||||
maximum: int | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaNumber(JsonSchemaType):
|
||||
type: Literal["number"]
|
||||
const: Optional[float]
|
||||
default: Optional[float]
|
||||
examples: Optional[List[float]]
|
||||
minimum: Optional[float]
|
||||
maximum: Optional[float]
|
||||
exclusiveMinimum: Optional[float]
|
||||
exclusiveMaximum: Optional[float]
|
||||
multipleOf: Optional[float]
|
||||
const: float | None
|
||||
default: float | None
|
||||
examples: list[float] | None
|
||||
minimum: float | None
|
||||
maximum: float | None
|
||||
exclusiveMinimum: float | None
|
||||
exclusiveMaximum: float | None
|
||||
multipleOf: float | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaString(JsonSchemaType):
|
||||
type: Literal["string"]
|
||||
const: Optional[str]
|
||||
default: Optional[str]
|
||||
examples: Optional[List[str]]
|
||||
enum: Optional[List[str]]
|
||||
minLength: Optional[int]
|
||||
maxLength: Optional[int]
|
||||
const: str | None
|
||||
default: str | None
|
||||
examples: list[str] | None
|
||||
enum: list[str] | None
|
||||
minLength: int | None
|
||||
maxLength: int | None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -102,9 +101,9 @@ class JsonSchemaArray(JsonSchemaType):
|
|||
@dataclass
|
||||
class JsonSchemaObject(JsonSchemaType):
|
||||
type: Literal["object"]
|
||||
properties: Optional[Dict[str, "JsonSchemaAny"]]
|
||||
additionalProperties: Optional[bool]
|
||||
required: Optional[List[str]]
|
||||
properties: dict[str, "JsonSchemaAny"] | None
|
||||
additionalProperties: bool | None
|
||||
required: list[str] | None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -114,24 +113,24 @@ class JsonSchemaRef(JsonSchemaNode):
|
|||
|
||||
@dataclass
|
||||
class JsonSchemaAllOf(JsonSchemaNode):
|
||||
allOf: List["JsonSchemaAny"]
|
||||
allOf: list["JsonSchemaAny"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaAnyOf(JsonSchemaNode):
|
||||
anyOf: List["JsonSchemaAny"]
|
||||
anyOf: list["JsonSchemaAny"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Discriminator:
|
||||
propertyName: str
|
||||
mapping: Dict[str, str]
|
||||
mapping: dict[str, str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonSchemaOneOf(JsonSchemaNode):
|
||||
oneOf: List["JsonSchemaAny"]
|
||||
discriminator: Optional[Discriminator]
|
||||
oneOf: list["JsonSchemaAny"]
|
||||
discriminator: Discriminator | None
|
||||
|
||||
|
||||
JsonSchemaAny = Union[
|
||||
|
@ -149,10 +148,10 @@ JsonSchemaAny = Union[
|
|||
@dataclass
|
||||
class JsonSchemaTopLevelObject(JsonSchemaObject):
|
||||
schema: Annotated[str, Alias("$schema")]
|
||||
definitions: Optional[Dict[str, JsonSchemaAny]]
|
||||
definitions: dict[str, JsonSchemaAny] | None
|
||||
|
||||
|
||||
def integer_range_to_type(min_value: float, max_value: float) -> type:
|
||||
def integer_range_to_type(min_value: float, max_value: float) -> Any:
|
||||
if min_value >= -(2**15) and max_value < 2**15:
|
||||
return int16
|
||||
elif min_value >= -(2**31) and max_value < 2**31:
|
||||
|
@ -173,11 +172,11 @@ def enum_safe_name(name: str) -> str:
|
|||
def enum_values_to_type(
|
||||
module: types.ModuleType,
|
||||
name: str,
|
||||
values: Dict[str, Any],
|
||||
title: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
) -> Type[enum.Enum]:
|
||||
enum_class: Type[enum.Enum] = enum.Enum(name, values) # type: ignore
|
||||
values: dict[str, Any],
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
) -> type[enum.Enum]:
|
||||
enum_class: type[enum.Enum] = enum.Enum(name, values) # type: ignore
|
||||
|
||||
# assign the newly created type to the same module where the defining class is
|
||||
enum_class.__module__ = module.__name__
|
||||
|
@ -330,7 +329,7 @@ def node_to_typedef(module: types.ModuleType, context: str, node: JsonSchemaNode
|
|||
type_def = node_to_typedef(module, context, node.items)
|
||||
if type_def.default is not dataclasses.MISSING:
|
||||
raise TypeError("disallowed: `default` for array element type")
|
||||
list_type = List[(type_def.type,)] # type: ignore
|
||||
list_type = list[(type_def.type,)] # type: ignore
|
||||
return TypeDef(list_type, dataclasses.MISSING)
|
||||
|
||||
elif isinstance(node, JsonSchemaObject):
|
||||
|
@ -344,8 +343,8 @@ def node_to_typedef(module: types.ModuleType, context: str, node: JsonSchemaNode
|
|||
|
||||
class_name = context
|
||||
|
||||
fields: List[Tuple[str, Any, dataclasses.Field]] = []
|
||||
params: Dict[str, DocstringParam] = {}
|
||||
fields: list[tuple[str, Any, dataclasses.Field]] = []
|
||||
params: dict[str, DocstringParam] = {}
|
||||
for prop_name, prop_node in node.properties.items():
|
||||
type_def = node_to_typedef(module, f"{class_name}__{prop_name}", prop_node)
|
||||
if prop_name in required:
|
||||
|
@ -358,10 +357,7 @@ def node_to_typedef(module: types.ModuleType, context: str, node: JsonSchemaNode
|
|||
params[prop_name] = DocstringParam(prop_name, prop_desc)
|
||||
|
||||
fields.sort(key=lambda t: t[2].default is not dataclasses.MISSING)
|
||||
if sys.version_info >= (3, 12):
|
||||
class_type = dataclasses.make_dataclass(class_name, fields, module=module.__name__)
|
||||
else:
|
||||
class_type = dataclasses.make_dataclass(class_name, fields, namespace={"__module__": module.__name__})
|
||||
class_type = dataclasses.make_dataclass(class_name, fields, module=module.__name__)
|
||||
class_type.__doc__ = str(
|
||||
Docstring(
|
||||
short_description=node.title,
|
||||
|
@ -388,7 +384,7 @@ class SchemaFlatteningOptions:
|
|||
recursive: bool = False
|
||||
|
||||
|
||||
def flatten_schema(schema: Schema, *, options: Optional[SchemaFlatteningOptions] = None) -> Schema:
|
||||
def flatten_schema(schema: Schema, *, options: SchemaFlatteningOptions | None = None) -> Schema:
|
||||
top_node = typing.cast(JsonSchemaTopLevelObject, json_to_object(JsonSchemaTopLevelObject, schema))
|
||||
flattener = SchemaFlattener(options)
|
||||
obj = flattener.flatten(top_node)
|
||||
|
@ -398,7 +394,7 @@ def flatten_schema(schema: Schema, *, options: Optional[SchemaFlatteningOptions]
|
|||
class SchemaFlattener:
|
||||
options: SchemaFlatteningOptions
|
||||
|
||||
def __init__(self, options: Optional[SchemaFlatteningOptions] = None) -> None:
|
||||
def __init__(self, options: SchemaFlatteningOptions | None = None) -> None:
|
||||
self.options = options or SchemaFlatteningOptions()
|
||||
|
||||
def flatten(self, source_node: JsonSchemaObject) -> JsonSchemaObject:
|
||||
|
@ -406,10 +402,10 @@ class SchemaFlattener:
|
|||
return source_node
|
||||
|
||||
source_props = source_node.properties or {}
|
||||
target_props: Dict[str, JsonSchemaAny] = {}
|
||||
target_props: dict[str, JsonSchemaAny] = {}
|
||||
|
||||
source_reqs = source_node.required or []
|
||||
target_reqs: List[str] = []
|
||||
target_reqs: list[str] = []
|
||||
|
||||
for name, prop in source_props.items():
|
||||
if not isinstance(prop, JsonSchemaObject):
|
||||
|
|
|
@ -10,7 +10,7 @@ Type-safe data interchange for Python data classes.
|
|||
:see: https://github.com/hunyadi/strong_typing
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Union
|
||||
from typing import Union
|
||||
|
||||
|
||||
class JsonObject:
|
||||
|
@ -28,8 +28,8 @@ JsonType = Union[
|
|||
int,
|
||||
float,
|
||||
str,
|
||||
Dict[str, "JsonType"],
|
||||
List["JsonType"],
|
||||
dict[str, "JsonType"],
|
||||
list["JsonType"],
|
||||
]
|
||||
|
||||
# a JSON type that cannot contain `null` values
|
||||
|
@ -38,9 +38,9 @@ StrictJsonType = Union[
|
|||
int,
|
||||
float,
|
||||
str,
|
||||
Dict[str, "StrictJsonType"],
|
||||
List["StrictJsonType"],
|
||||
dict[str, "StrictJsonType"],
|
||||
list["StrictJsonType"],
|
||||
]
|
||||
|
||||
# a meta-type that captures the object type in a JSON schema
|
||||
Schema = Dict[str, JsonType]
|
||||
Schema = dict[str, JsonType]
|
||||
|
|
|
@ -20,19 +20,14 @@ import ipaddress
|
|||
import sys
|
||||
import typing
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from types import ModuleType
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
@ -70,7 +65,7 @@ V = TypeVar("V")
|
|||
class Deserializer(abc.ABC, Generic[T]):
|
||||
"Parses a JSON value into a Python type."
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
"""
|
||||
Creates auxiliary parsers that this parser is depending on.
|
||||
|
||||
|
@ -203,19 +198,19 @@ class IPv6Deserializer(Deserializer[ipaddress.IPv6Address]):
|
|||
return ipaddress.IPv6Address(data)
|
||||
|
||||
|
||||
class ListDeserializer(Deserializer[List[T]]):
|
||||
class ListDeserializer(Deserializer[list[T]]):
|
||||
"Recursively de-serializes a JSON array into a Python `list`."
|
||||
|
||||
item_type: Type[T]
|
||||
item_type: type[T]
|
||||
item_parser: Deserializer
|
||||
|
||||
def __init__(self, item_type: Type[T]) -> None:
|
||||
def __init__(self, item_type: type[T]) -> None:
|
||||
self.item_type = item_type
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
self.item_parser = _get_deserializer(self.item_type, context)
|
||||
|
||||
def parse(self, data: JsonType) -> List[T]:
|
||||
def parse(self, data: JsonType) -> list[T]:
|
||||
if not isinstance(data, list):
|
||||
type_name = python_type_to_str(self.item_type)
|
||||
raise JsonTypeError(f"type `List[{type_name}]` expects JSON `array` data but instead received: {data}")
|
||||
|
@ -223,19 +218,19 @@ class ListDeserializer(Deserializer[List[T]]):
|
|||
return [self.item_parser.parse(item) for item in data]
|
||||
|
||||
|
||||
class DictDeserializer(Deserializer[Dict[K, V]]):
|
||||
class DictDeserializer(Deserializer[dict[K, V]]):
|
||||
"Recursively de-serializes a JSON object into a Python `dict`."
|
||||
|
||||
key_type: Type[K]
|
||||
value_type: Type[V]
|
||||
key_type: type[K]
|
||||
value_type: type[V]
|
||||
value_parser: Deserializer[V]
|
||||
|
||||
def __init__(self, key_type: Type[K], value_type: Type[V]) -> None:
|
||||
def __init__(self, key_type: type[K], value_type: type[V]) -> None:
|
||||
self.key_type = key_type
|
||||
self.value_type = value_type
|
||||
self._check_key_type()
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
self.value_parser = _get_deserializer(self.value_type, context)
|
||||
|
||||
def _check_key_type(self) -> None:
|
||||
|
@ -264,48 +259,48 @@ class DictDeserializer(Deserializer[Dict[K, V]]):
|
|||
value_type_name = python_type_to_str(self.value_type)
|
||||
return f"Dict[{key_type_name}, {value_type_name}]"
|
||||
|
||||
def parse(self, data: JsonType) -> Dict[K, V]:
|
||||
def parse(self, data: JsonType) -> dict[K, V]:
|
||||
if not isinstance(data, dict):
|
||||
raise JsonTypeError(
|
||||
f"`type `{self.container_type}` expects JSON `object` data but instead received: {data}"
|
||||
)
|
||||
|
||||
return dict(
|
||||
(self.key_type(key), self.value_parser.parse(value)) # type: ignore[call-arg]
|
||||
return {
|
||||
self.key_type(key): self.value_parser.parse(value) # type: ignore[call-arg]
|
||||
for key, value in data.items()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class SetDeserializer(Deserializer[Set[T]]):
|
||||
class SetDeserializer(Deserializer[set[T]]):
|
||||
"Recursively de-serializes a JSON list into a Python `set`."
|
||||
|
||||
member_type: Type[T]
|
||||
member_type: type[T]
|
||||
member_parser: Deserializer
|
||||
|
||||
def __init__(self, member_type: Type[T]) -> None:
|
||||
def __init__(self, member_type: type[T]) -> None:
|
||||
self.member_type = member_type
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
self.member_parser = _get_deserializer(self.member_type, context)
|
||||
|
||||
def parse(self, data: JsonType) -> Set[T]:
|
||||
def parse(self, data: JsonType) -> set[T]:
|
||||
if not isinstance(data, list):
|
||||
type_name = python_type_to_str(self.member_type)
|
||||
raise JsonTypeError(f"type `Set[{type_name}]` expects JSON `array` data but instead received: {data}")
|
||||
|
||||
return set(self.member_parser.parse(item) for item in data)
|
||||
return {self.member_parser.parse(item) for item in data}
|
||||
|
||||
|
||||
class TupleDeserializer(Deserializer[Tuple[Any, ...]]):
|
||||
class TupleDeserializer(Deserializer[tuple[Any, ...]]):
|
||||
"Recursively de-serializes a JSON list into a Python `tuple`."
|
||||
|
||||
item_types: Tuple[Type[Any], ...]
|
||||
item_parsers: Tuple[Deserializer[Any], ...]
|
||||
item_types: tuple[type[Any], ...]
|
||||
item_parsers: tuple[Deserializer[Any], ...]
|
||||
|
||||
def __init__(self, item_types: Tuple[Type[Any], ...]) -> None:
|
||||
def __init__(self, item_types: tuple[type[Any], ...]) -> None:
|
||||
self.item_types = item_types
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
self.item_parsers = tuple(_get_deserializer(item_type, context) for item_type in self.item_types)
|
||||
|
||||
@property
|
||||
|
@ -313,7 +308,7 @@ class TupleDeserializer(Deserializer[Tuple[Any, ...]]):
|
|||
type_names = ", ".join(python_type_to_str(item_type) for item_type in self.item_types)
|
||||
return f"Tuple[{type_names}]"
|
||||
|
||||
def parse(self, data: JsonType) -> Tuple[Any, ...]:
|
||||
def parse(self, data: JsonType) -> tuple[Any, ...]:
|
||||
if not isinstance(data, list) or len(data) != len(self.item_parsers):
|
||||
if not isinstance(data, list):
|
||||
raise JsonTypeError(
|
||||
|
@ -331,13 +326,13 @@ class TupleDeserializer(Deserializer[Tuple[Any, ...]]):
|
|||
class UnionDeserializer(Deserializer):
|
||||
"De-serializes a JSON value (of any type) into a Python union type."
|
||||
|
||||
member_types: Tuple[type, ...]
|
||||
member_parsers: Tuple[Deserializer, ...]
|
||||
member_types: tuple[type, ...]
|
||||
member_parsers: tuple[Deserializer, ...]
|
||||
|
||||
def __init__(self, member_types: Tuple[type, ...]) -> None:
|
||||
def __init__(self, member_types: tuple[type, ...]) -> None:
|
||||
self.member_types = member_types
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
self.member_parsers = tuple(_get_deserializer(member_type, context) for member_type in self.member_types)
|
||||
|
||||
def parse(self, data: JsonType) -> Any:
|
||||
|
@ -354,15 +349,15 @@ class UnionDeserializer(Deserializer):
|
|||
raise JsonKeyError(f"type `Union[{type_names}]` could not be instantiated from: {data}")
|
||||
|
||||
|
||||
def get_literal_properties(typ: type) -> Set[str]:
|
||||
def get_literal_properties(typ: type) -> set[str]:
|
||||
"Returns the names of all properties in a class that are of a literal type."
|
||||
|
||||
return set(
|
||||
return {
|
||||
property_name for property_name, property_type in get_class_properties(typ) if is_type_literal(property_type)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def get_discriminating_properties(types: Tuple[type, ...]) -> Set[str]:
|
||||
def get_discriminating_properties(types: tuple[type, ...]) -> set[str]:
|
||||
"Returns a set of properties with literal type that are common across all specified classes."
|
||||
|
||||
if not types or not all(isinstance(typ, type) for typ in types):
|
||||
|
@ -378,15 +373,15 @@ def get_discriminating_properties(types: Tuple[type, ...]) -> Set[str]:
|
|||
class TaggedUnionDeserializer(Deserializer):
|
||||
"De-serializes a JSON value with one or more disambiguating properties into a Python union type."
|
||||
|
||||
member_types: Tuple[type, ...]
|
||||
disambiguating_properties: Set[str]
|
||||
member_parsers: Dict[Tuple[str, Any], Deserializer]
|
||||
member_types: tuple[type, ...]
|
||||
disambiguating_properties: set[str]
|
||||
member_parsers: dict[tuple[str, Any], Deserializer]
|
||||
|
||||
def __init__(self, member_types: Tuple[type, ...]) -> None:
|
||||
def __init__(self, member_types: tuple[type, ...]) -> None:
|
||||
self.member_types = member_types
|
||||
self.disambiguating_properties = get_discriminating_properties(member_types)
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
self.member_parsers = {}
|
||||
for member_type in self.member_types:
|
||||
for property_name in self.disambiguating_properties:
|
||||
|
@ -435,13 +430,13 @@ class TaggedUnionDeserializer(Deserializer):
|
|||
class LiteralDeserializer(Deserializer):
|
||||
"De-serializes a JSON value into a Python literal type."
|
||||
|
||||
values: Tuple[Any, ...]
|
||||
values: tuple[Any, ...]
|
||||
parser: Deserializer
|
||||
|
||||
def __init__(self, values: Tuple[Any, ...]) -> None:
|
||||
def __init__(self, values: tuple[Any, ...]) -> None:
|
||||
self.values = values
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
literal_type_tuple = tuple(type(value) for value in self.values)
|
||||
literal_type_set = set(literal_type_tuple)
|
||||
if len(literal_type_set) != 1:
|
||||
|
@ -464,9 +459,9 @@ class LiteralDeserializer(Deserializer):
|
|||
class EnumDeserializer(Deserializer[E]):
|
||||
"Returns an enumeration instance based on the enumeration value read from a JSON value."
|
||||
|
||||
enum_type: Type[E]
|
||||
enum_type: type[E]
|
||||
|
||||
def __init__(self, enum_type: Type[E]) -> None:
|
||||
def __init__(self, enum_type: type[E]) -> None:
|
||||
self.enum_type = enum_type
|
||||
|
||||
def parse(self, data: JsonType) -> E:
|
||||
|
@ -504,13 +499,13 @@ class FieldDeserializer(abc.ABC, Generic[T, R]):
|
|||
self.parser = parser
|
||||
|
||||
@abc.abstractmethod
|
||||
def parse_field(self, data: Dict[str, JsonType]) -> R: ...
|
||||
def parse_field(self, data: dict[str, JsonType]) -> R: ...
|
||||
|
||||
|
||||
class RequiredFieldDeserializer(FieldDeserializer[T, T]):
|
||||
"Deserializes a JSON property into a mandatory Python object field."
|
||||
|
||||
def parse_field(self, data: Dict[str, JsonType]) -> T:
|
||||
def parse_field(self, data: dict[str, JsonType]) -> T:
|
||||
if self.property_name not in data:
|
||||
raise JsonKeyError(f"missing required property `{self.property_name}` from JSON object: {data}")
|
||||
|
||||
|
@ -520,7 +515,7 @@ class RequiredFieldDeserializer(FieldDeserializer[T, T]):
|
|||
class OptionalFieldDeserializer(FieldDeserializer[T, Optional[T]]):
|
||||
"Deserializes a JSON property into an optional Python object field with a default value of `None`."
|
||||
|
||||
def parse_field(self, data: Dict[str, JsonType]) -> Optional[T]:
|
||||
def parse_field(self, data: dict[str, JsonType]) -> T | None:
|
||||
value = data.get(self.property_name)
|
||||
if value is not None:
|
||||
return self.parser.parse(value)
|
||||
|
@ -543,7 +538,7 @@ class DefaultFieldDeserializer(FieldDeserializer[T, T]):
|
|||
super().__init__(property_name, field_name, parser)
|
||||
self.default_value = default_value
|
||||
|
||||
def parse_field(self, data: Dict[str, JsonType]) -> T:
|
||||
def parse_field(self, data: dict[str, JsonType]) -> T:
|
||||
value = data.get(self.property_name)
|
||||
if value is not None:
|
||||
return self.parser.parse(value)
|
||||
|
@ -566,7 +561,7 @@ class DefaultFactoryFieldDeserializer(FieldDeserializer[T, T]):
|
|||
super().__init__(property_name, field_name, parser)
|
||||
self.default_factory = default_factory
|
||||
|
||||
def parse_field(self, data: Dict[str, JsonType]) -> T:
|
||||
def parse_field(self, data: dict[str, JsonType]) -> T:
|
||||
value = data.get(self.property_name)
|
||||
if value is not None:
|
||||
return self.parser.parse(value)
|
||||
|
@ -578,22 +573,22 @@ class ClassDeserializer(Deserializer[T]):
|
|||
"Base class for de-serializing class-like types such as data classes, named tuples and regular classes."
|
||||
|
||||
class_type: type
|
||||
property_parsers: List[FieldDeserializer]
|
||||
property_fields: Set[str]
|
||||
property_parsers: list[FieldDeserializer]
|
||||
property_fields: set[str]
|
||||
|
||||
def __init__(self, class_type: Type[T]) -> None:
|
||||
def __init__(self, class_type: type[T]) -> None:
|
||||
self.class_type = class_type
|
||||
|
||||
def assign(self, property_parsers: List[FieldDeserializer]) -> None:
|
||||
def assign(self, property_parsers: list[FieldDeserializer]) -> None:
|
||||
self.property_parsers = property_parsers
|
||||
self.property_fields = set(property_parser.property_name for property_parser in property_parsers)
|
||||
self.property_fields = {property_parser.property_name for property_parser in property_parsers}
|
||||
|
||||
def parse(self, data: JsonType) -> T:
|
||||
if not isinstance(data, dict):
|
||||
type_name = python_type_to_str(self.class_type)
|
||||
raise JsonTypeError(f"`type `{type_name}` expects JSON `object` data but instead received: {data}")
|
||||
|
||||
object_data: Dict[str, JsonType] = typing.cast(Dict[str, JsonType], data)
|
||||
object_data: dict[str, JsonType] = typing.cast(dict[str, JsonType], data)
|
||||
|
||||
field_values = {}
|
||||
for property_parser in self.property_parsers:
|
||||
|
@ -619,8 +614,8 @@ class ClassDeserializer(Deserializer[T]):
|
|||
class NamedTupleDeserializer(ClassDeserializer[NamedTuple]):
|
||||
"De-serializes a named tuple from a JSON `object`."
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
property_parsers: List[FieldDeserializer] = [
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
property_parsers: list[FieldDeserializer] = [
|
||||
RequiredFieldDeserializer(field_name, field_name, _get_deserializer(field_type, context))
|
||||
for field_name, field_type in get_resolved_hints(self.class_type).items()
|
||||
]
|
||||
|
@ -634,13 +629,13 @@ class NamedTupleDeserializer(ClassDeserializer[NamedTuple]):
|
|||
class DataclassDeserializer(ClassDeserializer[T]):
|
||||
"De-serializes a data class from a JSON `object`."
|
||||
|
||||
def __init__(self, class_type: Type[T]) -> None:
|
||||
def __init__(self, class_type: type[T]) -> None:
|
||||
if not dataclasses.is_dataclass(class_type):
|
||||
raise TypeError("expected: data-class type")
|
||||
super().__init__(class_type) # type: ignore[arg-type]
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
property_parsers: List[FieldDeserializer] = []
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
property_parsers: list[FieldDeserializer] = []
|
||||
resolved_hints = get_resolved_hints(self.class_type)
|
||||
for field in dataclasses.fields(self.class_type):
|
||||
field_type = resolved_hints[field.name]
|
||||
|
@ -651,7 +646,7 @@ class DataclassDeserializer(ClassDeserializer[T]):
|
|||
has_default_factory = field.default_factory is not dataclasses.MISSING
|
||||
|
||||
if is_optional:
|
||||
required_type: Type[T] = unwrap_optional_type(field_type)
|
||||
required_type: type[T] = unwrap_optional_type(field_type)
|
||||
else:
|
||||
required_type = field_type
|
||||
|
||||
|
@ -691,15 +686,15 @@ class FrozenDataclassDeserializer(DataclassDeserializer[T]):
|
|||
class TypedClassDeserializer(ClassDeserializer[T]):
|
||||
"De-serializes a class with type annotations from a JSON `object` by iterating over class properties."
|
||||
|
||||
def build(self, context: Optional[ModuleType]) -> None:
|
||||
property_parsers: List[FieldDeserializer] = []
|
||||
def build(self, context: ModuleType | None) -> None:
|
||||
property_parsers: list[FieldDeserializer] = []
|
||||
for field_name, field_type in get_resolved_hints(self.class_type).items():
|
||||
property_name = python_field_to_json_property(field_name, field_type)
|
||||
|
||||
is_optional = is_type_optional(field_type)
|
||||
|
||||
if is_optional:
|
||||
required_type: Type[T] = unwrap_optional_type(field_type)
|
||||
required_type: type[T] = unwrap_optional_type(field_type)
|
||||
else:
|
||||
required_type = field_type
|
||||
|
||||
|
@ -715,7 +710,7 @@ class TypedClassDeserializer(ClassDeserializer[T]):
|
|||
super().assign(property_parsers)
|
||||
|
||||
|
||||
def create_deserializer(typ: TypeLike, context: Optional[ModuleType] = None) -> Deserializer:
|
||||
def create_deserializer(typ: TypeLike, context: ModuleType | None = None) -> Deserializer:
|
||||
"""
|
||||
Creates a de-serializer engine to produce a Python object from an object obtained from a JSON string.
|
||||
|
||||
|
@ -741,15 +736,15 @@ def create_deserializer(typ: TypeLike, context: Optional[ModuleType] = None) ->
|
|||
return _get_deserializer(typ, context)
|
||||
|
||||
|
||||
_CACHE: Dict[Tuple[str, str], Deserializer] = {}
|
||||
_CACHE: dict[tuple[str, str], Deserializer] = {}
|
||||
|
||||
|
||||
def _get_deserializer(typ: TypeLike, context: Optional[ModuleType]) -> Deserializer:
|
||||
def _get_deserializer(typ: TypeLike, context: ModuleType | None) -> Deserializer:
|
||||
"Creates or re-uses a de-serializer engine to parse an object obtained from a JSON string."
|
||||
|
||||
cache_key = None
|
||||
|
||||
if isinstance(typ, (str, typing.ForwardRef)):
|
||||
if isinstance(typ, str | typing.ForwardRef):
|
||||
if context is None:
|
||||
raise TypeError(f"missing context for evaluating type: {typ}")
|
||||
|
||||
|
|
|
@ -15,17 +15,12 @@ import collections.abc
|
|||
import dataclasses
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import typing
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from io import StringIO
|
||||
from typing import Any, Callable, Dict, Optional, Protocol, Type, TypeVar
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeGuard
|
||||
else:
|
||||
from typing_extensions import TypeGuard
|
||||
from typing import Any, Protocol, TypeGuard, TypeVar
|
||||
|
||||
from .inspection import (
|
||||
DataclassInstance,
|
||||
|
@ -110,14 +105,14 @@ class Docstring:
|
|||
:param returns: The returns declaration extracted from a docstring.
|
||||
"""
|
||||
|
||||
short_description: Optional[str] = None
|
||||
long_description: Optional[str] = None
|
||||
params: Dict[str, DocstringParam] = dataclasses.field(default_factory=dict)
|
||||
returns: Optional[DocstringReturns] = None
|
||||
raises: Dict[str, DocstringRaises] = dataclasses.field(default_factory=dict)
|
||||
short_description: str | None = None
|
||||
long_description: str | None = None
|
||||
params: dict[str, DocstringParam] = dataclasses.field(default_factory=dict)
|
||||
returns: DocstringReturns | None = None
|
||||
raises: dict[str, DocstringRaises] = dataclasses.field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def full_description(self) -> Optional[str]:
|
||||
def full_description(self) -> str | None:
|
||||
if self.short_description and self.long_description:
|
||||
return f"{self.short_description}\n\n{self.long_description}"
|
||||
elif self.short_description:
|
||||
|
@ -158,18 +153,18 @@ class Docstring:
|
|||
return s
|
||||
|
||||
|
||||
def is_exception(member: object) -> TypeGuard[Type[BaseException]]:
|
||||
def is_exception(member: object) -> TypeGuard[type[BaseException]]:
|
||||
return isinstance(member, type) and issubclass(member, BaseException)
|
||||
|
||||
|
||||
def get_exceptions(module: types.ModuleType) -> Dict[str, Type[BaseException]]:
|
||||
def get_exceptions(module: types.ModuleType) -> dict[str, type[BaseException]]:
|
||||
"Returns all exception classes declared in a module."
|
||||
|
||||
return {name: class_type for name, class_type in inspect.getmembers(module, is_exception)}
|
||||
return dict(inspect.getmembers(module, is_exception))
|
||||
|
||||
|
||||
class SupportsDoc(Protocol):
|
||||
__doc__: Optional[str]
|
||||
__doc__: str | None
|
||||
|
||||
|
||||
def _maybe_unwrap_async_iterator(t):
|
||||
|
@ -213,7 +208,7 @@ def parse_type(typ: SupportsDoc) -> Docstring:
|
|||
# assign exception types
|
||||
defining_module = inspect.getmodule(typ)
|
||||
if defining_module:
|
||||
context: Dict[str, type] = {}
|
||||
context: dict[str, type] = {}
|
||||
context.update(get_exceptions(builtins))
|
||||
context.update(get_exceptions(defining_module))
|
||||
for exc_name, exc in docstring.raises.items():
|
||||
|
@ -262,8 +257,8 @@ def parse_text(text: str) -> Docstring:
|
|||
else:
|
||||
long_description = None
|
||||
|
||||
params: Dict[str, DocstringParam] = {}
|
||||
raises: Dict[str, DocstringRaises] = {}
|
||||
params: dict[str, DocstringParam] = {}
|
||||
raises: dict[str, DocstringRaises] = {}
|
||||
returns = None
|
||||
for match in re.finditer(r"(^:.*?)(?=^:|\Z)", meta_chunk, flags=re.DOTALL | re.MULTILINE):
|
||||
chunk = match.group(0)
|
||||
|
@ -325,7 +320,7 @@ def has_docstring(typ: SupportsDoc) -> bool:
|
|||
return bool(typ.__doc__)
|
||||
|
||||
|
||||
def get_docstring(typ: SupportsDoc) -> Optional[str]:
|
||||
def get_docstring(typ: SupportsDoc) -> str | None:
|
||||
if typ.__doc__ is None:
|
||||
return None
|
||||
|
||||
|
@ -348,7 +343,7 @@ def check_docstring(typ: SupportsDoc, docstring: Docstring, strict: bool = False
|
|||
check_function_docstring(typ, docstring, strict)
|
||||
|
||||
|
||||
def check_dataclass_docstring(typ: Type[DataclassInstance], docstring: Docstring, strict: bool = False) -> None:
|
||||
def check_dataclass_docstring(typ: type[DataclassInstance], docstring: Docstring, strict: bool = False) -> None:
|
||||
"""
|
||||
Verifies the doc-string of a data-class type.
|
||||
|
||||
|
|
|
@ -22,34 +22,19 @@ import sys
|
|||
import types
|
||||
import typing
|
||||
import uuid
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import (
|
||||
Annotated,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Protocol,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeGuard,
|
||||
TypeVar,
|
||||
Union,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from typing import Annotated
|
||||
else:
|
||||
from typing_extensions import Annotated
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeGuard
|
||||
else:
|
||||
from typing_extensions import TypeGuard
|
||||
|
||||
S = TypeVar("S")
|
||||
T = TypeVar("T")
|
||||
K = TypeVar("K")
|
||||
|
@ -80,28 +65,20 @@ def _is_type_like(data_type: object) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
TypeLike = Union[type, types.GenericAlias, typing.ForwardRef, Any]
|
||||
TypeLike = Union[type, types.GenericAlias, typing.ForwardRef, Any]
|
||||
|
||||
def is_type_like(
|
||||
data_type: object,
|
||||
) -> TypeGuard[TypeLike]:
|
||||
"""
|
||||
Checks if the object is a type or type-like object (e.g. generic type).
|
||||
|
||||
:param data_type: The object to validate.
|
||||
:returns: True if the object is a type or type-like object.
|
||||
"""
|
||||
def is_type_like(
|
||||
data_type: object,
|
||||
) -> TypeGuard[TypeLike]:
|
||||
"""
|
||||
Checks if the object is a type or type-like object (e.g. generic type).
|
||||
|
||||
return _is_type_like(data_type)
|
||||
:param data_type: The object to validate.
|
||||
:returns: True if the object is a type or type-like object.
|
||||
"""
|
||||
|
||||
else:
|
||||
TypeLike = object
|
||||
|
||||
def is_type_like(
|
||||
data_type: object,
|
||||
) -> bool:
|
||||
return _is_type_like(data_type)
|
||||
return _is_type_like(data_type)
|
||||
|
||||
|
||||
def evaluate_member_type(typ: Any, cls: type) -> Any:
|
||||
|
@ -129,20 +106,17 @@ def evaluate_type(typ: Any, module: types.ModuleType) -> Any:
|
|||
# evaluate data-class field whose type annotation is a string
|
||||
return eval(typ, module.__dict__, locals())
|
||||
if isinstance(typ, typing.ForwardRef):
|
||||
if sys.version_info >= (3, 9):
|
||||
return typ._evaluate(module.__dict__, locals(), recursive_guard=frozenset())
|
||||
else:
|
||||
return typ._evaluate(module.__dict__, locals())
|
||||
return typ._evaluate(module.__dict__, locals(), recursive_guard=frozenset())
|
||||
else:
|
||||
return typ
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class DataclassInstance(Protocol):
|
||||
__dataclass_fields__: typing.ClassVar[Dict[str, dataclasses.Field]]
|
||||
__dataclass_fields__: typing.ClassVar[dict[str, dataclasses.Field]]
|
||||
|
||||
|
||||
def is_dataclass_type(typ: Any) -> TypeGuard[Type[DataclassInstance]]:
|
||||
def is_dataclass_type(typ: Any) -> TypeGuard[type[DataclassInstance]]:
|
||||
"True if the argument corresponds to a data class type (but not an instance)."
|
||||
|
||||
typ = unwrap_annotated_type(typ)
|
||||
|
@ -167,14 +141,14 @@ class DataclassField:
|
|||
self.default = default
|
||||
|
||||
|
||||
def dataclass_fields(cls: Type[DataclassInstance]) -> Iterable[DataclassField]:
|
||||
def dataclass_fields(cls: type[DataclassInstance]) -> Iterable[DataclassField]:
|
||||
"Generates the fields of a data-class resolving forward references."
|
||||
|
||||
for field in dataclasses.fields(cls):
|
||||
yield DataclassField(field.name, evaluate_member_type(field.type, cls), field.default)
|
||||
|
||||
|
||||
def dataclass_field_by_name(cls: Type[DataclassInstance], name: str) -> DataclassField:
|
||||
def dataclass_field_by_name(cls: type[DataclassInstance], name: str) -> DataclassField:
|
||||
"Looks up a field in a data-class by its field name."
|
||||
|
||||
for field in dataclasses.fields(cls):
|
||||
|
@ -190,7 +164,7 @@ def is_named_tuple_instance(obj: Any) -> TypeGuard[NamedTuple]:
|
|||
return is_named_tuple_type(type(obj))
|
||||
|
||||
|
||||
def is_named_tuple_type(typ: Any) -> TypeGuard[Type[NamedTuple]]:
|
||||
def is_named_tuple_type(typ: Any) -> TypeGuard[type[NamedTuple]]:
|
||||
"""
|
||||
True if the argument corresponds to a named tuple type.
|
||||
|
||||
|
@ -217,26 +191,14 @@ def is_named_tuple_type(typ: Any) -> TypeGuard[Type[NamedTuple]]:
|
|||
return all(isinstance(n, str) for n in f)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
def is_type_enum(typ: object) -> TypeGuard[type[enum.Enum]]:
|
||||
"True if the specified type is an enumeration type."
|
||||
|
||||
def is_type_enum(typ: object) -> TypeGuard[Type[enum.Enum]]:
|
||||
"True if the specified type is an enumeration type."
|
||||
|
||||
typ = unwrap_annotated_type(typ)
|
||||
return isinstance(typ, enum.EnumType)
|
||||
|
||||
else:
|
||||
|
||||
def is_type_enum(typ: object) -> TypeGuard[Type[enum.Enum]]:
|
||||
"True if the specified type is an enumeration type."
|
||||
|
||||
typ = unwrap_annotated_type(typ)
|
||||
|
||||
# use an explicit isinstance(..., type) check to filter out special forms like generics
|
||||
return isinstance(typ, type) and issubclass(typ, enum.Enum)
|
||||
typ = unwrap_annotated_type(typ)
|
||||
return isinstance(typ, enum.EnumType)
|
||||
|
||||
|
||||
def enum_value_types(enum_type: Type[enum.Enum]) -> List[type]:
|
||||
def enum_value_types(enum_type: type[enum.Enum]) -> list[type]:
|
||||
"""
|
||||
Returns all unique value types of the `enum.Enum` type in definition order.
|
||||
"""
|
||||
|
@ -246,8 +208,8 @@ def enum_value_types(enum_type: Type[enum.Enum]) -> List[type]:
|
|||
|
||||
|
||||
def extend_enum(
|
||||
source: Type[enum.Enum],
|
||||
) -> Callable[[Type[enum.Enum]], Type[enum.Enum]]:
|
||||
source: type[enum.Enum],
|
||||
) -> Callable[[type[enum.Enum]], type[enum.Enum]]:
|
||||
"""
|
||||
Creates a new enumeration type extending the set of values in an existing type.
|
||||
|
||||
|
@ -255,13 +217,13 @@ def extend_enum(
|
|||
:returns: A new enumeration type with the extended set of values.
|
||||
"""
|
||||
|
||||
def wrap(extend: Type[enum.Enum]) -> Type[enum.Enum]:
|
||||
def wrap(extend: type[enum.Enum]) -> type[enum.Enum]:
|
||||
# create new enumeration type combining the values from both types
|
||||
values: Dict[str, Any] = {}
|
||||
values: dict[str, Any] = {}
|
||||
values.update((e.name, e.value) for e in source)
|
||||
values.update((e.name, e.value) for e in extend)
|
||||
# mypy fails to determine that __name__ is always a string; hence the `ignore` directive.
|
||||
enum_class: Type[enum.Enum] = enum.Enum(extend.__name__, values) # type: ignore[misc]
|
||||
enum_class: type[enum.Enum] = enum.Enum(extend.__name__, values) # type: ignore[misc]
|
||||
|
||||
# assign the newly created type to the same module where the extending class is defined
|
||||
enum_class.__module__ = extend.__module__
|
||||
|
@ -273,22 +235,13 @@ def extend_enum(
|
|||
return wrap
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
def _is_union_like(typ: object) -> bool:
|
||||
"True if type is a union such as `Union[T1, T2, ...]` or a union type `T1 | T2`."
|
||||
|
||||
def _is_union_like(typ: object) -> bool:
|
||||
"True if type is a union such as `Union[T1, T2, ...]` or a union type `T1 | T2`."
|
||||
|
||||
return typing.get_origin(typ) is Union or isinstance(typ, types.UnionType)
|
||||
|
||||
else:
|
||||
|
||||
def _is_union_like(typ: object) -> bool:
|
||||
"True if type is a union such as `Union[T1, T2, ...]` or a union type `T1 | T2`."
|
||||
|
||||
return typing.get_origin(typ) is Union
|
||||
return typing.get_origin(typ) is Union or isinstance(typ, types.UnionType)
|
||||
|
||||
|
||||
def is_type_optional(typ: object, strict: bool = False) -> TypeGuard[Type[Optional[Any]]]:
|
||||
def is_type_optional(typ: object, strict: bool = False) -> TypeGuard[type[Any | None]]:
|
||||
"""
|
||||
True if the type annotation corresponds to an optional type (e.g. `Optional[T]` or `Union[T1,T2,None]`).
|
||||
|
||||
|
@ -309,7 +262,7 @@ def is_type_optional(typ: object, strict: bool = False) -> TypeGuard[Type[Option
|
|||
return False
|
||||
|
||||
|
||||
def unwrap_optional_type(typ: Type[Optional[T]]) -> Type[T]:
|
||||
def unwrap_optional_type(typ: type[T | None]) -> type[T]:
|
||||
"""
|
||||
Extracts the inner type of an optional type.
|
||||
|
||||
|
@ -320,7 +273,7 @@ def unwrap_optional_type(typ: Type[Optional[T]]) -> Type[T]:
|
|||
return rewrap_annotated_type(_unwrap_optional_type, typ)
|
||||
|
||||
|
||||
def _unwrap_optional_type(typ: Type[Optional[T]]) -> Type[T]:
|
||||
def _unwrap_optional_type(typ: type[T | None]) -> type[T]:
|
||||
"Extracts the type qualified as optional (e.g. returns `T` for `Optional[T]`)."
|
||||
|
||||
# Optional[T] is represented internally as Union[T, None]
|
||||
|
@ -342,7 +295,7 @@ def is_type_union(typ: object) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def unwrap_union_types(typ: object) -> Tuple[object, ...]:
|
||||
def unwrap_union_types(typ: object) -> tuple[object, ...]:
|
||||
"""
|
||||
Extracts the inner types of a union type.
|
||||
|
||||
|
@ -354,7 +307,7 @@ def unwrap_union_types(typ: object) -> Tuple[object, ...]:
|
|||
return _unwrap_union_types(typ)
|
||||
|
||||
|
||||
def _unwrap_union_types(typ: object) -> Tuple[object, ...]:
|
||||
def _unwrap_union_types(typ: object) -> tuple[object, ...]:
|
||||
"Extracts the types in a union (e.g. returns a tuple of types `T1` and `T2` for `Union[T1, T2]`)."
|
||||
|
||||
if not _is_union_like(typ):
|
||||
|
@ -385,7 +338,7 @@ def unwrap_literal_value(typ: object) -> Any:
|
|||
return args[0]
|
||||
|
||||
|
||||
def unwrap_literal_values(typ: object) -> Tuple[Any, ...]:
|
||||
def unwrap_literal_values(typ: object) -> tuple[Any, ...]:
|
||||
"""
|
||||
Extracts the constant values captured by a literal type.
|
||||
|
||||
|
@ -397,7 +350,7 @@ def unwrap_literal_values(typ: object) -> Tuple[Any, ...]:
|
|||
return typing.get_args(typ)
|
||||
|
||||
|
||||
def unwrap_literal_types(typ: object) -> Tuple[type, ...]:
|
||||
def unwrap_literal_types(typ: object) -> tuple[type, ...]:
|
||||
"""
|
||||
Extracts the types of the constant values captured by a literal type.
|
||||
|
||||
|
@ -408,14 +361,14 @@ def unwrap_literal_types(typ: object) -> Tuple[type, ...]:
|
|||
return tuple(type(t) for t in unwrap_literal_values(typ))
|
||||
|
||||
|
||||
def is_generic_list(typ: object) -> TypeGuard[Type[list]]:
|
||||
def is_generic_list(typ: object) -> TypeGuard[type[list]]:
|
||||
"True if the specified type is a generic list, i.e. `List[T]`."
|
||||
|
||||
typ = unwrap_annotated_type(typ)
|
||||
return typing.get_origin(typ) is list
|
||||
|
||||
|
||||
def unwrap_generic_list(typ: Type[List[T]]) -> Type[T]:
|
||||
def unwrap_generic_list(typ: type[list[T]]) -> type[T]:
|
||||
"""
|
||||
Extracts the item type of a list type.
|
||||
|
||||
|
@ -426,21 +379,21 @@ def unwrap_generic_list(typ: Type[List[T]]) -> Type[T]:
|
|||
return rewrap_annotated_type(_unwrap_generic_list, typ)
|
||||
|
||||
|
||||
def _unwrap_generic_list(typ: Type[List[T]]) -> Type[T]:
|
||||
def _unwrap_generic_list(typ: type[list[T]]) -> type[T]:
|
||||
"Extracts the item type of a list type (e.g. returns `T` for `List[T]`)."
|
||||
|
||||
(list_type,) = typing.get_args(typ) # unpack single tuple element
|
||||
return list_type # type: ignore[no-any-return]
|
||||
|
||||
|
||||
def is_generic_set(typ: object) -> TypeGuard[Type[set]]:
|
||||
def is_generic_set(typ: object) -> TypeGuard[type[set]]:
|
||||
"True if the specified type is a generic set, i.e. `Set[T]`."
|
||||
|
||||
typ = unwrap_annotated_type(typ)
|
||||
return typing.get_origin(typ) is set
|
||||
|
||||
|
||||
def unwrap_generic_set(typ: Type[Set[T]]) -> Type[T]:
|
||||
def unwrap_generic_set(typ: type[set[T]]) -> type[T]:
|
||||
"""
|
||||
Extracts the item type of a set type.
|
||||
|
||||
|
@ -451,21 +404,21 @@ def unwrap_generic_set(typ: Type[Set[T]]) -> Type[T]:
|
|||
return rewrap_annotated_type(_unwrap_generic_set, typ)
|
||||
|
||||
|
||||
def _unwrap_generic_set(typ: Type[Set[T]]) -> Type[T]:
|
||||
def _unwrap_generic_set(typ: type[set[T]]) -> type[T]:
|
||||
"Extracts the item type of a set type (e.g. returns `T` for `Set[T]`)."
|
||||
|
||||
(set_type,) = typing.get_args(typ) # unpack single tuple element
|
||||
return set_type # type: ignore[no-any-return]
|
||||
|
||||
|
||||
def is_generic_dict(typ: object) -> TypeGuard[Type[dict]]:
|
||||
def is_generic_dict(typ: object) -> TypeGuard[type[dict]]:
|
||||
"True if the specified type is a generic dictionary, i.e. `Dict[KeyType, ValueType]`."
|
||||
|
||||
typ = unwrap_annotated_type(typ)
|
||||
return typing.get_origin(typ) is dict
|
||||
|
||||
|
||||
def unwrap_generic_dict(typ: Type[Dict[K, V]]) -> Tuple[Type[K], Type[V]]:
|
||||
def unwrap_generic_dict(typ: type[dict[K, V]]) -> tuple[type[K], type[V]]:
|
||||
"""
|
||||
Extracts the key and value types of a dictionary type as a tuple.
|
||||
|
||||
|
@ -476,7 +429,7 @@ def unwrap_generic_dict(typ: Type[Dict[K, V]]) -> Tuple[Type[K], Type[V]]:
|
|||
return _unwrap_generic_dict(unwrap_annotated_type(typ))
|
||||
|
||||
|
||||
def _unwrap_generic_dict(typ: Type[Dict[K, V]]) -> Tuple[Type[K], Type[V]]:
|
||||
def _unwrap_generic_dict(typ: type[dict[K, V]]) -> tuple[type[K], type[V]]:
|
||||
"Extracts the key and value types of a dict type (e.g. returns (`K`, `V`) for `Dict[K, V]`)."
|
||||
|
||||
key_type, value_type = typing.get_args(typ)
|
||||
|
@ -489,7 +442,7 @@ def is_type_annotated(typ: TypeLike) -> bool:
|
|||
return getattr(typ, "__metadata__", None) is not None
|
||||
|
||||
|
||||
def get_annotation(data_type: TypeLike, annotation_type: Type[T]) -> Optional[T]:
|
||||
def get_annotation(data_type: TypeLike, annotation_type: type[T]) -> T | None:
|
||||
"""
|
||||
Returns the first annotation on a data type that matches the expected annotation type.
|
||||
|
||||
|
@ -518,7 +471,7 @@ def unwrap_annotated_type(typ: T) -> T:
|
|||
return typ
|
||||
|
||||
|
||||
def rewrap_annotated_type(transform: Callable[[Type[S]], Type[T]], typ: Type[S]) -> Type[T]:
|
||||
def rewrap_annotated_type(transform: Callable[[type[S]], type[T]], typ: type[S]) -> type[T]:
|
||||
"""
|
||||
Un-boxes, transforms and re-boxes an optionally annotated type.
|
||||
|
||||
|
@ -542,7 +495,7 @@ def rewrap_annotated_type(transform: Callable[[Type[S]], Type[T]], typ: Type[S])
|
|||
return transformed_type
|
||||
|
||||
|
||||
def get_module_classes(module: types.ModuleType) -> List[type]:
|
||||
def get_module_classes(module: types.ModuleType) -> list[type]:
|
||||
"Returns all classes declared directly in a module."
|
||||
|
||||
def is_class_member(member: object) -> TypeGuard[type]:
|
||||
|
@ -551,18 +504,11 @@ def get_module_classes(module: types.ModuleType) -> List[type]:
|
|||
return [class_type for _, class_type in inspect.getmembers(module, is_class_member)]
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
|
||||
def get_resolved_hints(typ: type) -> Dict[str, type]:
|
||||
return typing.get_type_hints(typ, include_extras=True)
|
||||
|
||||
else:
|
||||
|
||||
def get_resolved_hints(typ: type) -> Dict[str, type]:
|
||||
return typing.get_type_hints(typ)
|
||||
def get_resolved_hints(typ: type) -> dict[str, type]:
|
||||
return typing.get_type_hints(typ, include_extras=True)
|
||||
|
||||
|
||||
def get_class_properties(typ: type) -> Iterable[Tuple[str, type | str]]:
|
||||
def get_class_properties(typ: type) -> Iterable[tuple[str, type | str]]:
|
||||
"Returns all properties of a class."
|
||||
|
||||
if is_dataclass_type(typ):
|
||||
|
@ -572,7 +518,7 @@ def get_class_properties(typ: type) -> Iterable[Tuple[str, type | str]]:
|
|||
return resolved_hints.items()
|
||||
|
||||
|
||||
def get_class_property(typ: type, name: str) -> Optional[type | str]:
|
||||
def get_class_property(typ: type, name: str) -> type | str | None:
|
||||
"Looks up the annotated type of a property in a class by its property name."
|
||||
|
||||
for property_name, property_type in get_class_properties(typ):
|
||||
|
@ -586,7 +532,7 @@ class _ROOT:
|
|||
pass
|
||||
|
||||
|
||||
def get_referenced_types(typ: TypeLike, module: Optional[types.ModuleType] = None) -> Set[type]:
|
||||
def get_referenced_types(typ: TypeLike, module: types.ModuleType | None = None) -> set[type]:
|
||||
"""
|
||||
Extracts types directly or indirectly referenced by this type.
|
||||
|
||||
|
@ -610,10 +556,10 @@ class TypeCollector:
|
|||
:param graph: The type dependency graph, linking types to types they depend on.
|
||||
"""
|
||||
|
||||
graph: Dict[type, Set[type]]
|
||||
graph: dict[type, set[type]]
|
||||
|
||||
@property
|
||||
def references(self) -> Set[type]:
|
||||
def references(self) -> set[type]:
|
||||
"Types collected by the type collector."
|
||||
|
||||
dependencies = set()
|
||||
|
@ -638,8 +584,8 @@ class TypeCollector:
|
|||
def run(
|
||||
self,
|
||||
typ: TypeLike,
|
||||
cls: Type[DataclassInstance],
|
||||
module: Optional[types.ModuleType],
|
||||
cls: type[DataclassInstance],
|
||||
module: types.ModuleType | None,
|
||||
) -> None:
|
||||
"""
|
||||
Extracts types indirectly referenced by this type.
|
||||
|
@ -702,26 +648,17 @@ class TypeCollector:
|
|||
for field in dataclass_fields(typ):
|
||||
self.run(field.type, typ, context)
|
||||
else:
|
||||
for field_name, field_type in get_resolved_hints(typ).items():
|
||||
for _field_name, field_type in get_resolved_hints(typ).items():
|
||||
self.run(field_type, typ, context)
|
||||
return
|
||||
|
||||
raise TypeError(f"expected: type-like; got: {typ}")
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
def get_signature(fn: Callable[..., Any]) -> inspect.Signature:
|
||||
"Extracts the signature of a function."
|
||||
|
||||
def get_signature(fn: Callable[..., Any]) -> inspect.Signature:
|
||||
"Extracts the signature of a function."
|
||||
|
||||
return inspect.signature(fn, eval_str=True)
|
||||
|
||||
else:
|
||||
|
||||
def get_signature(fn: Callable[..., Any]) -> inspect.Signature:
|
||||
"Extracts the signature of a function."
|
||||
|
||||
return inspect.signature(fn)
|
||||
return inspect.signature(fn, eval_str=True)
|
||||
|
||||
|
||||
def is_reserved_property(name: str) -> bool:
|
||||
|
@ -756,51 +693,20 @@ def create_module(name: str) -> types.ModuleType:
|
|||
return module
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
def create_data_type(class_name: str, fields: list[tuple[str, type]]) -> type:
|
||||
"""
|
||||
Creates a new data-class type dynamically.
|
||||
|
||||
def create_data_type(class_name: str, fields: List[Tuple[str, type]]) -> type:
|
||||
"""
|
||||
Creates a new data-class type dynamically.
|
||||
:param class_name: The name of new data-class type.
|
||||
:param fields: A list of fields (and their type) that the new data-class type is expected to have.
|
||||
:returns: The newly created data-class type.
|
||||
"""
|
||||
|
||||
:param class_name: The name of new data-class type.
|
||||
:param fields: A list of fields (and their type) that the new data-class type is expected to have.
|
||||
:returns: The newly created data-class type.
|
||||
"""
|
||||
|
||||
# has the `slots` parameter
|
||||
return dataclasses.make_dataclass(class_name, fields, slots=True)
|
||||
|
||||
else:
|
||||
|
||||
def create_data_type(class_name: str, fields: List[Tuple[str, type]]) -> type:
|
||||
"""
|
||||
Creates a new data-class type dynamically.
|
||||
|
||||
:param class_name: The name of new data-class type.
|
||||
:param fields: A list of fields (and their type) that the new data-class type is expected to have.
|
||||
:returns: The newly created data-class type.
|
||||
"""
|
||||
|
||||
cls = dataclasses.make_dataclass(class_name, fields)
|
||||
|
||||
cls_dict = dict(cls.__dict__)
|
||||
field_names = tuple(field.name for field in dataclasses.fields(cls))
|
||||
|
||||
cls_dict["__slots__"] = field_names
|
||||
|
||||
for field_name in field_names:
|
||||
cls_dict.pop(field_name, None)
|
||||
cls_dict.pop("__dict__", None)
|
||||
|
||||
qualname = getattr(cls, "__qualname__", None)
|
||||
cls = type(cls)(cls.__name__, (), cls_dict)
|
||||
if qualname is not None:
|
||||
cls.__qualname__ = qualname
|
||||
|
||||
return cls
|
||||
# has the `slots` parameter
|
||||
return dataclasses.make_dataclass(class_name, fields, slots=True)
|
||||
|
||||
|
||||
def create_object(typ: Type[T]) -> T:
|
||||
def create_object(typ: type[T]) -> T:
|
||||
"Creates an instance of a type."
|
||||
|
||||
if issubclass(typ, Exception):
|
||||
|
@ -811,11 +717,7 @@ def create_object(typ: Type[T]) -> T:
|
|||
return object.__new__(typ)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
TypeOrGeneric = Union[type, types.GenericAlias]
|
||||
|
||||
else:
|
||||
TypeOrGeneric = object
|
||||
TypeOrGeneric = Union[type, types.GenericAlias]
|
||||
|
||||
|
||||
def is_generic_instance(obj: Any, typ: TypeLike) -> bool:
|
||||
|
@ -885,7 +787,7 @@ def is_generic_instance(obj: Any, typ: TypeLike) -> bool:
|
|||
|
||||
|
||||
class RecursiveChecker:
|
||||
_pred: Optional[Callable[[type, Any], bool]]
|
||||
_pred: Callable[[type, Any], bool] | None
|
||||
|
||||
def __init__(self, pred: Callable[[type, Any], bool]) -> None:
|
||||
"""
|
||||
|
@ -997,9 +899,9 @@ def check_recursive(
|
|||
obj: object,
|
||||
/,
|
||||
*,
|
||||
pred: Optional[Callable[[type, Any], bool]] = None,
|
||||
type_pred: Optional[Callable[[type], bool]] = None,
|
||||
value_pred: Optional[Callable[[Any], bool]] = None,
|
||||
pred: Callable[[type, Any], bool] | None = None,
|
||||
type_pred: Callable[[type], bool] | None = None,
|
||||
value_pred: Callable[[Any], bool] | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Checks if a predicate applies to all nested member properties of an object recursively.
|
||||
|
@ -1015,7 +917,7 @@ def check_recursive(
|
|||
if pred is not None:
|
||||
raise TypeError("filter predicate not permitted when type and value predicates are present")
|
||||
|
||||
type_p: Callable[[Type[T]], bool] = type_pred
|
||||
type_p: Callable[[type[T]], bool] = type_pred
|
||||
value_p: Callable[[T], bool] = value_pred
|
||||
pred = lambda typ, obj: not type_p(typ) or value_p(obj) # noqa: E731
|
||||
|
||||
|
|
|
@ -11,13 +11,12 @@ Type-safe data interchange for Python data classes.
|
|||
"""
|
||||
|
||||
import keyword
|
||||
from typing import Optional
|
||||
|
||||
from .auxiliary import Alias
|
||||
from .inspection import get_annotation
|
||||
|
||||
|
||||
def python_field_to_json_property(python_id: str, python_type: Optional[object] = None) -> str:
|
||||
def python_field_to_json_property(python_id: str, python_type: object | None = None) -> str:
|
||||
"""
|
||||
Map a Python field identifier to a JSON property name.
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Type-safe data interchange for Python data classes.
|
|||
"""
|
||||
|
||||
import typing
|
||||
from typing import Any, Literal, Optional, Tuple, Union
|
||||
from typing import Any, Literal, Union
|
||||
|
||||
from .auxiliary import _auxiliary_types
|
||||
from .inspection import (
|
||||
|
@ -39,7 +39,7 @@ class TypeFormatter:
|
|||
def __init__(self, use_union_operator: bool = False) -> None:
|
||||
self.use_union_operator = use_union_operator
|
||||
|
||||
def union_to_str(self, data_type_args: Tuple[TypeLike, ...]) -> str:
|
||||
def union_to_str(self, data_type_args: tuple[TypeLike, ...]) -> str:
|
||||
if self.use_union_operator:
|
||||
return " | ".join(self.python_type_to_str(t) for t in data_type_args)
|
||||
else:
|
||||
|
@ -100,7 +100,7 @@ class TypeFormatter:
|
|||
metadata = getattr(data_type, "__metadata__", None)
|
||||
if metadata is not None:
|
||||
# type is Annotated[T, ...]
|
||||
metatuple: Tuple[Any, ...] = metadata
|
||||
metatuple: tuple[Any, ...] = metadata
|
||||
arg = typing.get_args(data_type)[0]
|
||||
|
||||
# check for auxiliary types with user-defined annotations
|
||||
|
@ -110,7 +110,7 @@ class TypeFormatter:
|
|||
if arg is not auxiliary_arg:
|
||||
continue
|
||||
|
||||
auxiliary_metatuple: Optional[Tuple[Any, ...]] = getattr(auxiliary_type, "__metadata__", None)
|
||||
auxiliary_metatuple: tuple[Any, ...] | None = getattr(auxiliary_type, "__metadata__", None)
|
||||
if auxiliary_metatuple is None:
|
||||
continue
|
||||
|
||||
|
|
|
@ -21,24 +21,19 @@ import json
|
|||
import types
|
||||
import typing
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from copy import deepcopy
|
||||
from typing import (
|
||||
Annotated,
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
import jsonschema
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from . import docstring
|
||||
from .auxiliary import (
|
||||
|
@ -71,7 +66,7 @@ OBJECT_ENUM_EXPANSION_LIMIT = 4
|
|||
T = TypeVar("T")
|
||||
|
||||
|
||||
def get_class_docstrings(data_type: type) -> Tuple[Optional[str], Optional[str]]:
|
||||
def get_class_docstrings(data_type: type) -> tuple[str | None, str | None]:
|
||||
docstr = docstring.parse_type(data_type)
|
||||
|
||||
# check if class has a doc-string other than the auto-generated string assigned by @dataclass
|
||||
|
@ -82,8 +77,8 @@ def get_class_docstrings(data_type: type) -> Tuple[Optional[str], Optional[str]]
|
|||
|
||||
|
||||
def get_class_property_docstrings(
|
||||
data_type: type, transform_fun: Optional[Callable[[type, str, str], str]] = None
|
||||
) -> Dict[str, str]:
|
||||
data_type: type, transform_fun: Callable[[type, str, str], str] | None = None
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
Extracts the documentation strings associated with the properties of a composite type.
|
||||
|
||||
|
@ -120,7 +115,7 @@ def docstring_to_schema(data_type: type) -> Schema:
|
|||
return schema
|
||||
|
||||
|
||||
def id_from_ref(data_type: Union[typing.ForwardRef, str, type]) -> str:
|
||||
def id_from_ref(data_type: typing.ForwardRef | str | type) -> str:
|
||||
"Extracts the name of a possibly forward-referenced type."
|
||||
|
||||
if isinstance(data_type, typing.ForwardRef):
|
||||
|
@ -132,7 +127,7 @@ def id_from_ref(data_type: Union[typing.ForwardRef, str, type]) -> str:
|
|||
return data_type.__name__
|
||||
|
||||
|
||||
def type_from_ref(data_type: Union[typing.ForwardRef, str, type]) -> Tuple[str, type]:
|
||||
def type_from_ref(data_type: typing.ForwardRef | str | type) -> tuple[str, type]:
|
||||
"Creates a type from a forward reference."
|
||||
|
||||
if isinstance(data_type, typing.ForwardRef):
|
||||
|
@ -148,16 +143,16 @@ def type_from_ref(data_type: Union[typing.ForwardRef, str, type]) -> Tuple[str,
|
|||
|
||||
@dataclasses.dataclass
|
||||
class TypeCatalogEntry:
|
||||
schema: Optional[Schema]
|
||||
schema: Schema | None
|
||||
identifier: str
|
||||
examples: Optional[JsonType] = None
|
||||
examples: JsonType | None = None
|
||||
|
||||
|
||||
class TypeCatalog:
|
||||
"Maintains an association of well-known Python types to their JSON schema."
|
||||
|
||||
_by_type: Dict[TypeLike, TypeCatalogEntry]
|
||||
_by_name: Dict[str, TypeCatalogEntry]
|
||||
_by_type: dict[TypeLike, TypeCatalogEntry]
|
||||
_by_name: dict[str, TypeCatalogEntry]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._by_type = {}
|
||||
|
@ -174,9 +169,9 @@ class TypeCatalog:
|
|||
def add(
|
||||
self,
|
||||
data_type: TypeLike,
|
||||
schema: Optional[Schema],
|
||||
schema: Schema | None,
|
||||
identifier: str,
|
||||
examples: Optional[List[JsonType]] = None,
|
||||
examples: list[JsonType] | None = None,
|
||||
) -> None:
|
||||
if isinstance(data_type, typing.ForwardRef):
|
||||
raise TypeError("forward references cannot be used to register a type")
|
||||
|
@ -202,17 +197,17 @@ class SchemaOptions:
|
|||
definitions_path: str = "#/definitions/"
|
||||
use_descriptions: bool = True
|
||||
use_examples: bool = True
|
||||
property_description_fun: Optional[Callable[[type, str, str], str]] = None
|
||||
property_description_fun: Callable[[type, str, str], str] | None = None
|
||||
|
||||
|
||||
class JsonSchemaGenerator:
|
||||
"Creates a JSON schema with user-defined type definitions."
|
||||
|
||||
type_catalog: ClassVar[TypeCatalog] = TypeCatalog()
|
||||
types_used: Dict[str, TypeLike]
|
||||
types_used: dict[str, TypeLike]
|
||||
options: SchemaOptions
|
||||
|
||||
def __init__(self, options: Optional[SchemaOptions] = None):
|
||||
def __init__(self, options: SchemaOptions | None = None):
|
||||
if options is None:
|
||||
self.options = SchemaOptions()
|
||||
else:
|
||||
|
@ -244,13 +239,13 @@ class JsonSchemaGenerator:
|
|||
def _(self, arg: MaxLength) -> Schema:
|
||||
return {"maxLength": arg.value}
|
||||
|
||||
def _with_metadata(self, type_schema: Schema, metadata: Optional[Tuple[Any, ...]]) -> Schema:
|
||||
def _with_metadata(self, type_schema: Schema, metadata: tuple[Any, ...] | None) -> Schema:
|
||||
if metadata:
|
||||
for m in metadata:
|
||||
type_schema.update(self._metadata_to_schema(m))
|
||||
return type_schema
|
||||
|
||||
def _simple_type_to_schema(self, typ: TypeLike, json_schema_extra: Optional[dict] = None) -> Optional[Schema]:
|
||||
def _simple_type_to_schema(self, typ: TypeLike, json_schema_extra: dict | None = None) -> Schema | None:
|
||||
"""
|
||||
Returns the JSON schema associated with a simple, unrestricted type.
|
||||
|
||||
|
@ -314,7 +309,7 @@ class JsonSchemaGenerator:
|
|||
self,
|
||||
data_type: TypeLike,
|
||||
force_expand: bool = False,
|
||||
json_schema_extra: Optional[dict] = None,
|
||||
json_schema_extra: dict | None = None,
|
||||
) -> Schema:
|
||||
common_info = {}
|
||||
if json_schema_extra and "deprecated" in json_schema_extra:
|
||||
|
@ -325,7 +320,7 @@ class JsonSchemaGenerator:
|
|||
self,
|
||||
data_type: TypeLike,
|
||||
force_expand: bool = False,
|
||||
json_schema_extra: Optional[dict] = None,
|
||||
json_schema_extra: dict | None = None,
|
||||
) -> Schema:
|
||||
"""
|
||||
Returns the JSON schema associated with a type.
|
||||
|
@ -381,7 +376,7 @@ class JsonSchemaGenerator:
|
|||
return {"$ref": f"{self.options.definitions_path}{identifier}"}
|
||||
|
||||
if is_type_enum(typ):
|
||||
enum_type: Type[enum.Enum] = typ
|
||||
enum_type: type[enum.Enum] = typ
|
||||
value_types = enum_value_types(enum_type)
|
||||
if len(value_types) != 1:
|
||||
raise ValueError(
|
||||
|
@ -496,8 +491,8 @@ class JsonSchemaGenerator:
|
|||
members = dict(inspect.getmembers(typ, lambda a: not inspect.isroutine(a)))
|
||||
|
||||
property_docstrings = get_class_property_docstrings(typ, self.options.property_description_fun)
|
||||
properties: Dict[str, Schema] = {}
|
||||
required: List[str] = []
|
||||
properties: dict[str, Schema] = {}
|
||||
required: list[str] = []
|
||||
for property_name, property_type in get_class_properties(typ):
|
||||
# rename property if an alias name is specified
|
||||
alias = get_annotation(property_type, Alias)
|
||||
|
@ -530,16 +525,7 @@ class JsonSchemaGenerator:
|
|||
# check if value can be directly represented in JSON
|
||||
if isinstance(
|
||||
def_value,
|
||||
(
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
str,
|
||||
enum.Enum,
|
||||
datetime.datetime,
|
||||
datetime.date,
|
||||
datetime.time,
|
||||
),
|
||||
bool | int | float | str | enum.Enum | datetime.datetime | datetime.date | datetime.time,
|
||||
):
|
||||
property_def["default"] = object_to_json(def_value)
|
||||
|
||||
|
@ -587,7 +573,7 @@ class JsonSchemaGenerator:
|
|||
|
||||
return type_schema
|
||||
|
||||
def classdef_to_schema(self, data_type: TypeLike, force_expand: bool = False) -> Tuple[Schema, Dict[str, Schema]]:
|
||||
def classdef_to_schema(self, data_type: TypeLike, force_expand: bool = False) -> tuple[Schema, dict[str, Schema]]:
|
||||
"""
|
||||
Returns the JSON schema associated with a type and any nested types.
|
||||
|
||||
|
@ -604,7 +590,7 @@ class JsonSchemaGenerator:
|
|||
try:
|
||||
type_schema = self.type_to_schema(data_type, force_expand=force_expand)
|
||||
|
||||
types_defined: Dict[str, Schema] = {}
|
||||
types_defined: dict[str, Schema] = {}
|
||||
while len(self.types_used) > len(types_defined):
|
||||
# make a snapshot copy; original collection is going to be modified
|
||||
types_undefined = {
|
||||
|
@ -635,7 +621,7 @@ class Validator(enum.Enum):
|
|||
|
||||
def classdef_to_schema(
|
||||
data_type: TypeLike,
|
||||
options: Optional[SchemaOptions] = None,
|
||||
options: SchemaOptions | None = None,
|
||||
validator: Validator = Validator.Latest,
|
||||
) -> Schema:
|
||||
"""
|
||||
|
@ -689,7 +675,7 @@ def print_schema(data_type: type) -> None:
|
|||
print(json.dumps(s, indent=4))
|
||||
|
||||
|
||||
def get_schema_identifier(data_type: type) -> Optional[str]:
|
||||
def get_schema_identifier(data_type: type) -> str | None:
|
||||
if data_type in JsonSchemaGenerator.type_catalog:
|
||||
return JsonSchemaGenerator.type_catalog.get(data_type).identifier
|
||||
else:
|
||||
|
@ -698,9 +684,9 @@ def get_schema_identifier(data_type: type) -> Optional[str]:
|
|||
|
||||
def register_schema(
|
||||
data_type: T,
|
||||
schema: Optional[Schema] = None,
|
||||
name: Optional[str] = None,
|
||||
examples: Optional[List[JsonType]] = None,
|
||||
schema: Schema | None = None,
|
||||
name: str | None = None,
|
||||
examples: list[JsonType] | None = None,
|
||||
) -> T:
|
||||
"""
|
||||
Associates a type with a JSON schema definition.
|
||||
|
@ -721,22 +707,22 @@ def register_schema(
|
|||
|
||||
|
||||
@overload
|
||||
def json_schema_type(cls: Type[T], /) -> Type[T]: ...
|
||||
def json_schema_type(cls: type[T], /) -> type[T]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def json_schema_type(cls: None, *, schema: Optional[Schema] = None) -> Callable[[Type[T]], Type[T]]: ...
|
||||
def json_schema_type(cls: None, *, schema: Schema | None = None) -> Callable[[type[T]], type[T]]: ...
|
||||
|
||||
|
||||
def json_schema_type(
|
||||
cls: Optional[Type[T]] = None,
|
||||
cls: type[T] | None = None,
|
||||
*,
|
||||
schema: Optional[Schema] = None,
|
||||
examples: Optional[List[JsonType]] = None,
|
||||
) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
|
||||
schema: Schema | None = None,
|
||||
examples: list[JsonType] | None = None,
|
||||
) -> type[T] | Callable[[type[T]], type[T]]:
|
||||
"""Decorator to add user-defined schema definition to a class."""
|
||||
|
||||
def wrap(cls: Type[T]) -> Type[T]:
|
||||
def wrap(cls: type[T]) -> type[T]:
|
||||
return register_schema(cls, schema, examples=examples)
|
||||
|
||||
# see if decorator is used as @json_schema_type or @json_schema_type()
|
||||
|
|
|
@ -14,7 +14,7 @@ import inspect
|
|||
import json
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Any, Optional, TextIO, TypeVar
|
||||
from typing import Any, TextIO, TypeVar
|
||||
|
||||
from .core import JsonType
|
||||
from .deserializer import create_deserializer
|
||||
|
@ -42,7 +42,7 @@ def object_to_json(obj: Any) -> JsonType:
|
|||
return generator.generate(obj)
|
||||
|
||||
|
||||
def json_to_object(typ: TypeLike, data: JsonType, *, context: Optional[ModuleType] = None) -> object:
|
||||
def json_to_object(typ: TypeLike, data: JsonType, *, context: ModuleType | None = None) -> object:
|
||||
"""
|
||||
Creates an object from a representation that has been de-serialized from JSON.
|
||||
|
||||
|
|
|
@ -20,19 +20,13 @@ import ipaddress
|
|||
import sys
|
||||
import typing
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from types import FunctionType, MethodType, ModuleType
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
@ -133,7 +127,7 @@ class IPv6Serializer(Serializer[ipaddress.IPv6Address]):
|
|||
|
||||
|
||||
class EnumSerializer(Serializer[enum.Enum]):
|
||||
def generate(self, obj: enum.Enum) -> Union[int, str]:
|
||||
def generate(self, obj: enum.Enum) -> int | str:
|
||||
value = obj.value
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
|
@ -141,12 +135,12 @@ class EnumSerializer(Serializer[enum.Enum]):
|
|||
|
||||
|
||||
class UntypedListSerializer(Serializer[list]):
|
||||
def generate(self, obj: list) -> List[JsonType]:
|
||||
def generate(self, obj: list) -> list[JsonType]:
|
||||
return [object_to_json(item) for item in obj]
|
||||
|
||||
|
||||
class UntypedDictSerializer(Serializer[dict]):
|
||||
def generate(self, obj: dict) -> Dict[str, JsonType]:
|
||||
def generate(self, obj: dict) -> dict[str, JsonType]:
|
||||
if obj and isinstance(next(iter(obj.keys())), enum.Enum):
|
||||
iterator = ((key.value, object_to_json(value)) for key, value in obj.items())
|
||||
else:
|
||||
|
@ -155,41 +149,41 @@ class UntypedDictSerializer(Serializer[dict]):
|
|||
|
||||
|
||||
class UntypedSetSerializer(Serializer[set]):
|
||||
def generate(self, obj: set) -> List[JsonType]:
|
||||
def generate(self, obj: set) -> list[JsonType]:
|
||||
return [object_to_json(item) for item in obj]
|
||||
|
||||
|
||||
class UntypedTupleSerializer(Serializer[tuple]):
|
||||
def generate(self, obj: tuple) -> List[JsonType]:
|
||||
def generate(self, obj: tuple) -> list[JsonType]:
|
||||
return [object_to_json(item) for item in obj]
|
||||
|
||||
|
||||
class TypedCollectionSerializer(Serializer, Generic[T]):
|
||||
generator: Serializer[T]
|
||||
|
||||
def __init__(self, item_type: Type[T], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, item_type: type[T], context: ModuleType | None) -> None:
|
||||
self.generator = _get_serializer(item_type, context)
|
||||
|
||||
|
||||
class TypedListSerializer(TypedCollectionSerializer[T]):
|
||||
def generate(self, obj: List[T]) -> List[JsonType]:
|
||||
def generate(self, obj: list[T]) -> list[JsonType]:
|
||||
return [self.generator.generate(item) for item in obj]
|
||||
|
||||
|
||||
class TypedStringDictSerializer(TypedCollectionSerializer[T]):
|
||||
def __init__(self, value_type: Type[T], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, value_type: type[T], context: ModuleType | None) -> None:
|
||||
super().__init__(value_type, context)
|
||||
|
||||
def generate(self, obj: Dict[str, T]) -> Dict[str, JsonType]:
|
||||
def generate(self, obj: dict[str, T]) -> dict[str, JsonType]:
|
||||
return {key: self.generator.generate(value) for key, value in obj.items()}
|
||||
|
||||
|
||||
class TypedEnumDictSerializer(TypedCollectionSerializer[T]):
|
||||
def __init__(
|
||||
self,
|
||||
key_type: Type[enum.Enum],
|
||||
value_type: Type[T],
|
||||
context: Optional[ModuleType],
|
||||
key_type: type[enum.Enum],
|
||||
value_type: type[T],
|
||||
context: ModuleType | None,
|
||||
) -> None:
|
||||
super().__init__(value_type, context)
|
||||
|
||||
|
@ -203,22 +197,22 @@ class TypedEnumDictSerializer(TypedCollectionSerializer[T]):
|
|||
if value_type is not str:
|
||||
raise JsonTypeError("invalid enumeration key type, expected `enum.Enum` with string values")
|
||||
|
||||
def generate(self, obj: Dict[enum.Enum, T]) -> Dict[str, JsonType]:
|
||||
def generate(self, obj: dict[enum.Enum, T]) -> dict[str, JsonType]:
|
||||
return {key.value: self.generator.generate(value) for key, value in obj.items()}
|
||||
|
||||
|
||||
class TypedSetSerializer(TypedCollectionSerializer[T]):
|
||||
def generate(self, obj: Set[T]) -> JsonType:
|
||||
def generate(self, obj: set[T]) -> JsonType:
|
||||
return [self.generator.generate(item) for item in obj]
|
||||
|
||||
|
||||
class TypedTupleSerializer(Serializer[tuple]):
|
||||
item_generators: Tuple[Serializer, ...]
|
||||
item_generators: tuple[Serializer, ...]
|
||||
|
||||
def __init__(self, item_types: Tuple[type, ...], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, item_types: tuple[type, ...], context: ModuleType | None) -> None:
|
||||
self.item_generators = tuple(_get_serializer(item_type, context) for item_type in item_types)
|
||||
|
||||
def generate(self, obj: tuple) -> List[JsonType]:
|
||||
def generate(self, obj: tuple) -> list[JsonType]:
|
||||
return [item_generator.generate(item) for item_generator, item in zip(self.item_generators, obj, strict=False)]
|
||||
|
||||
|
||||
|
@ -250,16 +244,16 @@ class FieldSerializer(Generic[T]):
|
|||
self.property_name = property_name
|
||||
self.generator = generator
|
||||
|
||||
def generate_field(self, obj: object, object_dict: Dict[str, JsonType]) -> None:
|
||||
def generate_field(self, obj: object, object_dict: dict[str, JsonType]) -> None:
|
||||
value = getattr(obj, self.field_name)
|
||||
if value is not None:
|
||||
object_dict[self.property_name] = self.generator.generate(value)
|
||||
|
||||
|
||||
class TypedClassSerializer(Serializer[T]):
|
||||
property_generators: List[FieldSerializer]
|
||||
property_generators: list[FieldSerializer]
|
||||
|
||||
def __init__(self, class_type: Type[T], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, class_type: type[T], context: ModuleType | None) -> None:
|
||||
self.property_generators = [
|
||||
FieldSerializer(
|
||||
field_name,
|
||||
|
@ -269,8 +263,8 @@ class TypedClassSerializer(Serializer[T]):
|
|||
for field_name, field_type in get_class_properties(class_type)
|
||||
]
|
||||
|
||||
def generate(self, obj: T) -> Dict[str, JsonType]:
|
||||
object_dict: Dict[str, JsonType] = {}
|
||||
def generate(self, obj: T) -> dict[str, JsonType]:
|
||||
object_dict: dict[str, JsonType] = {}
|
||||
for property_generator in self.property_generators:
|
||||
property_generator.generate_field(obj, object_dict)
|
||||
|
||||
|
@ -278,12 +272,12 @@ class TypedClassSerializer(Serializer[T]):
|
|||
|
||||
|
||||
class TypedNamedTupleSerializer(TypedClassSerializer[NamedTuple]):
|
||||
def __init__(self, class_type: Type[NamedTuple], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, class_type: type[NamedTuple], context: ModuleType | None) -> None:
|
||||
super().__init__(class_type, context)
|
||||
|
||||
|
||||
class DataclassSerializer(TypedClassSerializer[T]):
|
||||
def __init__(self, class_type: Type[T], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, class_type: type[T], context: ModuleType | None) -> None:
|
||||
super().__init__(class_type, context)
|
||||
|
||||
|
||||
|
@ -295,7 +289,7 @@ class UnionSerializer(Serializer):
|
|||
class LiteralSerializer(Serializer):
|
||||
generator: Serializer
|
||||
|
||||
def __init__(self, values: Tuple[Any, ...], context: Optional[ModuleType]) -> None:
|
||||
def __init__(self, values: tuple[Any, ...], context: ModuleType | None) -> None:
|
||||
literal_type_tuple = tuple(type(value) for value in values)
|
||||
literal_type_set = set(literal_type_tuple)
|
||||
if len(literal_type_set) != 1:
|
||||
|
@ -312,12 +306,12 @@ class LiteralSerializer(Serializer):
|
|||
|
||||
|
||||
class UntypedNamedTupleSerializer(Serializer):
|
||||
fields: Dict[str, str]
|
||||
fields: dict[str, str]
|
||||
|
||||
def __init__(self, class_type: Type[NamedTuple]) -> None:
|
||||
def __init__(self, class_type: type[NamedTuple]) -> None:
|
||||
# named tuples are also instances of tuple
|
||||
self.fields = {}
|
||||
field_names: Tuple[str, ...] = class_type._fields
|
||||
field_names: tuple[str, ...] = class_type._fields
|
||||
for field_name in field_names:
|
||||
self.fields[field_name] = python_field_to_json_property(field_name)
|
||||
|
||||
|
@ -351,7 +345,7 @@ class UntypedClassSerializer(Serializer):
|
|||
return object_dict
|
||||
|
||||
|
||||
def create_serializer(typ: TypeLike, context: Optional[ModuleType] = None) -> Serializer:
|
||||
def create_serializer(typ: TypeLike, context: ModuleType | None = None) -> Serializer:
|
||||
"""
|
||||
Creates a serializer engine to produce an object that can be directly converted into a JSON string.
|
||||
|
||||
|
@ -376,8 +370,8 @@ def create_serializer(typ: TypeLike, context: Optional[ModuleType] = None) -> Se
|
|||
return _get_serializer(typ, context)
|
||||
|
||||
|
||||
def _get_serializer(typ: TypeLike, context: Optional[ModuleType]) -> Serializer:
|
||||
if isinstance(typ, (str, typing.ForwardRef)):
|
||||
def _get_serializer(typ: TypeLike, context: ModuleType | None) -> Serializer:
|
||||
if isinstance(typ, str | typing.ForwardRef):
|
||||
if context is None:
|
||||
raise TypeError(f"missing context for evaluating type: {typ}")
|
||||
|
||||
|
@ -390,13 +384,13 @@ def _get_serializer(typ: TypeLike, context: Optional[ModuleType]) -> Serializer:
|
|||
return _create_serializer(typ, context)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
@functools.cache
|
||||
def _fetch_serializer(typ: type) -> Serializer:
|
||||
context = sys.modules[typ.__module__]
|
||||
return _create_serializer(typ, context)
|
||||
|
||||
|
||||
def _create_serializer(typ: TypeLike, context: Optional[ModuleType]) -> Serializer:
|
||||
def _create_serializer(typ: TypeLike, context: ModuleType | None) -> Serializer:
|
||||
# check for well-known types
|
||||
if typ is type(None):
|
||||
return NoneSerializer()
|
||||
|
|
|
@ -4,18 +4,18 @@
|
|||
# This source code is licensed under the terms described in the LICENSE file in
|
||||
# the root directory of this source tree.
|
||||
|
||||
from typing import Any, Dict, Tuple, Type, TypeVar
|
||||
from typing import Any, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class SlotsMeta(type):
|
||||
def __new__(cls: Type[T], name: str, bases: Tuple[type, ...], ns: Dict[str, Any]) -> T:
|
||||
def __new__(cls: type[T], name: str, bases: tuple[type, ...], ns: dict[str, Any]) -> T:
|
||||
# caller may have already provided slots, in which case just retain them and keep going
|
||||
slots: Tuple[str, ...] = ns.get("__slots__", ())
|
||||
slots: tuple[str, ...] = ns.get("__slots__", ())
|
||||
|
||||
# add fields with type annotations to slots
|
||||
annotations: Dict[str, Any] = ns.get("__annotations__", {})
|
||||
annotations: dict[str, Any] = ns.get("__annotations__", {})
|
||||
members = tuple(member for member in annotations.keys() if member not in slots)
|
||||
|
||||
# assign slots
|
||||
|
|
|
@ -10,14 +10,15 @@ Type-safe data interchange for Python data classes.
|
|||
:see: https://github.com/hunyadi/strong_typing
|
||||
"""
|
||||
|
||||
from typing import Callable, Dict, Iterable, List, Optional, Set, TypeVar
|
||||
from collections.abc import Callable, Iterable
|
||||
from typing import TypeVar
|
||||
|
||||
from .inspection import TypeCollector
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def topological_sort(graph: Dict[T, Set[T]]) -> List[T]:
|
||||
def topological_sort(graph: dict[T, set[T]]) -> list[T]:
|
||||
"""
|
||||
Performs a topological sort of a graph.
|
||||
|
||||
|
@ -29,9 +30,9 @@ def topological_sort(graph: Dict[T, Set[T]]) -> List[T]:
|
|||
"""
|
||||
|
||||
# empty list that will contain the sorted nodes (in reverse order)
|
||||
ordered: List[T] = []
|
||||
ordered: list[T] = []
|
||||
|
||||
seen: Dict[T, bool] = {}
|
||||
seen: dict[T, bool] = {}
|
||||
|
||||
def _visit(n: T) -> None:
|
||||
status = seen.get(n)
|
||||
|
@ -57,8 +58,8 @@ def topological_sort(graph: Dict[T, Set[T]]) -> List[T]:
|
|||
|
||||
def type_topological_sort(
|
||||
types: Iterable[type],
|
||||
dependency_fn: Optional[Callable[[type], Iterable[type]]] = None,
|
||||
) -> List[type]:
|
||||
dependency_fn: Callable[[type], Iterable[type]] | None = None,
|
||||
) -> list[type]:
|
||||
"""
|
||||
Performs a topological sort of a list of types.
|
||||
|
||||
|
@ -78,7 +79,7 @@ def type_topological_sort(
|
|||
graph = collector.graph
|
||||
|
||||
if dependency_fn:
|
||||
new_types: Set[type] = set()
|
||||
new_types: set[type] = set()
|
||||
for source_type, references in graph.items():
|
||||
dependent_types = dependency_fn(source_type)
|
||||
references.update(dependent_types)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue