This commit is contained in:
lz_db
2025-11-16 12:31:03 +08:00
commit 0fab423a18
1451 changed files with 743213 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
import typing
import marshmallow
class Sequence(marshmallow.fields.List):
"""
A sequence field, basically an immutable version of the list field.
"""
def _deserialize( # type: ignore[override]
self,
value: typing.Any,
attr: typing.Any,
data: typing.Any,
**kwargs: typing.Any,
) -> typing.Optional[typing.Sequence[typing.Any]]:
optional_list = super()._deserialize(value, attr, data, **kwargs)
return None if optional_list is None else tuple(optional_list)
class Set(marshmallow.fields.List):
"""
A set field. A set is an unordered/mutable collection of unique elements, same for frozenset
except it's immutable.
Notes:
Beware the a Set guarantees uniqueness in the resulting list but in return the item's order
will be random. So if the order matters, use a List or Sequence !
"""
def __init__(
self,
cls_or_instance: typing.Union[marshmallow.fields.Field, type],
frozen: bool = False,
**kwargs,
):
super().__init__(cls_or_instance, **kwargs)
self.set_type: typing.Type[typing.Union[frozenset, set]] = (
frozenset if frozen else set
)
def _deserialize( # type: ignore[override]
self,
value: typing.Any,
attr: typing.Any,
data: typing.Any,
**kwargs: typing.Any,
) -> typing.Union[typing.Set[typing.Any], typing.FrozenSet[typing.Any], None]:
optional_list = super()._deserialize(value, attr, data, **kwargs)
return None if optional_list is None else self.set_type(optional_list)

View File

@@ -0,0 +1,45 @@
from typing import Any, Callable, Optional
__all__ = ("lazy_class_attribute",)
class LazyClassAttribute:
"""Descriptor decorator implementing a class-level, read-only
property, which caches its results on the class(es) on which it
operates.
"""
__slots__ = ("func", "name", "called", "forward_value")
def __init__(
self,
func: Callable[..., Any],
name: Optional[str] = None,
forward_value: Any = None,
):
self.func = func
self.name = name
self.called = False
self.forward_value = forward_value
def __get__(self, instance, cls=None):
if not cls:
cls = type(instance)
# avoid recursion
if self.called:
return self.forward_value
self.called = True
setattr(cls, self.name, self.func())
# "getattr" is used to handle bounded methods
return getattr(cls, self.name)
def __set_name__(self, owner, name):
self.name = self.name or name
lazy_class_attribute = LazyClassAttribute

View File

@@ -0,0 +1,71 @@
import inspect
from typing import Callable, Optional, Type
from mypy import nodes
from mypy.plugin import DynamicClassDefContext, Plugin
from mypy.plugins import dataclasses
import marshmallow_dataclass
_NEW_TYPE_SIG = inspect.signature(marshmallow_dataclass.NewType)
def plugin(version: str) -> Type[Plugin]:
return MarshmallowDataclassPlugin
class MarshmallowDataclassPlugin(Plugin):
def get_dynamic_class_hook(
self, fullname: str
) -> Optional[Callable[[DynamicClassDefContext], None]]:
if fullname == "marshmallow_dataclass.NewType":
return new_type_hook
return None
def get_class_decorator_hook(self, fullname: str):
if fullname == "marshmallow_dataclass.dataclass":
return dataclasses.dataclass_class_maker_callback
return None
def new_type_hook(ctx: DynamicClassDefContext) -> None:
"""
Dynamic class hook for :func:`marshmallow_dataclass.NewType`.
Uses the type of the ``typ`` argument.
"""
typ = _get_arg_by_name(ctx.call, "typ", _NEW_TYPE_SIG)
if not isinstance(typ, nodes.RefExpr):
return
info = typ.node
if not isinstance(info, nodes.TypeInfo):
return
ctx.api.add_symbol_table_node(ctx.name, nodes.SymbolTableNode(nodes.GDEF, info))
def _get_arg_by_name(
call: nodes.CallExpr, name: str, sig: inspect.Signature
) -> Optional[nodes.Expression]:
"""
Get value of argument from a call.
:return: The argument value, or ``None`` if it cannot be found.
.. warning::
This probably doesn't yet work for calls with ``*args`` and/or ``*kwargs``.
"""
args = []
kwargs = {}
for arg_name, arg_value in zip(call.arg_names, call.args):
if arg_name is None:
args.append(arg_value)
else:
kwargs[arg_name] = arg_value
try:
bound_args = sig.bind(*args, **kwargs)
except TypeError:
return None
try:
return bound_args.arguments[name]
except KeyError:
return None

View File

@@ -0,0 +1,14 @@
import sys
import marshmallow.fields
if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated
Url = Annotated[str, marshmallow.fields.Url]
Email = Annotated[str, marshmallow.fields.Email]
# Aliases
URL = Url

View File

@@ -0,0 +1,82 @@
import copy
import inspect
from typing import List, Tuple, Any, Optional
import typeguard
from marshmallow import fields, Schema, ValidationError
try:
from typeguard import TypeCheckError # type: ignore[attr-defined]
except ImportError:
# typeguard < 3
TypeCheckError = TypeError # type: ignore[misc, assignment]
if "argname" not in inspect.signature(typeguard.check_type).parameters:
def _check_type(value, expected_type, argname: str):
return typeguard.check_type(value=value, expected_type=expected_type)
else:
# typeguard < 3.0.0rc2
def _check_type(value, expected_type, argname: str):
return typeguard.check_type( # type: ignore[call-overload]
value=value, expected_type=expected_type, argname=argname
)
class Union(fields.Field):
"""A union field, composed other `Field` classes or instances.
This field serializes elements based on their type, with one of its child fields.
Example: ::
number_or_string = UnionField([
(float, fields.Float()),
(str, fields.Str())
])
:param union_fields: A list of types and their associated field instance.
:param kwargs: The same keyword arguments that :class:`Field` receives.
"""
def __init__(self, union_fields: List[Tuple[type, fields.Field]], **kwargs):
super().__init__(**kwargs)
self.union_fields = union_fields
def _bind_to_schema(self, field_name: str, schema: Schema) -> None:
super()._bind_to_schema(field_name, schema)
new_union_fields = []
for typ, field in self.union_fields:
field = copy.deepcopy(field)
field._bind_to_schema(field_name, self)
new_union_fields.append((typ, field))
self.union_fields = new_union_fields
def _serialize(self, value: Any, attr: Optional[str], obj, **kwargs) -> Any:
errors = []
if value is None:
return value
for typ, field in self.union_fields:
try:
_check_type(value=value, expected_type=typ, argname=attr or "anonymous")
return field._serialize(value, attr, obj, **kwargs)
except TypeCheckError as e:
errors.append(e)
raise TypeError(
f"Unable to serialize value with any of the fields in the union: {errors}"
)
def _deserialize(self, value: Any, attr: Optional[str], data, **kwargs) -> Any:
errors = []
for typ, field in self.union_fields:
try:
result = field.deserialize(value, **kwargs)
_check_type(
value=result, expected_type=typ, argname=attr or "anonymous"
)
return result
except (TypeCheckError, ValidationError) as e:
errors.append(e)
raise ValidationError(errors)