1st Version
This commit is contained in:
81
.venv/lib/python3.10/site-packages/marshmallow/__init__.py
Normal file
81
.venv/lib/python3.10/site-packages/marshmallow/__init__.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.metadata
|
||||
import typing
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
from marshmallow.decorators import (
|
||||
post_dump,
|
||||
post_load,
|
||||
pre_dump,
|
||||
pre_load,
|
||||
validates,
|
||||
validates_schema,
|
||||
)
|
||||
from marshmallow.exceptions import ValidationError
|
||||
from marshmallow.schema import Schema, SchemaOpts
|
||||
from marshmallow.utils import EXCLUDE, INCLUDE, RAISE, missing, pprint
|
||||
|
||||
from . import fields
|
||||
|
||||
|
||||
def __getattr__(name: str) -> typing.Any:
|
||||
import warnings
|
||||
|
||||
if name == "__version__":
|
||||
warnings.warn(
|
||||
"The '__version__' attribute is deprecated and will be removed in"
|
||||
" in a future version. Use feature detection or"
|
||||
" 'importlib.metadata.version(\"marshmallow\")' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return importlib.metadata.version("marshmallow")
|
||||
|
||||
if name == "__parsed_version__":
|
||||
warnings.warn(
|
||||
"The '__parsed_version__' attribute is deprecated and will be removed in"
|
||||
" in a future version. Use feature detection or"
|
||||
" 'packaging.Version(importlib.metadata.version(\"marshmallow\"))' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return Version(importlib.metadata.version("marshmallow"))
|
||||
|
||||
if name == "__version_info__":
|
||||
warnings.warn(
|
||||
"The '__version_info__' attribute is deprecated and will be removed in"
|
||||
" in a future version. Use feature detection or"
|
||||
" 'packaging.Version(importlib.metadata.version(\"marshmallow\")).release' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
__parsed_version__ = Version(importlib.metadata.version("marshmallow"))
|
||||
__version_info__: tuple[int, int, int] | tuple[
|
||||
int, int, int, str, int
|
||||
] = __parsed_version__.release # type: ignore[assignment]
|
||||
if __parsed_version__.pre:
|
||||
__version_info__ += __parsed_version__.pre # type: ignore[assignment]
|
||||
return __version_info__
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"EXCLUDE",
|
||||
"INCLUDE",
|
||||
"RAISE",
|
||||
"Schema",
|
||||
"SchemaOpts",
|
||||
"fields",
|
||||
"validates",
|
||||
"validates_schema",
|
||||
"pre_dump",
|
||||
"post_dump",
|
||||
"pre_load",
|
||||
"post_load",
|
||||
"pprint",
|
||||
"ValidationError",
|
||||
"missing",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
64
.venv/lib/python3.10/site-packages/marshmallow/base.py
Normal file
64
.venv/lib/python3.10/site-packages/marshmallow/base.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Abstract base classes.
|
||||
|
||||
These are necessary to avoid circular imports between schema.py and fields.py.
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is treated as private API.
|
||||
Users should not need to use this module directly.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class FieldABC(ABC):
|
||||
"""Abstract base class from which all Field classes inherit."""
|
||||
|
||||
parent = None
|
||||
name = None
|
||||
root = None
|
||||
|
||||
@abstractmethod
|
||||
def serialize(self, attr, obj, accessor=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def deserialize(self, value):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _serialize(self, value, attr, obj, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _deserialize(self, value, attr, data, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class SchemaABC(ABC):
|
||||
"""Abstract base class from which all Schemas inherit."""
|
||||
|
||||
@abstractmethod
|
||||
def dump(self, obj, *, many: bool | None = None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dumps(self, obj, *, many: bool | None = None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def load(self, data, *, many: bool | None = None, partial=None, unknown=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def loads(
|
||||
self,
|
||||
json_data,
|
||||
*,
|
||||
many: bool | None = None,
|
||||
partial=None,
|
||||
unknown=None,
|
||||
**kwargs,
|
||||
):
|
||||
pass
|
||||
@@ -0,0 +1,92 @@
|
||||
"""A registry of :class:`Schema <marshmallow.Schema>` classes. This allows for string
|
||||
lookup of schemas, which may be used with
|
||||
class:`fields.Nested <marshmallow.fields.Nested>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is treated as private API.
|
||||
Users should not need to use this module directly.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from marshmallow.exceptions import RegistryError
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from marshmallow import Schema
|
||||
|
||||
SchemaType = typing.Type[Schema]
|
||||
|
||||
# {
|
||||
# <class_name>: <list of class objects>
|
||||
# <module_path_to_class>: <list of class objects>
|
||||
# }
|
||||
_registry = {} # type: dict[str, list[SchemaType]]
|
||||
|
||||
|
||||
def register(classname: str, cls: SchemaType) -> None:
|
||||
"""Add a class to the registry of serializer classes. When a class is
|
||||
registered, an entry for both its classname and its full, module-qualified
|
||||
path are added to the registry.
|
||||
|
||||
Example: ::
|
||||
|
||||
class MyClass:
|
||||
pass
|
||||
|
||||
register('MyClass', MyClass)
|
||||
# Registry:
|
||||
# {
|
||||
# 'MyClass': [path.to.MyClass],
|
||||
# 'path.to.MyClass': [path.to.MyClass],
|
||||
# }
|
||||
|
||||
"""
|
||||
# Module where the class is located
|
||||
module = cls.__module__
|
||||
# Full module path to the class
|
||||
# e.g. user.schemas.UserSchema
|
||||
fullpath = ".".join([module, classname])
|
||||
# If the class is already registered; need to check if the entries are
|
||||
# in the same module as cls to avoid having multiple instances of the same
|
||||
# class in the registry
|
||||
if classname in _registry and not any(
|
||||
each.__module__ == module for each in _registry[classname]
|
||||
):
|
||||
_registry[classname].append(cls)
|
||||
elif classname not in _registry:
|
||||
_registry[classname] = [cls]
|
||||
|
||||
# Also register the full path
|
||||
if fullpath not in _registry:
|
||||
_registry.setdefault(fullpath, []).append(cls)
|
||||
else:
|
||||
# If fullpath does exist, replace existing entry
|
||||
_registry[fullpath] = [cls]
|
||||
return None
|
||||
|
||||
|
||||
def get_class(classname: str, all: bool = False) -> list[SchemaType] | SchemaType:
|
||||
"""Retrieve a class from the registry.
|
||||
|
||||
:raises: marshmallow.exceptions.RegistryError if the class cannot be found
|
||||
or if there are multiple entries for the given class name.
|
||||
"""
|
||||
try:
|
||||
classes = _registry[classname]
|
||||
except KeyError as error:
|
||||
raise RegistryError(
|
||||
f"Class with name {classname!r} was not found. You may need "
|
||||
"to import the class."
|
||||
) from error
|
||||
if len(classes) > 1:
|
||||
if all:
|
||||
return _registry[classname]
|
||||
raise RegistryError(
|
||||
f"Multiple classes with name {classname!r} "
|
||||
"were found. Please use the full, "
|
||||
"module-qualified path."
|
||||
)
|
||||
else:
|
||||
return _registry[classname][0]
|
||||
224
.venv/lib/python3.10/site-packages/marshmallow/decorators.py
Normal file
224
.venv/lib/python3.10/site-packages/marshmallow/decorators.py
Normal file
@@ -0,0 +1,224 @@
|
||||
"""Decorators for registering schema pre-processing and post-processing methods.
|
||||
These should be imported from the top-level `marshmallow` module.
|
||||
|
||||
Methods decorated with
|
||||
`pre_load <marshmallow.decorators.pre_load>`, `post_load <marshmallow.decorators.post_load>`,
|
||||
`pre_dump <marshmallow.decorators.pre_dump>`, `post_dump <marshmallow.decorators.post_dump>`,
|
||||
and `validates_schema <marshmallow.decorators.validates_schema>` receive
|
||||
``many`` as a keyword argument. In addition, `pre_load <marshmallow.decorators.pre_load>`,
|
||||
`post_load <marshmallow.decorators.post_load>`,
|
||||
and `validates_schema <marshmallow.decorators.validates_schema>` receive
|
||||
``partial``. If you don't need these arguments, add ``**kwargs`` to your method
|
||||
signature.
|
||||
|
||||
|
||||
Example: ::
|
||||
|
||||
from marshmallow import (
|
||||
Schema, pre_load, pre_dump, post_load, validates_schema,
|
||||
validates, fields, ValidationError
|
||||
)
|
||||
|
||||
class UserSchema(Schema):
|
||||
|
||||
email = fields.Str(required=True)
|
||||
age = fields.Integer(required=True)
|
||||
|
||||
@post_load
|
||||
def lowerstrip_email(self, item, many, **kwargs):
|
||||
item['email'] = item['email'].lower().strip()
|
||||
return item
|
||||
|
||||
@pre_load(pass_many=True)
|
||||
def remove_envelope(self, data, many, **kwargs):
|
||||
namespace = 'results' if many else 'result'
|
||||
return data[namespace]
|
||||
|
||||
@post_dump(pass_many=True)
|
||||
def add_envelope(self, data, many, **kwargs):
|
||||
namespace = 'results' if many else 'result'
|
||||
return {namespace: data}
|
||||
|
||||
@validates_schema
|
||||
def validate_email(self, data, **kwargs):
|
||||
if len(data['email']) < 3:
|
||||
raise ValidationError('Email must be more than 3 characters', 'email')
|
||||
|
||||
@validates('age')
|
||||
def validate_age(self, data, **kwargs):
|
||||
if data < 14:
|
||||
raise ValidationError('Too young!')
|
||||
|
||||
.. note::
|
||||
These decorators only work with instance methods. Class and static
|
||||
methods are not supported.
|
||||
|
||||
.. warning::
|
||||
The invocation order of decorated methods of the same type is not guaranteed.
|
||||
If you need to guarantee order of different processing steps, you should put
|
||||
them in the same processing method.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import Any, Callable, cast
|
||||
|
||||
PRE_DUMP = "pre_dump"
|
||||
POST_DUMP = "post_dump"
|
||||
PRE_LOAD = "pre_load"
|
||||
POST_LOAD = "post_load"
|
||||
VALIDATES = "validates"
|
||||
VALIDATES_SCHEMA = "validates_schema"
|
||||
|
||||
|
||||
class MarshmallowHook:
|
||||
__marshmallow_hook__: dict[tuple[str, bool] | str, Any] | None = None
|
||||
|
||||
|
||||
def validates(field_name: str) -> Callable[..., Any]:
|
||||
"""Register a field validator.
|
||||
|
||||
:param str field_name: Name of the field that the method validates.
|
||||
"""
|
||||
return set_hook(None, VALIDATES, field_name=field_name)
|
||||
|
||||
|
||||
def validates_schema(
|
||||
fn: Callable[..., Any] | None = None,
|
||||
pass_many: bool = False,
|
||||
pass_original: bool = False,
|
||||
skip_on_field_errors: bool = True,
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a schema-level validator.
|
||||
|
||||
By default it receives a single object at a time, transparently handling the ``many``
|
||||
argument passed to the `Schema`'s :func:`~marshmallow.Schema.validate` call.
|
||||
If ``pass_many=True``, the raw data (which may be a collection) is passed.
|
||||
|
||||
If ``pass_original=True``, the original data (before unmarshalling) will be passed as
|
||||
an additional argument to the method.
|
||||
|
||||
If ``skip_on_field_errors=True``, this validation method will be skipped whenever
|
||||
validation errors have been detected when validating fields.
|
||||
|
||||
.. versionchanged:: 3.0.0b1
|
||||
``skip_on_field_errors`` defaults to `True`.
|
||||
|
||||
.. versionchanged:: 3.0.0
|
||||
``partial`` and ``many`` are always passed as keyword arguments to
|
||||
the decorated method.
|
||||
"""
|
||||
return set_hook(
|
||||
fn,
|
||||
(VALIDATES_SCHEMA, pass_many),
|
||||
pass_original=pass_original,
|
||||
skip_on_field_errors=skip_on_field_errors,
|
||||
)
|
||||
|
||||
|
||||
def pre_dump(
|
||||
fn: Callable[..., Any] | None = None, pass_many: bool = False
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a method to invoke before serializing an object. The method
|
||||
receives the object to be serialized and returns the processed object.
|
||||
|
||||
By default it receives a single object at a time, transparently handling the ``many``
|
||||
argument passed to the `Schema`'s :func:`~marshmallow.Schema.dump` call.
|
||||
If ``pass_many=True``, the raw data (which may be a collection) is passed.
|
||||
|
||||
.. versionchanged:: 3.0.0
|
||||
``many`` is always passed as a keyword arguments to the decorated method.
|
||||
"""
|
||||
return set_hook(fn, (PRE_DUMP, pass_many))
|
||||
|
||||
|
||||
def post_dump(
|
||||
fn: Callable[..., Any] | None = None,
|
||||
pass_many: bool = False,
|
||||
pass_original: bool = False,
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a method to invoke after serializing an object. The method
|
||||
receives the serialized object and returns the processed object.
|
||||
|
||||
By default it receives a single object at a time, transparently handling the ``many``
|
||||
argument passed to the `Schema`'s :func:`~marshmallow.Schema.dump` call.
|
||||
If ``pass_many=True``, the raw data (which may be a collection) is passed.
|
||||
|
||||
If ``pass_original=True``, the original data (before serializing) will be passed as
|
||||
an additional argument to the method.
|
||||
|
||||
.. versionchanged:: 3.0.0
|
||||
``many`` is always passed as a keyword arguments to the decorated method.
|
||||
"""
|
||||
return set_hook(fn, (POST_DUMP, pass_many), pass_original=pass_original)
|
||||
|
||||
|
||||
def pre_load(
|
||||
fn: Callable[..., Any] | None = None, pass_many: bool = False
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a method to invoke before deserializing an object. The method
|
||||
receives the data to be deserialized and returns the processed data.
|
||||
|
||||
By default it receives a single object at a time, transparently handling the ``many``
|
||||
argument passed to the `Schema`'s :func:`~marshmallow.Schema.load` call.
|
||||
If ``pass_many=True``, the raw data (which may be a collection) is passed.
|
||||
|
||||
.. versionchanged:: 3.0.0
|
||||
``partial`` and ``many`` are always passed as keyword arguments to
|
||||
the decorated method.
|
||||
"""
|
||||
return set_hook(fn, (PRE_LOAD, pass_many))
|
||||
|
||||
|
||||
def post_load(
|
||||
fn: Callable[..., Any] | None = None,
|
||||
pass_many: bool = False,
|
||||
pass_original: bool = False,
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a method to invoke after deserializing an object. The method
|
||||
receives the deserialized data and returns the processed data.
|
||||
|
||||
By default it receives a single object at a time, transparently handling the ``many``
|
||||
argument passed to the `Schema`'s :func:`~marshmallow.Schema.load` call.
|
||||
If ``pass_many=True``, the raw data (which may be a collection) is passed.
|
||||
|
||||
If ``pass_original=True``, the original data (before deserializing) will be passed as
|
||||
an additional argument to the method.
|
||||
|
||||
.. versionchanged:: 3.0.0
|
||||
``partial`` and ``many`` are always passed as keyword arguments to
|
||||
the decorated method.
|
||||
"""
|
||||
return set_hook(fn, (POST_LOAD, pass_many), pass_original=pass_original)
|
||||
|
||||
|
||||
def set_hook(
|
||||
fn: Callable[..., Any] | None, key: tuple[str, bool] | str, **kwargs: Any
|
||||
) -> Callable[..., Any]:
|
||||
"""Mark decorated function as a hook to be picked up later.
|
||||
You should not need to use this method directly.
|
||||
|
||||
.. note::
|
||||
Currently only works with functions and instance methods. Class and
|
||||
static methods are not supported.
|
||||
|
||||
:return: Decorated function if supplied, else this decorator with its args
|
||||
bound.
|
||||
"""
|
||||
# Allow using this as either a decorator or a decorator factory.
|
||||
if fn is None:
|
||||
return functools.partial(set_hook, key=key, **kwargs)
|
||||
|
||||
# Set a __marshmallow_hook__ attribute instead of wrapping in some class,
|
||||
# because I still want this to end up as a normal (unbound) method.
|
||||
function = cast(MarshmallowHook, fn)
|
||||
try:
|
||||
hook_config = function.__marshmallow_hook__
|
||||
except AttributeError:
|
||||
function.__marshmallow_hook__ = hook_config = {}
|
||||
# Also save the kwargs for the tagged function on
|
||||
# __marshmallow_hook__, keyed by (<tag>, <pass_many>)
|
||||
if hook_config is not None:
|
||||
hook_config[key] = kwargs
|
||||
|
||||
return fn
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Utilities for storing collections of error messages.
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is treated as private API.
|
||||
Users should not need to use this module directly.
|
||||
"""
|
||||
|
||||
from marshmallow.exceptions import SCHEMA
|
||||
|
||||
|
||||
class ErrorStore:
|
||||
def __init__(self):
|
||||
#: Dictionary of errors stored during serialization
|
||||
self.errors = {}
|
||||
|
||||
def store_error(self, messages, field_name=SCHEMA, index=None):
|
||||
# field error -> store/merge error messages under field name key
|
||||
# schema error -> if string or list, store/merge under _schema key
|
||||
# -> if dict, store/merge with other top-level keys
|
||||
if field_name != SCHEMA or not isinstance(messages, dict):
|
||||
messages = {field_name: messages}
|
||||
if index is not None:
|
||||
messages = {index: messages}
|
||||
self.errors = merge_errors(self.errors, messages)
|
||||
|
||||
|
||||
def merge_errors(errors1, errors2):
|
||||
"""Deeply merge two error messages.
|
||||
|
||||
The format of ``errors1`` and ``errors2`` matches the ``message``
|
||||
parameter of :exc:`marshmallow.exceptions.ValidationError`.
|
||||
"""
|
||||
if not errors1:
|
||||
return errors2
|
||||
if not errors2:
|
||||
return errors1
|
||||
if isinstance(errors1, list):
|
||||
if isinstance(errors2, list):
|
||||
return errors1 + errors2
|
||||
if isinstance(errors2, dict):
|
||||
return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
|
||||
return errors1 + [errors2]
|
||||
if isinstance(errors1, dict):
|
||||
if isinstance(errors2, list):
|
||||
return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
|
||||
if isinstance(errors2, dict):
|
||||
errors = dict(errors1)
|
||||
for key, val in errors2.items():
|
||||
if key in errors:
|
||||
errors[key] = merge_errors(errors[key], val)
|
||||
else:
|
||||
errors[key] = val
|
||||
return errors
|
||||
return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
|
||||
if isinstance(errors2, list):
|
||||
return [errors1] + errors2
|
||||
if isinstance(errors2, dict):
|
||||
return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
|
||||
return [errors1, errors2]
|
||||
70
.venv/lib/python3.10/site-packages/marshmallow/exceptions.py
Normal file
70
.venv/lib/python3.10/site-packages/marshmallow/exceptions.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Exception classes for marshmallow-related errors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
# Key used for schema-level validation errors
|
||||
SCHEMA = "_schema"
|
||||
|
||||
|
||||
class MarshmallowError(Exception):
|
||||
"""Base class for all marshmallow-related errors."""
|
||||
|
||||
|
||||
class ValidationError(MarshmallowError):
|
||||
"""Raised when validation fails on a field or schema.
|
||||
|
||||
Validators and custom fields should raise this exception.
|
||||
|
||||
:param message: An error message, list of error messages, or dict of
|
||||
error messages. If a dict, the keys are subitems and the values are error messages.
|
||||
:param field_name: Field name to store the error on.
|
||||
If `None`, the error is stored as schema-level error.
|
||||
:param data: Raw input data.
|
||||
:param valid_data: Valid (de)serialized data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str | list | dict,
|
||||
field_name: str = SCHEMA,
|
||||
data: typing.Mapping[str, typing.Any]
|
||||
| typing.Iterable[typing.Mapping[str, typing.Any]]
|
||||
| None = None,
|
||||
valid_data: list[dict[str, typing.Any]] | dict[str, typing.Any] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.messages = [message] if isinstance(message, (str, bytes)) else message
|
||||
self.field_name = field_name
|
||||
self.data = data
|
||||
self.valid_data = valid_data
|
||||
self.kwargs = kwargs
|
||||
super().__init__(message)
|
||||
|
||||
def normalized_messages(self):
|
||||
if self.field_name == SCHEMA and isinstance(self.messages, dict):
|
||||
return self.messages
|
||||
return {self.field_name: self.messages}
|
||||
|
||||
@property
|
||||
def messages_dict(self) -> dict[str, typing.Any]:
|
||||
if not isinstance(self.messages, dict):
|
||||
raise TypeError(
|
||||
"cannot access 'messages_dict' when 'messages' is of type "
|
||||
+ type(self.messages).__name__
|
||||
)
|
||||
return self.messages
|
||||
|
||||
|
||||
class RegistryError(NameError):
|
||||
"""Raised when an invalid operation is performed on the serializer
|
||||
class registry.
|
||||
"""
|
||||
|
||||
|
||||
class StringNotCollectionError(MarshmallowError, TypeError):
|
||||
"""Raised when a string is passed when a list of strings is expected."""
|
||||
|
||||
|
||||
class FieldInstanceResolutionError(MarshmallowError, TypeError):
|
||||
"""Raised when schema to instantiate is neither a Schema class nor an instance."""
|
||||
2112
.venv/lib/python3.10/site-packages/marshmallow/fields.py
Normal file
2112
.venv/lib/python3.10/site-packages/marshmallow/fields.py
Normal file
File diff suppressed because it is too large
Load Diff
89
.venv/lib/python3.10/site-packages/marshmallow/orderedset.py
Normal file
89
.venv/lib/python3.10/site-packages/marshmallow/orderedset.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# OrderedSet
|
||||
# Copyright (c) 2009 Raymond Hettinger
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
from collections.abc import MutableSet
|
||||
|
||||
|
||||
class OrderedSet(MutableSet):
|
||||
def __init__(self, iterable=None):
|
||||
self.end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.map = {} # key --> [key, prev, next]
|
||||
if iterable is not None:
|
||||
self |= iterable
|
||||
|
||||
def __len__(self):
|
||||
return len(self.map)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.map
|
||||
|
||||
def add(self, key):
|
||||
if key not in self.map:
|
||||
end = self.end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.map[key] = [key, curr, end]
|
||||
|
||||
def discard(self, key):
|
||||
if key in self.map:
|
||||
key, prev, next = self.map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def pop(self, last=True):
|
||||
if not self:
|
||||
raise KeyError("set is empty")
|
||||
key = self.end[1][0] if last else self.end[2][0]
|
||||
self.discard(key)
|
||||
return key
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return f"{self.__class__.__name__}()"
|
||||
return f"{self.__class__.__name__}({list(self)!r})"
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedSet):
|
||||
return len(self) == len(other) and list(self) == list(other)
|
||||
return set(self) == set(other)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = OrderedSet("abracadaba")
|
||||
t = OrderedSet("simsalabim")
|
||||
print(s | t)
|
||||
print(s & t)
|
||||
print(s - t)
|
||||
1229
.venv/lib/python3.10/site-packages/marshmallow/schema.py
Normal file
1229
.venv/lib/python3.10/site-packages/marshmallow/schema.py
Normal file
File diff suppressed because it is too large
Load Diff
11
.venv/lib/python3.10/site-packages/marshmallow/types.py
Normal file
11
.venv/lib/python3.10/site-packages/marshmallow/types.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Type aliases.
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is provisional. Types may be modified, added, and removed between minor releases.
|
||||
"""
|
||||
import typing
|
||||
|
||||
StrSequenceOrSet = typing.Union[typing.Sequence[str], typing.AbstractSet[str]]
|
||||
Tag = typing.Union[str, typing.Tuple[str, bool]]
|
||||
Validator = typing.Callable[[typing.Any], typing.Any]
|
||||
375
.venv/lib/python3.10/site-packages/marshmallow/utils.py
Normal file
375
.venv/lib/python3.10/site-packages/marshmallow/utils.py
Normal file
@@ -0,0 +1,375 @@
|
||||
"""Utility methods for marshmallow."""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import datetime as dt
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
import typing
|
||||
import warnings
|
||||
from collections.abc import Mapping
|
||||
from email.utils import format_datetime, parsedate_to_datetime
|
||||
from pprint import pprint as py_pprint
|
||||
|
||||
from marshmallow.base import FieldABC
|
||||
from marshmallow.exceptions import FieldInstanceResolutionError
|
||||
from marshmallow.warnings import RemovedInMarshmallow4Warning
|
||||
|
||||
EXCLUDE = "exclude"
|
||||
INCLUDE = "include"
|
||||
RAISE = "raise"
|
||||
_UNKNOWN_VALUES = {EXCLUDE, INCLUDE, RAISE}
|
||||
|
||||
|
||||
class _Missing:
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __copy__(self):
|
||||
return self
|
||||
|
||||
def __deepcopy__(self, _):
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "<marshmallow.missing>"
|
||||
|
||||
|
||||
# Singleton value that indicates that a field's value is missing from input
|
||||
# dict passed to :meth:`Schema.load`. If the field's value is not required,
|
||||
# it's ``default`` value is used.
|
||||
missing = _Missing()
|
||||
|
||||
|
||||
def is_generator(obj) -> bool:
|
||||
"""Return True if ``obj`` is a generator"""
|
||||
return inspect.isgeneratorfunction(obj) or inspect.isgenerator(obj)
|
||||
|
||||
|
||||
def is_iterable_but_not_string(obj) -> bool:
|
||||
"""Return True if ``obj`` is an iterable object that isn't a string."""
|
||||
return (hasattr(obj, "__iter__") and not hasattr(obj, "strip")) or is_generator(obj)
|
||||
|
||||
|
||||
def is_collection(obj) -> bool:
|
||||
"""Return True if ``obj`` is a collection type, e.g list, tuple, queryset."""
|
||||
return is_iterable_but_not_string(obj) and not isinstance(obj, Mapping)
|
||||
|
||||
|
||||
def is_instance_or_subclass(val, class_) -> bool:
|
||||
"""Return True if ``val`` is either a subclass or instance of ``class_``."""
|
||||
try:
|
||||
return issubclass(val, class_)
|
||||
except TypeError:
|
||||
return isinstance(val, class_)
|
||||
|
||||
|
||||
def is_keyed_tuple(obj) -> bool:
|
||||
"""Return True if ``obj`` has keyed tuple behavior, such as
|
||||
namedtuples or SQLAlchemy's KeyedTuples.
|
||||
"""
|
||||
return isinstance(obj, tuple) and hasattr(obj, "_fields")
|
||||
|
||||
|
||||
def pprint(obj, *args, **kwargs) -> None:
|
||||
"""Pretty-printing function that can pretty-print OrderedDicts
|
||||
like regular dictionaries. Useful for printing the output of
|
||||
:meth:`marshmallow.Schema.dump`.
|
||||
|
||||
.. deprecated:: 3.7.0
|
||||
marshmallow.pprint will be removed in marshmallow 4.
|
||||
"""
|
||||
warnings.warn(
|
||||
"marshmallow's pprint function is deprecated and will be removed in marshmallow 4.",
|
||||
RemovedInMarshmallow4Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if isinstance(obj, collections.OrderedDict):
|
||||
print(json.dumps(obj, *args, **kwargs))
|
||||
else:
|
||||
py_pprint(obj, *args, **kwargs)
|
||||
|
||||
|
||||
# https://stackoverflow.com/a/27596917
|
||||
def is_aware(datetime: dt.datetime) -> bool:
|
||||
return (
|
||||
datetime.tzinfo is not None and datetime.tzinfo.utcoffset(datetime) is not None
|
||||
)
|
||||
|
||||
|
||||
def from_rfc(datestring: str) -> dt.datetime:
|
||||
"""Parse a RFC822-formatted datetime string and return a datetime object.
|
||||
|
||||
https://stackoverflow.com/questions/885015/how-to-parse-a-rfc-2822-date-time-into-a-python-datetime # noqa: B950
|
||||
"""
|
||||
return parsedate_to_datetime(datestring)
|
||||
|
||||
|
||||
def rfcformat(datetime: dt.datetime) -> str:
|
||||
"""Return the RFC822-formatted representation of a datetime object.
|
||||
|
||||
:param datetime datetime: The datetime.
|
||||
"""
|
||||
return format_datetime(datetime)
|
||||
|
||||
|
||||
# Hat tip to Django for ISO8601 deserialization functions
|
||||
|
||||
_iso8601_datetime_re = re.compile(
|
||||
r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})"
|
||||
r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
|
||||
r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
|
||||
r"(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$"
|
||||
)
|
||||
|
||||
_iso8601_date_re = re.compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$")
|
||||
|
||||
_iso8601_time_re = re.compile(
|
||||
r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})"
|
||||
r"(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?"
|
||||
)
|
||||
|
||||
|
||||
def get_fixed_timezone(offset: int | float | dt.timedelta) -> dt.timezone:
|
||||
"""Return a tzinfo instance with a fixed offset from UTC."""
|
||||
if isinstance(offset, dt.timedelta):
|
||||
offset = offset.total_seconds() // 60
|
||||
sign = "-" if offset < 0 else "+"
|
||||
hhmm = "%02d%02d" % divmod(abs(offset), 60)
|
||||
name = sign + hhmm
|
||||
return dt.timezone(dt.timedelta(minutes=offset), name)
|
||||
|
||||
|
||||
def from_iso_datetime(value):
|
||||
"""Parse a string and return a datetime.datetime.
|
||||
|
||||
This function supports time zone offsets. When the input contains one,
|
||||
the output uses a timezone with a fixed offset from UTC.
|
||||
"""
|
||||
match = _iso8601_datetime_re.match(value)
|
||||
if not match:
|
||||
raise ValueError("Not a valid ISO8601-formatted datetime string")
|
||||
kw = match.groupdict()
|
||||
kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
|
||||
tzinfo = kw.pop("tzinfo")
|
||||
if tzinfo == "Z":
|
||||
tzinfo = dt.timezone.utc
|
||||
elif tzinfo is not None:
|
||||
offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0
|
||||
offset = 60 * int(tzinfo[1:3]) + offset_mins
|
||||
if tzinfo[0] == "-":
|
||||
offset = -offset
|
||||
tzinfo = get_fixed_timezone(offset)
|
||||
kw = {k: int(v) for k, v in kw.items() if v is not None}
|
||||
kw["tzinfo"] = tzinfo
|
||||
return dt.datetime(**kw)
|
||||
|
||||
|
||||
def from_iso_time(value):
|
||||
"""Parse a string and return a datetime.time.
|
||||
|
||||
This function doesn't support time zone offsets.
|
||||
"""
|
||||
match = _iso8601_time_re.match(value)
|
||||
if not match:
|
||||
raise ValueError("Not a valid ISO8601-formatted time string")
|
||||
kw = match.groupdict()
|
||||
kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
|
||||
kw = {k: int(v) for k, v in kw.items() if v is not None}
|
||||
return dt.time(**kw)
|
||||
|
||||
|
||||
def from_iso_date(value):
|
||||
"""Parse a string and return a datetime.date."""
|
||||
match = _iso8601_date_re.match(value)
|
||||
if not match:
|
||||
raise ValueError("Not a valid ISO8601-formatted date string")
|
||||
kw = {k: int(v) for k, v in match.groupdict().items()}
|
||||
return dt.date(**kw)
|
||||
|
||||
|
||||
def from_timestamp(value: typing.Any) -> dt.datetime:
|
||||
value = float(value)
|
||||
if value < 0:
|
||||
raise ValueError("Not a valid POSIX timestamp")
|
||||
|
||||
# Load a timestamp with utc as timezone to prevent using system timezone.
|
||||
# Then set timezone to None, to let the Field handle adding timezone info.
|
||||
try:
|
||||
return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).replace(tzinfo=None)
|
||||
except OverflowError as exc:
|
||||
raise ValueError("Timestamp is too large") from exc
|
||||
except OSError as exc:
|
||||
raise ValueError("Error converting value to datetime") from exc
|
||||
|
||||
|
||||
def from_timestamp_ms(value: typing.Any) -> dt.datetime:
|
||||
value = float(value)
|
||||
return from_timestamp(value / 1000)
|
||||
|
||||
|
||||
def timestamp(
|
||||
value: dt.datetime,
|
||||
) -> float:
|
||||
if not is_aware(value):
|
||||
# When a date is naive, use UTC as zone info to prevent using system timezone.
|
||||
value = value.replace(tzinfo=dt.timezone.utc)
|
||||
return value.timestamp()
|
||||
|
||||
|
||||
def timestamp_ms(value: dt.datetime) -> float:
|
||||
return timestamp(value) * 1000
|
||||
|
||||
|
||||
def isoformat(datetime: dt.datetime) -> str:
|
||||
"""Return the ISO8601-formatted representation of a datetime object.
|
||||
|
||||
:param datetime datetime: The datetime.
|
||||
"""
|
||||
return datetime.isoformat()
|
||||
|
||||
|
||||
def to_iso_time(time: dt.time) -> str:
|
||||
return dt.time.isoformat(time)
|
||||
|
||||
|
||||
def to_iso_date(date: dt.date) -> str:
|
||||
return dt.date.isoformat(date)
|
||||
|
||||
|
||||
def ensure_text_type(val: str | bytes) -> str:
|
||||
if isinstance(val, bytes):
|
||||
val = val.decode("utf-8")
|
||||
return str(val)
|
||||
|
||||
|
||||
def pluck(dictlist: list[dict[str, typing.Any]], key: str):
|
||||
"""Extracts a list of dictionary values from a list of dictionaries.
|
||||
::
|
||||
|
||||
>>> dlist = [{'id': 1, 'name': 'foo'}, {'id': 2, 'name': 'bar'}]
|
||||
>>> pluck(dlist, 'id')
|
||||
[1, 2]
|
||||
"""
|
||||
return [d[key] for d in dictlist]
|
||||
|
||||
|
||||
# Various utilities for pulling keyed values from objects
|
||||
|
||||
|
||||
def get_value(obj, key: int | str, default=missing):
|
||||
"""Helper for pulling a keyed value off various types of objects. Fields use
|
||||
this method by default to access attributes of the source object. For object `x`
|
||||
and attribute `i`, this method first tries to access `x[i]`, and then falls back to
|
||||
`x.i` if an exception is raised.
|
||||
|
||||
.. warning::
|
||||
If an object `x` does not raise an exception when `x[i]` does not exist,
|
||||
`get_value` will never check the value `x.i`. Consider overriding
|
||||
`marshmallow.fields.Field.get_value` in this case.
|
||||
"""
|
||||
if not isinstance(key, int) and "." in key:
|
||||
return _get_value_for_keys(obj, key.split("."), default)
|
||||
else:
|
||||
return _get_value_for_key(obj, key, default)
|
||||
|
||||
|
||||
def _get_value_for_keys(obj, keys, default):
|
||||
if len(keys) == 1:
|
||||
return _get_value_for_key(obj, keys[0], default)
|
||||
else:
|
||||
return _get_value_for_keys(
|
||||
_get_value_for_key(obj, keys[0], default), keys[1:], default
|
||||
)
|
||||
|
||||
|
||||
def _get_value_for_key(obj, key, default):
|
||||
if not hasattr(obj, "__getitem__"):
|
||||
return getattr(obj, key, default)
|
||||
|
||||
try:
|
||||
return obj[key]
|
||||
except (KeyError, IndexError, TypeError, AttributeError):
|
||||
return getattr(obj, key, default)
|
||||
|
||||
|
||||
def set_value(dct: dict[str, typing.Any], key: str, value: typing.Any):
|
||||
"""Set a value in a dict. If `key` contains a '.', it is assumed
|
||||
be a path (i.e. dot-delimited string) to the value's location.
|
||||
|
||||
::
|
||||
|
||||
>>> d = {}
|
||||
>>> set_value(d, 'foo.bar', 42)
|
||||
>>> d
|
||||
{'foo': {'bar': 42}}
|
||||
"""
|
||||
if "." in key:
|
||||
head, rest = key.split(".", 1)
|
||||
target = dct.setdefault(head, {})
|
||||
if not isinstance(target, dict):
|
||||
raise ValueError(
|
||||
f"Cannot set {key} in {head} " f"due to existing value: {target}"
|
||||
)
|
||||
set_value(target, rest, value)
|
||||
else:
|
||||
dct[key] = value
|
||||
|
||||
|
||||
def callable_or_raise(obj):
|
||||
"""Check that an object is callable, else raise a :exc:`TypeError`."""
|
||||
if not callable(obj):
|
||||
raise TypeError(f"Object {obj!r} is not callable.")
|
||||
return obj
|
||||
|
||||
|
||||
def _signature(func: typing.Callable) -> list[str]:
|
||||
return list(inspect.signature(func).parameters.keys())
|
||||
|
||||
|
||||
def get_func_args(func: typing.Callable) -> list[str]:
|
||||
"""Given a callable, return a list of argument names. Handles
|
||||
`functools.partial` objects and class-based callables.
|
||||
|
||||
.. versionchanged:: 3.0.0a1
|
||||
Do not return bound arguments, eg. ``self``.
|
||||
"""
|
||||
if inspect.isfunction(func) or inspect.ismethod(func):
|
||||
return _signature(func)
|
||||
if isinstance(func, functools.partial):
|
||||
return _signature(func.func)
|
||||
# Callable class
|
||||
return _signature(func)
|
||||
|
||||
|
||||
def resolve_field_instance(cls_or_instance):
|
||||
"""Return a Schema instance from a Schema class or instance.
|
||||
|
||||
:param type|Schema cls_or_instance: Marshmallow Schema class or instance.
|
||||
"""
|
||||
if isinstance(cls_or_instance, type):
|
||||
if not issubclass(cls_or_instance, FieldABC):
|
||||
raise FieldInstanceResolutionError
|
||||
return cls_or_instance()
|
||||
else:
|
||||
if not isinstance(cls_or_instance, FieldABC):
|
||||
raise FieldInstanceResolutionError
|
||||
return cls_or_instance
|
||||
|
||||
|
||||
def timedelta_to_microseconds(value: dt.timedelta) -> int:
|
||||
"""Compute the total microseconds of a timedelta
|
||||
|
||||
https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Lib/datetime.py#L665-L667 # noqa: B950
|
||||
"""
|
||||
return (value.days * (24 * 3600) + value.seconds) * 1000000 + value.microseconds
|
||||
|
||||
|
||||
def validate_unknown_parameter_value(obj: typing.Any) -> str:
|
||||
if obj not in _UNKNOWN_VALUES:
|
||||
raise ValueError(
|
||||
f"Object {obj!r} is not a valid value for the 'unknown' parameter"
|
||||
)
|
||||
return obj
|
||||
680
.venv/lib/python3.10/site-packages/marshmallow/validate.py
Normal file
680
.venv/lib/python3.10/site-packages/marshmallow/validate.py
Normal file
@@ -0,0 +1,680 @@
|
||||
"""Validation classes for various types of data."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import typing
|
||||
from abc import ABC, abstractmethod
|
||||
from itertools import zip_longest
|
||||
from operator import attrgetter
|
||||
|
||||
from marshmallow import types
|
||||
from marshmallow.exceptions import ValidationError
|
||||
|
||||
_T = typing.TypeVar("_T")
|
||||
|
||||
|
||||
class Validator(ABC):
|
||||
"""Abstract base class for validators.
|
||||
|
||||
.. note::
|
||||
This class does not provide any validation behavior. It is only used to
|
||||
add a useful `__repr__` implementation for validators.
|
||||
"""
|
||||
|
||||
error = None # type: str | None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args = self._repr_args()
|
||||
args = f"{args}, " if args else ""
|
||||
|
||||
return f"<{self.__class__.__name__}({args}error={self.error!r})>"
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
"""A string representation of the args passed to this validator. Used by
|
||||
`__repr__`.
|
||||
"""
|
||||
return ""
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, value: typing.Any) -> typing.Any:
|
||||
...
|
||||
|
||||
|
||||
class And(Validator):
|
||||
"""Compose multiple validators and combine their error messages.
|
||||
|
||||
Example: ::
|
||||
|
||||
from marshmallow import validate, ValidationError
|
||||
|
||||
def is_even(value):
|
||||
if value % 2 != 0:
|
||||
raise ValidationError("Not an even value.")
|
||||
|
||||
validator = validate.And(validate.Range(min=0), is_even)
|
||||
validator(-1)
|
||||
# ValidationError: ['Must be greater than or equal to 0.', 'Not an even value.']
|
||||
|
||||
:param validators: Validators to combine.
|
||||
:param error: Error message to use when a validator returns ``False``.
|
||||
"""
|
||||
|
||||
default_error_message = "Invalid value."
|
||||
|
||||
def __init__(self, *validators: types.Validator, error: str | None = None):
|
||||
self.validators = tuple(validators)
|
||||
self.error = error or self.default_error_message # type: str
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"validators={self.validators!r}"
|
||||
|
||||
def __call__(self, value: typing.Any) -> typing.Any:
|
||||
errors = []
|
||||
kwargs = {}
|
||||
for validator in self.validators:
|
||||
try:
|
||||
r = validator(value)
|
||||
if not isinstance(validator, Validator) and r is False:
|
||||
raise ValidationError(self.error)
|
||||
except ValidationError as err:
|
||||
kwargs.update(err.kwargs)
|
||||
if isinstance(err.messages, dict):
|
||||
errors.append(err.messages)
|
||||
else:
|
||||
# FIXME : Get rid of cast
|
||||
errors.extend(typing.cast(list, err.messages))
|
||||
if errors:
|
||||
raise ValidationError(errors, **kwargs)
|
||||
return value
|
||||
|
||||
|
||||
class URL(Validator):
|
||||
"""Validate a URL.
|
||||
|
||||
:param relative: Whether to allow relative URLs.
|
||||
:param absolute: Whether to allow absolute URLs.
|
||||
:param error: Error message to raise in case of a validation error.
|
||||
Can be interpolated with `{input}`.
|
||||
:param schemes: Valid schemes. By default, ``http``, ``https``,
|
||||
``ftp``, and ``ftps`` are allowed.
|
||||
:param require_tld: Whether to reject non-FQDN hostnames.
|
||||
"""
|
||||
|
||||
class RegexMemoizer:
|
||||
def __init__(self):
|
||||
self._memoized = {}
|
||||
|
||||
def _regex_generator(
|
||||
self, relative: bool, absolute: bool, require_tld: bool
|
||||
) -> typing.Pattern:
|
||||
hostname_variants = [
|
||||
# a normal domain name, expressed in [A-Z0-9] chars with hyphens allowed only in the middle
|
||||
# note that the regex will be compiled with IGNORECASE, so these are upper and lowercase chars
|
||||
(
|
||||
r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
|
||||
r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)"
|
||||
),
|
||||
# or the special string 'localhost'
|
||||
r"localhost",
|
||||
# or IPv4
|
||||
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",
|
||||
# or IPv6
|
||||
r"\[[A-F0-9]*:[A-F0-9:]+\]",
|
||||
]
|
||||
if not require_tld:
|
||||
# allow dotless hostnames
|
||||
hostname_variants.append(r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)")
|
||||
|
||||
absolute_part = "".join(
|
||||
(
|
||||
# scheme (e.g. 'https://', 'ftp://', etc)
|
||||
# this is validated separately against allowed schemes, so in the regex
|
||||
# we simply want to capture its existence
|
||||
r"(?:[a-z0-9\.\-\+]*)://",
|
||||
# userinfo, for URLs encoding authentication
|
||||
# e.g. 'ftp://foo:bar@ftp.example.org/'
|
||||
r"(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?",
|
||||
# netloc, the hostname/domain part of the URL plus the optional port
|
||||
r"(?:",
|
||||
"|".join(hostname_variants),
|
||||
r")",
|
||||
r"(?::\d+)?",
|
||||
)
|
||||
)
|
||||
relative_part = r"(?:/?|[/?]\S+)\Z"
|
||||
|
||||
if relative:
|
||||
if absolute:
|
||||
parts: tuple[str, ...] = (
|
||||
r"^(",
|
||||
absolute_part,
|
||||
r")?",
|
||||
relative_part,
|
||||
)
|
||||
else:
|
||||
parts = (r"^", relative_part)
|
||||
else:
|
||||
parts = (r"^", absolute_part, relative_part)
|
||||
|
||||
return re.compile("".join(parts), re.IGNORECASE)
|
||||
|
||||
def __call__(
|
||||
self, relative: bool, absolute: bool, require_tld: bool
|
||||
) -> typing.Pattern:
|
||||
key = (relative, absolute, require_tld)
|
||||
if key not in self._memoized:
|
||||
self._memoized[key] = self._regex_generator(
|
||||
relative, absolute, require_tld
|
||||
)
|
||||
|
||||
return self._memoized[key]
|
||||
|
||||
_regex = RegexMemoizer()
|
||||
|
||||
default_message = "Not a valid URL."
|
||||
default_schemes = {"http", "https", "ftp", "ftps"}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
relative: bool = False,
|
||||
absolute: bool = True,
|
||||
schemes: types.StrSequenceOrSet | None = None,
|
||||
require_tld: bool = True,
|
||||
error: str | None = None,
|
||||
):
|
||||
if not relative and not absolute:
|
||||
raise ValueError(
|
||||
"URL validation cannot set both relative and absolute to False."
|
||||
)
|
||||
self.relative = relative
|
||||
self.absolute = absolute
|
||||
self.error = error or self.default_message # type: str
|
||||
self.schemes = schemes or self.default_schemes
|
||||
self.require_tld = require_tld
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"relative={self.relative!r}, absolute={self.absolute!r}"
|
||||
|
||||
def _format_error(self, value) -> str:
|
||||
return self.error.format(input=value)
|
||||
|
||||
def __call__(self, value: str) -> str:
|
||||
message = self._format_error(value)
|
||||
if not value:
|
||||
raise ValidationError(message)
|
||||
|
||||
# Check first if the scheme is valid
|
||||
if "://" in value:
|
||||
scheme = value.split("://")[0].lower()
|
||||
if scheme not in self.schemes:
|
||||
raise ValidationError(message)
|
||||
|
||||
regex = self._regex(self.relative, self.absolute, self.require_tld)
|
||||
|
||||
if not regex.search(value):
|
||||
raise ValidationError(message)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Email(Validator):
|
||||
"""Validate an email address.
|
||||
|
||||
:param error: Error message to raise in case of a validation error. Can be
|
||||
interpolated with `{input}`.
|
||||
"""
|
||||
|
||||
USER_REGEX = re.compile(
|
||||
r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*\Z" # dot-atom
|
||||
# quoted-string
|
||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]'
|
||||
r'|\\[\001-\011\013\014\016-\177])*"\Z)',
|
||||
re.IGNORECASE | re.UNICODE,
|
||||
)
|
||||
|
||||
DOMAIN_REGEX = re.compile(
|
||||
# domain
|
||||
r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+"
|
||||
r"(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\Z"
|
||||
# literal form, ipv4 address (SMTP 4.1.3)
|
||||
r"|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)"
|
||||
r"(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z",
|
||||
re.IGNORECASE | re.UNICODE,
|
||||
)
|
||||
|
||||
DOMAIN_WHITELIST = ("localhost",)
|
||||
|
||||
default_message = "Not a valid email address."
|
||||
|
||||
def __init__(self, *, error: str | None = None):
|
||||
self.error = error or self.default_message # type: str
|
||||
|
||||
def _format_error(self, value: str) -> str:
|
||||
return self.error.format(input=value)
|
||||
|
||||
def __call__(self, value: str) -> str:
|
||||
message = self._format_error(value)
|
||||
|
||||
if not value or "@" not in value:
|
||||
raise ValidationError(message)
|
||||
|
||||
user_part, domain_part = value.rsplit("@", 1)
|
||||
|
||||
if not self.USER_REGEX.match(user_part):
|
||||
raise ValidationError(message)
|
||||
|
||||
if domain_part not in self.DOMAIN_WHITELIST:
|
||||
if not self.DOMAIN_REGEX.match(domain_part):
|
||||
try:
|
||||
domain_part = domain_part.encode("idna").decode("ascii")
|
||||
except UnicodeError:
|
||||
pass
|
||||
else:
|
||||
if self.DOMAIN_REGEX.match(domain_part):
|
||||
return value
|
||||
raise ValidationError(message)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Range(Validator):
|
||||
"""Validator which succeeds if the value passed to it is within the specified
|
||||
range. If ``min`` is not specified, or is specified as `None`,
|
||||
no lower bound exists. If ``max`` is not specified, or is specified as `None`,
|
||||
no upper bound exists. The inclusivity of the bounds (if they exist) is configurable.
|
||||
If ``min_inclusive`` is not specified, or is specified as `True`, then
|
||||
the ``min`` bound is included in the range. If ``max_inclusive`` is not specified,
|
||||
or is specified as `True`, then the ``max`` bound is included in the range.
|
||||
|
||||
:param min: The minimum value (lower bound). If not provided, minimum
|
||||
value will not be checked.
|
||||
:param max: The maximum value (upper bound). If not provided, maximum
|
||||
value will not be checked.
|
||||
:param min_inclusive: Whether the `min` bound is included in the range.
|
||||
:param max_inclusive: Whether the `max` bound is included in the range.
|
||||
:param error: Error message to raise in case of a validation error.
|
||||
Can be interpolated with `{input}`, `{min}` and `{max}`.
|
||||
"""
|
||||
|
||||
message_min = "Must be {min_op} {{min}}."
|
||||
message_max = "Must be {max_op} {{max}}."
|
||||
message_all = "Must be {min_op} {{min}} and {max_op} {{max}}."
|
||||
|
||||
message_gte = "greater than or equal to"
|
||||
message_gt = "greater than"
|
||||
message_lte = "less than or equal to"
|
||||
message_lt = "less than"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min=None,
|
||||
max=None,
|
||||
*,
|
||||
min_inclusive: bool = True,
|
||||
max_inclusive: bool = True,
|
||||
error: str | None = None,
|
||||
):
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.error = error
|
||||
self.min_inclusive = min_inclusive
|
||||
self.max_inclusive = max_inclusive
|
||||
|
||||
# interpolate messages based on bound inclusivity
|
||||
self.message_min = self.message_min.format(
|
||||
min_op=self.message_gte if self.min_inclusive else self.message_gt
|
||||
)
|
||||
self.message_max = self.message_max.format(
|
||||
max_op=self.message_lte if self.max_inclusive else self.message_lt
|
||||
)
|
||||
self.message_all = self.message_all.format(
|
||||
min_op=self.message_gte if self.min_inclusive else self.message_gt,
|
||||
max_op=self.message_lte if self.max_inclusive else self.message_lt,
|
||||
)
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return "min={!r}, max={!r}, min_inclusive={!r}, max_inclusive={!r}".format(
|
||||
self.min, self.max, self.min_inclusive, self.max_inclusive
|
||||
)
|
||||
|
||||
def _format_error(self, value: _T, message: str) -> str:
|
||||
return (self.error or message).format(input=value, min=self.min, max=self.max)
|
||||
|
||||
def __call__(self, value: _T) -> _T:
|
||||
if self.min is not None and (
|
||||
value < self.min if self.min_inclusive else value <= self.min
|
||||
):
|
||||
message = self.message_min if self.max is None else self.message_all
|
||||
raise ValidationError(self._format_error(value, message))
|
||||
|
||||
if self.max is not None and (
|
||||
value > self.max if self.max_inclusive else value >= self.max
|
||||
):
|
||||
message = self.message_max if self.min is None else self.message_all
|
||||
raise ValidationError(self._format_error(value, message))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Length(Validator):
|
||||
"""Validator which succeeds if the value passed to it has a
|
||||
length between a minimum and maximum. Uses len(), so it
|
||||
can work for strings, lists, or anything with length.
|
||||
|
||||
:param min: The minimum length. If not provided, minimum length
|
||||
will not be checked.
|
||||
:param max: The maximum length. If not provided, maximum length
|
||||
will not be checked.
|
||||
:param equal: The exact length. If provided, maximum and minimum
|
||||
length will not be checked.
|
||||
:param error: Error message to raise in case of a validation error.
|
||||
Can be interpolated with `{input}`, `{min}` and `{max}`.
|
||||
"""
|
||||
|
||||
message_min = "Shorter than minimum length {min}."
|
||||
message_max = "Longer than maximum length {max}."
|
||||
message_all = "Length must be between {min} and {max}."
|
||||
message_equal = "Length must be {equal}."
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min: int | None = None,
|
||||
max: int | None = None,
|
||||
*,
|
||||
equal: int | None = None,
|
||||
error: str | None = None,
|
||||
):
|
||||
if equal is not None and any([min, max]):
|
||||
raise ValueError(
|
||||
"The `equal` parameter was provided, maximum or "
|
||||
"minimum parameter must not be provided."
|
||||
)
|
||||
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.error = error
|
||||
self.equal = equal
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"min={self.min!r}, max={self.max!r}, equal={self.equal!r}"
|
||||
|
||||
def _format_error(self, value: typing.Sized, message: str) -> str:
|
||||
return (self.error or message).format(
|
||||
input=value, min=self.min, max=self.max, equal=self.equal
|
||||
)
|
||||
|
||||
def __call__(self, value: typing.Sized) -> typing.Sized:
|
||||
length = len(value)
|
||||
|
||||
if self.equal is not None:
|
||||
if length != self.equal:
|
||||
raise ValidationError(self._format_error(value, self.message_equal))
|
||||
return value
|
||||
|
||||
if self.min is not None and length < self.min:
|
||||
message = self.message_min if self.max is None else self.message_all
|
||||
raise ValidationError(self._format_error(value, message))
|
||||
|
||||
if self.max is not None and length > self.max:
|
||||
message = self.message_max if self.min is None else self.message_all
|
||||
raise ValidationError(self._format_error(value, message))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Equal(Validator):
|
||||
"""Validator which succeeds if the ``value`` passed to it is
|
||||
equal to ``comparable``.
|
||||
|
||||
:param comparable: The object to compare to.
|
||||
:param error: Error message to raise in case of a validation error.
|
||||
Can be interpolated with `{input}` and `{other}`.
|
||||
"""
|
||||
|
||||
default_message = "Must be equal to {other}."
|
||||
|
||||
def __init__(self, comparable, *, error: str | None = None):
|
||||
self.comparable = comparable
|
||||
self.error = error or self.default_message # type: str
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"comparable={self.comparable!r}"
|
||||
|
||||
def _format_error(self, value: _T) -> str:
|
||||
return self.error.format(input=value, other=self.comparable)
|
||||
|
||||
def __call__(self, value: _T) -> _T:
|
||||
if value != self.comparable:
|
||||
raise ValidationError(self._format_error(value))
|
||||
return value
|
||||
|
||||
|
||||
class Regexp(Validator):
|
||||
"""Validator which succeeds if the ``value`` matches ``regex``.
|
||||
|
||||
.. note::
|
||||
|
||||
Uses `re.match`, which searches for a match at the beginning of a string.
|
||||
|
||||
:param regex: The regular expression string to use. Can also be a compiled
|
||||
regular expression pattern.
|
||||
:param flags: The regexp flags to use, for example re.IGNORECASE. Ignored
|
||||
if ``regex`` is not a string.
|
||||
:param error: Error message to raise in case of a validation error.
|
||||
Can be interpolated with `{input}` and `{regex}`.
|
||||
"""
|
||||
|
||||
default_message = "String does not match expected pattern."
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
regex: str | bytes | typing.Pattern,
|
||||
flags: int = 0,
|
||||
*,
|
||||
error: str | None = None,
|
||||
):
|
||||
self.regex = (
|
||||
re.compile(regex, flags) if isinstance(regex, (str, bytes)) else regex
|
||||
)
|
||||
self.error = error or self.default_message # type: str
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"regex={self.regex!r}"
|
||||
|
||||
def _format_error(self, value: str | bytes) -> str:
|
||||
return self.error.format(input=value, regex=self.regex.pattern)
|
||||
|
||||
@typing.overload
|
||||
def __call__(self, value: str) -> str:
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
def __call__(self, value: bytes) -> bytes:
|
||||
...
|
||||
|
||||
def __call__(self, value):
|
||||
if self.regex.match(value) is None:
|
||||
raise ValidationError(self._format_error(value))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Predicate(Validator):
|
||||
"""Call the specified ``method`` of the ``value`` object. The
|
||||
validator succeeds if the invoked method returns an object that
|
||||
evaluates to True in a Boolean context. Any additional keyword
|
||||
argument will be passed to the method.
|
||||
|
||||
:param method: The name of the method to invoke.
|
||||
:param error: Error message to raise in case of a validation error.
|
||||
Can be interpolated with `{input}` and `{method}`.
|
||||
:param kwargs: Additional keyword arguments to pass to the method.
|
||||
"""
|
||||
|
||||
default_message = "Invalid input."
|
||||
|
||||
def __init__(self, method: str, *, error: str | None = None, **kwargs):
|
||||
self.method = method
|
||||
self.error = error or self.default_message # type: str
|
||||
self.kwargs = kwargs
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"method={self.method!r}, kwargs={self.kwargs!r}"
|
||||
|
||||
def _format_error(self, value: typing.Any) -> str:
|
||||
return self.error.format(input=value, method=self.method)
|
||||
|
||||
def __call__(self, value: typing.Any) -> typing.Any:
|
||||
method = getattr(value, self.method)
|
||||
|
||||
if not method(**self.kwargs):
|
||||
raise ValidationError(self._format_error(value))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class NoneOf(Validator):
|
||||
"""Validator which fails if ``value`` is a member of ``iterable``.
|
||||
|
||||
:param iterable: A sequence of invalid values.
|
||||
:param error: Error message to raise in case of a validation error. Can be
|
||||
interpolated using `{input}` and `{values}`.
|
||||
"""
|
||||
|
||||
default_message = "Invalid input."
|
||||
|
||||
def __init__(self, iterable: typing.Iterable, *, error: str | None = None):
|
||||
self.iterable = iterable
|
||||
self.values_text = ", ".join(str(each) for each in self.iterable)
|
||||
self.error = error or self.default_message # type: str
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"iterable={self.iterable!r}"
|
||||
|
||||
def _format_error(self, value) -> str:
|
||||
return self.error.format(input=value, values=self.values_text)
|
||||
|
||||
def __call__(self, value: typing.Any) -> typing.Any:
|
||||
try:
|
||||
if value in self.iterable:
|
||||
raise ValidationError(self._format_error(value))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class OneOf(Validator):
|
||||
"""Validator which succeeds if ``value`` is a member of ``choices``.
|
||||
|
||||
:param choices: A sequence of valid values.
|
||||
:param labels: Optional sequence of labels to pair with the choices.
|
||||
:param error: Error message to raise in case of a validation error. Can be
|
||||
interpolated with `{input}`, `{choices}` and `{labels}`.
|
||||
"""
|
||||
|
||||
default_message = "Must be one of: {choices}."
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
choices: typing.Iterable,
|
||||
labels: typing.Iterable[str] | None = None,
|
||||
*,
|
||||
error: str | None = None,
|
||||
):
|
||||
self.choices = choices
|
||||
self.choices_text = ", ".join(str(choice) for choice in self.choices)
|
||||
self.labels = labels if labels is not None else []
|
||||
self.labels_text = ", ".join(str(label) for label in self.labels)
|
||||
self.error = error or self.default_message # type: str
|
||||
|
||||
def _repr_args(self) -> str:
|
||||
return f"choices={self.choices!r}, labels={self.labels!r}"
|
||||
|
||||
def _format_error(self, value) -> str:
|
||||
return self.error.format(
|
||||
input=value, choices=self.choices_text, labels=self.labels_text
|
||||
)
|
||||
|
||||
def __call__(self, value: typing.Any) -> typing.Any:
|
||||
try:
|
||||
if value not in self.choices:
|
||||
raise ValidationError(self._format_error(value))
|
||||
except TypeError as error:
|
||||
raise ValidationError(self._format_error(value)) from error
|
||||
|
||||
return value
|
||||
|
||||
def options(
|
||||
self,
|
||||
valuegetter: str | typing.Callable[[typing.Any], typing.Any] = str,
|
||||
) -> typing.Iterable[tuple[typing.Any, str]]:
|
||||
"""Return a generator over the (value, label) pairs, where value
|
||||
is a string associated with each choice. This convenience method
|
||||
is useful to populate, for instance, a form select field.
|
||||
|
||||
:param valuegetter: Can be a callable or a string. In the former case, it must
|
||||
be a one-argument callable which returns the value of a
|
||||
choice. In the latter case, the string specifies the name
|
||||
of an attribute of the choice objects. Defaults to `str()`
|
||||
or `str()`.
|
||||
"""
|
||||
valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter)
|
||||
pairs = zip_longest(self.choices, self.labels, fillvalue="")
|
||||
|
||||
return ((valuegetter(choice), label) for choice, label in pairs)
|
||||
|
||||
|
||||
class ContainsOnly(OneOf):
|
||||
"""Validator which succeeds if ``value`` is a sequence and each element
|
||||
in the sequence is also in the sequence passed as ``choices``. Empty input
|
||||
is considered valid.
|
||||
|
||||
:param iterable choices: Same as :class:`OneOf`.
|
||||
:param iterable labels: Same as :class:`OneOf`.
|
||||
:param str error: Same as :class:`OneOf`.
|
||||
|
||||
.. versionchanged:: 3.0.0b2
|
||||
Duplicate values are considered valid.
|
||||
.. versionchanged:: 3.0.0b2
|
||||
Empty input is considered valid. Use `validate.Length(min=1) <marshmallow.validate.Length>`
|
||||
to validate against empty inputs.
|
||||
"""
|
||||
|
||||
default_message = "One or more of the choices you made was not in: {choices}."
|
||||
|
||||
def _format_error(self, value) -> str:
|
||||
value_text = ", ".join(str(val) for val in value)
|
||||
return super()._format_error(value_text)
|
||||
|
||||
def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
|
||||
# We can't use set.issubset because does not handle unhashable types
|
||||
for val in value:
|
||||
if val not in self.choices:
|
||||
raise ValidationError(self._format_error(value))
|
||||
return value
|
||||
|
||||
|
||||
class ContainsNoneOf(NoneOf):
|
||||
"""Validator which fails if ``value`` is a sequence and any element
|
||||
in the sequence is a member of the sequence passed as ``iterable``. Empty input
|
||||
is considered valid.
|
||||
|
||||
:param iterable iterable: Same as :class:`NoneOf`.
|
||||
:param str error: Same as :class:`NoneOf`.
|
||||
|
||||
.. versionadded:: 3.6.0
|
||||
"""
|
||||
|
||||
default_message = "One or more of the choices you made was in: {values}."
|
||||
|
||||
def _format_error(self, value) -> str:
|
||||
value_text = ", ".join(str(val) for val in value)
|
||||
return super()._format_error(value_text)
|
||||
|
||||
def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]:
|
||||
for val in value:
|
||||
if val in self.iterable:
|
||||
raise ValidationError(self._format_error(value))
|
||||
return value
|
||||
@@ -0,0 +1,2 @@
|
||||
class RemovedInMarshmallow4Warning(DeprecationWarning):
|
||||
pass
|
||||
Reference in New Issue
Block a user