new models, frontend functions, public pages
This commit is contained in:
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2017-2021 Ingy döt Net
|
||||
Copyright (c) 2006-2016 Kirill Simonov
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,46 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: PyYAML
|
||||
Version: 6.0.2
|
||||
Summary: YAML parser and emitter for Python
|
||||
Home-page: https://pyyaml.org/
|
||||
Download-URL: https://pypi.org/project/PyYAML/
|
||||
Author: Kirill Simonov
|
||||
Author-email: xi@resolvent.net
|
||||
License: MIT
|
||||
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
|
||||
Project-URL: CI, https://github.com/yaml/pyyaml/actions
|
||||
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
|
||||
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
|
||||
Project-URL: Source Code, https://github.com/yaml/pyyaml
|
||||
Platform: Any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Cython
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Text Processing :: Markup
|
||||
Requires-Python: >=3.8
|
||||
License-File: LICENSE
|
||||
|
||||
YAML is a data serialization format designed for human readability
|
||||
and interaction with scripting languages. PyYAML is a YAML parser
|
||||
and emitter for Python.
|
||||
|
||||
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
|
||||
support, capable extension API, and sensible error messages. PyYAML
|
||||
supports standard YAML tags and provides Python-specific tags that
|
||||
allow to represent an arbitrary Python object.
|
||||
|
||||
PyYAML is applicable for a broad range of tasks from complex
|
||||
configuration files to object serialization and persistence.
|
||||
@@ -0,0 +1,43 @@
|
||||
PyYAML-6.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
PyYAML-6.0.2.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
||||
PyYAML-6.0.2.dist-info/METADATA,sha256=9-odFB5seu4pGPcEv7E8iyxNF51_uKnaNGjLAhz2lto,2060
|
||||
PyYAML-6.0.2.dist-info/RECORD,,
|
||||
PyYAML-6.0.2.dist-info/WHEEL,sha256=baMMpUvyD0gnRdCe6fvqCg8rft4FNTdLqZQ01WfKJmc,152
|
||||
PyYAML-6.0.2.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
|
||||
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
|
||||
_yaml/__pycache__/__init__.cpython-310.pyc,,
|
||||
yaml/__init__.py,sha256=N35S01HMesFTe0aRRMWkPj0Pa8IEbHpE9FK7cr5Bdtw,12311
|
||||
yaml/__pycache__/__init__.cpython-310.pyc,,
|
||||
yaml/__pycache__/composer.cpython-310.pyc,,
|
||||
yaml/__pycache__/constructor.cpython-310.pyc,,
|
||||
yaml/__pycache__/cyaml.cpython-310.pyc,,
|
||||
yaml/__pycache__/dumper.cpython-310.pyc,,
|
||||
yaml/__pycache__/emitter.cpython-310.pyc,,
|
||||
yaml/__pycache__/error.cpython-310.pyc,,
|
||||
yaml/__pycache__/events.cpython-310.pyc,,
|
||||
yaml/__pycache__/loader.cpython-310.pyc,,
|
||||
yaml/__pycache__/nodes.cpython-310.pyc,,
|
||||
yaml/__pycache__/parser.cpython-310.pyc,,
|
||||
yaml/__pycache__/reader.cpython-310.pyc,,
|
||||
yaml/__pycache__/representer.cpython-310.pyc,,
|
||||
yaml/__pycache__/resolver.cpython-310.pyc,,
|
||||
yaml/__pycache__/scanner.cpython-310.pyc,,
|
||||
yaml/__pycache__/serializer.cpython-310.pyc,,
|
||||
yaml/__pycache__/tokens.cpython-310.pyc,,
|
||||
yaml/_yaml.cpython-310-x86_64-linux-gnu.so,sha256=20HV-cVpIFuOuVUTmQ1-PQIbyt0n8ctfXq7JCMIfbrU,2383664
|
||||
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
|
||||
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
|
||||
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
|
||||
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
|
||||
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
|
||||
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
|
||||
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
|
||||
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
|
||||
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
|
||||
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
|
||||
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
|
||||
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190
|
||||
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004
|
||||
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279
|
||||
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
|
||||
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
|
||||
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.44.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp310-cp310-manylinux_2_17_x86_64
|
||||
Tag: cp310-cp310-manylinux2014_x86_64
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
_yaml
|
||||
yaml
|
||||
33
.venv/lib/python3.10/site-packages/_yaml/__init__.py
Normal file
33
.venv/lib/python3.10/site-packages/_yaml/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# This is a stub package designed to roughly emulate the _yaml
|
||||
# extension module, which previously existed as a standalone module
|
||||
# and has been moved into the `yaml` package namespace.
|
||||
# It does not perfectly mimic its old counterpart, but should get
|
||||
# close enough for anyone who's relying on it even when they shouldn't.
|
||||
import yaml
|
||||
|
||||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
||||
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
||||
if not getattr(yaml, '__with_libyaml__', False):
|
||||
from sys import version_info
|
||||
|
||||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
||||
raise exc("No module named '_yaml'")
|
||||
else:
|
||||
from yaml._yaml import *
|
||||
import warnings
|
||||
warnings.warn(
|
||||
'The _yaml extension module is now located at yaml._yaml'
|
||||
' and its location is subject to change. To use the'
|
||||
' LibYAML-based parser and emitter, import from `yaml`:'
|
||||
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
||||
DeprecationWarning
|
||||
)
|
||||
del warnings
|
||||
# Don't `del yaml` here because yaml is actually an existing
|
||||
# namespace member of _yaml.
|
||||
|
||||
__name__ = '_yaml'
|
||||
# If the module is top-level (i.e. not a part of any specific package)
|
||||
# then the attribute should be set to ''.
|
||||
# https://docs.python.org/3.8/library/types.html
|
||||
__package__ = ''
|
||||
104
.venv/lib/python3.10/site-packages/attr/__init__.py
Normal file
104
.venv/lib/python3.10/site-packages/attr/__init__.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Classes Without Boilerplate
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
from typing import Callable, Literal, Protocol
|
||||
|
||||
from . import converters, exceptions, filters, setters, validators
|
||||
from ._cmp import cmp_using
|
||||
from ._config import get_run_validators, set_run_validators
|
||||
from ._funcs import asdict, assoc, astuple, has, resolve_types
|
||||
from ._make import (
|
||||
NOTHING,
|
||||
Attribute,
|
||||
Converter,
|
||||
Factory,
|
||||
_Nothing,
|
||||
attrib,
|
||||
attrs,
|
||||
evolve,
|
||||
fields,
|
||||
fields_dict,
|
||||
make_class,
|
||||
validate,
|
||||
)
|
||||
from ._next_gen import define, field, frozen, mutable
|
||||
from ._version_info import VersionInfo
|
||||
|
||||
|
||||
s = attributes = attrs
|
||||
ib = attr = attrib
|
||||
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
|
||||
|
||||
|
||||
class AttrsInstance(Protocol):
|
||||
pass
|
||||
|
||||
|
||||
NothingType = Literal[_Nothing.NOTHING]
|
||||
|
||||
__all__ = [
|
||||
"NOTHING",
|
||||
"Attribute",
|
||||
"AttrsInstance",
|
||||
"Converter",
|
||||
"Factory",
|
||||
"NothingType",
|
||||
"asdict",
|
||||
"assoc",
|
||||
"astuple",
|
||||
"attr",
|
||||
"attrib",
|
||||
"attributes",
|
||||
"attrs",
|
||||
"cmp_using",
|
||||
"converters",
|
||||
"define",
|
||||
"evolve",
|
||||
"exceptions",
|
||||
"field",
|
||||
"fields",
|
||||
"fields_dict",
|
||||
"filters",
|
||||
"frozen",
|
||||
"get_run_validators",
|
||||
"has",
|
||||
"ib",
|
||||
"make_class",
|
||||
"mutable",
|
||||
"resolve_types",
|
||||
"s",
|
||||
"set_run_validators",
|
||||
"setters",
|
||||
"validate",
|
||||
"validators",
|
||||
]
|
||||
|
||||
|
||||
def _make_getattr(mod_name: str) -> Callable:
|
||||
"""
|
||||
Create a metadata proxy for packaging information that uses *mod_name* in
|
||||
its warnings and errors.
|
||||
"""
|
||||
|
||||
def __getattr__(name: str) -> str:
|
||||
if name not in ("__version__", "__version_info__"):
|
||||
msg = f"module {mod_name} has no attribute {name}"
|
||||
raise AttributeError(msg)
|
||||
|
||||
from importlib.metadata import metadata
|
||||
|
||||
meta = metadata("attrs")
|
||||
|
||||
if name == "__version_info__":
|
||||
return VersionInfo._from_version_string(meta["version"])
|
||||
|
||||
return meta["version"]
|
||||
|
||||
return __getattr__
|
||||
|
||||
|
||||
__getattr__ = _make_getattr(__name__)
|
||||
389
.venv/lib/python3.10/site-packages/attr/__init__.pyi
Normal file
389
.venv/lib/python3.10/site-packages/attr/__init__.pyi
Normal file
@@ -0,0 +1,389 @@
|
||||
import enum
|
||||
import sys
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Generic,
|
||||
Literal,
|
||||
Mapping,
|
||||
Protocol,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
overload,
|
||||
)
|
||||
|
||||
# `import X as X` is required to make these public
|
||||
from . import converters as converters
|
||||
from . import exceptions as exceptions
|
||||
from . import filters as filters
|
||||
from . import setters as setters
|
||||
from . import validators as validators
|
||||
from ._cmp import cmp_using as cmp_using
|
||||
from ._typing_compat import AttrsInstance_
|
||||
from ._version_info import VersionInfo
|
||||
from attrs import (
|
||||
define as define,
|
||||
field as field,
|
||||
mutable as mutable,
|
||||
frozen as frozen,
|
||||
_EqOrderType,
|
||||
_ValidatorType,
|
||||
_ConverterType,
|
||||
_ReprArgType,
|
||||
_OnSetAttrType,
|
||||
_OnSetAttrArgType,
|
||||
_FieldTransformer,
|
||||
_ValidatorArgType,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeGuard, TypeAlias
|
||||
else:
|
||||
from typing_extensions import TypeGuard, TypeAlias
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import dataclass_transform
|
||||
else:
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
__version__: str
|
||||
__version_info__: VersionInfo
|
||||
__title__: str
|
||||
__description__: str
|
||||
__url__: str
|
||||
__uri__: str
|
||||
__author__: str
|
||||
__email__: str
|
||||
__license__: str
|
||||
__copyright__: str
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_C = TypeVar("_C", bound=type)
|
||||
|
||||
_FilterType = Callable[["Attribute[_T]", _T], bool]
|
||||
|
||||
# We subclass this here to keep the protocol's qualified name clean.
|
||||
class AttrsInstance(AttrsInstance_, Protocol):
|
||||
pass
|
||||
|
||||
_A = TypeVar("_A", bound=type[AttrsInstance])
|
||||
|
||||
class _Nothing(enum.Enum):
|
||||
NOTHING = enum.auto()
|
||||
|
||||
NOTHING = _Nothing.NOTHING
|
||||
NothingType: TypeAlias = Literal[_Nothing.NOTHING]
|
||||
|
||||
# NOTE: Factory lies about its return type to make this possible:
|
||||
# `x: List[int] # = Factory(list)`
|
||||
# Work around mypy issue #4554 in the common case by using an overload.
|
||||
|
||||
@overload
|
||||
def Factory(factory: Callable[[], _T]) -> _T: ...
|
||||
@overload
|
||||
def Factory(
|
||||
factory: Callable[[Any], _T],
|
||||
takes_self: Literal[True],
|
||||
) -> _T: ...
|
||||
@overload
|
||||
def Factory(
|
||||
factory: Callable[[], _T],
|
||||
takes_self: Literal[False],
|
||||
) -> _T: ...
|
||||
|
||||
In = TypeVar("In")
|
||||
Out = TypeVar("Out")
|
||||
|
||||
class Converter(Generic[In, Out]):
|
||||
@overload
|
||||
def __init__(self, converter: Callable[[In], Out]) -> None: ...
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
converter: Callable[[In, AttrsInstance, Attribute], Out],
|
||||
*,
|
||||
takes_self: Literal[True],
|
||||
takes_field: Literal[True],
|
||||
) -> None: ...
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
converter: Callable[[In, Attribute], Out],
|
||||
*,
|
||||
takes_field: Literal[True],
|
||||
) -> None: ...
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
converter: Callable[[In, AttrsInstance], Out],
|
||||
*,
|
||||
takes_self: Literal[True],
|
||||
) -> None: ...
|
||||
|
||||
class Attribute(Generic[_T]):
|
||||
name: str
|
||||
default: _T | None
|
||||
validator: _ValidatorType[_T] | None
|
||||
repr: _ReprArgType
|
||||
cmp: _EqOrderType
|
||||
eq: _EqOrderType
|
||||
order: _EqOrderType
|
||||
hash: bool | None
|
||||
init: bool
|
||||
converter: Converter | None
|
||||
metadata: dict[Any, Any]
|
||||
type: type[_T] | None
|
||||
kw_only: bool
|
||||
on_setattr: _OnSetAttrType
|
||||
alias: str | None
|
||||
|
||||
def evolve(self, **changes: Any) -> "Attribute[Any]": ...
|
||||
|
||||
# NOTE: We had several choices for the annotation to use for type arg:
|
||||
# 1) Type[_T]
|
||||
# - Pros: Handles simple cases correctly
|
||||
# - Cons: Might produce less informative errors in the case of conflicting
|
||||
# TypeVars e.g. `attr.ib(default='bad', type=int)`
|
||||
# 2) Callable[..., _T]
|
||||
# - Pros: Better error messages than #1 for conflicting TypeVars
|
||||
# - Cons: Terrible error messages for validator checks.
|
||||
# e.g. attr.ib(type=int, validator=validate_str)
|
||||
# -> error: Cannot infer function type argument
|
||||
# 3) type (and do all of the work in the mypy plugin)
|
||||
# - Pros: Simple here, and we could customize the plugin with our own errors.
|
||||
# - Cons: Would need to write mypy plugin code to handle all the cases.
|
||||
# We chose option #1.
|
||||
|
||||
# `attr` lies about its return type to make the following possible:
|
||||
# attr() -> Any
|
||||
# attr(8) -> int
|
||||
# attr(validator=<some callable>) -> Whatever the callable expects.
|
||||
# This makes this type of assignments possible:
|
||||
# x: int = attr(8)
|
||||
#
|
||||
# This form catches explicit None or no default but with no other arguments
|
||||
# returns Any.
|
||||
@overload
|
||||
def attrib(
|
||||
default: None = ...,
|
||||
validator: None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
type: None = ...,
|
||||
converter: None = ...,
|
||||
factory: None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
) -> Any: ...
|
||||
|
||||
# This form catches an explicit None or no default and infers the type from the
|
||||
# other arguments.
|
||||
@overload
|
||||
def attrib(
|
||||
default: None = ...,
|
||||
validator: _ValidatorArgType[_T] | None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
type: type[_T] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
) -> _T: ...
|
||||
|
||||
# This form catches an explicit default argument.
|
||||
@overload
|
||||
def attrib(
|
||||
default: _T,
|
||||
validator: _ValidatorArgType[_T] | None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
type: type[_T] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
) -> _T: ...
|
||||
|
||||
# This form covers type=non-Type: e.g. forward references (str), Any
|
||||
@overload
|
||||
def attrib(
|
||||
default: _T | None = ...,
|
||||
validator: _ValidatorArgType[_T] | None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
type: object = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
) -> Any: ...
|
||||
@overload
|
||||
@dataclass_transform(order_default=True, field_specifiers=(attrib, field))
|
||||
def attrs(
|
||||
maybe_cls: _C,
|
||||
these: dict[str, Any] | None = ...,
|
||||
repr_ns: str | None = ...,
|
||||
repr: bool = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
auto_detect: bool = ...,
|
||||
collect_by_mro: bool = ...,
|
||||
getstate_setstate: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
) -> _C: ...
|
||||
@overload
|
||||
@dataclass_transform(order_default=True, field_specifiers=(attrib, field))
|
||||
def attrs(
|
||||
maybe_cls: None = ...,
|
||||
these: dict[str, Any] | None = ...,
|
||||
repr_ns: str | None = ...,
|
||||
repr: bool = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
auto_detect: bool = ...,
|
||||
collect_by_mro: bool = ...,
|
||||
getstate_setstate: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
) -> Callable[[_C], _C]: ...
|
||||
def fields(cls: type[AttrsInstance]) -> Any: ...
|
||||
def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
|
||||
def validate(inst: AttrsInstance) -> None: ...
|
||||
def resolve_types(
|
||||
cls: _A,
|
||||
globalns: dict[str, Any] | None = ...,
|
||||
localns: dict[str, Any] | None = ...,
|
||||
attribs: list[Attribute[Any]] | None = ...,
|
||||
include_extras: bool = ...,
|
||||
) -> _A: ...
|
||||
|
||||
# TODO: add support for returning a proper attrs class from the mypy plugin
|
||||
# we use Any instead of _CountingAttr so that e.g. `make_class('Foo',
|
||||
# [attr.ib()])` is valid
|
||||
def make_class(
|
||||
name: str,
|
||||
attrs: list[str] | tuple[str, ...] | dict[str, Any],
|
||||
bases: tuple[type, ...] = ...,
|
||||
class_body: dict[str, Any] | None = ...,
|
||||
repr_ns: str | None = ...,
|
||||
repr: bool = ...,
|
||||
cmp: _EqOrderType | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
collect_by_mro: bool = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
) -> type: ...
|
||||
|
||||
# _funcs --
|
||||
|
||||
# TODO: add support for returning TypedDict from the mypy plugin
|
||||
# FIXME: asdict/astuple do not honor their factory args. Waiting on one of
|
||||
# these:
|
||||
# https://github.com/python/mypy/issues/4236
|
||||
# https://github.com/python/typing/issues/253
|
||||
# XXX: remember to fix attrs.asdict/astuple too!
|
||||
def asdict(
|
||||
inst: AttrsInstance,
|
||||
recurse: bool = ...,
|
||||
filter: _FilterType[Any] | None = ...,
|
||||
dict_factory: type[Mapping[Any, Any]] = ...,
|
||||
retain_collection_types: bool = ...,
|
||||
value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ...,
|
||||
tuple_keys: bool | None = ...,
|
||||
) -> dict[str, Any]: ...
|
||||
|
||||
# TODO: add support for returning NamedTuple from the mypy plugin
|
||||
def astuple(
|
||||
inst: AttrsInstance,
|
||||
recurse: bool = ...,
|
||||
filter: _FilterType[Any] | None = ...,
|
||||
tuple_factory: type[Sequence[Any]] = ...,
|
||||
retain_collection_types: bool = ...,
|
||||
) -> tuple[Any, ...]: ...
|
||||
def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ...
|
||||
def assoc(inst: _T, **changes: Any) -> _T: ...
|
||||
def evolve(inst: _T, **changes: Any) -> _T: ...
|
||||
|
||||
# _config --
|
||||
|
||||
def set_run_validators(run: bool) -> None: ...
|
||||
def get_run_validators() -> bool: ...
|
||||
|
||||
# aliases --
|
||||
|
||||
s = attributes = attrs
|
||||
ib = attr = attrib
|
||||
dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;)
|
||||
160
.venv/lib/python3.10/site-packages/attr/_cmp.py
Normal file
160
.venv/lib/python3.10/site-packages/attr/_cmp.py
Normal file
@@ -0,0 +1,160 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
import functools
|
||||
import types
|
||||
|
||||
from ._make import __ne__
|
||||
|
||||
|
||||
_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}
|
||||
|
||||
|
||||
def cmp_using(
|
||||
eq=None,
|
||||
lt=None,
|
||||
le=None,
|
||||
gt=None,
|
||||
ge=None,
|
||||
require_same_type=True,
|
||||
class_name="Comparable",
|
||||
):
|
||||
"""
|
||||
Create a class that can be passed into `attrs.field`'s ``eq``, ``order``,
|
||||
and ``cmp`` arguments to customize field comparison.
|
||||
|
||||
The resulting class will have a full set of ordering methods if at least
|
||||
one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
|
||||
|
||||
Args:
|
||||
eq (typing.Callable | None):
|
||||
Callable used to evaluate equality of two objects.
|
||||
|
||||
lt (typing.Callable | None):
|
||||
Callable used to evaluate whether one object is less than another
|
||||
object.
|
||||
|
||||
le (typing.Callable | None):
|
||||
Callable used to evaluate whether one object is less than or equal
|
||||
to another object.
|
||||
|
||||
gt (typing.Callable | None):
|
||||
Callable used to evaluate whether one object is greater than
|
||||
another object.
|
||||
|
||||
ge (typing.Callable | None):
|
||||
Callable used to evaluate whether one object is greater than or
|
||||
equal to another object.
|
||||
|
||||
require_same_type (bool):
|
||||
When `True`, equality and ordering methods will return
|
||||
`NotImplemented` if objects are not of the same type.
|
||||
|
||||
class_name (str | None): Name of class. Defaults to "Comparable".
|
||||
|
||||
See `comparison` for more details.
|
||||
|
||||
.. versionadded:: 21.1.0
|
||||
"""
|
||||
|
||||
body = {
|
||||
"__slots__": ["value"],
|
||||
"__init__": _make_init(),
|
||||
"_requirements": [],
|
||||
"_is_comparable_to": _is_comparable_to,
|
||||
}
|
||||
|
||||
# Add operations.
|
||||
num_order_functions = 0
|
||||
has_eq_function = False
|
||||
|
||||
if eq is not None:
|
||||
has_eq_function = True
|
||||
body["__eq__"] = _make_operator("eq", eq)
|
||||
body["__ne__"] = __ne__
|
||||
|
||||
if lt is not None:
|
||||
num_order_functions += 1
|
||||
body["__lt__"] = _make_operator("lt", lt)
|
||||
|
||||
if le is not None:
|
||||
num_order_functions += 1
|
||||
body["__le__"] = _make_operator("le", le)
|
||||
|
||||
if gt is not None:
|
||||
num_order_functions += 1
|
||||
body["__gt__"] = _make_operator("gt", gt)
|
||||
|
||||
if ge is not None:
|
||||
num_order_functions += 1
|
||||
body["__ge__"] = _make_operator("ge", ge)
|
||||
|
||||
type_ = types.new_class(
|
||||
class_name, (object,), {}, lambda ns: ns.update(body)
|
||||
)
|
||||
|
||||
# Add same type requirement.
|
||||
if require_same_type:
|
||||
type_._requirements.append(_check_same_type)
|
||||
|
||||
# Add total ordering if at least one operation was defined.
|
||||
if 0 < num_order_functions < 4:
|
||||
if not has_eq_function:
|
||||
# functools.total_ordering requires __eq__ to be defined,
|
||||
# so raise early error here to keep a nice stack.
|
||||
msg = "eq must be define is order to complete ordering from lt, le, gt, ge."
|
||||
raise ValueError(msg)
|
||||
type_ = functools.total_ordering(type_)
|
||||
|
||||
return type_
|
||||
|
||||
|
||||
def _make_init():
|
||||
"""
|
||||
Create __init__ method.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Initialize object with *value*.
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
return __init__
|
||||
|
||||
|
||||
def _make_operator(name, func):
|
||||
"""
|
||||
Create operator method.
|
||||
"""
|
||||
|
||||
def method(self, other):
|
||||
if not self._is_comparable_to(other):
|
||||
return NotImplemented
|
||||
|
||||
result = func(self.value, other.value)
|
||||
if result is NotImplemented:
|
||||
return NotImplemented
|
||||
|
||||
return result
|
||||
|
||||
method.__name__ = f"__{name}__"
|
||||
method.__doc__ = (
|
||||
f"Return a {_operation_names[name]} b. Computed by attrs."
|
||||
)
|
||||
|
||||
return method
|
||||
|
||||
|
||||
def _is_comparable_to(self, other):
|
||||
"""
|
||||
Check whether `other` is comparable to `self`.
|
||||
"""
|
||||
return all(func(self, other) for func in self._requirements)
|
||||
|
||||
|
||||
def _check_same_type(self, other):
|
||||
"""
|
||||
Return True if *self* and *other* are of the same type, False otherwise.
|
||||
"""
|
||||
return other.value.__class__ is self.value.__class__
|
||||
13
.venv/lib/python3.10/site-packages/attr/_cmp.pyi
Normal file
13
.venv/lib/python3.10/site-packages/attr/_cmp.pyi
Normal file
@@ -0,0 +1,13 @@
|
||||
from typing import Any, Callable
|
||||
|
||||
_CompareWithType = Callable[[Any, Any], bool]
|
||||
|
||||
def cmp_using(
|
||||
eq: _CompareWithType | None = ...,
|
||||
lt: _CompareWithType | None = ...,
|
||||
le: _CompareWithType | None = ...,
|
||||
gt: _CompareWithType | None = ...,
|
||||
ge: _CompareWithType | None = ...,
|
||||
require_same_type: bool = ...,
|
||||
class_name: str = ...,
|
||||
) -> type: ...
|
||||
94
.venv/lib/python3.10/site-packages/attr/_compat.py
Normal file
94
.venv/lib/python3.10/site-packages/attr/_compat.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import inspect
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from collections.abc import Mapping, Sequence # noqa: F401
|
||||
from typing import _GenericAlias
|
||||
|
||||
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
|
||||
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
|
||||
PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
|
||||
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
|
||||
PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
|
||||
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
|
||||
|
||||
|
||||
if PY_3_14_PLUS: # pragma: no cover
|
||||
import annotationlib
|
||||
|
||||
_get_annotations = annotationlib.get_annotations
|
||||
|
||||
else:
|
||||
|
||||
def _get_annotations(cls):
|
||||
"""
|
||||
Get annotations for *cls*.
|
||||
"""
|
||||
return cls.__dict__.get("__annotations__", {})
|
||||
|
||||
|
||||
class _AnnotationExtractor:
|
||||
"""
|
||||
Extract type annotations from a callable, returning None whenever there
|
||||
is none.
|
||||
"""
|
||||
|
||||
__slots__ = ["sig"]
|
||||
|
||||
def __init__(self, callable):
|
||||
try:
|
||||
self.sig = inspect.signature(callable)
|
||||
except (ValueError, TypeError): # inspect failed
|
||||
self.sig = None
|
||||
|
||||
def get_first_param_type(self):
|
||||
"""
|
||||
Return the type annotation of the first argument if it's not empty.
|
||||
"""
|
||||
if not self.sig:
|
||||
return None
|
||||
|
||||
params = list(self.sig.parameters.values())
|
||||
if params and params[0].annotation is not inspect.Parameter.empty:
|
||||
return params[0].annotation
|
||||
|
||||
return None
|
||||
|
||||
def get_return_type(self):
|
||||
"""
|
||||
Return the return type if it's not empty.
|
||||
"""
|
||||
if (
|
||||
self.sig
|
||||
and self.sig.return_annotation is not inspect.Signature.empty
|
||||
):
|
||||
return self.sig.return_annotation
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# Thread-local global to track attrs instances which are already being repr'd.
|
||||
# This is needed because there is no other (thread-safe) way to pass info
|
||||
# about the instances that are already being repr'd through the call stack
|
||||
# in order to ensure we don't perform infinite recursion.
|
||||
#
|
||||
# For instance, if an instance contains a dict which contains that instance,
|
||||
# we need to know that we're already repr'ing the outside instance from within
|
||||
# the dict's repr() call.
|
||||
#
|
||||
# This lives here rather than in _make.py so that the functions in _make.py
|
||||
# don't have a direct reference to the thread-local in their globals dict.
|
||||
# If they have such a reference, it breaks cloudpickle.
|
||||
repr_context = threading.local()
|
||||
|
||||
|
||||
def get_generic_base(cl):
|
||||
"""If this is a generic class (A[str]), return the generic base for it."""
|
||||
if cl.__class__ is _GenericAlias:
|
||||
return cl.__origin__
|
||||
return None
|
||||
31
.venv/lib/python3.10/site-packages/attr/_config.py
Normal file
31
.venv/lib/python3.10/site-packages/attr/_config.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
__all__ = ["get_run_validators", "set_run_validators"]
|
||||
|
||||
_run_validators = True
|
||||
|
||||
|
||||
def set_run_validators(run):
|
||||
"""
|
||||
Set whether or not validators are run. By default, they are run.
|
||||
|
||||
.. deprecated:: 21.3.0 It will not be removed, but it also will not be
|
||||
moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()`
|
||||
instead.
|
||||
"""
|
||||
if not isinstance(run, bool):
|
||||
msg = "'run' must be bool."
|
||||
raise TypeError(msg)
|
||||
global _run_validators
|
||||
_run_validators = run
|
||||
|
||||
|
||||
def get_run_validators():
|
||||
"""
|
||||
Return whether or not validators are run.
|
||||
|
||||
.. deprecated:: 21.3.0 It will not be removed, but it also will not be
|
||||
moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()`
|
||||
instead.
|
||||
"""
|
||||
return _run_validators
|
||||
468
.venv/lib/python3.10/site-packages/attr/_funcs.py
Normal file
468
.venv/lib/python3.10/site-packages/attr/_funcs.py
Normal file
@@ -0,0 +1,468 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
import copy
|
||||
|
||||
from ._compat import PY_3_9_PLUS, get_generic_base
|
||||
from ._make import _OBJ_SETATTR, NOTHING, fields
|
||||
from .exceptions import AttrsAttributeNotFoundError
|
||||
|
||||
|
||||
def asdict(
|
||||
inst,
|
||||
recurse=True,
|
||||
filter=None,
|
||||
dict_factory=dict,
|
||||
retain_collection_types=False,
|
||||
value_serializer=None,
|
||||
):
|
||||
"""
|
||||
Return the *attrs* attribute values of *inst* as a dict.
|
||||
|
||||
Optionally recurse into other *attrs*-decorated classes.
|
||||
|
||||
Args:
|
||||
inst: Instance of an *attrs*-decorated class.
|
||||
|
||||
recurse (bool): Recurse into classes that are also *attrs*-decorated.
|
||||
|
||||
filter (~typing.Callable):
|
||||
A callable whose return code determines whether an attribute or
|
||||
element is included (`True`) or dropped (`False`). Is called with
|
||||
the `attrs.Attribute` as the first argument and the value as the
|
||||
second argument.
|
||||
|
||||
dict_factory (~typing.Callable):
|
||||
A callable to produce dictionaries from. For example, to produce
|
||||
ordered dictionaries instead of normal Python dictionaries, pass in
|
||||
``collections.OrderedDict``.
|
||||
|
||||
retain_collection_types (bool):
|
||||
Do not convert to `list` when encountering an attribute whose type
|
||||
is `tuple` or `set`. Only meaningful if *recurse* is `True`.
|
||||
|
||||
value_serializer (typing.Callable | None):
|
||||
A hook that is called for every attribute or dict key/value. It
|
||||
receives the current instance, field and value and must return the
|
||||
(updated) value. The hook is run *after* the optional *filter* has
|
||||
been applied.
|
||||
|
||||
Returns:
|
||||
Return type of *dict_factory*.
|
||||
|
||||
Raises:
|
||||
attrs.exceptions.NotAnAttrsClassError:
|
||||
If *cls* is not an *attrs* class.
|
||||
|
||||
.. versionadded:: 16.0.0 *dict_factory*
|
||||
.. versionadded:: 16.1.0 *retain_collection_types*
|
||||
.. versionadded:: 20.3.0 *value_serializer*
|
||||
.. versionadded:: 21.3.0
|
||||
If a dict has a collection for a key, it is serialized as a tuple.
|
||||
"""
|
||||
attrs = fields(inst.__class__)
|
||||
rv = dict_factory()
|
||||
for a in attrs:
|
||||
v = getattr(inst, a.name)
|
||||
if filter is not None and not filter(a, v):
|
||||
continue
|
||||
|
||||
if value_serializer is not None:
|
||||
v = value_serializer(inst, a, v)
|
||||
|
||||
if recurse is True:
|
||||
if has(v.__class__):
|
||||
rv[a.name] = asdict(
|
||||
v,
|
||||
recurse=True,
|
||||
filter=filter,
|
||||
dict_factory=dict_factory,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
)
|
||||
elif isinstance(v, (tuple, list, set, frozenset)):
|
||||
cf = v.__class__ if retain_collection_types is True else list
|
||||
items = [
|
||||
_asdict_anything(
|
||||
i,
|
||||
is_key=False,
|
||||
filter=filter,
|
||||
dict_factory=dict_factory,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
)
|
||||
for i in v
|
||||
]
|
||||
try:
|
||||
rv[a.name] = cf(items)
|
||||
except TypeError:
|
||||
if not issubclass(cf, tuple):
|
||||
raise
|
||||
# Workaround for TypeError: cf.__new__() missing 1 required
|
||||
# positional argument (which appears, for a namedturle)
|
||||
rv[a.name] = cf(*items)
|
||||
elif isinstance(v, dict):
|
||||
df = dict_factory
|
||||
rv[a.name] = df(
|
||||
(
|
||||
_asdict_anything(
|
||||
kk,
|
||||
is_key=True,
|
||||
filter=filter,
|
||||
dict_factory=df,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
),
|
||||
_asdict_anything(
|
||||
vv,
|
||||
is_key=False,
|
||||
filter=filter,
|
||||
dict_factory=df,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
),
|
||||
)
|
||||
for kk, vv in v.items()
|
||||
)
|
||||
else:
|
||||
rv[a.name] = v
|
||||
else:
|
||||
rv[a.name] = v
|
||||
return rv
|
||||
|
||||
|
||||
def _asdict_anything(
|
||||
val,
|
||||
is_key,
|
||||
filter,
|
||||
dict_factory,
|
||||
retain_collection_types,
|
||||
value_serializer,
|
||||
):
|
||||
"""
|
||||
``asdict`` only works on attrs instances, this works on anything.
|
||||
"""
|
||||
if getattr(val.__class__, "__attrs_attrs__", None) is not None:
|
||||
# Attrs class.
|
||||
rv = asdict(
|
||||
val,
|
||||
recurse=True,
|
||||
filter=filter,
|
||||
dict_factory=dict_factory,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
)
|
||||
elif isinstance(val, (tuple, list, set, frozenset)):
|
||||
if retain_collection_types is True:
|
||||
cf = val.__class__
|
||||
elif is_key:
|
||||
cf = tuple
|
||||
else:
|
||||
cf = list
|
||||
|
||||
rv = cf(
|
||||
[
|
||||
_asdict_anything(
|
||||
i,
|
||||
is_key=False,
|
||||
filter=filter,
|
||||
dict_factory=dict_factory,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
)
|
||||
for i in val
|
||||
]
|
||||
)
|
||||
elif isinstance(val, dict):
|
||||
df = dict_factory
|
||||
rv = df(
|
||||
(
|
||||
_asdict_anything(
|
||||
kk,
|
||||
is_key=True,
|
||||
filter=filter,
|
||||
dict_factory=df,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
),
|
||||
_asdict_anything(
|
||||
vv,
|
||||
is_key=False,
|
||||
filter=filter,
|
||||
dict_factory=df,
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
),
|
||||
)
|
||||
for kk, vv in val.items()
|
||||
)
|
||||
else:
|
||||
rv = val
|
||||
if value_serializer is not None:
|
||||
rv = value_serializer(None, None, rv)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def astuple(
|
||||
inst,
|
||||
recurse=True,
|
||||
filter=None,
|
||||
tuple_factory=tuple,
|
||||
retain_collection_types=False,
|
||||
):
|
||||
"""
|
||||
Return the *attrs* attribute values of *inst* as a tuple.
|
||||
|
||||
Optionally recurse into other *attrs*-decorated classes.
|
||||
|
||||
Args:
|
||||
inst: Instance of an *attrs*-decorated class.
|
||||
|
||||
recurse (bool):
|
||||
Recurse into classes that are also *attrs*-decorated.
|
||||
|
||||
filter (~typing.Callable):
|
||||
A callable whose return code determines whether an attribute or
|
||||
element is included (`True`) or dropped (`False`). Is called with
|
||||
the `attrs.Attribute` as the first argument and the value as the
|
||||
second argument.
|
||||
|
||||
tuple_factory (~typing.Callable):
|
||||
A callable to produce tuples from. For example, to produce lists
|
||||
instead of tuples.
|
||||
|
||||
retain_collection_types (bool):
|
||||
Do not convert to `list` or `dict` when encountering an attribute
|
||||
which type is `tuple`, `dict` or `set`. Only meaningful if
|
||||
*recurse* is `True`.
|
||||
|
||||
Returns:
|
||||
Return type of *tuple_factory*
|
||||
|
||||
Raises:
|
||||
attrs.exceptions.NotAnAttrsClassError:
|
||||
If *cls* is not an *attrs* class.
|
||||
|
||||
.. versionadded:: 16.2.0
|
||||
"""
|
||||
attrs = fields(inst.__class__)
|
||||
rv = []
|
||||
retain = retain_collection_types # Very long. :/
|
||||
for a in attrs:
|
||||
v = getattr(inst, a.name)
|
||||
if filter is not None and not filter(a, v):
|
||||
continue
|
||||
if recurse is True:
|
||||
if has(v.__class__):
|
||||
rv.append(
|
||||
astuple(
|
||||
v,
|
||||
recurse=True,
|
||||
filter=filter,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain,
|
||||
)
|
||||
)
|
||||
elif isinstance(v, (tuple, list, set, frozenset)):
|
||||
cf = v.__class__ if retain is True else list
|
||||
items = [
|
||||
(
|
||||
astuple(
|
||||
j,
|
||||
recurse=True,
|
||||
filter=filter,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain,
|
||||
)
|
||||
if has(j.__class__)
|
||||
else j
|
||||
)
|
||||
for j in v
|
||||
]
|
||||
try:
|
||||
rv.append(cf(items))
|
||||
except TypeError:
|
||||
if not issubclass(cf, tuple):
|
||||
raise
|
||||
# Workaround for TypeError: cf.__new__() missing 1 required
|
||||
# positional argument (which appears, for a namedturle)
|
||||
rv.append(cf(*items))
|
||||
elif isinstance(v, dict):
|
||||
df = v.__class__ if retain is True else dict
|
||||
rv.append(
|
||||
df(
|
||||
(
|
||||
(
|
||||
astuple(
|
||||
kk,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain,
|
||||
)
|
||||
if has(kk.__class__)
|
||||
else kk
|
||||
),
|
||||
(
|
||||
astuple(
|
||||
vv,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain,
|
||||
)
|
||||
if has(vv.__class__)
|
||||
else vv
|
||||
),
|
||||
)
|
||||
for kk, vv in v.items()
|
||||
)
|
||||
)
|
||||
else:
|
||||
rv.append(v)
|
||||
else:
|
||||
rv.append(v)
|
||||
|
||||
return rv if tuple_factory is list else tuple_factory(rv)
|
||||
|
||||
|
||||
def has(cls):
|
||||
"""
|
||||
Check whether *cls* is a class with *attrs* attributes.
|
||||
|
||||
Args:
|
||||
cls (type): Class to introspect.
|
||||
|
||||
Raises:
|
||||
TypeError: If *cls* is not a class.
|
||||
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
attrs = getattr(cls, "__attrs_attrs__", None)
|
||||
if attrs is not None:
|
||||
return True
|
||||
|
||||
# No attrs, maybe it's a specialized generic (A[str])?
|
||||
generic_base = get_generic_base(cls)
|
||||
if generic_base is not None:
|
||||
generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
|
||||
if generic_attrs is not None:
|
||||
# Stick it on here for speed next time.
|
||||
cls.__attrs_attrs__ = generic_attrs
|
||||
return generic_attrs is not None
|
||||
return False
|
||||
|
||||
|
||||
def assoc(inst, **changes):
|
||||
"""
|
||||
Copy *inst* and apply *changes*.
|
||||
|
||||
This is different from `evolve` that applies the changes to the arguments
|
||||
that create the new instance.
|
||||
|
||||
`evolve`'s behavior is preferable, but there are `edge cases`_ where it
|
||||
doesn't work. Therefore `assoc` is deprecated, but will not be removed.
|
||||
|
||||
.. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
|
||||
|
||||
Args:
|
||||
inst: Instance of a class with *attrs* attributes.
|
||||
|
||||
changes: Keyword changes in the new copy.
|
||||
|
||||
Returns:
|
||||
A copy of inst with *changes* incorporated.
|
||||
|
||||
Raises:
|
||||
attrs.exceptions.AttrsAttributeNotFoundError:
|
||||
If *attr_name* couldn't be found on *cls*.
|
||||
|
||||
attrs.exceptions.NotAnAttrsClassError:
|
||||
If *cls* is not an *attrs* class.
|
||||
|
||||
.. deprecated:: 17.1.0
|
||||
Use `attrs.evolve` instead if you can. This function will not be
|
||||
removed du to the slightly different approach compared to
|
||||
`attrs.evolve`, though.
|
||||
"""
|
||||
new = copy.copy(inst)
|
||||
attrs = fields(inst.__class__)
|
||||
for k, v in changes.items():
|
||||
a = getattr(attrs, k, NOTHING)
|
||||
if a is NOTHING:
|
||||
msg = f"{k} is not an attrs attribute on {new.__class__}."
|
||||
raise AttrsAttributeNotFoundError(msg)
|
||||
_OBJ_SETATTR(new, k, v)
|
||||
return new
|
||||
|
||||
|
||||
def resolve_types(
|
||||
cls, globalns=None, localns=None, attribs=None, include_extras=True
|
||||
):
|
||||
"""
|
||||
Resolve any strings and forward annotations in type annotations.
|
||||
|
||||
This is only required if you need concrete types in :class:`Attribute`'s
|
||||
*type* field. In other words, you don't need to resolve your types if you
|
||||
only use them for static type checking.
|
||||
|
||||
With no arguments, names will be looked up in the module in which the class
|
||||
was created. If this is not what you want, for example, if the name only
|
||||
exists inside a method, you may pass *globalns* or *localns* to specify
|
||||
other dictionaries in which to look up these names. See the docs of
|
||||
`typing.get_type_hints` for more details.
|
||||
|
||||
Args:
|
||||
cls (type): Class to resolve.
|
||||
|
||||
globalns (dict | None): Dictionary containing global variables.
|
||||
|
||||
localns (dict | None): Dictionary containing local variables.
|
||||
|
||||
attribs (list | None):
|
||||
List of attribs for the given class. This is necessary when calling
|
||||
from inside a ``field_transformer`` since *cls* is not an *attrs*
|
||||
class yet.
|
||||
|
||||
include_extras (bool):
|
||||
Resolve more accurately, if possible. Pass ``include_extras`` to
|
||||
``typing.get_hints``, if supported by the typing module. On
|
||||
supported Python versions (3.9+), this resolves the types more
|
||||
accurately.
|
||||
|
||||
Raises:
|
||||
TypeError: If *cls* is not a class.
|
||||
|
||||
attrs.exceptions.NotAnAttrsClassError:
|
||||
If *cls* is not an *attrs* class and you didn't pass any attribs.
|
||||
|
||||
NameError: If types cannot be resolved because of missing variables.
|
||||
|
||||
Returns:
|
||||
*cls* so you can use this function also as a class decorator. Please
|
||||
note that you have to apply it **after** `attrs.define`. That means the
|
||||
decorator has to come in the line **before** `attrs.define`.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
.. versionadded:: 21.1.0 *attribs*
|
||||
.. versionadded:: 23.1.0 *include_extras*
|
||||
"""
|
||||
# Since calling get_type_hints is expensive we cache whether we've
|
||||
# done it already.
|
||||
if getattr(cls, "__attrs_types_resolved__", None) != cls:
|
||||
import typing
|
||||
|
||||
kwargs = {"globalns": globalns, "localns": localns}
|
||||
|
||||
if PY_3_9_PLUS:
|
||||
kwargs["include_extras"] = include_extras
|
||||
|
||||
hints = typing.get_type_hints(cls, **kwargs)
|
||||
for field in fields(cls) if attribs is None else attribs:
|
||||
if field.name in hints:
|
||||
# Since fields have been frozen we must work around it.
|
||||
_OBJ_SETATTR(field, "type", hints[field.name])
|
||||
# We store the class we resolved so that subclasses know they haven't
|
||||
# been resolved.
|
||||
cls.__attrs_types_resolved__ = cls
|
||||
|
||||
# Return the class so you can use it as a decorator too.
|
||||
return cls
|
||||
3123
.venv/lib/python3.10/site-packages/attr/_make.py
Normal file
3123
.venv/lib/python3.10/site-packages/attr/_make.py
Normal file
File diff suppressed because it is too large
Load Diff
623
.venv/lib/python3.10/site-packages/attr/_next_gen.py
Normal file
623
.venv/lib/python3.10/site-packages/attr/_next_gen.py
Normal file
@@ -0,0 +1,623 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
These are keyword-only APIs that call `attr.s` and `attr.ib` with different
|
||||
default values.
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
|
||||
from . import setters
|
||||
from ._funcs import asdict as _asdict
|
||||
from ._funcs import astuple as _astuple
|
||||
from ._make import (
|
||||
_DEFAULT_ON_SETATTR,
|
||||
NOTHING,
|
||||
_frozen_setattrs,
|
||||
attrib,
|
||||
attrs,
|
||||
)
|
||||
from .exceptions import UnannotatedAttributeError
|
||||
|
||||
|
||||
def define(
|
||||
maybe_cls=None,
|
||||
*,
|
||||
these=None,
|
||||
repr=None,
|
||||
unsafe_hash=None,
|
||||
hash=None,
|
||||
init=None,
|
||||
slots=True,
|
||||
frozen=False,
|
||||
weakref_slot=True,
|
||||
str=False,
|
||||
auto_attribs=None,
|
||||
kw_only=False,
|
||||
cache_hash=False,
|
||||
auto_exc=True,
|
||||
eq=None,
|
||||
order=False,
|
||||
auto_detect=True,
|
||||
getstate_setstate=None,
|
||||
on_setattr=None,
|
||||
field_transformer=None,
|
||||
match_args=True,
|
||||
):
|
||||
r"""
|
||||
A class decorator that adds :term:`dunder methods` according to
|
||||
:term:`fields <field>` specified using :doc:`type annotations <types>`,
|
||||
`field()` calls, or the *these* argument.
|
||||
|
||||
Since *attrs* patches or replaces an existing class, you cannot use
|
||||
`object.__init_subclass__` with *attrs* classes, because it runs too early.
|
||||
As a replacement, you can define ``__attrs_init_subclass__`` on your class.
|
||||
It will be called by *attrs* classes that subclass it after they're
|
||||
created. See also :ref:`init-subclass`.
|
||||
|
||||
Args:
|
||||
slots (bool):
|
||||
Create a :term:`slotted class <slotted classes>` that's more
|
||||
memory-efficient. Slotted classes are generally superior to the
|
||||
default dict classes, but have some gotchas you should know about,
|
||||
so we encourage you to read the :term:`glossary entry <slotted
|
||||
classes>`.
|
||||
|
||||
auto_detect (bool):
|
||||
Instead of setting the *init*, *repr*, *eq*, and *hash* arguments
|
||||
explicitly, assume they are set to True **unless any** of the
|
||||
involved methods for one of the arguments is implemented in the
|
||||
*current* class (meaning, it is *not* inherited from some base
|
||||
class).
|
||||
|
||||
So, for example by implementing ``__eq__`` on a class yourself,
|
||||
*attrs* will deduce ``eq=False`` and will create *neither*
|
||||
``__eq__`` *nor* ``__ne__`` (but Python classes come with a
|
||||
sensible ``__ne__`` by default, so it *should* be enough to only
|
||||
implement ``__eq__`` in most cases).
|
||||
|
||||
Passing True or False` to *init*, *repr*, *eq*, or *hash*
|
||||
overrides whatever *auto_detect* would determine.
|
||||
|
||||
auto_exc (bool):
|
||||
If the class subclasses `BaseException` (which implicitly includes
|
||||
any subclass of any exception), the following happens to behave
|
||||
like a well-behaved Python exception class:
|
||||
|
||||
- the values for *eq*, *order*, and *hash* are ignored and the
|
||||
instances compare and hash by the instance's ids [#]_ ,
|
||||
- all attributes that are either passed into ``__init__`` or have a
|
||||
default value are additionally available as a tuple in the
|
||||
``args`` attribute,
|
||||
- the value of *str* is ignored leaving ``__str__`` to base
|
||||
classes.
|
||||
|
||||
.. [#]
|
||||
Note that *attrs* will *not* remove existing implementations of
|
||||
``__hash__`` or the equality methods. It just won't add own
|
||||
ones.
|
||||
|
||||
on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]):
|
||||
A callable that is run whenever the user attempts to set an
|
||||
attribute (either by assignment like ``i.x = 42`` or by using
|
||||
`setattr` like ``setattr(i, "x", 42)``). It receives the same
|
||||
arguments as validators: the instance, the attribute that is being
|
||||
modified, and the new value.
|
||||
|
||||
If no exception is raised, the attribute is set to the return value
|
||||
of the callable.
|
||||
|
||||
If a list of callables is passed, they're automatically wrapped in
|
||||
an `attrs.setters.pipe`.
|
||||
|
||||
If left None, the default behavior is to run converters and
|
||||
validators whenever an attribute is set.
|
||||
|
||||
init (bool):
|
||||
Create a ``__init__`` method that initializes the *attrs*
|
||||
attributes. Leading underscores are stripped for the argument name,
|
||||
unless an alias is set on the attribute.
|
||||
|
||||
.. seealso::
|
||||
`init` shows advanced ways to customize the generated
|
||||
``__init__`` method, including executing code before and after.
|
||||
|
||||
repr(bool):
|
||||
Create a ``__repr__`` method with a human readable representation
|
||||
of *attrs* attributes.
|
||||
|
||||
str (bool):
|
||||
Create a ``__str__`` method that is identical to ``__repr__``. This
|
||||
is usually not necessary except for `Exception`\ s.
|
||||
|
||||
eq (bool | None):
|
||||
If True or None (default), add ``__eq__`` and ``__ne__`` methods
|
||||
that check two instances for equality.
|
||||
|
||||
.. seealso::
|
||||
`comparison` describes how to customize the comparison behavior
|
||||
going as far comparing NumPy arrays.
|
||||
|
||||
order (bool | None):
|
||||
If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__``
|
||||
methods that behave like *eq* above and allow instances to be
|
||||
ordered.
|
||||
|
||||
They compare the instances as if they were tuples of their *attrs*
|
||||
attributes if and only if the types of both classes are
|
||||
*identical*.
|
||||
|
||||
If `None` mirror value of *eq*.
|
||||
|
||||
.. seealso:: `comparison`
|
||||
|
||||
unsafe_hash (bool | None):
|
||||
If None (default), the ``__hash__`` method is generated according
|
||||
how *eq* and *frozen* are set.
|
||||
|
||||
1. If *both* are True, *attrs* will generate a ``__hash__`` for
|
||||
you.
|
||||
2. If *eq* is True and *frozen* is False, ``__hash__`` will be set
|
||||
to None, marking it unhashable (which it is).
|
||||
3. If *eq* is False, ``__hash__`` will be left untouched meaning
|
||||
the ``__hash__`` method of the base class will be used. If the
|
||||
base class is `object`, this means it will fall back to id-based
|
||||
hashing.
|
||||
|
||||
Although not recommended, you can decide for yourself and force
|
||||
*attrs* to create one (for example, if the class is immutable even
|
||||
though you didn't freeze it programmatically) by passing True or
|
||||
not. Both of these cases are rather special and should be used
|
||||
carefully.
|
||||
|
||||
.. seealso::
|
||||
|
||||
- Our documentation on `hashing`,
|
||||
- Python's documentation on `object.__hash__`,
|
||||
- and the `GitHub issue that led to the default \ behavior
|
||||
<https://github.com/python-attrs/attrs/issues/136>`_ for more
|
||||
details.
|
||||
|
||||
hash (bool | None):
|
||||
Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence.
|
||||
|
||||
cache_hash (bool):
|
||||
Ensure that the object's hash code is computed only once and stored
|
||||
on the object. If this is set to True, hashing must be either
|
||||
explicitly or implicitly enabled for this class. If the hash code
|
||||
is cached, avoid any reassignments of fields involved in hash code
|
||||
computation or mutations of the objects those fields point to after
|
||||
object creation. If such changes occur, the behavior of the
|
||||
object's hash code is undefined.
|
||||
|
||||
frozen (bool):
|
||||
Make instances immutable after initialization. If someone attempts
|
||||
to modify a frozen instance, `attrs.exceptions.FrozenInstanceError`
|
||||
is raised.
|
||||
|
||||
.. note::
|
||||
|
||||
1. This is achieved by installing a custom ``__setattr__``
|
||||
method on your class, so you can't implement your own.
|
||||
|
||||
2. True immutability is impossible in Python.
|
||||
|
||||
3. This *does* have a minor a runtime performance `impact
|
||||
<how-frozen>` when initializing new instances. In other
|
||||
words: ``__init__`` is slightly slower with ``frozen=True``.
|
||||
|
||||
4. If a class is frozen, you cannot modify ``self`` in
|
||||
``__attrs_post_init__`` or a self-written ``__init__``. You
|
||||
can circumvent that limitation by using
|
||||
``object.__setattr__(self, "attribute_name", value)``.
|
||||
|
||||
5. Subclasses of a frozen class are frozen too.
|
||||
|
||||
kw_only (bool):
|
||||
Make all attributes keyword-only in the generated ``__init__`` (if
|
||||
*init* is False, this parameter is ignored).
|
||||
|
||||
weakref_slot (bool):
|
||||
Make instances weak-referenceable. This has no effect unless
|
||||
*slots* is True.
|
||||
|
||||
field_transformer (~typing.Callable | None):
|
||||
A function that is called with the original class object and all
|
||||
fields right before *attrs* finalizes the class. You can use this,
|
||||
for example, to automatically add converters or validators to
|
||||
fields based on their types.
|
||||
|
||||
.. seealso:: `transform-fields`
|
||||
|
||||
match_args (bool):
|
||||
If True (default), set ``__match_args__`` on the class to support
|
||||
:pep:`634` (*Structural Pattern Matching*). It is a tuple of all
|
||||
non-keyword-only ``__init__`` parameter names on Python 3.10 and
|
||||
later. Ignored on older Python versions.
|
||||
|
||||
collect_by_mro (bool):
|
||||
If True, *attrs* collects attributes from base classes correctly
|
||||
according to the `method resolution order
|
||||
<https://docs.python.org/3/howto/mro.html>`_. If False, *attrs*
|
||||
will mimic the (wrong) behavior of `dataclasses` and :pep:`681`.
|
||||
|
||||
See also `issue #428
|
||||
<https://github.com/python-attrs/attrs/issues/428>`_.
|
||||
|
||||
getstate_setstate (bool | None):
|
||||
.. note::
|
||||
|
||||
This is usually only interesting for slotted classes and you
|
||||
should probably just set *auto_detect* to True.
|
||||
|
||||
If True, ``__getstate__`` and ``__setstate__`` are generated and
|
||||
attached to the class. This is necessary for slotted classes to be
|
||||
pickleable. If left None, it's True by default for slotted classes
|
||||
and False for dict classes.
|
||||
|
||||
If *auto_detect* is True, and *getstate_setstate* is left None, and
|
||||
**either** ``__getstate__`` or ``__setstate__`` is detected
|
||||
directly on the class (meaning: not inherited), it is set to False
|
||||
(this is usually what you want).
|
||||
|
||||
auto_attribs (bool | None):
|
||||
If True, look at type annotations to determine which attributes to
|
||||
use, like `dataclasses`. If False, it will only look for explicit
|
||||
:func:`field` class attributes, like classic *attrs*.
|
||||
|
||||
If left None, it will guess:
|
||||
|
||||
1. If any attributes are annotated and no unannotated
|
||||
`attrs.field`\ s are found, it assumes *auto_attribs=True*.
|
||||
2. Otherwise it assumes *auto_attribs=False* and tries to collect
|
||||
`attrs.field`\ s.
|
||||
|
||||
If *attrs* decides to look at type annotations, **all** fields
|
||||
**must** be annotated. If *attrs* encounters a field that is set to
|
||||
a :func:`field` / `attr.ib` but lacks a type annotation, an
|
||||
`attrs.exceptions.UnannotatedAttributeError` is raised. Use
|
||||
``field_name: typing.Any = field(...)`` if you don't want to set a
|
||||
type.
|
||||
|
||||
.. warning::
|
||||
|
||||
For features that use the attribute name to create decorators
|
||||
(for example, :ref:`validators <validators>`), you still *must*
|
||||
assign :func:`field` / `attr.ib` to them. Otherwise Python will
|
||||
either not find the name or try to use the default value to
|
||||
call, for example, ``validator`` on it.
|
||||
|
||||
Attributes annotated as `typing.ClassVar`, and attributes that are
|
||||
neither annotated nor set to an `field()` are **ignored**.
|
||||
|
||||
these (dict[str, object]):
|
||||
A dictionary of name to the (private) return value of `field()`
|
||||
mappings. This is useful to avoid the definition of your attributes
|
||||
within the class body because you can't (for example, if you want
|
||||
to add ``__repr__`` methods to Django models) or don't want to.
|
||||
|
||||
If *these* is not `None`, *attrs* will *not* search the class body
|
||||
for attributes and will *not* remove any attributes from it.
|
||||
|
||||
The order is deduced from the order of the attributes inside
|
||||
*these*.
|
||||
|
||||
Arguably, this is a rather obscure feature.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
.. versionchanged:: 21.3.0 Converters are also run ``on_setattr``.
|
||||
.. versionadded:: 22.2.0
|
||||
*unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
|
||||
.. versionchanged:: 24.1.0
|
||||
Instances are not compared as tuples of attributes anymore, but using a
|
||||
big ``and`` condition. This is faster and has more correct behavior for
|
||||
uncomparable values like `math.nan`.
|
||||
.. versionadded:: 24.1.0
|
||||
If a class has an *inherited* classmethod called
|
||||
``__attrs_init_subclass__``, it is executed after the class is created.
|
||||
.. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
|
||||
.. versionadded:: 24.3.0
|
||||
Unless already present, a ``__replace__`` method is automatically
|
||||
created for `copy.replace` (Python 3.13+ only).
|
||||
|
||||
.. note::
|
||||
|
||||
The main differences to the classic `attr.s` are:
|
||||
|
||||
- Automatically detect whether or not *auto_attribs* should be `True`
|
||||
(c.f. *auto_attribs* parameter).
|
||||
- Converters and validators run when attributes are set by default --
|
||||
if *frozen* is `False`.
|
||||
- *slots=True*
|
||||
|
||||
Usually, this has only upsides and few visible effects in everyday
|
||||
programming. But it *can* lead to some surprising behaviors, so
|
||||
please make sure to read :term:`slotted classes`.
|
||||
|
||||
- *auto_exc=True*
|
||||
- *auto_detect=True*
|
||||
- *order=False*
|
||||
- Some options that were only relevant on Python 2 or were kept around
|
||||
for backwards-compatibility have been removed.
|
||||
|
||||
"""
|
||||
|
||||
def do_it(cls, auto_attribs):
|
||||
return attrs(
|
||||
maybe_cls=cls,
|
||||
these=these,
|
||||
repr=repr,
|
||||
hash=hash,
|
||||
unsafe_hash=unsafe_hash,
|
||||
init=init,
|
||||
slots=slots,
|
||||
frozen=frozen,
|
||||
weakref_slot=weakref_slot,
|
||||
str=str,
|
||||
auto_attribs=auto_attribs,
|
||||
kw_only=kw_only,
|
||||
cache_hash=cache_hash,
|
||||
auto_exc=auto_exc,
|
||||
eq=eq,
|
||||
order=order,
|
||||
auto_detect=auto_detect,
|
||||
collect_by_mro=True,
|
||||
getstate_setstate=getstate_setstate,
|
||||
on_setattr=on_setattr,
|
||||
field_transformer=field_transformer,
|
||||
match_args=match_args,
|
||||
)
|
||||
|
||||
def wrap(cls):
|
||||
"""
|
||||
Making this a wrapper ensures this code runs during class creation.
|
||||
|
||||
We also ensure that frozen-ness of classes is inherited.
|
||||
"""
|
||||
nonlocal frozen, on_setattr
|
||||
|
||||
had_on_setattr = on_setattr not in (None, setters.NO_OP)
|
||||
|
||||
# By default, mutable classes convert & validate on setattr.
|
||||
if frozen is False and on_setattr is None:
|
||||
on_setattr = _DEFAULT_ON_SETATTR
|
||||
|
||||
# However, if we subclass a frozen class, we inherit the immutability
|
||||
# and disable on_setattr.
|
||||
for base_cls in cls.__bases__:
|
||||
if base_cls.__setattr__ is _frozen_setattrs:
|
||||
if had_on_setattr:
|
||||
msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)."
|
||||
raise ValueError(msg)
|
||||
|
||||
on_setattr = setters.NO_OP
|
||||
break
|
||||
|
||||
if auto_attribs is not None:
|
||||
return do_it(cls, auto_attribs)
|
||||
|
||||
try:
|
||||
return do_it(cls, True)
|
||||
except UnannotatedAttributeError:
|
||||
return do_it(cls, False)
|
||||
|
||||
# maybe_cls's type depends on the usage of the decorator. It's a class
|
||||
# if it's used as `@attrs` but `None` if used as `@attrs()`.
|
||||
if maybe_cls is None:
|
||||
return wrap
|
||||
|
||||
return wrap(maybe_cls)
|
||||
|
||||
|
||||
mutable = define
|
||||
frozen = partial(define, frozen=True, on_setattr=None)
|
||||
|
||||
|
||||
def field(
|
||||
*,
|
||||
default=NOTHING,
|
||||
validator=None,
|
||||
repr=True,
|
||||
hash=None,
|
||||
init=True,
|
||||
metadata=None,
|
||||
type=None,
|
||||
converter=None,
|
||||
factory=None,
|
||||
kw_only=False,
|
||||
eq=None,
|
||||
order=None,
|
||||
on_setattr=None,
|
||||
alias=None,
|
||||
):
|
||||
"""
|
||||
Create a new :term:`field` / :term:`attribute` on a class.
|
||||
|
||||
.. warning::
|
||||
|
||||
Does **nothing** unless the class is also decorated with
|
||||
`attrs.define` (or similar)!
|
||||
|
||||
Args:
|
||||
default:
|
||||
A value that is used if an *attrs*-generated ``__init__`` is used
|
||||
and no value is passed while instantiating or the attribute is
|
||||
excluded using ``init=False``.
|
||||
|
||||
If the value is an instance of `attrs.Factory`, its callable will
|
||||
be used to construct a new value (useful for mutable data types
|
||||
like lists or dicts).
|
||||
|
||||
If a default is not set (or set manually to `attrs.NOTHING`), a
|
||||
value *must* be supplied when instantiating; otherwise a
|
||||
`TypeError` will be raised.
|
||||
|
||||
.. seealso:: `defaults`
|
||||
|
||||
factory (~typing.Callable):
|
||||
Syntactic sugar for ``default=attr.Factory(factory)``.
|
||||
|
||||
validator (~typing.Callable | list[~typing.Callable]):
|
||||
Callable that is called by *attrs*-generated ``__init__`` methods
|
||||
after the instance has been initialized. They receive the
|
||||
initialized instance, the :func:`~attrs.Attribute`, and the passed
|
||||
value.
|
||||
|
||||
The return value is *not* inspected so the validator has to throw
|
||||
an exception itself.
|
||||
|
||||
If a `list` is passed, its items are treated as validators and must
|
||||
all pass.
|
||||
|
||||
Validators can be globally disabled and re-enabled using
|
||||
`attrs.validators.get_disabled` / `attrs.validators.set_disabled`.
|
||||
|
||||
The validator can also be set using decorator notation as shown
|
||||
below.
|
||||
|
||||
.. seealso:: :ref:`validators`
|
||||
|
||||
repr (bool | ~typing.Callable):
|
||||
Include this attribute in the generated ``__repr__`` method. If
|
||||
True, include the attribute; if False, omit it. By default, the
|
||||
built-in ``repr()`` function is used. To override how the attribute
|
||||
value is formatted, pass a ``callable`` that takes a single value
|
||||
and returns a string. Note that the resulting string is used as-is,
|
||||
which means it will be used directly *instead* of calling
|
||||
``repr()`` (the default).
|
||||
|
||||
eq (bool | ~typing.Callable):
|
||||
If True (default), include this attribute in the generated
|
||||
``__eq__`` and ``__ne__`` methods that check two instances for
|
||||
equality. To override how the attribute value is compared, pass a
|
||||
callable that takes a single value and returns the value to be
|
||||
compared.
|
||||
|
||||
.. seealso:: `comparison`
|
||||
|
||||
order (bool | ~typing.Callable):
|
||||
If True (default), include this attributes in the generated
|
||||
``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To
|
||||
override how the attribute value is ordered, pass a callable that
|
||||
takes a single value and returns the value to be ordered.
|
||||
|
||||
.. seealso:: `comparison`
|
||||
|
||||
hash (bool | None):
|
||||
Include this attribute in the generated ``__hash__`` method. If
|
||||
None (default), mirror *eq*'s value. This is the correct behavior
|
||||
according the Python spec. Setting this value to anything else
|
||||
than None is *discouraged*.
|
||||
|
||||
.. seealso:: `hashing`
|
||||
|
||||
init (bool):
|
||||
Include this attribute in the generated ``__init__`` method.
|
||||
|
||||
It is possible to set this to False and set a default value. In
|
||||
that case this attributed is unconditionally initialized with the
|
||||
specified default value or factory.
|
||||
|
||||
.. seealso:: `init`
|
||||
|
||||
converter (typing.Callable | Converter):
|
||||
A callable that is called by *attrs*-generated ``__init__`` methods
|
||||
to convert attribute's value to the desired format.
|
||||
|
||||
If a vanilla callable is passed, it is given the passed-in value as
|
||||
the only positional argument. It is possible to receive additional
|
||||
arguments by wrapping the callable in a `Converter`.
|
||||
|
||||
Either way, the returned value will be used as the new value of the
|
||||
attribute. The value is converted before being passed to the
|
||||
validator, if any.
|
||||
|
||||
.. seealso:: :ref:`converters`
|
||||
|
||||
metadata (dict | None):
|
||||
An arbitrary mapping, to be used by third-party code.
|
||||
|
||||
.. seealso:: `extending-metadata`.
|
||||
|
||||
type (type):
|
||||
The type of the attribute. Nowadays, the preferred method to
|
||||
specify the type is using a variable annotation (see :pep:`526`).
|
||||
This argument is provided for backwards-compatibility and for usage
|
||||
with `make_class`. Regardless of the approach used, the type will
|
||||
be stored on ``Attribute.type``.
|
||||
|
||||
Please note that *attrs* doesn't do anything with this metadata by
|
||||
itself. You can use it as part of your own code or for `static type
|
||||
checking <types>`.
|
||||
|
||||
kw_only (bool):
|
||||
Make this attribute keyword-only in the generated ``__init__`` (if
|
||||
``init`` is False, this parameter is ignored).
|
||||
|
||||
on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]):
|
||||
Allows to overwrite the *on_setattr* setting from `attr.s`. If left
|
||||
None, the *on_setattr* value from `attr.s` is used. Set to
|
||||
`attrs.setters.NO_OP` to run **no** `setattr` hooks for this
|
||||
attribute -- regardless of the setting in `define()`.
|
||||
|
||||
alias (str | None):
|
||||
Override this attribute's parameter name in the generated
|
||||
``__init__`` method. If left None, default to ``name`` stripped
|
||||
of leading underscores. See `private-attributes`.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
.. versionchanged:: 21.1.0
|
||||
*eq*, *order*, and *cmp* also accept a custom callable
|
||||
.. versionadded:: 22.2.0 *alias*
|
||||
.. versionadded:: 23.1.0
|
||||
The *type* parameter has been re-added; mostly for `attrs.make_class`.
|
||||
Please note that type checkers ignore this metadata.
|
||||
|
||||
.. seealso::
|
||||
|
||||
`attr.ib`
|
||||
"""
|
||||
return attrib(
|
||||
default=default,
|
||||
validator=validator,
|
||||
repr=repr,
|
||||
hash=hash,
|
||||
init=init,
|
||||
metadata=metadata,
|
||||
type=type,
|
||||
converter=converter,
|
||||
factory=factory,
|
||||
kw_only=kw_only,
|
||||
eq=eq,
|
||||
order=order,
|
||||
on_setattr=on_setattr,
|
||||
alias=alias,
|
||||
)
|
||||
|
||||
|
||||
def asdict(inst, *, recurse=True, filter=None, value_serializer=None):
|
||||
"""
|
||||
Same as `attr.asdict`, except that collections types are always retained
|
||||
and dict is always used as *dict_factory*.
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _asdict(
|
||||
inst=inst,
|
||||
recurse=recurse,
|
||||
filter=filter,
|
||||
value_serializer=value_serializer,
|
||||
retain_collection_types=True,
|
||||
)
|
||||
|
||||
|
||||
def astuple(inst, *, recurse=True, filter=None):
|
||||
"""
|
||||
Same as `attr.astuple`, except that collections types are always retained
|
||||
and `tuple` is always used as the *tuple_factory*.
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _astuple(
|
||||
inst=inst, recurse=recurse, filter=filter, retain_collection_types=True
|
||||
)
|
||||
15
.venv/lib/python3.10/site-packages/attr/_typing_compat.pyi
Normal file
15
.venv/lib/python3.10/site-packages/attr/_typing_compat.pyi
Normal file
@@ -0,0 +1,15 @@
|
||||
from typing import Any, ClassVar, Protocol
|
||||
|
||||
# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`.
|
||||
MYPY = False
|
||||
|
||||
if MYPY:
|
||||
# A protocol to be able to statically accept an attrs class.
|
||||
class AttrsInstance_(Protocol):
|
||||
__attrs_attrs__: ClassVar[Any]
|
||||
|
||||
else:
|
||||
# For type checkers without plug-in support use an empty protocol that
|
||||
# will (hopefully) be combined into a union.
|
||||
class AttrsInstance_(Protocol):
|
||||
pass
|
||||
86
.venv/lib/python3.10/site-packages/attr/_version_info.py
Normal file
86
.venv/lib/python3.10/site-packages/attr/_version_info.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
from functools import total_ordering
|
||||
|
||||
from ._funcs import astuple
|
||||
from ._make import attrib, attrs
|
||||
|
||||
|
||||
@total_ordering
|
||||
@attrs(eq=False, order=False, slots=True, frozen=True)
|
||||
class VersionInfo:
|
||||
"""
|
||||
A version object that can be compared to tuple of length 1--4:
|
||||
|
||||
>>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2)
|
||||
True
|
||||
>>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1)
|
||||
True
|
||||
>>> vi = attr.VersionInfo(19, 2, 0, "final")
|
||||
>>> vi < (19, 1, 1)
|
||||
False
|
||||
>>> vi < (19,)
|
||||
False
|
||||
>>> vi == (19, 2,)
|
||||
True
|
||||
>>> vi == (19, 2, 1)
|
||||
False
|
||||
|
||||
.. versionadded:: 19.2
|
||||
"""
|
||||
|
||||
year = attrib(type=int)
|
||||
minor = attrib(type=int)
|
||||
micro = attrib(type=int)
|
||||
releaselevel = attrib(type=str)
|
||||
|
||||
@classmethod
|
||||
def _from_version_string(cls, s):
|
||||
"""
|
||||
Parse *s* and return a _VersionInfo.
|
||||
"""
|
||||
v = s.split(".")
|
||||
if len(v) == 3:
|
||||
v.append("final")
|
||||
|
||||
return cls(
|
||||
year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3]
|
||||
)
|
||||
|
||||
def _ensure_tuple(self, other):
|
||||
"""
|
||||
Ensure *other* is a tuple of a valid length.
|
||||
|
||||
Returns a possibly transformed *other* and ourselves as a tuple of
|
||||
the same length as *other*.
|
||||
"""
|
||||
|
||||
if self.__class__ is other.__class__:
|
||||
other = astuple(other)
|
||||
|
||||
if not isinstance(other, tuple):
|
||||
raise NotImplementedError
|
||||
|
||||
if not (1 <= len(other) <= 4):
|
||||
raise NotImplementedError
|
||||
|
||||
return astuple(self)[: len(other)], other
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
us, them = self._ensure_tuple(other)
|
||||
except NotImplementedError:
|
||||
return NotImplemented
|
||||
|
||||
return us == them
|
||||
|
||||
def __lt__(self, other):
|
||||
try:
|
||||
us, them = self._ensure_tuple(other)
|
||||
except NotImplementedError:
|
||||
return NotImplemented
|
||||
|
||||
# Since alphabetically "dev0" < "final" < "post1" < "post2", we don't
|
||||
# have to do anything special with releaselevel for now.
|
||||
return us < them
|
||||
@@ -0,0 +1,9 @@
|
||||
class VersionInfo:
|
||||
@property
|
||||
def year(self) -> int: ...
|
||||
@property
|
||||
def minor(self) -> int: ...
|
||||
@property
|
||||
def micro(self) -> int: ...
|
||||
@property
|
||||
def releaselevel(self) -> str: ...
|
||||
162
.venv/lib/python3.10/site-packages/attr/converters.py
Normal file
162
.venv/lib/python3.10/site-packages/attr/converters.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Commonly useful converters.
|
||||
"""
|
||||
|
||||
import typing
|
||||
|
||||
from ._compat import _AnnotationExtractor
|
||||
from ._make import NOTHING, Converter, Factory, pipe
|
||||
|
||||
|
||||
__all__ = [
|
||||
"default_if_none",
|
||||
"optional",
|
||||
"pipe",
|
||||
"to_bool",
|
||||
]
|
||||
|
||||
|
||||
def optional(converter):
|
||||
"""
|
||||
A converter that allows an attribute to be optional. An optional attribute
|
||||
is one which can be set to `None`.
|
||||
|
||||
Type annotations will be inferred from the wrapped converter's, if it has
|
||||
any.
|
||||
|
||||
Args:
|
||||
converter (typing.Callable):
|
||||
the converter that is used for non-`None` values.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
|
||||
if isinstance(converter, Converter):
|
||||
|
||||
def optional_converter(val, inst, field):
|
||||
if val is None:
|
||||
return None
|
||||
return converter(val, inst, field)
|
||||
|
||||
else:
|
||||
|
||||
def optional_converter(val):
|
||||
if val is None:
|
||||
return None
|
||||
return converter(val)
|
||||
|
||||
xtr = _AnnotationExtractor(converter)
|
||||
|
||||
t = xtr.get_first_param_type()
|
||||
if t:
|
||||
optional_converter.__annotations__["val"] = typing.Optional[t]
|
||||
|
||||
rt = xtr.get_return_type()
|
||||
if rt:
|
||||
optional_converter.__annotations__["return"] = typing.Optional[rt]
|
||||
|
||||
if isinstance(converter, Converter):
|
||||
return Converter(optional_converter, takes_self=True, takes_field=True)
|
||||
|
||||
return optional_converter
|
||||
|
||||
|
||||
def default_if_none(default=NOTHING, factory=None):
|
||||
"""
|
||||
A converter that allows to replace `None` values by *default* or the result
|
||||
of *factory*.
|
||||
|
||||
Args:
|
||||
default:
|
||||
Value to be used if `None` is passed. Passing an instance of
|
||||
`attrs.Factory` is supported, however the ``takes_self`` option is
|
||||
*not*.
|
||||
|
||||
factory (typing.Callable):
|
||||
A callable that takes no parameters whose result is used if `None`
|
||||
is passed.
|
||||
|
||||
Raises:
|
||||
TypeError: If **neither** *default* or *factory* is passed.
|
||||
|
||||
TypeError: If **both** *default* and *factory* are passed.
|
||||
|
||||
ValueError:
|
||||
If an instance of `attrs.Factory` is passed with
|
||||
``takes_self=True``.
|
||||
|
||||
.. versionadded:: 18.2.0
|
||||
"""
|
||||
if default is NOTHING and factory is None:
|
||||
msg = "Must pass either `default` or `factory`."
|
||||
raise TypeError(msg)
|
||||
|
||||
if default is not NOTHING and factory is not None:
|
||||
msg = "Must pass either `default` or `factory` but not both."
|
||||
raise TypeError(msg)
|
||||
|
||||
if factory is not None:
|
||||
default = Factory(factory)
|
||||
|
||||
if isinstance(default, Factory):
|
||||
if default.takes_self:
|
||||
msg = "`takes_self` is not supported by default_if_none."
|
||||
raise ValueError(msg)
|
||||
|
||||
def default_if_none_converter(val):
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
return default.factory()
|
||||
|
||||
else:
|
||||
|
||||
def default_if_none_converter(val):
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
return default
|
||||
|
||||
return default_if_none_converter
|
||||
|
||||
|
||||
def to_bool(val):
|
||||
"""
|
||||
Convert "boolean" strings (for example, from environment variables) to real
|
||||
booleans.
|
||||
|
||||
Values mapping to `True`:
|
||||
|
||||
- ``True``
|
||||
- ``"true"`` / ``"t"``
|
||||
- ``"yes"`` / ``"y"``
|
||||
- ``"on"``
|
||||
- ``"1"``
|
||||
- ``1``
|
||||
|
||||
Values mapping to `False`:
|
||||
|
||||
- ``False``
|
||||
- ``"false"`` / ``"f"``
|
||||
- ``"no"`` / ``"n"``
|
||||
- ``"off"``
|
||||
- ``"0"``
|
||||
- ``0``
|
||||
|
||||
Raises:
|
||||
ValueError: For any other value.
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
if isinstance(val, str):
|
||||
val = val.lower()
|
||||
|
||||
if val in (True, "true", "t", "yes", "y", "on", "1", 1):
|
||||
return True
|
||||
if val in (False, "false", "f", "no", "n", "off", "0", 0):
|
||||
return False
|
||||
|
||||
msg = f"Cannot convert value to bool: {val!r}"
|
||||
raise ValueError(msg)
|
||||
19
.venv/lib/python3.10/site-packages/attr/converters.pyi
Normal file
19
.venv/lib/python3.10/site-packages/attr/converters.pyi
Normal file
@@ -0,0 +1,19 @@
|
||||
from typing import Callable, Any, overload
|
||||
|
||||
from attrs import _ConverterType, _CallableConverterType
|
||||
|
||||
@overload
|
||||
def pipe(*validators: _CallableConverterType) -> _CallableConverterType: ...
|
||||
@overload
|
||||
def pipe(*validators: _ConverterType) -> _ConverterType: ...
|
||||
@overload
|
||||
def optional(converter: _CallableConverterType) -> _CallableConverterType: ...
|
||||
@overload
|
||||
def optional(converter: _ConverterType) -> _ConverterType: ...
|
||||
@overload
|
||||
def default_if_none(default: Any) -> _CallableConverterType: ...
|
||||
@overload
|
||||
def default_if_none(
|
||||
*, factory: Callable[[], Any]
|
||||
) -> _CallableConverterType: ...
|
||||
def to_bool(val: str | int | bool) -> bool: ...
|
||||
95
.venv/lib/python3.10/site-packages/attr/exceptions.py
Normal file
95
.venv/lib/python3.10/site-packages/attr/exceptions.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
class FrozenError(AttributeError):
|
||||
"""
|
||||
A frozen/immutable instance or attribute have been attempted to be
|
||||
modified.
|
||||
|
||||
It mirrors the behavior of ``namedtuples`` by using the same error message
|
||||
and subclassing `AttributeError`.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
|
||||
msg = "can't set attribute"
|
||||
args: ClassVar[tuple[str]] = [msg]
|
||||
|
||||
|
||||
class FrozenInstanceError(FrozenError):
|
||||
"""
|
||||
A frozen instance has been attempted to be modified.
|
||||
|
||||
.. versionadded:: 16.1.0
|
||||
"""
|
||||
|
||||
|
||||
class FrozenAttributeError(FrozenError):
|
||||
"""
|
||||
A frozen attribute has been attempted to be modified.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
|
||||
|
||||
class AttrsAttributeNotFoundError(ValueError):
|
||||
"""
|
||||
An *attrs* function couldn't find an attribute that the user asked for.
|
||||
|
||||
.. versionadded:: 16.2.0
|
||||
"""
|
||||
|
||||
|
||||
class NotAnAttrsClassError(ValueError):
|
||||
"""
|
||||
A non-*attrs* class has been passed into an *attrs* function.
|
||||
|
||||
.. versionadded:: 16.2.0
|
||||
"""
|
||||
|
||||
|
||||
class DefaultAlreadySetError(RuntimeError):
|
||||
"""
|
||||
A default has been set when defining the field and is attempted to be reset
|
||||
using the decorator.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
|
||||
|
||||
class UnannotatedAttributeError(RuntimeError):
|
||||
"""
|
||||
A class with ``auto_attribs=True`` has a field without a type annotation.
|
||||
|
||||
.. versionadded:: 17.3.0
|
||||
"""
|
||||
|
||||
|
||||
class PythonTooOldError(RuntimeError):
|
||||
"""
|
||||
It was attempted to use an *attrs* feature that requires a newer Python
|
||||
version.
|
||||
|
||||
.. versionadded:: 18.2.0
|
||||
"""
|
||||
|
||||
|
||||
class NotCallableError(TypeError):
|
||||
"""
|
||||
A field requiring a callable has been set with a value that is not
|
||||
callable.
|
||||
|
||||
.. versionadded:: 19.2.0
|
||||
"""
|
||||
|
||||
def __init__(self, msg, value):
|
||||
super(TypeError, self).__init__(msg, value)
|
||||
self.msg = msg
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.msg)
|
||||
17
.venv/lib/python3.10/site-packages/attr/exceptions.pyi
Normal file
17
.venv/lib/python3.10/site-packages/attr/exceptions.pyi
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import Any
|
||||
|
||||
class FrozenError(AttributeError):
|
||||
msg: str = ...
|
||||
|
||||
class FrozenInstanceError(FrozenError): ...
|
||||
class FrozenAttributeError(FrozenError): ...
|
||||
class AttrsAttributeNotFoundError(ValueError): ...
|
||||
class NotAnAttrsClassError(ValueError): ...
|
||||
class DefaultAlreadySetError(RuntimeError): ...
|
||||
class UnannotatedAttributeError(RuntimeError): ...
|
||||
class PythonTooOldError(RuntimeError): ...
|
||||
|
||||
class NotCallableError(TypeError):
|
||||
msg: str = ...
|
||||
value: Any = ...
|
||||
def __init__(self, msg: str, value: Any) -> None: ...
|
||||
72
.venv/lib/python3.10/site-packages/attr/filters.py
Normal file
72
.venv/lib/python3.10/site-packages/attr/filters.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Commonly useful filters for `attrs.asdict` and `attrs.astuple`.
|
||||
"""
|
||||
|
||||
from ._make import Attribute
|
||||
|
||||
|
||||
def _split_what(what):
|
||||
"""
|
||||
Returns a tuple of `frozenset`s of classes and attributes.
|
||||
"""
|
||||
return (
|
||||
frozenset(cls for cls in what if isinstance(cls, type)),
|
||||
frozenset(cls for cls in what if isinstance(cls, str)),
|
||||
frozenset(cls for cls in what if isinstance(cls, Attribute)),
|
||||
)
|
||||
|
||||
|
||||
def include(*what):
|
||||
"""
|
||||
Create a filter that only allows *what*.
|
||||
|
||||
Args:
|
||||
what (list[type, str, attrs.Attribute]):
|
||||
What to include. Can be a type, a name, or an attribute.
|
||||
|
||||
Returns:
|
||||
Callable:
|
||||
A callable that can be passed to `attrs.asdict`'s and
|
||||
`attrs.astuple`'s *filter* argument.
|
||||
|
||||
.. versionchanged:: 23.1.0 Accept strings with field names.
|
||||
"""
|
||||
cls, names, attrs = _split_what(what)
|
||||
|
||||
def include_(attribute, value):
|
||||
return (
|
||||
value.__class__ in cls
|
||||
or attribute.name in names
|
||||
or attribute in attrs
|
||||
)
|
||||
|
||||
return include_
|
||||
|
||||
|
||||
def exclude(*what):
|
||||
"""
|
||||
Create a filter that does **not** allow *what*.
|
||||
|
||||
Args:
|
||||
what (list[type, str, attrs.Attribute]):
|
||||
What to exclude. Can be a type, a name, or an attribute.
|
||||
|
||||
Returns:
|
||||
Callable:
|
||||
A callable that can be passed to `attrs.asdict`'s and
|
||||
`attrs.astuple`'s *filter* argument.
|
||||
|
||||
.. versionchanged:: 23.3.0 Accept field name string as input argument
|
||||
"""
|
||||
cls, names, attrs = _split_what(what)
|
||||
|
||||
def exclude_(attribute, value):
|
||||
return not (
|
||||
value.__class__ in cls
|
||||
or attribute.name in names
|
||||
or attribute in attrs
|
||||
)
|
||||
|
||||
return exclude_
|
||||
6
.venv/lib/python3.10/site-packages/attr/filters.pyi
Normal file
6
.venv/lib/python3.10/site-packages/attr/filters.pyi
Normal file
@@ -0,0 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from . import Attribute, _FilterType
|
||||
|
||||
def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ...
|
||||
def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ...
|
||||
0
.venv/lib/python3.10/site-packages/attr/py.typed
Normal file
0
.venv/lib/python3.10/site-packages/attr/py.typed
Normal file
79
.venv/lib/python3.10/site-packages/attr/setters.py
Normal file
79
.venv/lib/python3.10/site-packages/attr/setters.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Commonly used hooks for on_setattr.
|
||||
"""
|
||||
|
||||
from . import _config
|
||||
from .exceptions import FrozenAttributeError
|
||||
|
||||
|
||||
def pipe(*setters):
|
||||
"""
|
||||
Run all *setters* and return the return value of the last one.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
|
||||
def wrapped_pipe(instance, attrib, new_value):
|
||||
rv = new_value
|
||||
|
||||
for setter in setters:
|
||||
rv = setter(instance, attrib, rv)
|
||||
|
||||
return rv
|
||||
|
||||
return wrapped_pipe
|
||||
|
||||
|
||||
def frozen(_, __, ___):
|
||||
"""
|
||||
Prevent an attribute to be modified.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
raise FrozenAttributeError
|
||||
|
||||
|
||||
def validate(instance, attrib, new_value):
|
||||
"""
|
||||
Run *attrib*'s validator on *new_value* if it has one.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
if _config._run_validators is False:
|
||||
return new_value
|
||||
|
||||
v = attrib.validator
|
||||
if not v:
|
||||
return new_value
|
||||
|
||||
v(instance, attrib, new_value)
|
||||
|
||||
return new_value
|
||||
|
||||
|
||||
def convert(instance, attrib, new_value):
|
||||
"""
|
||||
Run *attrib*'s converter -- if it has one -- on *new_value* and return the
|
||||
result.
|
||||
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
c = attrib.converter
|
||||
if c:
|
||||
# This can be removed once we drop 3.8 and use attrs.Converter instead.
|
||||
from ._make import Converter
|
||||
|
||||
if not isinstance(c, Converter):
|
||||
return c(new_value)
|
||||
|
||||
return c(new_value, instance, attrib)
|
||||
|
||||
return new_value
|
||||
|
||||
|
||||
# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes.
|
||||
# Sphinx's autodata stopped working, so the docstring is inlined in the API
|
||||
# docs.
|
||||
NO_OP = object()
|
||||
20
.venv/lib/python3.10/site-packages/attr/setters.pyi
Normal file
20
.venv/lib/python3.10/site-packages/attr/setters.pyi
Normal file
@@ -0,0 +1,20 @@
|
||||
from typing import Any, NewType, NoReturn, TypeVar
|
||||
|
||||
from . import Attribute
|
||||
from attrs import _OnSetAttrType
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
def frozen(
|
||||
instance: Any, attribute: Attribute[Any], new_value: Any
|
||||
) -> NoReturn: ...
|
||||
def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ...
|
||||
def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ...
|
||||
|
||||
# convert is allowed to return Any, because they can be chained using pipe.
|
||||
def convert(
|
||||
instance: Any, attribute: Attribute[Any], new_value: Any
|
||||
) -> Any: ...
|
||||
|
||||
_NoOpType = NewType("_NoOpType", object)
|
||||
NO_OP: _NoOpType
|
||||
710
.venv/lib/python3.10/site-packages/attr/validators.py
Normal file
710
.venv/lib/python3.10/site-packages/attr/validators.py
Normal file
@@ -0,0 +1,710 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Commonly useful validators.
|
||||
"""
|
||||
|
||||
import operator
|
||||
import re
|
||||
|
||||
from contextlib import contextmanager
|
||||
from re import Pattern
|
||||
|
||||
from ._config import get_run_validators, set_run_validators
|
||||
from ._make import _AndValidator, and_, attrib, attrs
|
||||
from .converters import default_if_none
|
||||
from .exceptions import NotCallableError
|
||||
|
||||
|
||||
__all__ = [
|
||||
"and_",
|
||||
"deep_iterable",
|
||||
"deep_mapping",
|
||||
"disabled",
|
||||
"ge",
|
||||
"get_disabled",
|
||||
"gt",
|
||||
"in_",
|
||||
"instance_of",
|
||||
"is_callable",
|
||||
"le",
|
||||
"lt",
|
||||
"matches_re",
|
||||
"max_len",
|
||||
"min_len",
|
||||
"not_",
|
||||
"optional",
|
||||
"or_",
|
||||
"set_disabled",
|
||||
]
|
||||
|
||||
|
||||
def set_disabled(disabled):
|
||||
"""
|
||||
Globally disable or enable running validators.
|
||||
|
||||
By default, they are run.
|
||||
|
||||
Args:
|
||||
disabled (bool): If `True`, disable running all validators.
|
||||
|
||||
.. warning::
|
||||
|
||||
This function is not thread-safe!
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
set_run_validators(not disabled)
|
||||
|
||||
|
||||
def get_disabled():
|
||||
"""
|
||||
Return a bool indicating whether validators are currently disabled or not.
|
||||
|
||||
Returns:
|
||||
bool:`True` if validators are currently disabled.
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return not get_run_validators()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def disabled():
|
||||
"""
|
||||
Context manager that disables running validators within its context.
|
||||
|
||||
.. warning::
|
||||
|
||||
This context manager is not thread-safe!
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
set_run_validators(False)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
set_run_validators(True)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _InstanceOfValidator:
|
||||
type = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not isinstance(value, self.type):
|
||||
msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."
|
||||
raise TypeError(
|
||||
msg,
|
||||
attr,
|
||||
self.type,
|
||||
value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<instance_of validator for type {self.type!r}>"
|
||||
|
||||
|
||||
def instance_of(type):
|
||||
"""
|
||||
A validator that raises a `TypeError` if the initializer is called with a
|
||||
wrong type for this particular attribute (checks are performed using
|
||||
`isinstance` therefore it's also valid to pass a tuple of types).
|
||||
|
||||
Args:
|
||||
type (type | tuple[type]): The type to check for.
|
||||
|
||||
Raises:
|
||||
TypeError:
|
||||
With a human readable error message, the attribute (of type
|
||||
`attrs.Attribute`), the expected type, and the value it got.
|
||||
"""
|
||||
return _InstanceOfValidator(type)
|
||||
|
||||
|
||||
@attrs(repr=False, frozen=True, slots=True)
|
||||
class _MatchesReValidator:
|
||||
pattern = attrib()
|
||||
match_func = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not self.match_func(value):
|
||||
msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"
|
||||
raise ValueError(
|
||||
msg,
|
||||
attr,
|
||||
self.pattern,
|
||||
value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<matches_re validator for pattern {self.pattern!r}>"
|
||||
|
||||
|
||||
def matches_re(regex, flags=0, func=None):
|
||||
r"""
|
||||
A validator that raises `ValueError` if the initializer is called with a
|
||||
string that doesn't match *regex*.
|
||||
|
||||
Args:
|
||||
regex (str, re.Pattern):
|
||||
A regex string or precompiled pattern to match against
|
||||
|
||||
flags (int):
|
||||
Flags that will be passed to the underlying re function (default 0)
|
||||
|
||||
func (typing.Callable):
|
||||
Which underlying `re` function to call. Valid options are
|
||||
`re.fullmatch`, `re.search`, and `re.match`; the default `None`
|
||||
means `re.fullmatch`. For performance reasons, the pattern is
|
||||
always precompiled using `re.compile`.
|
||||
|
||||
.. versionadded:: 19.2.0
|
||||
.. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
|
||||
"""
|
||||
valid_funcs = (re.fullmatch, None, re.search, re.match)
|
||||
if func not in valid_funcs:
|
||||
msg = "'func' must be one of {}.".format(
|
||||
", ".join(
|
||||
sorted((e and e.__name__) or "None" for e in set(valid_funcs))
|
||||
)
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
if isinstance(regex, Pattern):
|
||||
if flags:
|
||||
msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"
|
||||
raise TypeError(msg)
|
||||
pattern = regex
|
||||
else:
|
||||
pattern = re.compile(regex, flags)
|
||||
|
||||
if func is re.match:
|
||||
match_func = pattern.match
|
||||
elif func is re.search:
|
||||
match_func = pattern.search
|
||||
else:
|
||||
match_func = pattern.fullmatch
|
||||
|
||||
return _MatchesReValidator(pattern, match_func)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _OptionalValidator:
|
||||
validator = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
if value is None:
|
||||
return
|
||||
|
||||
self.validator(inst, attr, value)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<optional validator for {self.validator!r} or None>"
|
||||
|
||||
|
||||
def optional(validator):
|
||||
"""
|
||||
A validator that makes an attribute optional. An optional attribute is one
|
||||
which can be set to `None` in addition to satisfying the requirements of
|
||||
the sub-validator.
|
||||
|
||||
Args:
|
||||
validator
|
||||
(typing.Callable | tuple[typing.Callable] | list[typing.Callable]):
|
||||
A validator (or validators) that is used for non-`None` values.
|
||||
|
||||
.. versionadded:: 15.1.0
|
||||
.. versionchanged:: 17.1.0 *validator* can be a list of validators.
|
||||
.. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
|
||||
"""
|
||||
if isinstance(validator, (list, tuple)):
|
||||
return _OptionalValidator(_AndValidator(validator))
|
||||
|
||||
return _OptionalValidator(validator)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _InValidator:
|
||||
options = attrib()
|
||||
_original_options = attrib(hash=False)
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
try:
|
||||
in_options = value in self.options
|
||||
except TypeError: # e.g. `1 in "abc"`
|
||||
in_options = False
|
||||
|
||||
if not in_options:
|
||||
msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})"
|
||||
raise ValueError(
|
||||
msg,
|
||||
attr,
|
||||
self._original_options,
|
||||
value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<in_ validator with options {self._original_options!r}>"
|
||||
|
||||
|
||||
def in_(options):
|
||||
"""
|
||||
A validator that raises a `ValueError` if the initializer is called with a
|
||||
value that does not belong in the *options* provided.
|
||||
|
||||
The check is performed using ``value in options``, so *options* has to
|
||||
support that operation.
|
||||
|
||||
To keep the validator hashable, dicts, lists, and sets are transparently
|
||||
transformed into a `tuple`.
|
||||
|
||||
Args:
|
||||
options: Allowed options.
|
||||
|
||||
Raises:
|
||||
ValueError:
|
||||
With a human readable error message, the attribute (of type
|
||||
`attrs.Attribute`), the expected options, and the value it got.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
.. versionchanged:: 22.1.0
|
||||
The ValueError was incomplete until now and only contained the human
|
||||
readable error message. Now it contains all the information that has
|
||||
been promised since 17.1.0.
|
||||
.. versionchanged:: 24.1.0
|
||||
*options* that are a list, dict, or a set are now transformed into a
|
||||
tuple to keep the validator hashable.
|
||||
"""
|
||||
repr_options = options
|
||||
if isinstance(options, (list, dict, set)):
|
||||
options = tuple(options)
|
||||
|
||||
return _InValidator(options, repr_options)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=False, unsafe_hash=True)
|
||||
class _IsCallableValidator:
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not callable(value):
|
||||
message = (
|
||||
"'{name}' must be callable "
|
||||
"(got {value!r} that is a {actual!r})."
|
||||
)
|
||||
raise NotCallableError(
|
||||
msg=message.format(
|
||||
name=attr.name, value=value, actual=value.__class__
|
||||
),
|
||||
value=value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<is_callable validator>"
|
||||
|
||||
|
||||
def is_callable():
|
||||
"""
|
||||
A validator that raises a `attrs.exceptions.NotCallableError` if the
|
||||
initializer is called with a value for this particular attribute that is
|
||||
not callable.
|
||||
|
||||
.. versionadded:: 19.1.0
|
||||
|
||||
Raises:
|
||||
attrs.exceptions.NotCallableError:
|
||||
With a human readable error message containing the attribute
|
||||
(`attrs.Attribute`) name, and the value it got.
|
||||
"""
|
||||
return _IsCallableValidator()
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _DeepIterable:
|
||||
member_validator = attrib(validator=is_callable())
|
||||
iterable_validator = attrib(
|
||||
default=None, validator=optional(is_callable())
|
||||
)
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if self.iterable_validator is not None:
|
||||
self.iterable_validator(inst, attr, value)
|
||||
|
||||
for member in value:
|
||||
self.member_validator(inst, attr, member)
|
||||
|
||||
def __repr__(self):
|
||||
iterable_identifier = (
|
||||
""
|
||||
if self.iterable_validator is None
|
||||
else f" {self.iterable_validator!r}"
|
||||
)
|
||||
return (
|
||||
f"<deep_iterable validator for{iterable_identifier}"
|
||||
f" iterables of {self.member_validator!r}>"
|
||||
)
|
||||
|
||||
|
||||
def deep_iterable(member_validator, iterable_validator=None):
|
||||
"""
|
||||
A validator that performs deep validation of an iterable.
|
||||
|
||||
Args:
|
||||
member_validator: Validator to apply to iterable members.
|
||||
|
||||
iterable_validator:
|
||||
Validator to apply to iterable itself (optional).
|
||||
|
||||
Raises
|
||||
TypeError: if any sub-validators fail
|
||||
|
||||
.. versionadded:: 19.1.0
|
||||
"""
|
||||
if isinstance(member_validator, (list, tuple)):
|
||||
member_validator = and_(*member_validator)
|
||||
return _DeepIterable(member_validator, iterable_validator)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _DeepMapping:
|
||||
key_validator = attrib(validator=is_callable())
|
||||
value_validator = attrib(validator=is_callable())
|
||||
mapping_validator = attrib(default=None, validator=optional(is_callable()))
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if self.mapping_validator is not None:
|
||||
self.mapping_validator(inst, attr, value)
|
||||
|
||||
for key in value:
|
||||
self.key_validator(inst, attr, key)
|
||||
self.value_validator(inst, attr, value[key])
|
||||
|
||||
def __repr__(self):
|
||||
return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
|
||||
|
||||
|
||||
def deep_mapping(key_validator, value_validator, mapping_validator=None):
|
||||
"""
|
||||
A validator that performs deep validation of a dictionary.
|
||||
|
||||
Args:
|
||||
key_validator: Validator to apply to dictionary keys.
|
||||
|
||||
value_validator: Validator to apply to dictionary values.
|
||||
|
||||
mapping_validator:
|
||||
Validator to apply to top-level mapping attribute (optional).
|
||||
|
||||
.. versionadded:: 19.1.0
|
||||
|
||||
Raises:
|
||||
TypeError: if any sub-validators fail
|
||||
"""
|
||||
return _DeepMapping(key_validator, value_validator, mapping_validator)
|
||||
|
||||
|
||||
@attrs(repr=False, frozen=True, slots=True)
|
||||
class _NumberValidator:
|
||||
bound = attrib()
|
||||
compare_op = attrib()
|
||||
compare_func = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not self.compare_func(value, self.bound):
|
||||
msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
|
||||
raise ValueError(msg)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Validator for x {self.compare_op} {self.bound}>"
|
||||
|
||||
|
||||
def lt(val):
|
||||
"""
|
||||
A validator that raises `ValueError` if the initializer is called with a
|
||||
number larger or equal to *val*.
|
||||
|
||||
The validator uses `operator.lt` to compare the values.
|
||||
|
||||
Args:
|
||||
val: Exclusive upper bound for values.
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _NumberValidator(val, "<", operator.lt)
|
||||
|
||||
|
||||
def le(val):
|
||||
"""
|
||||
A validator that raises `ValueError` if the initializer is called with a
|
||||
number greater than *val*.
|
||||
|
||||
The validator uses `operator.le` to compare the values.
|
||||
|
||||
Args:
|
||||
val: Inclusive upper bound for values.
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _NumberValidator(val, "<=", operator.le)
|
||||
|
||||
|
||||
def ge(val):
|
||||
"""
|
||||
A validator that raises `ValueError` if the initializer is called with a
|
||||
number smaller than *val*.
|
||||
|
||||
The validator uses `operator.ge` to compare the values.
|
||||
|
||||
Args:
|
||||
val: Inclusive lower bound for values
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _NumberValidator(val, ">=", operator.ge)
|
||||
|
||||
|
||||
def gt(val):
|
||||
"""
|
||||
A validator that raises `ValueError` if the initializer is called with a
|
||||
number smaller or equal to *val*.
|
||||
|
||||
The validator uses `operator.ge` to compare the values.
|
||||
|
||||
Args:
|
||||
val: Exclusive lower bound for values
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _NumberValidator(val, ">", operator.gt)
|
||||
|
||||
|
||||
@attrs(repr=False, frozen=True, slots=True)
|
||||
class _MaxLengthValidator:
|
||||
max_length = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if len(value) > self.max_length:
|
||||
msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<max_len validator for {self.max_length}>"
|
||||
|
||||
|
||||
def max_len(length):
|
||||
"""
|
||||
A validator that raises `ValueError` if the initializer is called
|
||||
with a string or iterable that is longer than *length*.
|
||||
|
||||
Args:
|
||||
length (int): Maximum length of the string or iterable
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
"""
|
||||
return _MaxLengthValidator(length)
|
||||
|
||||
|
||||
@attrs(repr=False, frozen=True, slots=True)
|
||||
class _MinLengthValidator:
|
||||
min_length = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if len(value) < self.min_length:
|
||||
msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<min_len validator for {self.min_length}>"
|
||||
|
||||
|
||||
def min_len(length):
|
||||
"""
|
||||
A validator that raises `ValueError` if the initializer is called
|
||||
with a string or iterable that is shorter than *length*.
|
||||
|
||||
Args:
|
||||
length (int): Minimum length of the string or iterable
|
||||
|
||||
.. versionadded:: 22.1.0
|
||||
"""
|
||||
return _MinLengthValidator(length)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _SubclassOfValidator:
|
||||
type = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not issubclass(value, self.type):
|
||||
msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
|
||||
raise TypeError(
|
||||
msg,
|
||||
attr,
|
||||
self.type,
|
||||
value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<subclass_of validator for type {self.type!r}>"
|
||||
|
||||
|
||||
def _subclass_of(type):
|
||||
"""
|
||||
A validator that raises a `TypeError` if the initializer is called with a
|
||||
wrong type for this particular attribute (checks are performed using
|
||||
`issubclass` therefore it's also valid to pass a tuple of types).
|
||||
|
||||
Args:
|
||||
type (type | tuple[type, ...]): The type(s) to check for.
|
||||
|
||||
Raises:
|
||||
TypeError:
|
||||
With a human readable error message, the attribute (of type
|
||||
`attrs.Attribute`), the expected type, and the value it got.
|
||||
"""
|
||||
return _SubclassOfValidator(type)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _NotValidator:
|
||||
validator = attrib()
|
||||
msg = attrib(
|
||||
converter=default_if_none(
|
||||
"not_ validator child '{validator!r}' "
|
||||
"did not raise a captured error"
|
||||
)
|
||||
)
|
||||
exc_types = attrib(
|
||||
validator=deep_iterable(
|
||||
member_validator=_subclass_of(Exception),
|
||||
iterable_validator=instance_of(tuple),
|
||||
),
|
||||
)
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
try:
|
||||
self.validator(inst, attr, value)
|
||||
except self.exc_types:
|
||||
pass # suppress error to invert validity
|
||||
else:
|
||||
raise ValueError(
|
||||
self.msg.format(
|
||||
validator=self.validator,
|
||||
exc_types=self.exc_types,
|
||||
),
|
||||
attr,
|
||||
self.validator,
|
||||
value,
|
||||
self.exc_types,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
|
||||
|
||||
|
||||
def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
|
||||
"""
|
||||
A validator that wraps and logically 'inverts' the validator passed to it.
|
||||
It will raise a `ValueError` if the provided validator *doesn't* raise a
|
||||
`ValueError` or `TypeError` (by default), and will suppress the exception
|
||||
if the provided validator *does*.
|
||||
|
||||
Intended to be used with existing validators to compose logic without
|
||||
needing to create inverted variants, for example, ``not_(in_(...))``.
|
||||
|
||||
Args:
|
||||
validator: A validator to be logically inverted.
|
||||
|
||||
msg (str):
|
||||
Message to raise if validator fails. Formatted with keys
|
||||
``exc_types`` and ``validator``.
|
||||
|
||||
exc_types (tuple[type, ...]):
|
||||
Exception type(s) to capture. Other types raised by child
|
||||
validators will not be intercepted and pass through.
|
||||
|
||||
Raises:
|
||||
ValueError:
|
||||
With a human readable error message, the attribute (of type
|
||||
`attrs.Attribute`), the validator that failed to raise an
|
||||
exception, the value it got, and the expected exception types.
|
||||
|
||||
.. versionadded:: 22.2.0
|
||||
"""
|
||||
try:
|
||||
exc_types = tuple(exc_types)
|
||||
except TypeError:
|
||||
exc_types = (exc_types,)
|
||||
return _NotValidator(validator, msg, exc_types)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _OrValidator:
|
||||
validators = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
for v in self.validators:
|
||||
try:
|
||||
v(inst, attr, value)
|
||||
except Exception: # noqa: BLE001, PERF203, S112
|
||||
continue
|
||||
else:
|
||||
return
|
||||
|
||||
msg = f"None of {self.validators!r} satisfied for value {value!r}"
|
||||
raise ValueError(msg)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<or validator wrapping {self.validators!r}>"
|
||||
|
||||
|
||||
def or_(*validators):
|
||||
"""
|
||||
A validator that composes multiple validators into one.
|
||||
|
||||
When called on a value, it runs all wrapped validators until one of them is
|
||||
satisfied.
|
||||
|
||||
Args:
|
||||
validators (~collections.abc.Iterable[typing.Callable]):
|
||||
Arbitrary number of validators.
|
||||
|
||||
Raises:
|
||||
ValueError:
|
||||
If no validator is satisfied. Raised with a human-readable error
|
||||
message listing all the wrapped validators and the value that
|
||||
failed all of them.
|
||||
|
||||
.. versionadded:: 24.1.0
|
||||
"""
|
||||
vals = []
|
||||
for v in validators:
|
||||
vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
|
||||
|
||||
return _OrValidator(tuple(vals))
|
||||
86
.venv/lib/python3.10/site-packages/attr/validators.pyi
Normal file
86
.venv/lib/python3.10/site-packages/attr/validators.pyi
Normal file
@@ -0,0 +1,86 @@
|
||||
from types import UnionType
|
||||
from typing import (
|
||||
Any,
|
||||
AnyStr,
|
||||
Callable,
|
||||
Container,
|
||||
ContextManager,
|
||||
Iterable,
|
||||
Mapping,
|
||||
Match,
|
||||
Pattern,
|
||||
TypeVar,
|
||||
overload,
|
||||
)
|
||||
|
||||
from attrs import _ValidatorType
|
||||
from attrs import _ValidatorArgType
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_T1 = TypeVar("_T1")
|
||||
_T2 = TypeVar("_T2")
|
||||
_T3 = TypeVar("_T3")
|
||||
_I = TypeVar("_I", bound=Iterable)
|
||||
_K = TypeVar("_K")
|
||||
_V = TypeVar("_V")
|
||||
_M = TypeVar("_M", bound=Mapping)
|
||||
|
||||
def set_disabled(run: bool) -> None: ...
|
||||
def get_disabled() -> bool: ...
|
||||
def disabled() -> ContextManager[None]: ...
|
||||
|
||||
# To be more precise on instance_of use some overloads.
|
||||
# If there are more than 3 items in the tuple then we fall back to Any
|
||||
@overload
|
||||
def instance_of(type: type[_T]) -> _ValidatorType[_T]: ...
|
||||
@overload
|
||||
def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ...
|
||||
@overload
|
||||
def instance_of(
|
||||
type: tuple[type[_T1], type[_T2]],
|
||||
) -> _ValidatorType[_T1 | _T2]: ...
|
||||
@overload
|
||||
def instance_of(
|
||||
type: tuple[type[_T1], type[_T2], type[_T3]],
|
||||
) -> _ValidatorType[_T1 | _T2 | _T3]: ...
|
||||
@overload
|
||||
def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ...
|
||||
@overload
|
||||
def instance_of(type: UnionType) -> _ValidatorType[Any]: ...
|
||||
def optional(
|
||||
validator: (
|
||||
_ValidatorType[_T]
|
||||
| list[_ValidatorType[_T]]
|
||||
| tuple[_ValidatorType[_T]]
|
||||
),
|
||||
) -> _ValidatorType[_T | None]: ...
|
||||
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
|
||||
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
|
||||
def matches_re(
|
||||
regex: Pattern[AnyStr] | AnyStr,
|
||||
flags: int = ...,
|
||||
func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ...,
|
||||
) -> _ValidatorType[AnyStr]: ...
|
||||
def deep_iterable(
|
||||
member_validator: _ValidatorArgType[_T],
|
||||
iterable_validator: _ValidatorType[_I] | None = ...,
|
||||
) -> _ValidatorType[_I]: ...
|
||||
def deep_mapping(
|
||||
key_validator: _ValidatorType[_K],
|
||||
value_validator: _ValidatorType[_V],
|
||||
mapping_validator: _ValidatorType[_M] | None = ...,
|
||||
) -> _ValidatorType[_M]: ...
|
||||
def is_callable() -> _ValidatorType[_T]: ...
|
||||
def lt(val: _T) -> _ValidatorType[_T]: ...
|
||||
def le(val: _T) -> _ValidatorType[_T]: ...
|
||||
def ge(val: _T) -> _ValidatorType[_T]: ...
|
||||
def gt(val: _T) -> _ValidatorType[_T]: ...
|
||||
def max_len(length: int) -> _ValidatorType[_T]: ...
|
||||
def min_len(length: int) -> _ValidatorType[_T]: ...
|
||||
def not_(
|
||||
validator: _ValidatorType[_T],
|
||||
*,
|
||||
msg: str | None = None,
|
||||
exc_types: type[Exception] | Iterable[type[Exception]] = ...,
|
||||
) -> _ValidatorType[_T]: ...
|
||||
def or_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,232 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: attrs
|
||||
Version: 25.3.0
|
||||
Summary: Classes Without Boilerplate
|
||||
Project-URL: Documentation, https://www.attrs.org/
|
||||
Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html
|
||||
Project-URL: GitHub, https://github.com/python-attrs/attrs
|
||||
Project-URL: Funding, https://github.com/sponsors/hynek
|
||||
Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi
|
||||
Author-email: Hynek Schlawack <hs@ox.cx>
|
||||
License-Expression: MIT
|
||||
License-File: LICENSE
|
||||
Keywords: attribute,boilerplate,class
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Typing :: Typed
|
||||
Requires-Python: >=3.8
|
||||
Provides-Extra: benchmark
|
||||
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'benchmark'
|
||||
Requires-Dist: hypothesis; extra == 'benchmark'
|
||||
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'benchmark'
|
||||
Requires-Dist: pympler; extra == 'benchmark'
|
||||
Requires-Dist: pytest-codspeed; extra == 'benchmark'
|
||||
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'benchmark'
|
||||
Requires-Dist: pytest-xdist[psutil]; extra == 'benchmark'
|
||||
Requires-Dist: pytest>=4.3.0; extra == 'benchmark'
|
||||
Provides-Extra: cov
|
||||
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'cov'
|
||||
Requires-Dist: coverage[toml]>=5.3; extra == 'cov'
|
||||
Requires-Dist: hypothesis; extra == 'cov'
|
||||
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'cov'
|
||||
Requires-Dist: pympler; extra == 'cov'
|
||||
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'cov'
|
||||
Requires-Dist: pytest-xdist[psutil]; extra == 'cov'
|
||||
Requires-Dist: pytest>=4.3.0; extra == 'cov'
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'dev'
|
||||
Requires-Dist: hypothesis; extra == 'dev'
|
||||
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'dev'
|
||||
Requires-Dist: pre-commit-uv; extra == 'dev'
|
||||
Requires-Dist: pympler; extra == 'dev'
|
||||
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'dev'
|
||||
Requires-Dist: pytest-xdist[psutil]; extra == 'dev'
|
||||
Requires-Dist: pytest>=4.3.0; extra == 'dev'
|
||||
Provides-Extra: docs
|
||||
Requires-Dist: cogapp; extra == 'docs'
|
||||
Requires-Dist: furo; extra == 'docs'
|
||||
Requires-Dist: myst-parser; extra == 'docs'
|
||||
Requires-Dist: sphinx; extra == 'docs'
|
||||
Requires-Dist: sphinx-notfound-page; extra == 'docs'
|
||||
Requires-Dist: sphinxcontrib-towncrier; extra == 'docs'
|
||||
Requires-Dist: towncrier; extra == 'docs'
|
||||
Provides-Extra: tests
|
||||
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'tests'
|
||||
Requires-Dist: hypothesis; extra == 'tests'
|
||||
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests'
|
||||
Requires-Dist: pympler; extra == 'tests'
|
||||
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests'
|
||||
Requires-Dist: pytest-xdist[psutil]; extra == 'tests'
|
||||
Requires-Dist: pytest>=4.3.0; extra == 'tests'
|
||||
Provides-Extra: tests-mypy
|
||||
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests-mypy'
|
||||
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests-mypy'
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.attrs.org/">
|
||||
<img src="https://raw.githubusercontent.com/python-attrs/attrs/main/docs/_static/attrs_logo.svg" width="35%" alt="attrs" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)).
|
||||
[Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020!
|
||||
|
||||
Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek).
|
||||
Especially those generously supporting us at the *The Organization* tier and higher:
|
||||
|
||||
<!-- sponsor-break-begin -->
|
||||
|
||||
<p align="center">
|
||||
|
||||
<!-- [[[cog
|
||||
import pathlib, tomllib
|
||||
|
||||
for sponsor in tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["sponcon"]["sponsors"]:
|
||||
print(f'<a href="{sponsor["url"]}"><img title="{sponsor["title"]}" src="https://www.attrs.org/en/25.3.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>')
|
||||
]]] -->
|
||||
<a href="https://www.variomedia.de/"><img title="Variomedia AG" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Variomedia.svg" width="190" /></a>
|
||||
<a href="https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek"><img title="Tidelift" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Tidelift.svg" width="190" /></a>
|
||||
<a href="https://klaviyo.com/"><img title="Klaviyo" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Klaviyo.svg" width="190" /></a>
|
||||
<a href="https://privacy-solutions.org/"><img title="Privacy Solutions" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Privacy-Solutions.svg" width="190" /></a>
|
||||
<a href="https://www.emsys-renewables.com/"><img title="emsys renewables" src="https://www.attrs.org/en/25.3.0/_static/sponsors/emsys-renewables.svg" width="190" /></a>
|
||||
<a href="https://filepreviews.io/"><img title="FilePreviews" src="https://www.attrs.org/en/25.3.0/_static/sponsors/FilePreviews.svg" width="190" /></a>
|
||||
<a href="https://polar.sh/"><img title="Polar" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Polar.svg" width="190" /></a>
|
||||
<!-- [[[end]]] -->
|
||||
|
||||
</p>
|
||||
|
||||
<!-- sponsor-break-end -->
|
||||
|
||||
<p align="center">
|
||||
<strong>Please consider <a href="https://github.com/sponsors/hynek">joining them</a> to help make <em>attrs</em>’s maintenance more sustainable!</strong>
|
||||
</p>
|
||||
|
||||
<!-- teaser-end -->
|
||||
|
||||
## Example
|
||||
|
||||
*attrs* gives you a class decorator and a way to declaratively define the attributes on that class:
|
||||
|
||||
<!-- code-begin -->
|
||||
|
||||
```pycon
|
||||
>>> from attrs import asdict, define, make_class, Factory
|
||||
|
||||
>>> @define
|
||||
... class SomeClass:
|
||||
... a_number: int = 42
|
||||
... list_of_numbers: list[int] = Factory(list)
|
||||
...
|
||||
... def hard_math(self, another_number):
|
||||
... return self.a_number + sum(self.list_of_numbers) * another_number
|
||||
|
||||
|
||||
>>> sc = SomeClass(1, [1, 2, 3])
|
||||
>>> sc
|
||||
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
|
||||
|
||||
>>> sc.hard_math(3)
|
||||
19
|
||||
>>> sc == SomeClass(1, [1, 2, 3])
|
||||
True
|
||||
>>> sc != SomeClass(2, [3, 2, 1])
|
||||
True
|
||||
|
||||
>>> asdict(sc)
|
||||
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}
|
||||
|
||||
>>> SomeClass()
|
||||
SomeClass(a_number=42, list_of_numbers=[])
|
||||
|
||||
>>> C = make_class("C", ["a", "b"])
|
||||
>>> C("foo", "bar")
|
||||
C(a='foo', b='bar')
|
||||
```
|
||||
|
||||
After *declaring* your attributes, *attrs* gives you:
|
||||
|
||||
- a concise and explicit overview of the class's attributes,
|
||||
- a nice human-readable `__repr__`,
|
||||
- equality-checking methods,
|
||||
- an initializer,
|
||||
- and much more,
|
||||
|
||||
*without* writing dull boilerplate code again and again and *without* runtime performance penalties.
|
||||
|
||||
---
|
||||
|
||||
This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0.
|
||||
The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**.
|
||||
|
||||
Check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for an in-depth explanation!
|
||||
|
||||
|
||||
### Hate Type Annotations!?
|
||||
|
||||
No problem!
|
||||
Types are entirely **optional** with *attrs*.
|
||||
Simply assign `attrs.field()` to the attributes instead of annotating them with types:
|
||||
|
||||
```python
|
||||
from attrs import define, field
|
||||
|
||||
@define
|
||||
class SomeClass:
|
||||
a_number = field(default=42)
|
||||
list_of_numbers = field(factory=list)
|
||||
```
|
||||
|
||||
|
||||
## Data Classes
|
||||
|
||||
On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*).
|
||||
In practice it does a lot more and is more flexible.
|
||||
For instance, it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), has a replacement for `__init_subclass__`, and allows for stepping through the generated methods using a debugger.
|
||||
|
||||
For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes), but generally speaking, we are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated in practice.
|
||||
|
||||
|
||||
## Project Information
|
||||
|
||||
- [**Changelog**](https://www.attrs.org/en/stable/changelog.html)
|
||||
- [**Documentation**](https://www.attrs.org/)
|
||||
- [**PyPI**](https://pypi.org/project/attrs/)
|
||||
- [**Source Code**](https://github.com/python-attrs/attrs)
|
||||
- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md)
|
||||
- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs)
|
||||
- **Get Help**: use the `python-attrs` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs)
|
||||
|
||||
|
||||
### *attrs* for Enterprise
|
||||
|
||||
Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek).
|
||||
|
||||
The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications.
|
||||
Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
|
||||
|
||||
## Release Information
|
||||
|
||||
### Changes
|
||||
|
||||
- Restore support for generator-based `field_transformer`s.
|
||||
[#1417](https://github.com/python-attrs/attrs/issues/1417)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Full changelog →](https://www.attrs.org/en/stable/changelog.html)
|
||||
@@ -0,0 +1,55 @@
|
||||
attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057
|
||||
attr/__init__.pyi,sha256=QIXnnHPoucmDWkbpNsWTP-cgJ1bn8le7DjyRa_wYdew,11281
|
||||
attr/__pycache__/__init__.cpython-310.pyc,,
|
||||
attr/__pycache__/_cmp.cpython-310.pyc,,
|
||||
attr/__pycache__/_compat.cpython-310.pyc,,
|
||||
attr/__pycache__/_config.cpython-310.pyc,,
|
||||
attr/__pycache__/_funcs.cpython-310.pyc,,
|
||||
attr/__pycache__/_make.cpython-310.pyc,,
|
||||
attr/__pycache__/_next_gen.cpython-310.pyc,,
|
||||
attr/__pycache__/_version_info.cpython-310.pyc,,
|
||||
attr/__pycache__/converters.cpython-310.pyc,,
|
||||
attr/__pycache__/exceptions.cpython-310.pyc,,
|
||||
attr/__pycache__/filters.cpython-310.pyc,,
|
||||
attr/__pycache__/setters.cpython-310.pyc,,
|
||||
attr/__pycache__/validators.cpython-310.pyc,,
|
||||
attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117
|
||||
attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368
|
||||
attr/_compat.py,sha256=4hlXbWhdDjQCDK6FKF1EgnZ3POiHgtpp54qE0nxaGHg,2704
|
||||
attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843
|
||||
attr/_funcs.py,sha256=5-tUKJtp3h5El55EcDl6GWXFp68fT8D8U7uCRN6497I,15854
|
||||
attr/_make.py,sha256=lBUPPmxiA1BeHzB6OlHoCEh--tVvM1ozXO8eXOa6g4c,96664
|
||||
attr/_next_gen.py,sha256=7FRkbtl_N017SuBhf_Vw3mw2c2pGZhtCGOzadgz7tp4,24395
|
||||
attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469
|
||||
attr/_version_info.py,sha256=exSqb3b5E-fMSsgZAlEw9XcLpEgobPORCZpcaEglAM4,2121
|
||||
attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209
|
||||
attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861
|
||||
attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643
|
||||
attr/exceptions.py,sha256=HRFq4iybmv7-DcZwyjl6M1euM2YeJVK_hFxuaBGAngI,1977
|
||||
attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539
|
||||
attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795
|
||||
attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208
|
||||
attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617
|
||||
attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584
|
||||
attr/validators.py,sha256=WaB1HLAHHqRHWsrv_K9H-sJ7ESil3H3Cmv2d8TtVZx4,20046
|
||||
attr/validators.pyi,sha256=s2WhKPqskxbsckJfKk8zOuuB088GfgpyxcCYSNFLqNU,2603
|
||||
attrs-25.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
attrs-25.3.0.dist-info/METADATA,sha256=W38cREj7s1wqNf1fg4hVwZmL1xh0AdSp4IhtTMROinw,10993
|
||||
attrs-25.3.0.dist-info/RECORD,,
|
||||
attrs-25.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
||||
attrs-25.3.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109
|
||||
attrs/__init__.py,sha256=qeQJZ4O08yczSn840v9bYOaZyRE81WsVi-QCrY3krCU,1107
|
||||
attrs/__init__.pyi,sha256=nZmInocjM7tHV4AQw0vxO_fo6oJjL_PonlV9zKKW8DY,7931
|
||||
attrs/__pycache__/__init__.cpython-310.pyc,,
|
||||
attrs/__pycache__/converters.cpython-310.pyc,,
|
||||
attrs/__pycache__/exceptions.cpython-310.pyc,,
|
||||
attrs/__pycache__/filters.cpython-310.pyc,,
|
||||
attrs/__pycache__/setters.cpython-310.pyc,,
|
||||
attrs/__pycache__/validators.cpython-310.pyc,,
|
||||
attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76
|
||||
attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76
|
||||
attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73
|
||||
attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73
|
||||
attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.27.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Hynek Schlawack and the attrs contributors
|
||||
|
||||
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.
|
||||
69
.venv/lib/python3.10/site-packages/attrs/__init__.py
Normal file
69
.venv/lib/python3.10/site-packages/attrs/__init__.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from attr import (
|
||||
NOTHING,
|
||||
Attribute,
|
||||
AttrsInstance,
|
||||
Converter,
|
||||
Factory,
|
||||
NothingType,
|
||||
_make_getattr,
|
||||
assoc,
|
||||
cmp_using,
|
||||
define,
|
||||
evolve,
|
||||
field,
|
||||
fields,
|
||||
fields_dict,
|
||||
frozen,
|
||||
has,
|
||||
make_class,
|
||||
mutable,
|
||||
resolve_types,
|
||||
validate,
|
||||
)
|
||||
from attr._next_gen import asdict, astuple
|
||||
|
||||
from . import converters, exceptions, filters, setters, validators
|
||||
|
||||
|
||||
__all__ = [
|
||||
"NOTHING",
|
||||
"Attribute",
|
||||
"AttrsInstance",
|
||||
"Converter",
|
||||
"Factory",
|
||||
"NothingType",
|
||||
"__author__",
|
||||
"__copyright__",
|
||||
"__description__",
|
||||
"__doc__",
|
||||
"__email__",
|
||||
"__license__",
|
||||
"__title__",
|
||||
"__url__",
|
||||
"__version__",
|
||||
"__version_info__",
|
||||
"asdict",
|
||||
"assoc",
|
||||
"astuple",
|
||||
"cmp_using",
|
||||
"converters",
|
||||
"define",
|
||||
"evolve",
|
||||
"exceptions",
|
||||
"field",
|
||||
"fields",
|
||||
"fields_dict",
|
||||
"filters",
|
||||
"frozen",
|
||||
"has",
|
||||
"make_class",
|
||||
"mutable",
|
||||
"resolve_types",
|
||||
"setters",
|
||||
"validate",
|
||||
"validators",
|
||||
]
|
||||
|
||||
__getattr__ = _make_getattr(__name__)
|
||||
263
.venv/lib/python3.10/site-packages/attrs/__init__.pyi
Normal file
263
.venv/lib/python3.10/site-packages/attrs/__init__.pyi
Normal file
@@ -0,0 +1,263 @@
|
||||
import sys
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Mapping,
|
||||
Sequence,
|
||||
overload,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
# Because we need to type our own stuff, we have to make everything from
|
||||
# attr explicitly public too.
|
||||
from attr import __author__ as __author__
|
||||
from attr import __copyright__ as __copyright__
|
||||
from attr import __description__ as __description__
|
||||
from attr import __email__ as __email__
|
||||
from attr import __license__ as __license__
|
||||
from attr import __title__ as __title__
|
||||
from attr import __url__ as __url__
|
||||
from attr import __version__ as __version__
|
||||
from attr import __version_info__ as __version_info__
|
||||
from attr import assoc as assoc
|
||||
from attr import Attribute as Attribute
|
||||
from attr import AttrsInstance as AttrsInstance
|
||||
from attr import cmp_using as cmp_using
|
||||
from attr import converters as converters
|
||||
from attr import Converter as Converter
|
||||
from attr import evolve as evolve
|
||||
from attr import exceptions as exceptions
|
||||
from attr import Factory as Factory
|
||||
from attr import fields as fields
|
||||
from attr import fields_dict as fields_dict
|
||||
from attr import filters as filters
|
||||
from attr import has as has
|
||||
from attr import make_class as make_class
|
||||
from attr import NOTHING as NOTHING
|
||||
from attr import resolve_types as resolve_types
|
||||
from attr import setters as setters
|
||||
from attr import validate as validate
|
||||
from attr import validators as validators
|
||||
from attr import attrib, asdict as asdict, astuple as astuple
|
||||
from attr import NothingType as NothingType
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import dataclass_transform
|
||||
else:
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_C = TypeVar("_C", bound=type)
|
||||
|
||||
_EqOrderType = bool | Callable[[Any], Any]
|
||||
_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any]
|
||||
_CallableConverterType = Callable[[Any], Any]
|
||||
_ConverterType = _CallableConverterType | Converter[Any, Any]
|
||||
_ReprType = Callable[[Any], str]
|
||||
_ReprArgType = bool | _ReprType
|
||||
_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any]
|
||||
_OnSetAttrArgType = _OnSetAttrType | list[_OnSetAttrType] | setters._NoOpType
|
||||
_FieldTransformer = Callable[
|
||||
[type, list["Attribute[Any]"]], list["Attribute[Any]"]
|
||||
]
|
||||
# FIXME: in reality, if multiple validators are passed they must be in a list
|
||||
# or tuple, but those are invariant and so would prevent subtypes of
|
||||
# _ValidatorType from working when passed in a list or tuple.
|
||||
_ValidatorArgType = _ValidatorType[_T] | Sequence[_ValidatorType[_T]]
|
||||
|
||||
@overload
|
||||
def field(
|
||||
*,
|
||||
default: None = ...,
|
||||
validator: None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: None = ...,
|
||||
factory: None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: bool | None = ...,
|
||||
order: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
type: type | None = ...,
|
||||
) -> Any: ...
|
||||
|
||||
# This form catches an explicit None or no default and infers the type from the
|
||||
# other arguments.
|
||||
@overload
|
||||
def field(
|
||||
*,
|
||||
default: None = ...,
|
||||
validator: _ValidatorArgType[_T] | None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
type: type | None = ...,
|
||||
) -> _T: ...
|
||||
|
||||
# This form catches an explicit default argument.
|
||||
@overload
|
||||
def field(
|
||||
*,
|
||||
default: _T,
|
||||
validator: _ValidatorArgType[_T] | None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
type: type | None = ...,
|
||||
) -> _T: ...
|
||||
|
||||
# This form covers type=non-Type: e.g. forward references (str), Any
|
||||
@overload
|
||||
def field(
|
||||
*,
|
||||
default: _T | None = ...,
|
||||
validator: _ValidatorArgType[_T] | None = ...,
|
||||
repr: _ReprArgType = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
alias: str | None = ...,
|
||||
type: type | None = ...,
|
||||
) -> Any: ...
|
||||
@overload
|
||||
@dataclass_transform(field_specifiers=(attrib, field))
|
||||
def define(
|
||||
maybe_cls: _C,
|
||||
*,
|
||||
these: dict[str, Any] | None = ...,
|
||||
repr: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: bool | None = ...,
|
||||
order: bool | None = ...,
|
||||
auto_detect: bool = ...,
|
||||
getstate_setstate: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
) -> _C: ...
|
||||
@overload
|
||||
@dataclass_transform(field_specifiers=(attrib, field))
|
||||
def define(
|
||||
maybe_cls: None = ...,
|
||||
*,
|
||||
these: dict[str, Any] | None = ...,
|
||||
repr: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: bool | None = ...,
|
||||
order: bool | None = ...,
|
||||
auto_detect: bool = ...,
|
||||
getstate_setstate: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
) -> Callable[[_C], _C]: ...
|
||||
|
||||
mutable = define
|
||||
|
||||
@overload
|
||||
@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field))
|
||||
def frozen(
|
||||
maybe_cls: _C,
|
||||
*,
|
||||
these: dict[str, Any] | None = ...,
|
||||
repr: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: bool | None = ...,
|
||||
order: bool | None = ...,
|
||||
auto_detect: bool = ...,
|
||||
getstate_setstate: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
) -> _C: ...
|
||||
@overload
|
||||
@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field))
|
||||
def frozen(
|
||||
maybe_cls: None = ...,
|
||||
*,
|
||||
these: dict[str, Any] | None = ...,
|
||||
repr: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
hash: bool | None = ...,
|
||||
init: bool = ...,
|
||||
slots: bool = ...,
|
||||
frozen: bool = ...,
|
||||
weakref_slot: bool = ...,
|
||||
str: bool = ...,
|
||||
auto_attribs: bool = ...,
|
||||
kw_only: bool = ...,
|
||||
cache_hash: bool = ...,
|
||||
auto_exc: bool = ...,
|
||||
eq: bool | None = ...,
|
||||
order: bool | None = ...,
|
||||
auto_detect: bool = ...,
|
||||
getstate_setstate: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
) -> Callable[[_C], _C]: ...
|
||||
3
.venv/lib/python3.10/site-packages/attrs/converters.py
Normal file
3
.venv/lib/python3.10/site-packages/attrs/converters.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from attr.converters import * # noqa: F403
|
||||
3
.venv/lib/python3.10/site-packages/attrs/exceptions.py
Normal file
3
.venv/lib/python3.10/site-packages/attrs/exceptions.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from attr.exceptions import * # noqa: F403
|
||||
3
.venv/lib/python3.10/site-packages/attrs/filters.py
Normal file
3
.venv/lib/python3.10/site-packages/attrs/filters.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from attr.filters import * # noqa: F403
|
||||
0
.venv/lib/python3.10/site-packages/attrs/py.typed
Normal file
0
.venv/lib/python3.10/site-packages/attrs/py.typed
Normal file
3
.venv/lib/python3.10/site-packages/attrs/setters.py
Normal file
3
.venv/lib/python3.10/site-packages/attrs/setters.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from attr.setters import * # noqa: F403
|
||||
3
.venv/lib/python3.10/site-packages/attrs/validators.py
Normal file
3
.venv/lib/python3.10/site-packages/attrs/validators.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from attr.validators import * # noqa: F403
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,175 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: django-extensions
|
||||
Version: 4.1
|
||||
Summary: Extensions for Django
|
||||
Author-email: Michael Trier <mtrier@gmail.com>
|
||||
Maintainer-email: Bas van Oostveen <v.oostveen@gmail.com>
|
||||
License-Expression: MIT
|
||||
Project-URL: Changelog, https://github.com/django-extensions/django-extensions/blob/main/CHANGELOG.md
|
||||
Project-URL: Documentation, https://django-extensions.readthedocs.io/
|
||||
Project-URL: Homepage, https://github.com/django-extensions/django-extensions
|
||||
Project-URL: Source, https://github.com/django-extensions/django-extensions
|
||||
Project-URL: Tracker, https://github.com/django-extensions/django-extensions/issues
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Framework :: Django
|
||||
Classifier: Framework :: Django :: 4.2
|
||||
Classifier: Framework :: Django :: 5.1
|
||||
Classifier: Framework :: Django :: 5.2
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Utilities
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
Requires-Dist: django>=4.2
|
||||
Dynamic: license-file
|
||||
|
||||
===================
|
||||
Django Extensions
|
||||
===================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/django-extensions.svg
|
||||
:target: https://raw.githubusercontent.com/django-extensions/django-extensions/master/LICENSE
|
||||
|
||||
.. image:: https://github.com/django-extensions/django-extensions/actions/workflows/compile_catalog.yml/badge.svg
|
||||
:target: https://github.com/django-extensions/django-extensions/actions
|
||||
|
||||
.. image:: https://github.com/django-extensions/django-extensions/actions/workflows/linters.yml/badge.svg
|
||||
:target: https://github.com/django-extensions/django-extensions/actions
|
||||
|
||||
.. image:: https://github.com/django-extensions/django-extensions/actions/workflows/precommit.yml/badge.svg
|
||||
:target: https://github.com/django-extensions/django-extensions/actions
|
||||
|
||||
.. image:: https://github.com/django-extensions/django-extensions/actions/workflows/pytest.yml/badge.svg
|
||||
:target: https://github.com/django-extensions/django-extensions/actions
|
||||
|
||||
.. image:: https://github.com/django-extensions/django-extensions/actions/workflows/security.yml/badge.svg
|
||||
:target: https://github.com/django-extensions/django-extensions/actions
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/django-extensions.svg
|
||||
:target: https://pypi.python.org/pypi/django-extensions/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/wheel/django-extensions.svg
|
||||
:target: https://pypi.python.org/pypi/django-extensions/
|
||||
:alt: Supports Wheel format
|
||||
|
||||
.. image:: https://coveralls.io/repos/django-extensions/django-extensions/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/django-extensions/django-extensions?branch=master
|
||||
:alt: Coverage
|
||||
|
||||
Django Extensions is a collection of custom extensions for the Django Framework.
|
||||
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
The easiest way to figure out what Django Extensions are all about is to watch the
|
||||
`excellent screencast by Eric Holscher`__ (`watch the video on vimeo`__). In a couple
|
||||
minutes Eric walks you through a half a dozen command extensions. There is also a
|
||||
`short screencast on GoDjango's Youtube Channel`__ to help show you even more.
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
Django Extensions requires Django 4.2 or later.
|
||||
|
||||
|
||||
Getting It
|
||||
==========
|
||||
|
||||
You can get Django Extensions by using pip::
|
||||
|
||||
$ pip install django-extensions
|
||||
|
||||
If you want to install it from source, grab the git repository from GitHub::
|
||||
|
||||
$ git clone git://github.com/django-extensions/django-extensions.git
|
||||
$ cd django-extensions
|
||||
$ pip install .
|
||||
|
||||
|
||||
Installing It
|
||||
=============
|
||||
|
||||
To enable `django_extensions` in your project you need to add it to `INSTALLED_APPS` in your projects
|
||||
`settings.py` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'django_extensions',
|
||||
...
|
||||
)
|
||||
|
||||
|
||||
Using It
|
||||
========
|
||||
|
||||
Generate (and view) a graphviz graph of app models::
|
||||
|
||||
$ python manage.py graph_models -a -o myapp_models.png
|
||||
|
||||
Produce a tab-separated list of `(url_pattern, view_function, name)` tuples for a project::
|
||||
|
||||
$ python manage.py show_urls
|
||||
|
||||
Check templates for rendering errors::
|
||||
|
||||
$ python manage.py validate_templates
|
||||
|
||||
Run the enhanced django shell::
|
||||
|
||||
$ python manage.py shell_plus
|
||||
|
||||
Run the enhanced django runserver, (requires Werkzeug install)::
|
||||
|
||||
$ python manage.py runserver_plus
|
||||
|
||||
|
||||
Getting Involved
|
||||
================
|
||||
|
||||
Open Source projects can always use more help. Fixing a problem, documenting a feature, adding
|
||||
translation in your language. If you have some time to spare and like to help us, here are the places to do so:
|
||||
|
||||
- GitHub: https://github.com/django-extensions/django-extensions
|
||||
- Mailing list: https://groups.google.com/group/django-extensions
|
||||
- Translations: https://www.transifex.com/projects/p/django-extensions/
|
||||
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can view documentation online at:
|
||||
|
||||
- https://django-extensions.readthedocs.io
|
||||
|
||||
Or you can look at the docs/ directory in the repository.
|
||||
|
||||
|
||||
Support
|
||||
=======
|
||||
|
||||
Django Extensions is free and always will be. It is developed and maintained by developers in an Open Source manner.
|
||||
Any support is welcome. You could help by writing documentation, pull-requests, report issues and/or translations.
|
||||
|
||||
Please remember that nobody is paid directly to develop or maintain Django Extensions so we do have to divide our time
|
||||
between putting food on the table, family, this project and the rest of life :-)
|
||||
|
||||
|
||||
__ https://ericholscher.com/blog/2008/sep/12/screencast-django-command-extensions/
|
||||
__ https://vimeo.com/1720508
|
||||
__ https://www.youtube.com/watch?v=1F6G3ONhr4k
|
||||
@@ -0,0 +1,278 @@
|
||||
django_extensions-4.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
django_extensions-4.1.dist-info/METADATA,sha256=Io0sR9z1a9ryd8sJ3W8y3ukkMjYfwlLKoQtl0r1d1ZA,6128
|
||||
django_extensions-4.1.dist-info/RECORD,,
|
||||
django_extensions-4.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions-4.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
||||
django_extensions-4.1.dist-info/licenses/LICENSE,sha256=hfh-J08r7s6vlJVWdNgyPZ_B69b8NdSvzdOLVEygyyA,1057
|
||||
django_extensions-4.1.dist-info/top_level.txt,sha256=a-Shg8eC0Rl6_AoTRvqIUhzOFzQeCFU1Z7ee7myIYMg,18
|
||||
django_extensions/__init__.py,sha256=1buxjH-04dCr3atSCJ4SLw1U0jsesMeqEKcujgH1nWs,138
|
||||
django_extensions/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/apps.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/collision_resolvers.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/compat.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/import_subclasses.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/models.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/settings.cpython-310.pyc,,
|
||||
django_extensions/__pycache__/validators.cpython-310.pyc,,
|
||||
django_extensions/admin/__init__.py,sha256=ihqhcGHxN5I82DgBQ4DLCieJ9zscQUiXtPq4eiRYI4M,7316
|
||||
django_extensions/admin/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/admin/__pycache__/filter.cpython-310.pyc,,
|
||||
django_extensions/admin/__pycache__/widgets.cpython-310.pyc,,
|
||||
django_extensions/admin/filter.py,sha256=TOK2bF9GjYhwl49L9Lfmsg_aGDENKzzgYtJ8EqeoNGo,1902
|
||||
django_extensions/admin/widgets.py,sha256=xy3hYTe8p89tu7IIm0L4T7RVAWWYjZiVBEncxiWyoyw,3361
|
||||
django_extensions/apps.py,sha256=sK4VOcpveKq7ODjBojfJLpr5yYHMvdYL_T106o8ibVk,171
|
||||
django_extensions/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/auth/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/auth/__pycache__/mixins.cpython-310.pyc,,
|
||||
django_extensions/auth/mixins.py,sha256=x8mBHy8mzQ2o7arKCTFN6Ku4Qufk6s8DltgvN17FlNg,488
|
||||
django_extensions/collision_resolvers.py,sha256=ksvGapdiBMRL-011fl15V0ExcsYdKTsm2hQnXbwpZgU,11109
|
||||
django_extensions/compat.py,sha256=KtFhHKGo116vlku5HzS7Y3cs9qe0e2WlzXy8Lv-VV_Q,1929
|
||||
django_extensions/conf/app_template/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/app_template/forms.py.tmpl,sha256=_K9nXjI1BEn-aPQYmNM9mcBwp21EnzAvtHF6lXeLQmY,55
|
||||
django_extensions/conf/app_template/migrations/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/app_template/models.py.tmpl,sha256=Vjc0p2XbAPgE6HyTF6vll98A4eDhA5AvaQqsc4kQ9AQ,57
|
||||
django_extensions/conf/app_template/urls.py.tmpl,sha256=nzK9G5Qi-8ECvgQ-5A5UhVYB9nmKTuWxKkrqWYgSzS4,69
|
||||
django_extensions/conf/app_template/views.py.tmpl,sha256=F42JXgnqFqK0fajXeutyJJxwOszRxoLMNkIhfc4Z7KI,26
|
||||
django_extensions/conf/command_template/management/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/command_template/management/commands/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/command_template/management/commands/sample.py.tmpl,sha256=VWqndBmkpZ5jw_3DrisYjXD76Si5lVSVcZlkifG57gs,306
|
||||
django_extensions/conf/jobs_template/jobs/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/jobs_template/jobs/daily/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/jobs_template/jobs/hourly/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/jobs_template/jobs/monthly/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/jobs_template/jobs/sample.py.tmpl,sha256=r2cd8E0jNTKIJYQmPULuxjZFxzg1yrv72IHsipWkWtY,178
|
||||
django_extensions/conf/jobs_template/jobs/weekly/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/jobs_template/jobs/yearly/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/template_tags_template/templatetags/__init__.py.tmpl,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/conf/template_tags_template/templatetags/sample.py.tmpl,sha256=IOMcdXaX3IBAawoGoteRYqF5Y2ggxsLweR5XZqxfpMk,59
|
||||
django_extensions/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/db/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/db/__pycache__/models.cpython-310.pyc,,
|
||||
django_extensions/db/fields/__init__.py,sha256=Z6UD1GhVBvCXGxVobeC_dVoK3TynLLvT6iZjvQbsLhs,21363
|
||||
django_extensions/db/fields/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/db/fields/__pycache__/json.cpython-310.pyc,,
|
||||
django_extensions/db/fields/json.py,sha256=UJm8E7VHqqW2dyuY2VzUvLOuAFx9XshQvB-mrBCbrUg,3168
|
||||
django_extensions/db/models.py,sha256=h-2AGZfP8r47ieUfTcK4Yg7qo9kLu1V4wO_GVi_Ufgg,3928
|
||||
django_extensions/import_subclasses.py,sha256=ViDGIEoRIrfIv1GVfG1PJmJs2WMRrPwKG_StAulNqic,2343
|
||||
django_extensions/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/jobs/daily/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/daily/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/jobs/daily/__pycache__/cache_cleanup.cpython-310.pyc,,
|
||||
django_extensions/jobs/daily/__pycache__/daily_cleanup.cpython-310.pyc,,
|
||||
django_extensions/jobs/daily/cache_cleanup.py,sha256=am0xy2aejVodGr7063ijNYQuiGhdg_PayflHte3_22w,646
|
||||
django_extensions/jobs/daily/daily_cleanup.py,sha256=hSLNGhiI9A-wKpxj8ie3wjmMTGOMq_kWjE-kt3_JqAU,389
|
||||
django_extensions/jobs/hourly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/hourly/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/jobs/minutely/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/minutely/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/jobs/monthly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/monthly/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/jobs/weekly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/weekly/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/jobs/yearly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/jobs/yearly/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/locale/ar/LC_MESSAGES/django.po,sha256=j23ombvXVqaqcz5Zr3XRs9NuEkbBI1FiZlsmdeiaPWU,3126
|
||||
django_extensions/locale/da/LC_MESSAGES/django.mo,sha256=R7WNKaXc0q4iM1cUsgzdbdRZ08r7m14EmM1znFTo1FI,797
|
||||
django_extensions/locale/da/LC_MESSAGES/django.po,sha256=Zl33Wn5Sz6JsDUcbR_2aMtFCTqmDYz1XpXXsrY2GBl0,1667
|
||||
django_extensions/locale/de/LC_MESSAGES/django.mo,sha256=kuhHiXWrfazxsvFzvENWfY5sN3XpLvy1-AKQ0jKOnAs,1227
|
||||
django_extensions/locale/de/LC_MESSAGES/django.po,sha256=OLFLDJbZLPk3oK5DUcJ-V7eeBKZcHjlu_Vl0WTHW9F4,1755
|
||||
django_extensions/locale/el/LC_MESSAGES/django.mo,sha256=0CafRFBnuy4QdqtoaipoKpONaVMvtfP1J_4eMBB2gAg,1581
|
||||
django_extensions/locale/el/LC_MESSAGES/django.po,sha256=UC2b1GCXVnUteg1ZFqooRp6wkcxBufQGWCSZW8Hxndw,2116
|
||||
django_extensions/locale/en/LC_MESSAGES/django.mo,sha256=9JJOWscsqQUH_P7IWH5P5MEJPDJjDGzGl-Zz5-xGDFo,367
|
||||
django_extensions/locale/en/LC_MESSAGES/django.po,sha256=l27VRI3peRt_aKdlaQ7zVXj03wR2PfIex2X3SWrrSBc,2229
|
||||
django_extensions/locale/es/LC_MESSAGES/django.mo,sha256=SH8ojro4wqhcR8yKM2vn9JVxTMbke7zwUjsc_W60jfA,1260
|
||||
django_extensions/locale/es/LC_MESSAGES/django.po,sha256=euh9NBu3f-f-CuNgPGaJDebN0TbalfKKJ_X5q55VqA8,1788
|
||||
django_extensions/locale/fr/LC_MESSAGES/django.mo,sha256=XIMBOSYt8pHAhP3pUBs1N-iKLn3e0LRgTNYe_ippr58,743
|
||||
django_extensions/locale/fr/LC_MESSAGES/django.po,sha256=xKyEMPuZ_jFx7_7kY0plfHZV_gB8gUr2nvKdU_X3CLY,1931
|
||||
django_extensions/locale/hu/LC_MESSAGES/django.mo,sha256=7rWzOkIurHDcvi4uCgh4hkQjUpV182FSyjKZz6mIBFU,1242
|
||||
django_extensions/locale/hu/LC_MESSAGES/django.po,sha256=SOHXX186PGybAII05VA5QRZvSjtXR9fLJpgS2acxwt8,1770
|
||||
django_extensions/locale/id/LC_MESSAGES/django.mo,sha256=X3tKDCM5qiuVi5dYOnzxAxx6mQ3w-wTJBvP7_ENnHhg,1508
|
||||
django_extensions/locale/id/LC_MESSAGES/django.po,sha256=a4dguUsySnXLdDDafyXcq2lXFmYN-DS6uoEOQQJGEV4,2243
|
||||
django_extensions/locale/it/LC_MESSAGES/django.mo,sha256=y3dS8jT30b2P9il5kxQaCj_JgaLLCCkR_vLEllX8L0g,1247
|
||||
django_extensions/locale/it/LC_MESSAGES/django.po,sha256=AENMGV_gkuUqp2gVWnENI5hlCtJipNykZkAWcvlRia0,1775
|
||||
django_extensions/locale/ja/LC_MESSAGES/django.mo,sha256=5fTQjN83bExfQbkaAMq3zve2B3fEWkf6rF-QYGZf9fA,1397
|
||||
django_extensions/locale/ja/LC_MESSAGES/django.po,sha256=CGrMk9hH64qBE_6NF-qPMwHpdfW57FwY3PlF0g0_g0M,1925
|
||||
django_extensions/locale/pl/LC_MESSAGES/django.mo,sha256=G3yZYzIwUHJ0PK14VhRXxJaYSXRkBQWa4yfFwJyhSBs,2002
|
||||
django_extensions/locale/pl/LC_MESSAGES/django.po,sha256=hVRdMxQmgRhtruCm66bZQVY-OIfSSYVBSJViuZNHB_4,2788
|
||||
django_extensions/locale/pt/LC_MESSAGES/django.mo,sha256=F_q92e6dFwPbjvYWHNBvCjgd5mIj3_ezrHvCOFeUZCw,1262
|
||||
django_extensions/locale/pt/LC_MESSAGES/django.po,sha256=oKucDPxqIFZAOeVa_mbvOsmXXwyTydd82_Z_pXpkfvI,1790
|
||||
django_extensions/locale/pt_BR/LC_MESSAGES/django.mo,sha256=bN2RG97zI3S6qEuMmvbDvPCo4YSZ_KEY5UxviD9WzlA,1310
|
||||
django_extensions/locale/pt_BR/LC_MESSAGES/django.po,sha256=VDIRUodyxJr4PDcgiOuR6o3k1Ss_4ge5rx0DZgk5QwY,2082
|
||||
django_extensions/locale/ro/LC_MESSAGES/django.mo,sha256=8-8B-I7iFCGZKBj1XKMbMqQLl6Yg2W1IEG39miSI8Hk,1352
|
||||
django_extensions/locale/ro/LC_MESSAGES/django.po,sha256=CWaWS2C08-8lNWMCtPSPvDj4xONYrD3UGx4QSWXuWgg,1891
|
||||
django_extensions/locale/ru/LC_MESSAGES/django.mo,sha256=C_kjCXvZuZ2ZdiU8ffcjKwcnA-d5IiUTgpglX7JdD-U,2009
|
||||
django_extensions/locale/ru/LC_MESSAGES/django.po,sha256=luenXP4hypDODQUVWowDSCkYW9VMF_9NBlTUVkAmB3o,3820
|
||||
django_extensions/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/logging/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/logging/__pycache__/filters.cpython-310.pyc,,
|
||||
django_extensions/logging/filters.py,sha256=sESzvZ3U6V4lH5kUKBl7zc0D9IFpCWND9flxBKVc7vA,1126
|
||||
django_extensions/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/management/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/base.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/color.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/debug_cursor.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/email_notifications.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/jobs.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/modelviz.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/mysql.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/notebook_extension.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/shells.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/signals.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/technical_response.cpython-310.pyc,,
|
||||
django_extensions/management/__pycache__/utils.cpython-310.pyc,,
|
||||
django_extensions/management/base.py,sha256=HUiUTdXwYkvjwwgxkTfcNw5TWbWwlLFNIP4kp_Cb1hg,1431
|
||||
django_extensions/management/color.py,sha256=cnoHNtMDJN_bXfBYyACWW_CQxiX-LU13-2CHxxjw4t8,907
|
||||
django_extensions/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/management/commands/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/admin_generator.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/clean_pyc.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/clear_cache.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/compile_pyc.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/create_command.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/create_jobs.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/create_template_tags.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/delete_squashed_migrations.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/describe_form.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/drop_test_database.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/dumpscript.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/export_emails.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/find_template.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/generate_password.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/generate_secret_key.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/graph_models.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/list_model_info.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/list_signals.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/mail_debug.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/managestate.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/merge_model_instances.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/notes.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/print_settings.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/print_user_for_session.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/raise_test_exception.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/reset_db.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/reset_schema.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/runjob.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/runjobs.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/runprofileserver.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/runscript.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/runserver_plus.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/set_default_site.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/set_fake_emails.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/set_fake_passwords.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/shell_plus.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/show_permissions.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/show_template_tags.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/show_urls.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/sqlcreate.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/sqldiff.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/sqldsn.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/sync_s3.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/syncdata.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/unreferenced_files.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/update_permissions.cpython-310.pyc,,
|
||||
django_extensions/management/commands/__pycache__/validate_templates.cpython-310.pyc,,
|
||||
django_extensions/management/commands/admin_generator.py,sha256=bnp8Y0huCsBID-aKcAvhoOBSWqtDY6y8KI2XpDiHjvE,12188
|
||||
django_extensions/management/commands/clean_pyc.py,sha256=S4udDKSlcF-MvVKZBcI3oPfi-TCxXeG68EHWqeKuP9E,1671
|
||||
django_extensions/management/commands/clear_cache.py,sha256=3xsdc0twEN99Ll6u2bW8UZwKVjIZVnePYCUVLLlffbc,1514
|
||||
django_extensions/management/commands/compile_pyc.py,sha256=7EvZLJR3PhLYFkbcGkFJH0WnYFztX-R6WrFx3GlXThw,1346
|
||||
django_extensions/management/commands/create_command.py,sha256=Sg6_SJgPeP2qUkfyWJ2zEbPnOzQ3woF2t7pIjHQjzyk,4068
|
||||
django_extensions/management/commands/create_jobs.py,sha256=jCvjlf9g-aduJx7PCIb0BKlOQ4dGc7ISzpu2fz_lSpM,2537
|
||||
django_extensions/management/commands/create_template_tags.py,sha256=PiKRh6P0_byU37Ni_tGYqd9Jog3CNfECyu0xAvuWVNQ,2981
|
||||
django_extensions/management/commands/delete_squashed_migrations.py,sha256=9zEI6Tqxmv5dI7k2DZOeTdHAhlqM6bS6shjotgv7urs,7711
|
||||
django_extensions/management/commands/describe_form.py,sha256=RvSaaYkiW-IT2PEdtethWQ4LNeVsL5Gcgilj4uNUW9w,2981
|
||||
django_extensions/management/commands/drop_test_database.py,sha256=ghYtG4HX3htOCJxYHeZEqHKHFF6xLakYal2P88ILdu0,9292
|
||||
django_extensions/management/commands/dumpscript.py,sha256=gnS8hzRO0fD9WuZnaT63Mx9JqX9xqgnp7_I_DUKt4yc,29150
|
||||
django_extensions/management/commands/export_emails.py,sha256=qXZPOt0XDGstS91piUoKUDAR2xX2doRsU5zcU66jl8w,6314
|
||||
django_extensions/management/commands/find_template.py,sha256=GImM8uPJ_3wNUDwOVTZNvWcyun4pa5aevBpsT2qtsMk,695
|
||||
django_extensions/management/commands/generate_password.py,sha256=if1gl5s3Bds6lpAiffvy34au3hpN0MNq5-QOMd4CNRU,1155
|
||||
django_extensions/management/commands/generate_secret_key.py,sha256=bisQO7XRV1sOe1GqQ0-mTQZgXviWQrdiLxJJ7qWSYq8,484
|
||||
django_extensions/management/commands/graph_models.py,sha256=Qf_rgGVEWgBbbWmg3VDqPtZ0xSim28rJJixax4fMGXs,17929
|
||||
django_extensions/management/commands/list_model_info.py,sha256=IUVNFVeK-P-93CCvjzqLLJD5htir5I4WEpskGuixucc,6532
|
||||
django_extensions/management/commands/list_signals.py,sha256=j8ow4YtbUf9ujZdQMl0Ce0zXA0oYn3uf8Xi5QI9N6bs,2974
|
||||
django_extensions/management/commands/mail_debug.py,sha256=3NH_xjx46O5t9vDxPsgEIu5qxYA2hAnz1HUAcyoULuk,3264
|
||||
django_extensions/management/commands/managestate.py,sha256=2PNf7Q6XxjQUxIvuFJeSM48AVv6eIMfdgdnuh2Rl1NM,7164
|
||||
django_extensions/management/commands/merge_model_instances.py,sha256=zF9kbfGL3C7D2WypXl3B-07kgjeX1EDO-1PGt1Wy6nY,9777
|
||||
django_extensions/management/commands/notes.py,sha256=7tW2Pcy1YaMfcnygg1TYZivZE7aIQN9x5iKqGGvSER0,3024
|
||||
django_extensions/management/commands/print_settings.py,sha256=L6Ros6V4o40cYnng2vhrIuMjLpDj3SiWA0Sfu0RKYSc,2728
|
||||
django_extensions/management/commands/print_user_for_session.py,sha256=RqTC0xgP2DWBVUvsTEFimSiCkzAemKIBAUKa2w4tamg,2120
|
||||
django_extensions/management/commands/raise_test_exception.py,sha256=71c2pIi__scpzvKrFGsfaS4TTYeuxulI4I4vlJaldQo,659
|
||||
django_extensions/management/commands/reset_db.py,sha256=x9RZM5PQ-SyMYmyOr97KEbMVb1BHeQ_M96-Ip41lt2s,8677
|
||||
django_extensions/management/commands/reset_schema.py,sha256=xA4S2_osQt0x9vS9egYyezsfX-LhYdNruKmS3VTSye8,3132
|
||||
django_extensions/management/commands/runjob.py,sha256=fl3WV3IZUxZhw6mmbWulCkHcJz0WcdTYeU30pOmUShE,2074
|
||||
django_extensions/management/commands/runjobs.py,sha256=Idd9aL7SRsk2YwoBlALkeM0OWJnD9p20NTRcBlXxfUE,3542
|
||||
django_extensions/management/commands/runprofileserver.py,sha256=xxhes9xQo_xT8jktTOzLihR2SZ3t9jiaOTn6FyASNN8,10761
|
||||
django_extensions/management/commands/runscript.py,sha256=j06vV2-fMyiPSELoijC1fyoXX6D1BG2143523lg8KZg,12975
|
||||
django_extensions/management/commands/runserver_plus.py,sha256=4pIaQJNlOO1CALbRVGqq9q2uHghw0eOAfo0dFOR9aSI,26690
|
||||
django_extensions/management/commands/set_default_site.py,sha256=jv9a9EN81_tlGR75Eua-P8b2fo6oIZ_UIgWxfvsuFjM,2915
|
||||
django_extensions/management/commands/set_fake_emails.py,sha256=LLTNC6QzGIxWwwv517Tu5U3FSVuf01SJfYpC7h3NjWE,4228
|
||||
django_extensions/management/commands/set_fake_passwords.py,sha256=hcLUiXu5iu6l3ACpObEEIzPJm6NXOQGO182nE60BxRU,1829
|
||||
django_extensions/management/commands/shell_plus.py,sha256=UKnpROY0nrQ2LVAdmqSucxxZvYG0FfGeAVh721cP3ak,25055
|
||||
django_extensions/management/commands/show_permissions.py,sha256=sCDPjeUus-Wl8-Imq0oSvsh_xV73g4QblcrMCPTrfq0,2383
|
||||
django_extensions/management/commands/show_template_tags.py,sha256=gTJtfR2VqjJJ4j5waq4XJ7oYNWeix5CLA-iS_3pgYvE,3939
|
||||
django_extensions/management/commands/show_urls.py,sha256=C96jaQu-FWWW9hJwplOBV_dnGah-K3HYauC3cfCBKhs,10482
|
||||
django_extensions/management/commands/sqlcreate.py,sha256=HSpvqRK4iXxx_SUEdnoX5os2RScqhmyNfC1BCYcleh0,4780
|
||||
django_extensions/management/commands/sqldiff.py,sha256=ScncsaTLd8dZWDkCFRB46ikYYY--a1o9ODRM7VqeiTs,71112
|
||||
django_extensions/management/commands/sqldsn.py,sha256=MrSb6bMnc3pV1tL4CzPugn_Gu2qa-3n9o6Z_0kABoRY,6632
|
||||
django_extensions/management/commands/sync_s3.py,sha256=8Yrm0NYL54EK0cnQMjxsu6U1JoPSzN9ZK9tITttEuZA,16905
|
||||
django_extensions/management/commands/syncdata.py,sha256=nbu-FRVZk2GJiooAkDB0q14O_FS6N-kXrqClLoXmDYg,11091
|
||||
django_extensions/management/commands/unreferenced_files.py,sha256=R7Ke26ubKUoQPXILJzUQoYn-R1_cFkKjOlfczTbHtag,1801
|
||||
django_extensions/management/commands/update_permissions.py,sha256=QxQitwADzlTfKPgJhMBngaKVrwbR_KGzVirDQWgmaus,2977
|
||||
django_extensions/management/commands/validate_templates.py,sha256=HwDA1394LpFgh2lVdUIyGPPXYO5_7VBDiiK51LtKfAE,4064
|
||||
django_extensions/management/debug_cursor.py,sha256=cUERDVfj6KTjd43oGYhsoBn66yzNtktj1x2Xo70fWLw,4846
|
||||
django_extensions/management/email_notifications.py,sha256=Ac2mOcehDK6FOKcIeus4YfEPSvJTqFQsnKOoDw4qDT8,5333
|
||||
django_extensions/management/jobs.py,sha256=Ihd4dVWdEWwTsc25YVmbeYBl2_eWvcOgqC8m1L79pdk,5432
|
||||
django_extensions/management/modelviz.py,sha256=75JKEWC2QzNhtl0T_cjo15iLQ_jYWEQzQLp_r80dP6c,19546
|
||||
django_extensions/management/mysql.py,sha256=GrAJHzMGZ7UUkmP6fovKQJSZtqNSSjSY4V-A6tBhbL8,1557
|
||||
django_extensions/management/notebook_extension.py,sha256=2UzcvcUpaD704dLx4uSVY3rZGN6RQ-L_OVOgjdDYKyI,324
|
||||
django_extensions/management/shells.py,sha256=xVscuW74iFhwTgzqjrNntmZxCypbSYp7h8uvFEEwJvI,18095
|
||||
django_extensions/management/signals.py,sha256=yWTcyz8hhcB-PLJKeH2UWOAypCIvaeijKy3CfNJ3ZhU,307
|
||||
django_extensions/management/technical_response.py,sha256=8WJUUH-ZDpbGrH6Oc0mHTLnIN5vQLOiB0aeMMvENqjs,1782
|
||||
django_extensions/management/utils.py,sha256=nK9pNrF2esYxIufxh5U9aKLKiZj2CVWK3nIRCmD6OJo,2351
|
||||
django_extensions/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/mongodb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/mongodb/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/mongodb/__pycache__/models.cpython-310.pyc,,
|
||||
django_extensions/mongodb/fields/__init__.py,sha256=nM7ikY-oMKxUBl5jSPiZq8INwjVZQraqWqXmwOl8hSg,9489
|
||||
django_extensions/mongodb/fields/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/mongodb/fields/__pycache__/json.cpython-310.pyc,,
|
||||
django_extensions/mongodb/fields/json.py,sha256=8JA5_ObbTlENTkVfHxeE_RtMYiYQw_5QSwaPGfPWKDk,2154
|
||||
django_extensions/mongodb/models.py,sha256=N-ABdyit6Y93diCnWsO0J8C7U1DyPbLrtNRuSGgmwh8,2458
|
||||
django_extensions/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/settings.py,sha256=9G14bG8M-1ucGFVh7PVXuiMdwaJ-QJ8jV58i4EnTm08,1369
|
||||
django_extensions/static/django_extensions/css/jquery.autocomplete.css,sha256=3yUz9XJFKdXHv34Xe4QNWjA9ghEr2ieEoQ0KaO3e49Q,740
|
||||
django_extensions/static/django_extensions/img/indicator.gif,sha256=0-OUTUZJRQ3uZqVcae7O0tgltsoaNJ9yx1_TeArj8AY,1553
|
||||
django_extensions/static/django_extensions/js/jquery.ajaxQueue.js,sha256=hPrJgQn9AOWP5fixvJ2ty6_ncPDKXGtW3hjZY6omo70,2800
|
||||
django_extensions/static/django_extensions/js/jquery.autocomplete.js,sha256=5DV9zxN6TgVpbkscqlkoiK5qhEEGdmxV2v7d265QQog,36679
|
||||
django_extensions/static/django_extensions/js/jquery.bgiframe.js,sha256=ACFjwe9ie5i4LysU-w9gpisHaCzHbS0kEOtIL0UcDM4,1821
|
||||
django_extensions/templates/django_extensions/graph_models/django2018/digraph.dot,sha256=Y0wcDGN58wjZQTvnytRcHzu3H0gOmnq_Qf4CkWTwrTQ,904
|
||||
django_extensions/templates/django_extensions/graph_models/django2018/label.dot,sha256=vsKxchMm6DQVTPx8gFxijpHOGNJuqdItN2WEI8mvc14,1875
|
||||
django_extensions/templates/django_extensions/graph_models/django2018/relation.dot,sha256=6KlECRFCmCmTTOQs5vYEr2sPWdDgiUbgH4ZkWLt_SjE,589
|
||||
django_extensions/templates/django_extensions/graph_models/django2018style/digraph.dot,sha256=X5gkfd8UNCo8JhlONpe30hxb4ZtomxccBUeC254GlG0,856
|
||||
django_extensions/templates/django_extensions/graph_models/django2018style/label.dot,sha256=hk1x5SYFEpxwccrqEV8ay80fh7YYi3TY0fWfbCS5820,1983
|
||||
django_extensions/templates/django_extensions/graph_models/django2018style/relation.dot,sha256=6KlECRFCmCmTTOQs5vYEr2sPWdDgiUbgH4ZkWLt_SjE,589
|
||||
django_extensions/templates/django_extensions/graph_models/original/digraph.dot,sha256=mwfMx95mrkZofdoGj7E4RVfQilGgSNV45-ZklCoBd0Y,909
|
||||
django_extensions/templates/django_extensions/graph_models/original/label.dot,sha256=0-UHhFDl-XTkOr1wUAoEBd-xSfnAea922vUX0N1cCeQ,1697
|
||||
django_extensions/templates/django_extensions/graph_models/original/relation.dot,sha256=Y-wvocs_14QreSILBv9ESWvnF6B3pUcrRrjx_q0oINk,591
|
||||
django_extensions/templates/django_extensions/widgets/foreignkey_searchinput.html,sha256=8DhLt6B0oUlpVq1gSoPy4uImyJxUueUczwYHraZeKNg,2032
|
||||
django_extensions/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
django_extensions/templatetags/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/templatetags/__pycache__/debugger_tags.cpython-310.pyc,,
|
||||
django_extensions/templatetags/__pycache__/highlighting.cpython-310.pyc,,
|
||||
django_extensions/templatetags/__pycache__/indent_text.cpython-310.pyc,,
|
||||
django_extensions/templatetags/__pycache__/syntax_color.cpython-310.pyc,,
|
||||
django_extensions/templatetags/__pycache__/widont.cpython-310.pyc,,
|
||||
django_extensions/templatetags/debugger_tags.py,sha256=I-IjazMNelcBx9tQ41-NA0ERntY04nrj1FFdYssQg9U,596
|
||||
django_extensions/templatetags/highlighting.py,sha256=F32yOpVmcv3MvlD2Se-U6zTLBDNI3k1KW5nI4HieNgY,3103
|
||||
django_extensions/templatetags/indent_text.py,sha256=kXmJmcrezyoHyTqsN1jxKljLCiLMMdakYls4E_dn5-c,1750
|
||||
django_extensions/templatetags/syntax_color.py,sha256=d_zCxFGNr3WXHHwtLwcSKscxFMcrTBWJIrZvoVWPmVg,3204
|
||||
django_extensions/templatetags/widont.py,sha256=kwqiQYOuCIxXGVYomacPzbZ9bcH2Ba9wEeDMiCWEckE,1957
|
||||
django_extensions/utils/__init__.py,sha256=Xb0RrRwc1dqCwkASV8I2MSLy14c_FhmE1HeaxxaeO1E,70
|
||||
django_extensions/utils/__pycache__/__init__.cpython-310.pyc,,
|
||||
django_extensions/utils/__pycache__/deprecation.cpython-310.pyc,,
|
||||
django_extensions/utils/__pycache__/dia2django.cpython-310.pyc,,
|
||||
django_extensions/utils/__pycache__/internal_ips.cpython-310.pyc,,
|
||||
django_extensions/utils/deprecation.py,sha256=t2fLgdgEj2l2dp_8S581QyDMwA2sg7OhE33AFYYxjts,156
|
||||
django_extensions/utils/dia2django.py,sha256=YuVbOTHIqYw9QcF1lHVA05EjMZvAwGcuVNaRGj0SfYo,12183
|
||||
django_extensions/utils/internal_ips.py,sha256=rmqZttBwaTWbPHW3DRGHtcA3HXT8NIpQ0TG7Sar44Ls,1952
|
||||
django_extensions/validators.py,sha256=mkhSbBOFggSfycePTLf1vkxxgC2tP44JGjUT8vhRMkE,4023
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (78.1.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2007 Michael Trier
|
||||
|
||||
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.
|
||||
@@ -0,0 +1 @@
|
||||
django_extensions
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.utils.version import get_version
|
||||
|
||||
VERSION = (4, 1, 0, "final", 0)
|
||||
|
||||
__version__ = get_version(VERSION)
|
||||
@@ -0,0 +1,195 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Autocomplete feature for admin panel
|
||||
#
|
||||
import operator
|
||||
from functools import update_wrapper, reduce
|
||||
from typing import Tuple, Dict, Callable # NOQA
|
||||
|
||||
from django.apps import apps
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.text import get_text_list
|
||||
from django.contrib import admin
|
||||
|
||||
from django_extensions.admin.widgets import ForeignKeySearchInput
|
||||
|
||||
|
||||
class ForeignKeyAutocompleteAdminMixin:
|
||||
"""
|
||||
Admin class for models using the autocomplete feature.
|
||||
|
||||
There are two additional fields:
|
||||
- related_search_fields: defines fields of managed model that
|
||||
have to be represented by autocomplete input, together with
|
||||
a list of target model fields that are searched for
|
||||
input string, e.g.:
|
||||
|
||||
related_search_fields = {
|
||||
'author': ('first_name', 'email'),
|
||||
}
|
||||
|
||||
- related_string_functions: contains optional functions which
|
||||
take target model instance as only argument and return string
|
||||
representation. By default __unicode__() method of target
|
||||
object is used.
|
||||
|
||||
And also an optional additional field to set the limit on the
|
||||
results returned by the autocomplete query. You can set this integer
|
||||
value in your settings file using FOREIGNKEY_AUTOCOMPLETE_LIMIT or
|
||||
you can set this per ForeignKeyAutocompleteAdmin basis. If any value
|
||||
is set the results will not be limited.
|
||||
"""
|
||||
|
||||
related_search_fields = {} # type: Dict[str, Tuple[str]]
|
||||
related_string_functions = {} # type: Dict[str, Callable]
|
||||
autocomplete_limit = getattr(settings, "FOREIGNKEY_AUTOCOMPLETE_LIMIT", None)
|
||||
|
||||
def get_urls(self):
|
||||
from django.urls import path
|
||||
|
||||
def wrap(view):
|
||||
def wrapper(*args, **kwargs):
|
||||
return self.admin_site.admin_view(view)(*args, **kwargs)
|
||||
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
return [
|
||||
path(
|
||||
"foreignkey_autocomplete/",
|
||||
wrap(self.foreignkey_autocomplete),
|
||||
name="%s_%s_autocomplete"
|
||||
% (self.model._meta.app_label, self.model._meta.model_name),
|
||||
)
|
||||
] + super().get_urls()
|
||||
|
||||
def foreignkey_autocomplete(self, request):
|
||||
"""
|
||||
Search in the fields of the given related model and returns the
|
||||
result as a simple string to be used by the jQuery Autocomplete plugin
|
||||
"""
|
||||
query = request.GET.get("q", None)
|
||||
app_label = request.GET.get("app_label", None)
|
||||
model_name = request.GET.get("model_name", None)
|
||||
search_fields = request.GET.get("search_fields", None)
|
||||
object_pk = request.GET.get("object_pk", None)
|
||||
|
||||
try:
|
||||
to_string_function = self.related_string_functions[model_name]
|
||||
except KeyError:
|
||||
to_string_function = lambda x: x.__str__()
|
||||
|
||||
if search_fields and app_label and model_name and (query or object_pk):
|
||||
|
||||
def construct_search(field_name):
|
||||
# use different lookup methods depending on the notation
|
||||
if field_name.startswith("^"):
|
||||
return "%s__istartswith" % field_name[1:]
|
||||
elif field_name.startswith("="):
|
||||
return "%s__iexact" % field_name[1:]
|
||||
elif field_name.startswith("@"):
|
||||
return "%s__search" % field_name[1:]
|
||||
else:
|
||||
return "%s__icontains" % field_name
|
||||
|
||||
model = apps.get_model(app_label, model_name)
|
||||
|
||||
queryset = model._default_manager.all()
|
||||
data = ""
|
||||
if query:
|
||||
for bit in query.split():
|
||||
or_queries = [
|
||||
models.Q(
|
||||
**{construct_search(smart_str(field_name)): smart_str(bit)}
|
||||
)
|
||||
for field_name in search_fields.split(",")
|
||||
]
|
||||
other_qs = QuerySet(model)
|
||||
other_qs.query.select_related = queryset.query.select_related
|
||||
other_qs = other_qs.filter(reduce(operator.or_, or_queries))
|
||||
queryset = queryset & other_qs
|
||||
|
||||
additional_filter = self.get_related_filter(model, request)
|
||||
if additional_filter:
|
||||
queryset = queryset.filter(additional_filter)
|
||||
|
||||
if self.autocomplete_limit:
|
||||
queryset = queryset[: self.autocomplete_limit]
|
||||
|
||||
data = "".join(
|
||||
[str("%s|%s\n") % (to_string_function(f), f.pk) for f in queryset]
|
||||
)
|
||||
elif object_pk:
|
||||
try:
|
||||
obj = queryset.get(pk=object_pk)
|
||||
except Exception: # FIXME: use stricter exception checking
|
||||
pass
|
||||
else:
|
||||
data = to_string_function(obj)
|
||||
return HttpResponse(data, content_type="text/plain")
|
||||
return HttpResponseNotFound()
|
||||
|
||||
def get_related_filter(self, model, request):
|
||||
"""
|
||||
Given a model class and current request return an optional Q object
|
||||
that should be applied as an additional filter for autocomplete query.
|
||||
If no additional filtering is needed, this method should return
|
||||
None.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_help_text(self, field_name, model_name):
|
||||
searchable_fields = self.related_search_fields.get(field_name, None)
|
||||
if searchable_fields:
|
||||
help_kwargs = {
|
||||
"model_name": model_name,
|
||||
"field_list": get_text_list(searchable_fields, _("and")),
|
||||
}
|
||||
return (
|
||||
_(
|
||||
"Use the left field to do %(model_name)s lookups "
|
||||
"in the fields %(field_list)s."
|
||||
)
|
||||
% help_kwargs
|
||||
)
|
||||
return ""
|
||||
|
||||
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
||||
"""
|
||||
Override the default widget for Foreignkey fields if they are
|
||||
specified in the related_search_fields class attribute.
|
||||
"""
|
||||
if (
|
||||
isinstance(db_field, models.ForeignKey)
|
||||
and db_field.name in self.related_search_fields
|
||||
):
|
||||
help_text = self.get_help_text(
|
||||
db_field.name, db_field.remote_field.model._meta.object_name
|
||||
)
|
||||
if kwargs.get("help_text"):
|
||||
help_text = str("%s %s") % (kwargs["help_text"], help_text)
|
||||
kwargs["widget"] = ForeignKeySearchInput(
|
||||
db_field.remote_field, self.related_search_fields[db_field.name]
|
||||
)
|
||||
kwargs["help_text"] = help_text
|
||||
return super().formfield_for_dbfield(db_field, request, **kwargs)
|
||||
|
||||
|
||||
class ForeignKeyAutocompleteAdmin(ForeignKeyAutocompleteAdminMixin, admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class ForeignKeyAutocompleteTabularInline(
|
||||
ForeignKeyAutocompleteAdminMixin, admin.TabularInline
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class ForeignKeyAutocompleteStackedInline(
|
||||
ForeignKeyAutocompleteAdminMixin, admin.StackedInline
|
||||
):
|
||||
pass
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.admin import FieldListFilter
|
||||
from django.contrib.admin.utils import prepare_lookup_value
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class NullFieldListFilter(FieldListFilter):
|
||||
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||
self.lookup_kwarg = "{0}__isnull".format(field_path)
|
||||
super().__init__(field, request, params, model, model_admin, field_path)
|
||||
lookup_choices = self.lookups(request, model_admin)
|
||||
self.lookup_choices = () if lookup_choices is None else list(lookup_choices)
|
||||
|
||||
def expected_parameters(self):
|
||||
return [self.lookup_kwarg]
|
||||
|
||||
def value(self):
|
||||
return self.used_parameters.get(self.lookup_kwarg, None)
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
("1", _("Yes")),
|
||||
("0", _("No")),
|
||||
)
|
||||
|
||||
def choices(self, cl):
|
||||
yield {
|
||||
"selected": self.value() is None,
|
||||
"query_string": cl.get_query_string({}, [self.lookup_kwarg]),
|
||||
"display": _("All"),
|
||||
}
|
||||
for lookup, title in self.lookup_choices:
|
||||
yield {
|
||||
"selected": self.value()
|
||||
== prepare_lookup_value(self.lookup_kwarg, lookup),
|
||||
"query_string": cl.get_query_string(
|
||||
{
|
||||
self.lookup_kwarg: lookup,
|
||||
},
|
||||
[],
|
||||
),
|
||||
"display": title,
|
||||
}
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() is not None:
|
||||
kwargs = {self.lookup_kwarg: self.value()}
|
||||
return queryset.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
|
||||
class NotNullFieldListFilter(NullFieldListFilter):
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
("0", _("Yes")),
|
||||
("1", _("No")),
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import urllib
|
||||
|
||||
from django import forms
|
||||
from django.contrib.admin.sites import site
|
||||
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
|
||||
from django.template.loader import render_to_string
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import Truncator
|
||||
|
||||
|
||||
class ForeignKeySearchInput(ForeignKeyRawIdWidget):
|
||||
"""
|
||||
Widget for displaying ForeignKeys in an autocomplete search input
|
||||
instead in a <select> box.
|
||||
"""
|
||||
|
||||
# Set in subclass to render the widget with a different template
|
||||
widget_template = None
|
||||
# Set this to the patch of the search view
|
||||
search_path = None
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
js_files = [
|
||||
static("django_extensions/js/jquery.bgiframe.js"),
|
||||
static("django_extensions/js/jquery.ajaxQueue.js"),
|
||||
static("django_extensions/js/jquery.autocomplete.js"),
|
||||
]
|
||||
|
||||
return forms.Media(
|
||||
css={"all": (static("django_extensions/css/jquery.autocomplete.css"),)},
|
||||
js=js_files,
|
||||
)
|
||||
|
||||
def label_for_value(self, value):
|
||||
key = self.rel.get_related_field().name
|
||||
obj = self.rel.model._default_manager.get(**{key: value})
|
||||
|
||||
return Truncator(obj).words(14, truncate="...")
|
||||
|
||||
def __init__(self, rel, search_fields, attrs=None):
|
||||
self.search_fields = search_fields
|
||||
super().__init__(rel, site, attrs)
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
opts = self.rel.model._meta
|
||||
app_label = opts.app_label
|
||||
model_name = opts.object_name.lower()
|
||||
related_url = reverse("admin:%s_%s_changelist" % (app_label, model_name))
|
||||
if not self.search_path:
|
||||
self.search_path = urllib.parse.urljoin(
|
||||
related_url, "foreignkey_autocomplete/"
|
||||
)
|
||||
params = self.url_parameters()
|
||||
if params:
|
||||
url = "?" + "&".join(["%s=%s" % (k, v) for k, v in params.items()])
|
||||
else:
|
||||
url = ""
|
||||
|
||||
if "class" not in attrs:
|
||||
attrs["class"] = "vForeignKeyRawIdAdminField"
|
||||
# Call the TextInput render method directly to have more control
|
||||
output = [forms.TextInput.render(self, name, value, attrs)]
|
||||
|
||||
if value:
|
||||
label = self.label_for_value(value)
|
||||
else:
|
||||
label = ""
|
||||
|
||||
context = {
|
||||
"url": url,
|
||||
"related_url": related_url,
|
||||
"search_path": self.search_path,
|
||||
"search_fields": ",".join(self.search_fields),
|
||||
"app_label": app_label,
|
||||
"model_name": model_name,
|
||||
"label": label,
|
||||
"name": name,
|
||||
}
|
||||
output.append(
|
||||
render_to_string(
|
||||
self.widget_template
|
||||
or (
|
||||
"django_extensions/widgets/%s/%s/foreignkey_searchinput.html"
|
||||
% (app_label, model_name),
|
||||
"django_extensions/widgets/%s/foreignkey_searchinput.html"
|
||||
% app_label,
|
||||
"django_extensions/widgets/foreignkey_searchinput.html",
|
||||
),
|
||||
context,
|
||||
)
|
||||
)
|
||||
output.reverse()
|
||||
|
||||
return mark_safe("".join(output))
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DjangoExtensionsConfig(AppConfig):
|
||||
name = "django_extensions"
|
||||
verbose_name = "Django Extensions"
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
|
||||
|
||||
class ModelUserFieldPermissionMixin(UserPassesTestMixin):
|
||||
model_permission_user_field = "user"
|
||||
|
||||
def get_model_permission_user_field(self):
|
||||
return self.model_permission_user_field
|
||||
|
||||
def test_func(self):
|
||||
model_attr = self.get_model_permission_user_field()
|
||||
current_user = self.request.user
|
||||
|
||||
return current_user == getattr(self.get_queryset().first(), model_attr)
|
||||
@@ -0,0 +1,294 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import inspect
|
||||
import sys
|
||||
from abc import abstractmethod, ABCMeta
|
||||
from typing import ( # NOQA
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
|
||||
class BaseCR(metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract base collision resolver. All collision resolvers needs to inherit from this class.
|
||||
To write custom collision resolver you need to overwrite resolve_collisions function.
|
||||
It receives Dict[str, List[str]], where key is model name and values are full model names
|
||||
(full model name means: module + model_name).
|
||||
You should return Dict[str, str], where key is model name and value is full model name.
|
||||
""" # noqa: E501
|
||||
|
||||
@classmethod
|
||||
def get_app_name_and_model(cls, full_model_path): # type: (str) -> Tuple[str, str]
|
||||
model_class = import_string(full_model_path)
|
||||
return model_class._meta.app_config.name, model_class.__name__
|
||||
|
||||
@abstractmethod
|
||||
def resolve_collisions(self, namespace): # type: (Dict[str, List[str]]) -> Dict[str, str]
|
||||
pass
|
||||
|
||||
|
||||
class LegacyCR(BaseCR):
|
||||
"""
|
||||
Default collision resolver.
|
||||
|
||||
Model from last application in alphabetical order is selected.
|
||||
"""
|
||||
|
||||
def resolve_collisions(self, namespace):
|
||||
result = {}
|
||||
for name, models in namespace.items():
|
||||
result[name] = models[-1]
|
||||
return result
|
||||
|
||||
|
||||
class AppsOrderCR(LegacyCR, metaclass=ABCMeta):
|
||||
APP_PRIORITIES = None # type: List[str]
|
||||
|
||||
def resolve_collisions(self, namespace):
|
||||
assert self.APP_PRIORITIES is not None, (
|
||||
"You must define APP_PRIORITIES in your resolver class!"
|
||||
)
|
||||
result = {}
|
||||
for name, models in namespace.items():
|
||||
if len(models) > 0:
|
||||
sorted_models = self._sort_models_depending_on_priorities(models)
|
||||
result[name] = sorted_models[0][1]
|
||||
return result
|
||||
|
||||
def _sort_models_depending_on_priorities(self, models): # type: (List[str]) -> List[Tuple[int, str]]
|
||||
models_with_priorities = []
|
||||
for model in models:
|
||||
try:
|
||||
app_name, _ = self.get_app_name_and_model(model)
|
||||
position = self.APP_PRIORITIES.index(app_name)
|
||||
except (ImportError, ValueError):
|
||||
position = sys.maxsize
|
||||
models_with_priorities.append((position, model))
|
||||
return sorted(models_with_priorities)
|
||||
|
||||
|
||||
class InstalledAppsOrderCR(AppsOrderCR):
|
||||
"""
|
||||
Collision resolver which selects first model from INSTALLED_APPS.
|
||||
You can set your own app priorities list by subclassing him and overwriting APP_PRIORITIES field.
|
||||
This collision resolver will select model from first app on this list.
|
||||
If both app's are absent on this list, resolver will choose model from first app in alphabetical order.
|
||||
""" # noqa: E501
|
||||
|
||||
@property
|
||||
def APP_PRIORITIES(self):
|
||||
from django.conf import settings
|
||||
|
||||
return getattr(settings, "INSTALLED_APPS", [])
|
||||
|
||||
|
||||
class PathBasedCR(LegacyCR, metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract resolver which transforms full model name into alias.
|
||||
To use him you need to overwrite transform_import function
|
||||
which should have one parameter. It will be full model name.
|
||||
It should return valid alias as str instance.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def transform_import(self, module_path): # type: (str) -> str
|
||||
pass
|
||||
|
||||
def resolve_collisions(self, namespace):
|
||||
base_imports = super(PathBasedCR, self).resolve_collisions(namespace)
|
||||
for name, models in namespace.items():
|
||||
if len(models) <= 1:
|
||||
continue
|
||||
for model in models:
|
||||
new_name = self.transform_import(model)
|
||||
assert isinstance(new_name, str), (
|
||||
"result of transform_import must be str!"
|
||||
)
|
||||
base_imports[new_name] = model
|
||||
return base_imports
|
||||
|
||||
|
||||
class FullPathCR(PathBasedCR):
|
||||
"""
|
||||
Collision resolver which transform full model name to alias by changing dots to underscores.
|
||||
He also removes 'models' part of alias, because all models are in models.py files.
|
||||
Model from last application in alphabetical order is selected.
|
||||
""" # noqa: E501
|
||||
|
||||
def transform_import(self, module_path):
|
||||
module, model = module_path.rsplit(".models", 1)
|
||||
module_path = module + model
|
||||
return module_path.replace(".", "_")
|
||||
|
||||
|
||||
class AppNameCR(PathBasedCR, metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract collision resolver which transform pair (app name, model_name) to alias by changing dots to underscores.
|
||||
You must define MODIFICATION_STRING which should be string to format with two keyword arguments:
|
||||
app_name and model_name. For example: "{app_name}_{model_name}".
|
||||
Model from last application in alphabetical order is selected.
|
||||
""" # noqa: E501
|
||||
|
||||
MODIFICATION_STRING = None # type: Optional[str]
|
||||
|
||||
def transform_import(self, module_path):
|
||||
assert self.MODIFICATION_STRING is not None, (
|
||||
"You must define MODIFICATION_STRING in your resolver class!"
|
||||
)
|
||||
app_name, model_name = self.get_app_name_and_model(module_path)
|
||||
app_name = app_name.replace(".", "_")
|
||||
return self.MODIFICATION_STRING.format(app_name=app_name, model_name=model_name)
|
||||
|
||||
|
||||
class AppNamePrefixCR(AppNameCR):
|
||||
"""
|
||||
Collision resolver which transform pair (app name, model_name) to alias "{app_name}_{model_name}".
|
||||
Model from last application in alphabetical order is selected.
|
||||
Result is different than FullPathCR, when model has app_label other than current app.
|
||||
""" # noqa: E501
|
||||
|
||||
MODIFICATION_STRING = "{app_name}_{model_name}"
|
||||
|
||||
|
||||
class AppNameSuffixCR(AppNameCR):
|
||||
"""
|
||||
Collision resolver which transform pair (app name, model_name) to alias "{model_name}_{app_name}"
|
||||
Model from last application in alphabetical order is selected.
|
||||
""" # noqa: E501
|
||||
|
||||
MODIFICATION_STRING = "{model_name}_{app_name}"
|
||||
|
||||
|
||||
class AppNamePrefixCustomOrderCR(AppNamePrefixCR, InstalledAppsOrderCR):
|
||||
"""
|
||||
Collision resolver which is mixin of AppNamePrefixCR and InstalledAppsOrderCR.
|
||||
In case of collisions he sets aliases like AppNamePrefixCR, but sets default model using InstalledAppsOrderCR.
|
||||
""" # noqa: E501
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AppNameSuffixCustomOrderCR(AppNameSuffixCR, InstalledAppsOrderCR):
|
||||
"""
|
||||
Collision resolver which is mixin of AppNameSuffixCR and InstalledAppsOrderCR.
|
||||
In case of collisions he sets aliases like AppNameSuffixCR, but sets default model using InstalledAppsOrderCR.
|
||||
""" # noqa: E501
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FullPathCustomOrderCR(FullPathCR, InstalledAppsOrderCR):
|
||||
"""
|
||||
Collision resolver which is mixin of FullPathCR and InstalledAppsOrderCR.
|
||||
In case of collisions he sets aliases like FullPathCR, but sets default model using InstalledAppsOrderCR.
|
||||
""" # noqa: E501
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AppLabelCR(PathBasedCR, metaclass=ABCMeta):
|
||||
"""
|
||||
Abstract collision resolver which transform pair (app_label, model_name) to alias.
|
||||
You must define MODIFICATION_STRING which should be string to format with two keyword arguments:
|
||||
app_label and model_name. For example: "{app_label}_{model_name}".
|
||||
This is different from AppNameCR when the app is nested with several level of namespace:
|
||||
Gives sites_Site instead of django_contrib_sites_Site
|
||||
Model from last application in alphabetical order is selected.
|
||||
""" # noqa: E501
|
||||
|
||||
MODIFICATION_STRING = None # type: Optional[str]
|
||||
|
||||
def transform_import(self, module_path):
|
||||
assert self.MODIFICATION_STRING is not None, (
|
||||
"You must define MODIFICATION_STRING in your resolver class!"
|
||||
)
|
||||
model_class = import_string(module_path)
|
||||
app_label, model_name = model_class._meta.app_label, model_class.__name__
|
||||
return self.MODIFICATION_STRING.format(
|
||||
app_label=app_label, model_name=model_name
|
||||
)
|
||||
|
||||
|
||||
class AppLabelPrefixCR(AppLabelCR):
|
||||
"""
|
||||
Collision resolver which transform pair (app_label, model_name) to alias "{app_label}_{model_name}".
|
||||
Model from last application in alphabetical order is selected.
|
||||
""" # noqa: E501
|
||||
|
||||
MODIFICATION_STRING = "{app_label}_{model_name}"
|
||||
|
||||
|
||||
class AppLabelSuffixCR(AppLabelCR):
|
||||
"""
|
||||
Collision resolver which transform pair (app_label, model_name) to alias "{model_name}_{app_label}".
|
||||
Model from last application in alphabetical order is selected.
|
||||
""" # noqa: E501
|
||||
|
||||
MODIFICATION_STRING = "{model_name}_{app_label}"
|
||||
|
||||
|
||||
class CollisionResolvingRunner:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def run_collision_resolver(self, models_to_import):
|
||||
# type: (Dict[str, List[str]]) -> Dict[str, List[Tuple[str, str]]]
|
||||
dictionary_of_names = self._get_dictionary_of_names(models_to_import) # type: Dict[str, str]
|
||||
return self._get_dictionary_of_modules(dictionary_of_names)
|
||||
|
||||
@classmethod
|
||||
def _get_dictionary_of_names(cls, models_to_import): # type: (Dict[str, List[str]]) -> (Dict[str, str])
|
||||
from django.conf import settings
|
||||
|
||||
collision_resolver_class = import_string(
|
||||
getattr(
|
||||
settings,
|
||||
"SHELL_PLUS_MODEL_IMPORTS_RESOLVER",
|
||||
"django_extensions.collision_resolvers.LegacyCR",
|
||||
)
|
||||
)
|
||||
|
||||
cls._assert_is_collision_resolver_class_correct(collision_resolver_class)
|
||||
result = collision_resolver_class().resolve_collisions(models_to_import)
|
||||
cls._assert_is_collision_resolver_result_correct(result)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _assert_is_collision_resolver_result_correct(cls, result):
|
||||
assert isinstance(result, dict), (
|
||||
"Result of resolve_collisions function must be a dict!"
|
||||
)
|
||||
for key, value in result.items():
|
||||
assert isinstance(key, str), (
|
||||
"key in collision resolver result should be str not %s" % key
|
||||
)
|
||||
assert isinstance(value, str), (
|
||||
"value in collision resolver result should be str not %s" % value
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _assert_is_collision_resolver_class_correct(cls, collision_resolver_class):
|
||||
assert inspect.isclass(collision_resolver_class) and issubclass(
|
||||
collision_resolver_class, BaseCR
|
||||
), "SHELL_PLUS_MODEL_IMPORTS_RESOLVER must be subclass of BaseCR!"
|
||||
assert (
|
||||
len(
|
||||
inspect.getfullargspec(collision_resolver_class.resolve_collisions).args
|
||||
)
|
||||
== 2
|
||||
), "resolve_collisions function must take one argument!"
|
||||
|
||||
@classmethod
|
||||
def _get_dictionary_of_modules(cls, dictionary_of_names):
|
||||
# type: (Dict[str, str]) -> Dict[str, List[Tuple[str, str]]]
|
||||
dictionary_of_modules = {} # type: Dict[str, List[Tuple[str, str]]]
|
||||
for alias, model in dictionary_of_names.items():
|
||||
module_path, model_name = model.rsplit(".", 1)
|
||||
dictionary_of_modules.setdefault(module_path, [])
|
||||
dictionary_of_modules[module_path].append((model_name, alias))
|
||||
return dictionary_of_modules
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from io import BytesIO
|
||||
|
||||
import csv
|
||||
import codecs
|
||||
import importlib
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
#
|
||||
# Django compatibility
|
||||
#
|
||||
def load_tag_library(libname):
|
||||
"""
|
||||
Load a templatetag library on multiple Django versions.
|
||||
|
||||
Returns None if the library isn't loaded.
|
||||
"""
|
||||
from django.template.backends.django import get_installed_libraries
|
||||
from django.template.library import InvalidTemplateLibrary
|
||||
|
||||
try:
|
||||
lib = get_installed_libraries()[libname]
|
||||
lib = importlib.import_module(lib).register
|
||||
return lib
|
||||
except (InvalidTemplateLibrary, KeyError):
|
||||
return None
|
||||
|
||||
|
||||
def get_template_setting(template_key, default=None):
|
||||
"""Read template settings"""
|
||||
templates_var = getattr(settings, "TEMPLATES", None)
|
||||
if templates_var:
|
||||
for tdict in templates_var:
|
||||
if template_key in tdict:
|
||||
return tdict[template_key]
|
||||
return default
|
||||
|
||||
|
||||
class UnicodeWriter:
|
||||
"""
|
||||
CSV writer which will write rows to CSV file "f",
|
||||
which is encoded in the given encoding.
|
||||
We are using this custom UnicodeWriter for python versions 2.x
|
||||
"""
|
||||
|
||||
def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
|
||||
self.queue = BytesIO()
|
||||
self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
|
||||
self.stream = f
|
||||
self.encoder = codecs.getincrementalencoder(encoding)()
|
||||
|
||||
def writerow(self, row):
|
||||
self.writer.writerow([s.encode("utf-8") for s in row])
|
||||
# Fetch UTF-8 output from the queue ...
|
||||
data = self.queue.getvalue()
|
||||
data = data.decode("utf-8")
|
||||
# ... and reencode it into the target encoding
|
||||
data = self.encoder.encode(data)
|
||||
# write to the target stream
|
||||
self.stream.write(data)
|
||||
# empty queue
|
||||
self.queue.truncate(0)
|
||||
|
||||
def writerows(self, rows):
|
||||
for row in rows:
|
||||
self.writerow(row)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django import forms
|
||||
|
||||
# place form definition here
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.urls import include, path
|
||||
|
||||
# place app url patterns here
|
||||
@@ -0,0 +1 @@
|
||||
# Create your views here.
|
||||
@@ -0,0 +1,11 @@
|
||||
from django.core.management.base import {{ base_command }}
|
||||
|
||||
|
||||
class Command({{ base_command }}):
|
||||
help = "My shiny new management command."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('sample', nargs='+')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,9 @@
|
||||
from django_extensions.management.jobs import BaseJob
|
||||
|
||||
|
||||
class Job(BaseJob):
|
||||
help = "My sample job."
|
||||
|
||||
def execute(self):
|
||||
# executing empty sample job
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
@@ -0,0 +1,638 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Django Extensions additional model fields
|
||||
|
||||
Some fields might require additional dependencies to be installed.
|
||||
"""
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
try:
|
||||
import uuid
|
||||
|
||||
HAS_UUID = True
|
||||
except ImportError:
|
||||
HAS_UUID = False
|
||||
|
||||
try:
|
||||
import shortuuid
|
||||
|
||||
HAS_SHORT_UUID = True
|
||||
except ImportError:
|
||||
HAS_SHORT_UUID = False
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.models import DateTimeField, CharField, SlugField, Q, UniqueConstraint
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
|
||||
MAX_UNIQUE_QUERY_ATTEMPTS = getattr(
|
||||
settings, "EXTENSIONS_MAX_UNIQUE_QUERY_ATTEMPTS", 100
|
||||
)
|
||||
|
||||
|
||||
class UniqueFieldMixin:
|
||||
def check_is_bool(self, attrname):
|
||||
if not isinstance(getattr(self, attrname), bool):
|
||||
raise ValueError("'{}' argument must be True or False".format(attrname))
|
||||
|
||||
@staticmethod
|
||||
def _get_fields(model_cls):
|
||||
return [
|
||||
(f, f.model if f.model != model_cls else None)
|
||||
for f in model_cls._meta.get_fields()
|
||||
if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
|
||||
]
|
||||
|
||||
def get_queryset(self, model_cls, slug_field):
|
||||
for field, model in self._get_fields(model_cls):
|
||||
if model and field == slug_field:
|
||||
return model._default_manager.all()
|
||||
return model_cls._default_manager.all()
|
||||
|
||||
def find_unique(self, model_instance, field, iterator, *args):
|
||||
# exclude the current model instance from the queryset used in finding
|
||||
# next valid hash
|
||||
queryset = self.get_queryset(model_instance.__class__, field)
|
||||
if model_instance.pk:
|
||||
queryset = queryset.exclude(pk=model_instance.pk)
|
||||
|
||||
# form a kwarg dict used to implement any unique_together constraints
|
||||
kwargs = {}
|
||||
for params in model_instance._meta.unique_together:
|
||||
if self.attname in params:
|
||||
for param in params:
|
||||
kwargs[param] = getattr(model_instance, param, None)
|
||||
|
||||
# for support django 2.2+
|
||||
query = Q()
|
||||
constraints = getattr(model_instance._meta, "constraints", None)
|
||||
if constraints:
|
||||
unique_constraints = filter(
|
||||
lambda c: isinstance(c, UniqueConstraint), constraints
|
||||
)
|
||||
for unique_constraint in unique_constraints:
|
||||
if self.attname in unique_constraint.fields:
|
||||
condition = {
|
||||
field: getattr(model_instance, field, None)
|
||||
for field in unique_constraint.fields
|
||||
if field != self.attname
|
||||
}
|
||||
query &= Q(**condition)
|
||||
|
||||
new = next(iterator)
|
||||
kwargs[self.attname] = new
|
||||
while not new or queryset.filter(query, **kwargs):
|
||||
new = next(iterator)
|
||||
kwargs[self.attname] = new
|
||||
setattr(model_instance, self.attname, new)
|
||||
return new
|
||||
|
||||
|
||||
class AutoSlugField(UniqueFieldMixin, SlugField):
|
||||
"""
|
||||
AutoSlugField
|
||||
|
||||
By default, sets editable=False, blank=True.
|
||||
|
||||
Required arguments:
|
||||
|
||||
populate_from
|
||||
Specifies which field, list of fields, or model method
|
||||
the slug will be populated from.
|
||||
|
||||
populate_from can traverse a ForeignKey relationship
|
||||
by using Django ORM syntax:
|
||||
populate_from = 'related_model__field'
|
||||
|
||||
Optional arguments:
|
||||
|
||||
separator
|
||||
Defines the used separator (default: '-')
|
||||
|
||||
overwrite
|
||||
If set to True, overwrites the slug on every save (default: False)
|
||||
|
||||
slugify_function
|
||||
Defines the function which will be used to "slugify" a content
|
||||
(default: :py:func:`~django.template.defaultfilters.slugify` )
|
||||
|
||||
It is possible to provide custom "slugify" function with
|
||||
the ``slugify_function`` function in a model class.
|
||||
|
||||
``slugify_function`` function in a model class takes priority over
|
||||
``slugify_function`` given as an argument to :py:class:`~AutoSlugField`.
|
||||
|
||||
Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# models.py
|
||||
|
||||
from django.db import models
|
||||
|
||||
from django_extensions.db.fields import AutoSlugField
|
||||
|
||||
|
||||
class MyModel(models.Model):
|
||||
def slugify_function(self, content):
|
||||
return content.replace('_', '-').lower()
|
||||
|
||||
title = models.CharField(max_length=42)
|
||||
slug = AutoSlugField(populate_from='title')
|
||||
|
||||
Inspired by SmileyChris' Unique Slugify snippet:
|
||||
https://www.djangosnippets.org/snippets/690/
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("blank", True)
|
||||
kwargs.setdefault("editable", False)
|
||||
|
||||
populate_from = kwargs.pop("populate_from", None)
|
||||
if populate_from is None:
|
||||
raise ValueError("missing 'populate_from' argument")
|
||||
else:
|
||||
self._populate_from = populate_from
|
||||
|
||||
if not callable(populate_from):
|
||||
if not isinstance(populate_from, (list, tuple)):
|
||||
populate_from = (populate_from,)
|
||||
|
||||
if not all(isinstance(e, str) for e in populate_from):
|
||||
raise TypeError(
|
||||
"'populate_from' must be str or list[str] or tuple[str], found `%s`"
|
||||
% populate_from
|
||||
)
|
||||
|
||||
self.slugify_function = kwargs.pop("slugify_function", slugify)
|
||||
self.separator = kwargs.pop("separator", "-")
|
||||
self.overwrite = kwargs.pop("overwrite", False)
|
||||
self.check_is_bool("overwrite")
|
||||
self.overwrite_on_add = kwargs.pop("overwrite_on_add", True)
|
||||
self.check_is_bool("overwrite_on_add")
|
||||
self.allow_duplicates = kwargs.pop("allow_duplicates", False)
|
||||
self.check_is_bool("allow_duplicates")
|
||||
self.max_unique_query_attempts = kwargs.pop(
|
||||
"max_unique_query_attempts", MAX_UNIQUE_QUERY_ATTEMPTS
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _slug_strip(self, value):
|
||||
"""
|
||||
Clean up a slug by removing slug separator characters that occur at
|
||||
the beginning or end of a slug.
|
||||
|
||||
If an alternate separator is used, it will also replace any instances
|
||||
of the default '-' separator with the new separator.
|
||||
"""
|
||||
re_sep = "(?:-|%s)" % re.escape(self.separator)
|
||||
value = re.sub("%s+" % re_sep, self.separator, value)
|
||||
return re.sub(r"^%s+|%s+$" % (re_sep, re_sep), "", value)
|
||||
|
||||
@staticmethod
|
||||
def slugify_func(content, slugify_function):
|
||||
if content:
|
||||
return slugify_function(content)
|
||||
return ""
|
||||
|
||||
def slug_generator(self, original_slug, start):
|
||||
yield original_slug
|
||||
for i in range(start, self.max_unique_query_attempts):
|
||||
slug = original_slug
|
||||
end = "%s%s" % (self.separator, i)
|
||||
end_len = len(end)
|
||||
if self.slug_len and len(slug) + end_len > self.slug_len:
|
||||
slug = slug[: self.slug_len - end_len]
|
||||
slug = self._slug_strip(slug)
|
||||
slug = "%s%s" % (slug, end)
|
||||
yield slug
|
||||
raise RuntimeError(
|
||||
"max slug attempts for %s exceeded (%s)"
|
||||
% (original_slug, self.max_unique_query_attempts)
|
||||
)
|
||||
|
||||
def create_slug(self, model_instance, add):
|
||||
slug = getattr(model_instance, self.attname)
|
||||
use_existing_slug = False
|
||||
if slug and not self.overwrite:
|
||||
# Existing slug and not configured to overwrite - Short-circuit
|
||||
# here to prevent slug generation when not required.
|
||||
use_existing_slug = True
|
||||
|
||||
if self.overwrite_on_add and add:
|
||||
use_existing_slug = False
|
||||
|
||||
if use_existing_slug:
|
||||
return slug
|
||||
|
||||
# get fields to populate from and slug field to set
|
||||
populate_from = self._populate_from
|
||||
if not isinstance(populate_from, (list, tuple)):
|
||||
populate_from = (populate_from,)
|
||||
|
||||
slug_field = model_instance._meta.get_field(self.attname)
|
||||
slugify_function = getattr(
|
||||
model_instance, "slugify_function", self.slugify_function
|
||||
)
|
||||
|
||||
# slugify the original field content and set next step to 2
|
||||
slug_for_field = lambda lookup_value: self.slugify_func(
|
||||
self.get_slug_fields(model_instance, lookup_value),
|
||||
slugify_function=slugify_function,
|
||||
)
|
||||
slug = self.separator.join(map(slug_for_field, populate_from))
|
||||
start = 2
|
||||
|
||||
# strip slug depending on max_length attribute of the slug field
|
||||
# and clean-up
|
||||
self.slug_len = slug_field.max_length
|
||||
if self.slug_len:
|
||||
slug = slug[: self.slug_len]
|
||||
slug = self._slug_strip(slug)
|
||||
original_slug = slug
|
||||
|
||||
if self.allow_duplicates:
|
||||
setattr(model_instance, self.attname, slug)
|
||||
return slug
|
||||
|
||||
return self.find_unique(
|
||||
model_instance, slug_field, self.slug_generator(original_slug, start)
|
||||
)
|
||||
|
||||
def get_slug_fields(self, model_instance, lookup_value):
|
||||
if callable(lookup_value):
|
||||
# A function has been provided
|
||||
return "%s" % lookup_value(model_instance)
|
||||
|
||||
lookup_value_path = lookup_value.split(LOOKUP_SEP)
|
||||
attr = model_instance
|
||||
for elem in lookup_value_path:
|
||||
try:
|
||||
attr = getattr(attr, elem)
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
"value {} in AutoSlugField's 'populate_from' argument {} returned an error - {} has no attribute {}".format( # noqa: E501
|
||||
elem, lookup_value, attr, elem
|
||||
)
|
||||
)
|
||||
if callable(attr):
|
||||
return "%s" % attr()
|
||||
|
||||
return attr
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
value = force_str(self.create_slug(model_instance, add))
|
||||
return value
|
||||
|
||||
def get_internal_type(self):
|
||||
return "SlugField"
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
kwargs["populate_from"] = self._populate_from
|
||||
if not self.separator == "-":
|
||||
kwargs["separator"] = self.separator
|
||||
if self.overwrite is not False:
|
||||
kwargs["overwrite"] = True
|
||||
if self.allow_duplicates is not False:
|
||||
kwargs["allow_duplicates"] = True
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
class RandomCharField(UniqueFieldMixin, CharField):
|
||||
"""
|
||||
RandomCharField
|
||||
|
||||
By default, sets editable=False, blank=True, unique=False.
|
||||
|
||||
Required arguments:
|
||||
|
||||
length
|
||||
Specifies the length of the field
|
||||
|
||||
Optional arguments:
|
||||
|
||||
unique
|
||||
If set to True, duplicate entries are not allowed (default: False)
|
||||
|
||||
lowercase
|
||||
If set to True, lowercase the alpha characters (default: False)
|
||||
|
||||
uppercase
|
||||
If set to True, uppercase the alpha characters (default: False)
|
||||
|
||||
include_alpha
|
||||
If set to True, include alpha characters (default: True)
|
||||
|
||||
include_digits
|
||||
If set to True, include digit characters (default: True)
|
||||
|
||||
include_punctuation
|
||||
If set to True, include punctuation characters (default: False)
|
||||
|
||||
keep_default
|
||||
If set to True, keeps the default initialization value (default: False)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("blank", True)
|
||||
kwargs.setdefault("editable", False)
|
||||
|
||||
self.length = kwargs.pop("length", None)
|
||||
if self.length is None:
|
||||
raise ValueError("missing 'length' argument")
|
||||
kwargs["max_length"] = self.length
|
||||
|
||||
self.lowercase = kwargs.pop("lowercase", False)
|
||||
self.check_is_bool("lowercase")
|
||||
self.uppercase = kwargs.pop("uppercase", False)
|
||||
self.check_is_bool("uppercase")
|
||||
if self.uppercase and self.lowercase:
|
||||
raise ValueError(
|
||||
"the 'lowercase' and 'uppercase' arguments are mutually exclusive"
|
||||
)
|
||||
self.include_digits = kwargs.pop("include_digits", True)
|
||||
self.check_is_bool("include_digits")
|
||||
self.include_alpha = kwargs.pop("include_alpha", True)
|
||||
self.check_is_bool("include_alpha")
|
||||
self.include_punctuation = kwargs.pop("include_punctuation", False)
|
||||
self.keep_default = kwargs.pop("keep_default", False)
|
||||
self.check_is_bool("include_punctuation")
|
||||
self.max_unique_query_attempts = kwargs.pop(
|
||||
"max_unique_query_attempts", MAX_UNIQUE_QUERY_ATTEMPTS
|
||||
)
|
||||
|
||||
# Set unique=False unless it's been set manually.
|
||||
if "unique" not in kwargs:
|
||||
kwargs["unique"] = False
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def random_char_generator(self, chars):
|
||||
for i in range(self.max_unique_query_attempts):
|
||||
yield "".join(get_random_string(self.length, chars))
|
||||
raise RuntimeError(
|
||||
"max random character attempts exceeded (%s)"
|
||||
% self.max_unique_query_attempts
|
||||
)
|
||||
|
||||
def in_unique_together(self, model_instance):
|
||||
for params in model_instance._meta.unique_together:
|
||||
if self.attname in params:
|
||||
return True
|
||||
return False
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
if (not add or self.keep_default) and getattr(
|
||||
model_instance, self.attname
|
||||
) != "":
|
||||
return getattr(model_instance, self.attname)
|
||||
|
||||
population = ""
|
||||
if self.include_alpha:
|
||||
if self.lowercase:
|
||||
population += string.ascii_lowercase
|
||||
elif self.uppercase:
|
||||
population += string.ascii_uppercase
|
||||
else:
|
||||
population += string.ascii_letters
|
||||
|
||||
if self.include_digits:
|
||||
population += string.digits
|
||||
|
||||
if self.include_punctuation:
|
||||
population += string.punctuation
|
||||
|
||||
random_chars = self.random_char_generator(population)
|
||||
if not self.unique and not self.in_unique_together(model_instance):
|
||||
new = next(random_chars)
|
||||
setattr(model_instance, self.attname, new)
|
||||
return new
|
||||
|
||||
return self.find_unique(
|
||||
model_instance,
|
||||
model_instance._meta.get_field(self.attname),
|
||||
random_chars,
|
||||
)
|
||||
|
||||
def internal_type(self):
|
||||
return "CharField"
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
kwargs["length"] = self.length
|
||||
del kwargs["max_length"]
|
||||
if self.lowercase is True:
|
||||
kwargs["lowercase"] = self.lowercase
|
||||
if self.uppercase is True:
|
||||
kwargs["uppercase"] = self.uppercase
|
||||
if self.include_alpha is False:
|
||||
kwargs["include_alpha"] = self.include_alpha
|
||||
if self.include_digits is False:
|
||||
kwargs["include_digits"] = self.include_digits
|
||||
if self.include_punctuation is True:
|
||||
kwargs["include_punctuation"] = self.include_punctuation
|
||||
if self.unique is True:
|
||||
kwargs["unique"] = self.unique
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
class CreationDateTimeField(DateTimeField):
|
||||
"""
|
||||
CreationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, auto_now_add=True
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("editable", False)
|
||||
kwargs.setdefault("blank", True)
|
||||
kwargs.setdefault("auto_now_add", True)
|
||||
DateTimeField.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
if self.editable is not False:
|
||||
kwargs["editable"] = True
|
||||
if self.blank is not True:
|
||||
kwargs["blank"] = False
|
||||
if self.auto_now_add is not False:
|
||||
kwargs["auto_now_add"] = True
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
class ModificationDateTimeField(CreationDateTimeField):
|
||||
"""
|
||||
ModificationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, auto_now=True
|
||||
|
||||
Sets value to now every time the object is saved.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("auto_now", True)
|
||||
DateTimeField.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
if self.auto_now is not False:
|
||||
kwargs["auto_now"] = True
|
||||
return name, path, args, kwargs
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
if not getattr(model_instance, "update_modified", True):
|
||||
return getattr(model_instance, self.attname)
|
||||
return super().pre_save(model_instance, add)
|
||||
|
||||
|
||||
class UUIDVersionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UUIDFieldMixin:
|
||||
"""
|
||||
UUIDFieldMixin
|
||||
|
||||
By default uses UUID version 4 (randomly generated UUID).
|
||||
|
||||
The field support all uuid versions which are natively supported by the uuid python module, except version 2.
|
||||
For more information see: https://docs.python.org/lib/module-uuid.html
|
||||
""" # noqa: E501
|
||||
|
||||
DEFAULT_MAX_LENGTH = 36
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
verbose_name=None,
|
||||
name=None,
|
||||
auto=True,
|
||||
version=4,
|
||||
node=None,
|
||||
clock_seq=None,
|
||||
namespace=None,
|
||||
uuid_name=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
if not HAS_UUID:
|
||||
raise ImproperlyConfigured(
|
||||
"'uuid' module is required for UUIDField. "
|
||||
"(Do you have Python 2.5 or higher installed ?)"
|
||||
)
|
||||
|
||||
kwargs.setdefault("max_length", self.DEFAULT_MAX_LENGTH)
|
||||
|
||||
if auto:
|
||||
self.empty_strings_allowed = False
|
||||
kwargs["blank"] = True
|
||||
kwargs.setdefault("editable", False)
|
||||
|
||||
self.auto = auto
|
||||
self.version = version
|
||||
self.node = node
|
||||
self.clock_seq = clock_seq
|
||||
self.namespace = namespace
|
||||
self.uuid_name = uuid_name or name
|
||||
|
||||
super().__init__(verbose_name=verbose_name, *args, **kwargs)
|
||||
|
||||
def create_uuid(self):
|
||||
if not self.version or self.version == 4:
|
||||
return uuid.uuid4()
|
||||
elif self.version == 1:
|
||||
return uuid.uuid1(self.node, self.clock_seq)
|
||||
elif self.version == 2:
|
||||
raise UUIDVersionError("UUID version 2 is not supported.")
|
||||
elif self.version == 3:
|
||||
return uuid.uuid3(self.namespace, self.uuid_name)
|
||||
elif self.version == 5:
|
||||
return uuid.uuid5(self.namespace, self.uuid_name)
|
||||
else:
|
||||
raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
value = super().pre_save(model_instance, add)
|
||||
|
||||
if self.auto and add and value is None:
|
||||
value = force_str(self.create_uuid())
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
else:
|
||||
if self.auto and not value:
|
||||
value = force_str(self.create_uuid())
|
||||
setattr(model_instance, self.attname, value)
|
||||
|
||||
return value
|
||||
|
||||
def formfield(self, form_class=None, choices_form_class=None, **kwargs):
|
||||
if self.auto:
|
||||
return None
|
||||
return super().formfield(form_class, choices_form_class, **kwargs)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
|
||||
if kwargs.get("max_length", None) == self.DEFAULT_MAX_LENGTH:
|
||||
del kwargs["max_length"]
|
||||
if self.auto is not True:
|
||||
kwargs["auto"] = self.auto
|
||||
if self.version != 4:
|
||||
kwargs["version"] = self.version
|
||||
if self.node is not None:
|
||||
kwargs["node"] = self.node
|
||||
if self.clock_seq is not None:
|
||||
kwargs["clock_seq"] = self.clock_seq
|
||||
if self.namespace is not None:
|
||||
kwargs["namespace"] = self.namespace
|
||||
if self.uuid_name is not None:
|
||||
kwargs["uuid_name"] = self.name
|
||||
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
class ShortUUIDField(UUIDFieldMixin, CharField):
|
||||
"""
|
||||
ShortUUIDFied
|
||||
|
||||
Generates concise (22 characters instead of 36), unambiguous, URL-safe UUIDs.
|
||||
|
||||
Based on `shortuuid`: https://github.com/stochastic-technologies/shortuuid
|
||||
"""
|
||||
|
||||
DEFAULT_MAX_LENGTH = 22
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not HAS_SHORT_UUID:
|
||||
raise ImproperlyConfigured(
|
||||
"'shortuuid' module is required for ShortUUIDField. "
|
||||
"(Do you have Python 2.5 or higher installed ?)"
|
||||
)
|
||||
kwargs.setdefault("max_length", self.DEFAULT_MAX_LENGTH)
|
||||
|
||||
def create_uuid(self):
|
||||
if not self.version or self.version == 4:
|
||||
return shortuuid.uuid()
|
||||
elif self.version == 1:
|
||||
return shortuuid.uuid()
|
||||
elif self.version == 2:
|
||||
raise UUIDVersionError("UUID version 2 is not supported.")
|
||||
elif self.version == 3:
|
||||
raise UUIDVersionError("UUID version 3 is not supported.")
|
||||
elif self.version == 5:
|
||||
return shortuuid.uuid(name=self.namespace)
|
||||
else:
|
||||
raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
||||
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JSONField automatically serializes most Python terms to JSON data.
|
||||
Creates a TEXT field with a default value of "{}". See test_json.py for
|
||||
more information.
|
||||
|
||||
from django.db import models
|
||||
from django_extensions.db.fields import json
|
||||
|
||||
class LOL(models.Model):
|
||||
extra = json.JSONField()
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import models
|
||||
from django.db.models import expressions
|
||||
|
||||
|
||||
def dumps(value):
|
||||
return DjangoJSONEncoder().encode(value)
|
||||
|
||||
|
||||
def loads(txt):
|
||||
return json.loads(txt)
|
||||
|
||||
|
||||
class JSONDict(dict):
|
||||
"""
|
||||
Hack so repr() called by dumpdata will output JSON instead of
|
||||
Python formatted data. This way fixtures will work!
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return dumps(self)
|
||||
|
||||
|
||||
class JSONList(list):
|
||||
"""
|
||||
Hack so repr() called by dumpdata will output JSON instead of
|
||||
Python formatted data. This way fixtures will work!
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return dumps(self)
|
||||
|
||||
|
||||
class JSONField(models.TextField):
|
||||
"""
|
||||
JSONField is a generic textfield that neatly serializes/unserializes
|
||||
JSON objects seamlessly. Main thingy must be a dict object.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["default"] = kwargs.get("default", dict)
|
||||
models.TextField.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_default(self):
|
||||
if self.has_default():
|
||||
default = self.default
|
||||
|
||||
if callable(default):
|
||||
default = default()
|
||||
|
||||
return self.to_python(default)
|
||||
return super().get_default()
|
||||
|
||||
def to_python(self, value):
|
||||
"""Convert our string value to JSON after we load it from the DB"""
|
||||
if value is None or value == "":
|
||||
return {}
|
||||
|
||||
if isinstance(value, str):
|
||||
res = loads(value)
|
||||
else:
|
||||
res = value
|
||||
|
||||
if isinstance(res, dict):
|
||||
return JSONDict(**res)
|
||||
elif isinstance(res, list):
|
||||
return JSONList(res)
|
||||
|
||||
return res
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if not isinstance(value, str):
|
||||
return dumps(value)
|
||||
return super(models.TextField, self).get_prep_value(value)
|
||||
|
||||
def from_db_value(self, value, expression, connection): # type: ignore
|
||||
return self.to_python(value)
|
||||
|
||||
def get_db_prep_save(self, value, connection, **kwargs):
|
||||
"""Convert our JSON object to a string before we save"""
|
||||
if value is None and self.null:
|
||||
return None
|
||||
|
||||
# default values come in as strings; only non-strings should be
|
||||
# run through `dumps`
|
||||
if (
|
||||
not isinstance(value, str)
|
||||
# https://github.com/django-extensions/django-extensions/issues/1924
|
||||
# https://code.djangoproject.com/ticket/35167
|
||||
and not isinstance(value, expressions.Expression)
|
||||
):
|
||||
value = dumps(value)
|
||||
|
||||
return super().get_db_prep_save(value, connection)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
if self.default == "{}":
|
||||
del kwargs["default"]
|
||||
return name, path, args, kwargs
|
||||
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_extensions.db.fields import (
|
||||
AutoSlugField,
|
||||
CreationDateTimeField,
|
||||
ModificationDateTimeField,
|
||||
)
|
||||
|
||||
|
||||
class TimeStampedModel(models.Model):
|
||||
"""
|
||||
TimeStampedModel
|
||||
|
||||
An abstract base class model that provides self-managed "created" and
|
||||
"modified" fields.
|
||||
"""
|
||||
|
||||
created = CreationDateTimeField(_("created"))
|
||||
modified = ModificationDateTimeField(_("modified"))
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.update_modified = kwargs.pop(
|
||||
"update_modified", getattr(self, "update_modified", True)
|
||||
)
|
||||
super().save(**kwargs)
|
||||
|
||||
class Meta:
|
||||
get_latest_by = "modified"
|
||||
abstract = True
|
||||
|
||||
|
||||
class TitleDescriptionModel(models.Model):
|
||||
"""
|
||||
TitleDescriptionModel
|
||||
|
||||
An abstract base class model that provides title and description fields.
|
||||
"""
|
||||
|
||||
title = models.CharField(_("title"), max_length=255)
|
||||
description = models.TextField(_("description"), blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class TitleSlugDescriptionModel(TitleDescriptionModel):
|
||||
"""
|
||||
TitleSlugDescriptionModel
|
||||
|
||||
An abstract base class model that provides title and description fields
|
||||
and a self-managed "slug" field that populates from the title.
|
||||
|
||||
.. note ::
|
||||
If you want to use custom "slugify" function, you could
|
||||
define ``slugify_function`` which then will be used
|
||||
in :py:class:`AutoSlugField` to slugify ``populate_from`` field.
|
||||
|
||||
See :py:class:`AutoSlugField` for more details.
|
||||
"""
|
||||
|
||||
slug = AutoSlugField(_("slug"), populate_from="title")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class ActivatorQuerySet(models.query.QuerySet):
|
||||
"""
|
||||
ActivatorQuerySet
|
||||
|
||||
Query set that returns statused results
|
||||
"""
|
||||
|
||||
def active(self):
|
||||
"""Return active query set"""
|
||||
return self.filter(status=ActivatorModel.ACTIVE_STATUS)
|
||||
|
||||
def inactive(self):
|
||||
"""Return inactive query set"""
|
||||
return self.filter(status=ActivatorModel.INACTIVE_STATUS)
|
||||
|
||||
|
||||
class ActivatorModelManager(models.Manager):
|
||||
"""
|
||||
ActivatorModelManager
|
||||
|
||||
Manager to return instances of ActivatorModel:
|
||||
SomeModel.objects.active() / .inactive()
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
"""Use ActivatorQuerySet for all results"""
|
||||
return ActivatorQuerySet(model=self.model, using=self._db)
|
||||
|
||||
def active(self):
|
||||
"""
|
||||
Return active instances of ActivatorModel:
|
||||
|
||||
SomeModel.objects.active(), proxy to ActivatorQuerySet.active
|
||||
"""
|
||||
return self.get_queryset().active()
|
||||
|
||||
def inactive(self):
|
||||
"""
|
||||
Return inactive instances of ActivatorModel:
|
||||
|
||||
SomeModel.objects.inactive(), proxy to ActivatorQuerySet.inactive
|
||||
"""
|
||||
return self.get_queryset().inactive()
|
||||
|
||||
|
||||
class ActivatorModel(models.Model):
|
||||
"""
|
||||
ActivatorModel
|
||||
|
||||
An abstract base class model that provides activate and deactivate fields.
|
||||
"""
|
||||
|
||||
INACTIVE_STATUS = 0
|
||||
ACTIVE_STATUS = 1
|
||||
|
||||
STATUS_CHOICES = (
|
||||
(INACTIVE_STATUS, _("Inactive")),
|
||||
(ACTIVE_STATUS, _("Active")),
|
||||
)
|
||||
status = models.IntegerField(
|
||||
_("status"), choices=STATUS_CHOICES, default=ACTIVE_STATUS
|
||||
)
|
||||
activate_date = models.DateTimeField(
|
||||
blank=True, null=True, help_text=_("keep empty for an immediate activation")
|
||||
)
|
||||
deactivate_date = models.DateTimeField(
|
||||
blank=True, null=True, help_text=_("keep empty for indefinite activation")
|
||||
)
|
||||
objects = ActivatorModelManager()
|
||||
|
||||
class Meta:
|
||||
ordering = (
|
||||
"status",
|
||||
"-activate_date",
|
||||
)
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.activate_date:
|
||||
self.activate_date = now()
|
||||
super().save(*args, **kwargs)
|
||||
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from importlib import import_module
|
||||
from inspect import (
|
||||
getmembers,
|
||||
isclass,
|
||||
)
|
||||
from pkgutil import walk_packages
|
||||
from typing import ( # NOQA
|
||||
Dict,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
|
||||
class SubclassesFinder:
|
||||
def __init__(self, base_classes_from_settings):
|
||||
self.base_classes = []
|
||||
for element in base_classes_from_settings:
|
||||
if isinstance(element, str):
|
||||
element = import_string(element)
|
||||
self.base_classes.append(element)
|
||||
|
||||
def _should_be_imported(self, candidate_to_import): # type: (Tuple[str, type]) -> bool
|
||||
for base_class in self.base_classes:
|
||||
if issubclass(candidate_to_import[1], base_class):
|
||||
return True
|
||||
return False
|
||||
|
||||
def collect_subclasses(self): # type: () -> Dict[str, List[Tuple[str, str]]]
|
||||
"""
|
||||
Collect all subclasses of user-defined base classes from project.
|
||||
:return: Dictionary from module name to list of tuples.
|
||||
First element of tuple is model name and second is alias.
|
||||
Currently we set alias equal to model name,
|
||||
but in future functionality of aliasing subclasses can be added.
|
||||
"""
|
||||
result = {} # type: Dict[str, List[Tuple[str, str]]]
|
||||
for loader, module_name, is_pkg in walk_packages(path=[str(settings.BASE_DIR)]):
|
||||
subclasses_from_module = self._collect_classes_from_module(module_name)
|
||||
if subclasses_from_module:
|
||||
result[module_name] = subclasses_from_module
|
||||
return result
|
||||
|
||||
def _collect_classes_from_module(self, module_name): # type: (str) -> List[Tuple[str, str]]
|
||||
for excluded_module in getattr(
|
||||
settings, "SHELL_PLUS_SUBCLASSES_IMPORT_MODULES_BLACKLIST", []
|
||||
):
|
||||
if module_name.startswith(excluded_module):
|
||||
return []
|
||||
imported_module = import_module(module_name)
|
||||
classes_to_import = getmembers(
|
||||
imported_module,
|
||||
lambda element: isclass(element)
|
||||
and element.__module__ == imported_module.__name__,
|
||||
)
|
||||
classes_to_import = list(filter(self._should_be_imported, classes_to_import))
|
||||
return [(name, name) for name, _ in classes_to_import]
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Daily cleanup job.
|
||||
|
||||
Can be run as a cronjob to clean out old data from the database (only expired
|
||||
sessions at the moment).
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
|
||||
from django_extensions.management.jobs import DailyJob
|
||||
|
||||
|
||||
class Job(DailyJob):
|
||||
help = "Cache (db) cleanup Job"
|
||||
|
||||
def execute(self):
|
||||
if hasattr(settings, "CACHES"):
|
||||
for cache_name, cache_options in settings.CACHES.items():
|
||||
if cache_options["BACKEND"].endswith("DatabaseCache"):
|
||||
cache = caches[cache_name]
|
||||
cache.clear()
|
||||
return
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Daily cleanup job.
|
||||
|
||||
Can be run as a cronjob to clean out old data from the database (only expired
|
||||
sessions at the moment).
|
||||
"""
|
||||
|
||||
from django_extensions.management.jobs import DailyJob
|
||||
|
||||
|
||||
class Job(DailyJob):
|
||||
help = "Django Daily Cleanup Job"
|
||||
|
||||
def execute(self):
|
||||
from django.core import management
|
||||
|
||||
management.call_command("clearsessions")
|
||||
@@ -0,0 +1,109 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-06-06 11:44+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: admin/__init__.py:139
|
||||
msgid "and"
|
||||
msgstr "و"
|
||||
|
||||
#: admin/__init__.py:141
|
||||
#, python-format
|
||||
msgid "Use the left field to do %(model_name)s lookups in the fields %(field_list)s."
|
||||
msgstr "إستعمل الحقل الأيسر من %(model_name)s لبحث ضمن الأحقال التالية %(field_list)s "
|
||||
|
||||
#: admin/filter.py:24 admin/filter.py:53
|
||||
msgid "Yes"
|
||||
msgstr "نعم"
|
||||
|
||||
#: admin/filter.py:25 admin/filter.py:54
|
||||
msgid "No"
|
||||
msgstr "لا"
|
||||
|
||||
#: admin/filter.py:32
|
||||
msgid "All"
|
||||
msgstr "كل"
|
||||
|
||||
#: db/models.py:18
|
||||
msgid "created"
|
||||
msgstr "تم تكونه"
|
||||
|
||||
#: db/models.py:19
|
||||
msgid "modified"
|
||||
msgstr "تم تعديله"
|
||||
|
||||
#: db/models.py:37
|
||||
msgid "title"
|
||||
msgstr "عنوان"
|
||||
|
||||
#: db/models.py:38
|
||||
msgid "description"
|
||||
msgstr "وصف"
|
||||
|
||||
#: db/models.py:59
|
||||
msgid "slug"
|
||||
msgstr "رابط "
|
||||
|
||||
#: db/models.py:120 mongodb/models.py:76
|
||||
msgid "Inactive"
|
||||
msgstr "غير نشط"
|
||||
|
||||
#: db/models.py:121 mongodb/models.py:77
|
||||
msgid "Active"
|
||||
msgstr "نشط"
|
||||
|
||||
#: db/models.py:123
|
||||
msgid "status"
|
||||
msgstr "الحالة"
|
||||
|
||||
#: db/models.py:124 mongodb/models.py:80
|
||||
msgid "keep empty for an immediate activation"
|
||||
msgstr "أترك الحقل فارغ ليتم التنشيط مباشرة"
|
||||
|
||||
#: db/models.py:125 mongodb/models.py:81
|
||||
msgid "keep empty for indefinite activation"
|
||||
msgstr "أترك الحقل فارغ لتنشيط لمدة غير محددة"
|
||||
|
||||
#: mongodb/fields/__init__.py:22
|
||||
#, python-format
|
||||
msgid "String (up to %(max_length)s)"
|
||||
msgstr "سلسلة الإحرف (طولها يصل إلى %(max_length)s)"
|
||||
|
||||
#: validators.py:14
|
||||
msgid "Control Characters like new lines or tabs are not allowed."
|
||||
msgstr "لا يسمح إستعمال أحرف تحكم مثل حرف العودة إلى السطر أو علامات التبويب"
|
||||
|
||||
#: validators.py:48
|
||||
msgid "Leading and Trailing whitespaces are not allowed."
|
||||
msgstr "المسافات البيضاء الزائدة عند البداية أو نهاية غير مسموح بها"
|
||||
|
||||
#: validators.py:74
|
||||
msgid "Only a hex string is allowed."
|
||||
msgstr "مسموح إستعمال سلسلة أحرف hex فقط"
|
||||
|
||||
#: validators.py:75
|
||||
#, python-format
|
||||
msgid "Invalid length. Must be %(length)d characters."
|
||||
msgstr "الطول غير مقبول, يجب أن لا يكون أطول من %(length)d"
|
||||
|
||||
#: validators.py:76
|
||||
#, python-format
|
||||
msgid "Ensure that there are more than %(min)s characters."
|
||||
msgstr "تأكد أن طول سلسلة الإحرف أطول من %(min)s "
|
||||
|
||||
#: validators.py:77
|
||||
#, python-format
|
||||
msgid "Ensure that there are no more than %(max)s characters."
|
||||
msgstr "تأكد أن طول سلسلة الأحرف لا تتجوز %(max)s "
|
||||
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
# django_extentions in Danish.
|
||||
# django_extensions på Dansk.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# Michael Lind Mortensen <illio@cs.au.dk>, 2009.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-02-02 11:42+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: admin/__init__.py:121
|
||||
msgid "and"
|
||||
msgstr "og"
|
||||
|
||||
#: admin/__init__.py:123
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Use the left field to do %(model_name)s lookups in the fields %(field_list)s."
|
||||
msgstr ""
|
||||
"Brug feltet til venstre til at lave %(model_name)s lookups i felterne %"
|
||||
"(field_list)s."
|
||||
|
||||
#: db/models.py:15
|
||||
msgid "created"
|
||||
msgstr "skabt"
|
||||
|
||||
#: db/models.py:16
|
||||
msgid "modified"
|
||||
msgstr "ændret"
|
||||
|
||||
#: db/models.py:26
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
#: db/models.py:27
|
||||
msgid "slug"
|
||||
msgstr "slug"
|
||||
|
||||
#: db/models.py:28
|
||||
msgid "description"
|
||||
msgstr "beskrivelse"
|
||||
|
||||
#: db/models.py:50
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:51
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:53
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:56
|
||||
msgid "keep empty for an immediate activation"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:58
|
||||
msgid "keep empty for indefinite activation"
|
||||
msgstr ""
|
||||
|
||||
#: management/commands/show_urls.py:34
|
||||
#, python-format
|
||||
msgid "%s does not appear to be a urlpattern object"
|
||||
msgstr ""
|
||||
|
||||
#: templates/django_extensions/widgets/foreignkey_searchinput.html:4
|
||||
msgid "Lookup"
|
||||
msgstr "Lookup"
|
||||
Binary file not shown.
@@ -0,0 +1,77 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-02-02 11:42+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: admin/__init__.py:121
|
||||
msgid "and"
|
||||
msgstr "und"
|
||||
|
||||
#: admin/__init__.py:123
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Use the left field to do %(model_name)s lookups in the fields %(field_list)s."
|
||||
msgstr ""
|
||||
"Das linke Feld benutzen, um %(model_name)s Abfragen in den Feldern %"
|
||||
"(field_list)s durchführen."
|
||||
|
||||
#: db/models.py:15
|
||||
msgid "created"
|
||||
msgstr "erstellt"
|
||||
|
||||
#: db/models.py:16
|
||||
msgid "modified"
|
||||
msgstr "geändert"
|
||||
|
||||
#: db/models.py:26
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: db/models.py:27
|
||||
msgid "slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: db/models.py:28
|
||||
msgid "description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: db/models.py:50
|
||||
msgid "Inactive"
|
||||
msgstr "Inaktiv"
|
||||
|
||||
#: db/models.py:51
|
||||
msgid "Active"
|
||||
msgstr "Aktiv"
|
||||
|
||||
#: db/models.py:53
|
||||
msgid "status"
|
||||
msgstr "Status"
|
||||
|
||||
#: db/models.py:56
|
||||
msgid "keep empty for an immediate activation"
|
||||
msgstr "Leer lassen für sofortige Aktivierung"
|
||||
|
||||
#: db/models.py:58
|
||||
msgid "keep empty for indefinite activation"
|
||||
msgstr "Leer lassen für unbefristete Aktivierung"
|
||||
|
||||
#: management/commands/show_urls.py:34
|
||||
#, python-format
|
||||
msgid "%s does not appear to be a urlpattern object"
|
||||
msgstr "%s ist kein urlpattern Objekt"
|
||||
|
||||
#: templates/django_extensions/widgets/foreignkey_searchinput.html:4
|
||||
msgid "Lookup"
|
||||
msgstr "Abfrage"
|
||||
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-extensions\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-02-02 11:42+0100\n"
|
||||
"PO-Revision-Date: 2011-02-02 10:38+0000\n"
|
||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: el\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#: admin/__init__.py:121
|
||||
msgid "and"
|
||||
msgstr "και"
|
||||
|
||||
#: admin/__init__.py:123
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Use the left field to do %(model_name)s lookups in the fields %(field_list)s."
|
||||
msgstr ""
|
||||
"Χρησιμοποίησε το αριστερό πεδίο για να κάνεις αναζήτηση του %(model_name)s "
|
||||
"με βάσει τα πεδία %(field_list)s."
|
||||
|
||||
#: db/models.py:15
|
||||
msgid "created"
|
||||
msgstr "δημιουργήθηκε"
|
||||
|
||||
#: db/models.py:16
|
||||
msgid "modified"
|
||||
msgstr "τροποποιήθηκε"
|
||||
|
||||
#: db/models.py:26
|
||||
msgid "title"
|
||||
msgstr "τίτλος"
|
||||
|
||||
#: db/models.py:27
|
||||
msgid "slug"
|
||||
msgstr "μίνι-όνομα"
|
||||
|
||||
#: db/models.py:28
|
||||
msgid "description"
|
||||
msgstr "περιγραφή"
|
||||
|
||||
#: db/models.py:50
|
||||
msgid "Inactive"
|
||||
msgstr "ανενεργό"
|
||||
|
||||
#: db/models.py:51
|
||||
msgid "Active"
|
||||
msgstr "Ενεργό"
|
||||
|
||||
#: db/models.py:53
|
||||
msgid "status"
|
||||
msgstr "κατάσταση"
|
||||
|
||||
#: db/models.py:56
|
||||
msgid "keep empty for an immediate activation"
|
||||
msgstr "αφήστε άδειο για άμεση ενεργοποίηση"
|
||||
|
||||
#: db/models.py:58
|
||||
msgid "keep empty for indefinite activation"
|
||||
msgstr "αφήστε άδειο για αόριστη ενεργοποίηση"
|
||||
|
||||
#: management/commands/show_urls.py:34
|
||||
#, python-format
|
||||
msgid "%s does not appear to be a urlpattern object"
|
||||
msgstr "%s δεν φαίνεται να είναι ένα αντικείμενο urlpattern"
|
||||
|
||||
#: templates/django_extensions/widgets/foreignkey_searchinput.html:4
|
||||
msgid "Lookup"
|
||||
msgstr "Αναζήτηση"
|
||||
Binary file not shown.
@@ -0,0 +1,112 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-02-10 20:37+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: admin/__init__.py:142
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: admin/__init__.py:144
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Use the left field to do %(model_name)s lookups in the fields %(field_list)s."
|
||||
msgstr ""
|
||||
|
||||
#: admin/filter.py:24 admin/filter.py:53
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: admin/filter.py:25 admin/filter.py:54
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: admin/filter.py:32
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:18
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:19
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:38
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:39
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:60
|
||||
msgid "slug"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:121 mongodb/models.py:76
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:122 mongodb/models.py:77
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:124
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:125 mongodb/models.py:80
|
||||
msgid "keep empty for an immediate activation"
|
||||
msgstr ""
|
||||
|
||||
#: db/models.py:126 mongodb/models.py:81
|
||||
msgid "keep empty for indefinite activation"
|
||||
msgstr ""
|
||||
|
||||
#: mongodb/fields/__init__.py:22
|
||||
#, python-format
|
||||
msgid "String (up to %(max_length)s)"
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:14
|
||||
msgid "Control Characters like new lines or tabs are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:48
|
||||
msgid "Leading and Trailing whitespaces are not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:74
|
||||
msgid "Only a hex string is allowed."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:75
|
||||
#, python-format
|
||||
msgid "Invalid length. Must be %(length)d characters."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:76
|
||||
#, python-format
|
||||
msgid "Ensure that there are more than %(min)s characters."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:77
|
||||
#, python-format
|
||||
msgid "Ensure that there are no more than %(max)s characters."
|
||||
msgstr ""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user