Загрузить файлы в «venv/Lib/site-packages/pydantic/v1»
This commit is contained in:
131
venv/Lib/site-packages/pydantic/v1/__init__.py
Normal file
131
venv/Lib/site-packages/pydantic/v1/__init__.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# flake8: noqa
|
||||||
|
from pydantic.v1 import dataclasses
|
||||||
|
from pydantic.v1.annotated_types import create_model_from_namedtuple, create_model_from_typeddict
|
||||||
|
from pydantic.v1.class_validators import root_validator, validator
|
||||||
|
from pydantic.v1.config import BaseConfig, ConfigDict, Extra
|
||||||
|
from pydantic.v1.decorator import validate_arguments
|
||||||
|
from pydantic.v1.env_settings import BaseSettings
|
||||||
|
from pydantic.v1.error_wrappers import ValidationError
|
||||||
|
from pydantic.v1.errors import *
|
||||||
|
from pydantic.v1.fields import Field, PrivateAttr, Required
|
||||||
|
from pydantic.v1.main import *
|
||||||
|
from pydantic.v1.networks import *
|
||||||
|
from pydantic.v1.parse import Protocol
|
||||||
|
from pydantic.v1.tools import *
|
||||||
|
from pydantic.v1.types import *
|
||||||
|
from pydantic.v1.version import VERSION, compiled
|
||||||
|
|
||||||
|
__version__ = VERSION
|
||||||
|
|
||||||
|
# WARNING __all__ from pydantic.errors is not included here, it will be removed as an export here in v2
|
||||||
|
# please use "from pydantic.v1.errors import ..." instead
|
||||||
|
__all__ = [
|
||||||
|
# annotated types utils
|
||||||
|
'create_model_from_namedtuple',
|
||||||
|
'create_model_from_typeddict',
|
||||||
|
# dataclasses
|
||||||
|
'dataclasses',
|
||||||
|
# class_validators
|
||||||
|
'root_validator',
|
||||||
|
'validator',
|
||||||
|
# config
|
||||||
|
'BaseConfig',
|
||||||
|
'ConfigDict',
|
||||||
|
'Extra',
|
||||||
|
# decorator
|
||||||
|
'validate_arguments',
|
||||||
|
# env_settings
|
||||||
|
'BaseSettings',
|
||||||
|
# error_wrappers
|
||||||
|
'ValidationError',
|
||||||
|
# fields
|
||||||
|
'Field',
|
||||||
|
'Required',
|
||||||
|
# main
|
||||||
|
'BaseModel',
|
||||||
|
'create_model',
|
||||||
|
'validate_model',
|
||||||
|
# network
|
||||||
|
'AnyUrl',
|
||||||
|
'AnyHttpUrl',
|
||||||
|
'FileUrl',
|
||||||
|
'HttpUrl',
|
||||||
|
'stricturl',
|
||||||
|
'EmailStr',
|
||||||
|
'NameEmail',
|
||||||
|
'IPvAnyAddress',
|
||||||
|
'IPvAnyInterface',
|
||||||
|
'IPvAnyNetwork',
|
||||||
|
'PostgresDsn',
|
||||||
|
'CockroachDsn',
|
||||||
|
'AmqpDsn',
|
||||||
|
'RedisDsn',
|
||||||
|
'MongoDsn',
|
||||||
|
'KafkaDsn',
|
||||||
|
'validate_email',
|
||||||
|
# parse
|
||||||
|
'Protocol',
|
||||||
|
# tools
|
||||||
|
'parse_file_as',
|
||||||
|
'parse_obj_as',
|
||||||
|
'parse_raw_as',
|
||||||
|
'schema_of',
|
||||||
|
'schema_json_of',
|
||||||
|
# types
|
||||||
|
'NoneStr',
|
||||||
|
'NoneBytes',
|
||||||
|
'StrBytes',
|
||||||
|
'NoneStrBytes',
|
||||||
|
'StrictStr',
|
||||||
|
'ConstrainedBytes',
|
||||||
|
'conbytes',
|
||||||
|
'ConstrainedList',
|
||||||
|
'conlist',
|
||||||
|
'ConstrainedSet',
|
||||||
|
'conset',
|
||||||
|
'ConstrainedFrozenSet',
|
||||||
|
'confrozenset',
|
||||||
|
'ConstrainedStr',
|
||||||
|
'constr',
|
||||||
|
'PyObject',
|
||||||
|
'ConstrainedInt',
|
||||||
|
'conint',
|
||||||
|
'PositiveInt',
|
||||||
|
'NegativeInt',
|
||||||
|
'NonNegativeInt',
|
||||||
|
'NonPositiveInt',
|
||||||
|
'ConstrainedFloat',
|
||||||
|
'confloat',
|
||||||
|
'PositiveFloat',
|
||||||
|
'NegativeFloat',
|
||||||
|
'NonNegativeFloat',
|
||||||
|
'NonPositiveFloat',
|
||||||
|
'FiniteFloat',
|
||||||
|
'ConstrainedDecimal',
|
||||||
|
'condecimal',
|
||||||
|
'ConstrainedDate',
|
||||||
|
'condate',
|
||||||
|
'UUID1',
|
||||||
|
'UUID3',
|
||||||
|
'UUID4',
|
||||||
|
'UUID5',
|
||||||
|
'FilePath',
|
||||||
|
'DirectoryPath',
|
||||||
|
'Json',
|
||||||
|
'JsonWrapper',
|
||||||
|
'SecretField',
|
||||||
|
'SecretStr',
|
||||||
|
'SecretBytes',
|
||||||
|
'StrictBool',
|
||||||
|
'StrictBytes',
|
||||||
|
'StrictInt',
|
||||||
|
'StrictFloat',
|
||||||
|
'PaymentCardNumber',
|
||||||
|
'PrivateAttr',
|
||||||
|
'ByteSize',
|
||||||
|
'PastDate',
|
||||||
|
'FutureDate',
|
||||||
|
# version
|
||||||
|
'compiled',
|
||||||
|
'VERSION',
|
||||||
|
]
|
||||||
391
venv/Lib/site-packages/pydantic/v1/_hypothesis_plugin.py
Normal file
391
venv/Lib/site-packages/pydantic/v1/_hypothesis_plugin.py
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
"""
|
||||||
|
Register Hypothesis strategies for Pydantic custom types.
|
||||||
|
|
||||||
|
This enables fully-automatic generation of test data for most Pydantic classes.
|
||||||
|
|
||||||
|
Note that this module has *no* runtime impact on Pydantic itself; instead it
|
||||||
|
is registered as a setuptools entry point and Hypothesis will import it if
|
||||||
|
Pydantic is installed. See also:
|
||||||
|
|
||||||
|
https://hypothesis.readthedocs.io/en/latest/strategies.html#registering-strategies-via-setuptools-entry-points
|
||||||
|
https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.register_type_strategy
|
||||||
|
https://hypothesis.readthedocs.io/en/latest/strategies.html#interaction-with-pytest-cov
|
||||||
|
https://docs.pydantic.dev/usage/types/#pydantic-types
|
||||||
|
|
||||||
|
Note that because our motivation is to *improve user experience*, the strategies
|
||||||
|
are always sound (never generate invalid data) but sacrifice completeness for
|
||||||
|
maintainability (ie may be unable to generate some tricky but valid data).
|
||||||
|
|
||||||
|
Finally, this module makes liberal use of `# type: ignore[<code>]` pragmas.
|
||||||
|
This is because Hypothesis annotates `register_type_strategy()` with
|
||||||
|
`(T, SearchStrategy[T])`, but in most cases we register e.g. `ConstrainedInt`
|
||||||
|
to generate instances of the builtin `int` type which match the constraints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import datetime
|
||||||
|
import ipaddress
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
from fractions import Fraction
|
||||||
|
from typing import Callable, Dict, Type, Union, cast, overload
|
||||||
|
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
import pydantic.color
|
||||||
|
import pydantic.types
|
||||||
|
from pydantic.v1.utils import lenient_issubclass
|
||||||
|
|
||||||
|
# FilePath and DirectoryPath are explicitly unsupported, as we'd have to create
|
||||||
|
# them on-disk, and that's unsafe in general without being told *where* to do so.
|
||||||
|
#
|
||||||
|
# URLs are unsupported because it's easy for users to define their own strategy for
|
||||||
|
# "normal" URLs, and hard for us to define a general strategy which includes "weird"
|
||||||
|
# URLs but doesn't also have unpredictable performance problems.
|
||||||
|
#
|
||||||
|
# conlist() and conset() are unsupported for now, because the workarounds for
|
||||||
|
# Cython and Hypothesis to handle parametrized generic types are incompatible.
|
||||||
|
# We are rethinking Hypothesis compatibility in Pydantic v2.
|
||||||
|
|
||||||
|
# Emails
|
||||||
|
try:
|
||||||
|
import email_validator
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
|
||||||
|
def is_valid_email(s: str) -> bool:
|
||||||
|
# Hypothesis' st.emails() occasionally generates emails like 0@A0--0.ac
|
||||||
|
# that are invalid according to email-validator, so we filter those out.
|
||||||
|
try:
|
||||||
|
email_validator.validate_email(s, check_deliverability=False)
|
||||||
|
return True
|
||||||
|
except email_validator.EmailNotValidError: # pragma: no cover
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Note that these strategies deliberately stay away from any tricky Unicode
|
||||||
|
# or other encoding issues; we're just trying to generate *something* valid.
|
||||||
|
st.register_type_strategy(pydantic.EmailStr, st.emails().filter(is_valid_email)) # type: ignore[arg-type]
|
||||||
|
st.register_type_strategy(
|
||||||
|
pydantic.NameEmail,
|
||||||
|
st.builds(
|
||||||
|
'{} <{}>'.format, # type: ignore[arg-type]
|
||||||
|
st.from_regex('[A-Za-z0-9_]+( [A-Za-z0-9_]+){0,5}', fullmatch=True),
|
||||||
|
st.emails().filter(is_valid_email),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# PyObject - dotted names, in this case taken from the math module.
|
||||||
|
st.register_type_strategy(
|
||||||
|
pydantic.PyObject, # type: ignore[arg-type]
|
||||||
|
st.sampled_from(
|
||||||
|
[cast(pydantic.PyObject, f'math.{name}') for name in sorted(vars(math)) if not name.startswith('_')]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# CSS3 Colors; as name, hex, rgb(a) tuples or strings, or hsl strings
|
||||||
|
_color_regexes = (
|
||||||
|
'|'.join(
|
||||||
|
(
|
||||||
|
pydantic.color.r_hex_short,
|
||||||
|
pydantic.color.r_hex_long,
|
||||||
|
pydantic.color.r_rgb,
|
||||||
|
pydantic.color.r_rgba,
|
||||||
|
pydantic.color.r_hsl,
|
||||||
|
pydantic.color.r_hsla,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Use more precise regex patterns to avoid value-out-of-range errors
|
||||||
|
.replace(pydantic.color._r_sl, r'(?:(\d\d?(?:\.\d+)?|100(?:\.0+)?)%)')
|
||||||
|
.replace(pydantic.color._r_alpha, r'(?:(0(?:\.\d+)?|1(?:\.0+)?|\.\d+|\d{1,2}%))')
|
||||||
|
.replace(pydantic.color._r_255, r'(?:((?:\d|\d\d|[01]\d\d|2[0-4]\d|25[0-4])(?:\.\d+)?|255(?:\.0+)?))')
|
||||||
|
)
|
||||||
|
st.register_type_strategy(
|
||||||
|
pydantic.color.Color,
|
||||||
|
st.one_of(
|
||||||
|
st.sampled_from(sorted(pydantic.color.COLORS_BY_NAME)),
|
||||||
|
st.tuples(
|
||||||
|
st.integers(0, 255),
|
||||||
|
st.integers(0, 255),
|
||||||
|
st.integers(0, 255),
|
||||||
|
st.none() | st.floats(0, 1) | st.floats(0, 100).map('{}%'.format),
|
||||||
|
),
|
||||||
|
st.from_regex(_color_regexes, fullmatch=True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Card numbers, valid according to the Luhn algorithm
|
||||||
|
|
||||||
|
|
||||||
|
def add_luhn_digit(card_number: str) -> str:
|
||||||
|
# See https://en.wikipedia.org/wiki/Luhn_algorithm
|
||||||
|
for digit in '0123456789':
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
pydantic.PaymentCardNumber.validate_luhn_check_digit(card_number + digit)
|
||||||
|
return card_number + digit
|
||||||
|
raise AssertionError('Unreachable') # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
card_patterns = (
|
||||||
|
# Note that these patterns omit the Luhn check digit; that's added by the function above
|
||||||
|
'4[0-9]{14}', # Visa
|
||||||
|
'5[12345][0-9]{13}', # Mastercard
|
||||||
|
'3[47][0-9]{12}', # American Express
|
||||||
|
'[0-26-9][0-9]{10,17}', # other (incomplete to avoid overlap)
|
||||||
|
)
|
||||||
|
st.register_type_strategy(
|
||||||
|
pydantic.PaymentCardNumber,
|
||||||
|
st.from_regex('|'.join(card_patterns), fullmatch=True).map(add_luhn_digit), # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
|
||||||
|
# UUIDs
|
||||||
|
st.register_type_strategy(pydantic.UUID1, st.uuids(version=1))
|
||||||
|
st.register_type_strategy(pydantic.UUID3, st.uuids(version=3))
|
||||||
|
st.register_type_strategy(pydantic.UUID4, st.uuids(version=4))
|
||||||
|
st.register_type_strategy(pydantic.UUID5, st.uuids(version=5))
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
st.register_type_strategy(pydantic.SecretBytes, st.binary().map(pydantic.SecretBytes))
|
||||||
|
st.register_type_strategy(pydantic.SecretStr, st.text().map(pydantic.SecretStr))
|
||||||
|
|
||||||
|
# IP addresses, networks, and interfaces
|
||||||
|
st.register_type_strategy(pydantic.IPvAnyAddress, st.ip_addresses()) # type: ignore[arg-type]
|
||||||
|
st.register_type_strategy(
|
||||||
|
pydantic.IPvAnyInterface,
|
||||||
|
st.from_type(ipaddress.IPv4Interface) | st.from_type(ipaddress.IPv6Interface), # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
st.register_type_strategy(
|
||||||
|
pydantic.IPvAnyNetwork,
|
||||||
|
st.from_type(ipaddress.IPv4Network) | st.from_type(ipaddress.IPv6Network), # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
|
||||||
|
# We hook into the con***() functions and the ConstrainedNumberMeta metaclass,
|
||||||
|
# so here we only have to register subclasses for other constrained types which
|
||||||
|
# don't go via those mechanisms. Then there are the registration hooks below.
|
||||||
|
st.register_type_strategy(pydantic.StrictBool, st.booleans())
|
||||||
|
st.register_type_strategy(pydantic.StrictStr, st.text())
|
||||||
|
|
||||||
|
|
||||||
|
# FutureDate, PastDate
|
||||||
|
st.register_type_strategy(pydantic.FutureDate, st.dates(min_value=datetime.date.today() + datetime.timedelta(days=1)))
|
||||||
|
st.register_type_strategy(pydantic.PastDate, st.dates(max_value=datetime.date.today() - datetime.timedelta(days=1)))
|
||||||
|
|
||||||
|
|
||||||
|
# Constrained-type resolver functions
|
||||||
|
#
|
||||||
|
# For these ones, we actually want to inspect the type in order to work out a
|
||||||
|
# satisfying strategy. First up, the machinery for tracking resolver functions:
|
||||||
|
|
||||||
|
RESOLVERS: Dict[type, Callable[[type], st.SearchStrategy]] = {} # type: ignore[type-arg]
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def _registered(typ: Type[pydantic.types.T]) -> Type[pydantic.types.T]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def _registered(typ: pydantic.types.ConstrainedNumberMeta) -> pydantic.types.ConstrainedNumberMeta:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _registered(
|
||||||
|
typ: Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta]
|
||||||
|
) -> Union[Type[pydantic.types.T], pydantic.types.ConstrainedNumberMeta]:
|
||||||
|
# This function replaces the version in `pydantic.types`, in order to
|
||||||
|
# effect the registration of new constrained types so that Hypothesis
|
||||||
|
# can generate valid examples.
|
||||||
|
pydantic.types._DEFINED_TYPES.add(typ)
|
||||||
|
for supertype, resolver in RESOLVERS.items():
|
||||||
|
if issubclass(typ, supertype):
|
||||||
|
st.register_type_strategy(typ, resolver(typ)) # type: ignore
|
||||||
|
return typ
|
||||||
|
raise NotImplementedError(f'Unknown type {typ!r} has no resolver to register') # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
def resolves(
|
||||||
|
typ: Union[type, pydantic.types.ConstrainedNumberMeta]
|
||||||
|
) -> Callable[[Callable[..., st.SearchStrategy]], Callable[..., st.SearchStrategy]]: # type: ignore[type-arg]
|
||||||
|
def inner(f): # type: ignore
|
||||||
|
assert f not in RESOLVERS
|
||||||
|
RESOLVERS[typ] = f
|
||||||
|
return f
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
# Type-to-strategy resolver functions
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.JsonWrapper)
|
||||||
|
def resolve_json(cls): # type: ignore[no-untyped-def]
|
||||||
|
try:
|
||||||
|
inner = st.none() if cls.inner_type is None else st.from_type(cls.inner_type)
|
||||||
|
except Exception: # pragma: no cover
|
||||||
|
finite = st.floats(allow_infinity=False, allow_nan=False)
|
||||||
|
inner = st.recursive(
|
||||||
|
base=st.one_of(st.none(), st.booleans(), st.integers(), finite, st.text()),
|
||||||
|
extend=lambda x: st.lists(x) | st.dictionaries(st.text(), x), # type: ignore
|
||||||
|
)
|
||||||
|
inner_type = getattr(cls, 'inner_type', None)
|
||||||
|
return st.builds(
|
||||||
|
cls.inner_type.json if lenient_issubclass(inner_type, pydantic.BaseModel) else json.dumps,
|
||||||
|
inner,
|
||||||
|
ensure_ascii=st.booleans(),
|
||||||
|
indent=st.none() | st.integers(0, 16),
|
||||||
|
sort_keys=st.booleans(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.ConstrainedBytes)
|
||||||
|
def resolve_conbytes(cls): # type: ignore[no-untyped-def] # pragma: no cover
|
||||||
|
min_size = cls.min_length or 0
|
||||||
|
max_size = cls.max_length
|
||||||
|
if not cls.strip_whitespace:
|
||||||
|
return st.binary(min_size=min_size, max_size=max_size)
|
||||||
|
# Fun with regex to ensure we neither start nor end with whitespace
|
||||||
|
repeats = '{{{},{}}}'.format(
|
||||||
|
min_size - 2 if min_size > 2 else 0,
|
||||||
|
max_size - 2 if (max_size or 0) > 2 else '',
|
||||||
|
)
|
||||||
|
if min_size >= 2:
|
||||||
|
pattern = rf'\W.{repeats}\W'
|
||||||
|
elif min_size == 1:
|
||||||
|
pattern = rf'\W(.{repeats}\W)?'
|
||||||
|
else:
|
||||||
|
assert min_size == 0
|
||||||
|
pattern = rf'(\W(.{repeats}\W)?)?'
|
||||||
|
return st.from_regex(pattern.encode(), fullmatch=True)
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.ConstrainedDecimal)
|
||||||
|
def resolve_condecimal(cls): # type: ignore[no-untyped-def]
|
||||||
|
min_value = cls.ge
|
||||||
|
max_value = cls.le
|
||||||
|
if cls.gt is not None:
|
||||||
|
assert min_value is None, 'Set `gt` or `ge`, but not both'
|
||||||
|
min_value = cls.gt
|
||||||
|
if cls.lt is not None:
|
||||||
|
assert max_value is None, 'Set `lt` or `le`, but not both'
|
||||||
|
max_value = cls.lt
|
||||||
|
s = st.decimals(min_value, max_value, allow_nan=False, places=cls.decimal_places)
|
||||||
|
if cls.lt is not None:
|
||||||
|
s = s.filter(lambda d: d < cls.lt)
|
||||||
|
if cls.gt is not None:
|
||||||
|
s = s.filter(lambda d: cls.gt < d)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.ConstrainedFloat)
|
||||||
|
def resolve_confloat(cls): # type: ignore[no-untyped-def]
|
||||||
|
min_value = cls.ge
|
||||||
|
max_value = cls.le
|
||||||
|
exclude_min = False
|
||||||
|
exclude_max = False
|
||||||
|
|
||||||
|
if cls.gt is not None:
|
||||||
|
assert min_value is None, 'Set `gt` or `ge`, but not both'
|
||||||
|
min_value = cls.gt
|
||||||
|
exclude_min = True
|
||||||
|
if cls.lt is not None:
|
||||||
|
assert max_value is None, 'Set `lt` or `le`, but not both'
|
||||||
|
max_value = cls.lt
|
||||||
|
exclude_max = True
|
||||||
|
|
||||||
|
if cls.multiple_of is None:
|
||||||
|
return st.floats(min_value, max_value, exclude_min=exclude_min, exclude_max=exclude_max, allow_nan=False)
|
||||||
|
|
||||||
|
if min_value is not None:
|
||||||
|
min_value = math.ceil(min_value / cls.multiple_of)
|
||||||
|
if exclude_min:
|
||||||
|
min_value = min_value + 1
|
||||||
|
if max_value is not None:
|
||||||
|
assert max_value >= cls.multiple_of, 'Cannot build model with max value smaller than multiple of'
|
||||||
|
max_value = math.floor(max_value / cls.multiple_of)
|
||||||
|
if exclude_max:
|
||||||
|
max_value = max_value - 1
|
||||||
|
|
||||||
|
return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of)
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.ConstrainedInt)
|
||||||
|
def resolve_conint(cls): # type: ignore[no-untyped-def]
|
||||||
|
min_value = cls.ge
|
||||||
|
max_value = cls.le
|
||||||
|
if cls.gt is not None:
|
||||||
|
assert min_value is None, 'Set `gt` or `ge`, but not both'
|
||||||
|
min_value = cls.gt + 1
|
||||||
|
if cls.lt is not None:
|
||||||
|
assert max_value is None, 'Set `lt` or `le`, but not both'
|
||||||
|
max_value = cls.lt - 1
|
||||||
|
|
||||||
|
if cls.multiple_of is None or cls.multiple_of == 1:
|
||||||
|
return st.integers(min_value, max_value)
|
||||||
|
|
||||||
|
# These adjustments and the .map handle integer-valued multiples, while the
|
||||||
|
# .filter handles trickier cases as for confloat.
|
||||||
|
if min_value is not None:
|
||||||
|
min_value = math.ceil(Fraction(min_value) / Fraction(cls.multiple_of))
|
||||||
|
if max_value is not None:
|
||||||
|
max_value = math.floor(Fraction(max_value) / Fraction(cls.multiple_of))
|
||||||
|
return st.integers(min_value, max_value).map(lambda x: x * cls.multiple_of)
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.ConstrainedDate)
|
||||||
|
def resolve_condate(cls): # type: ignore[no-untyped-def]
|
||||||
|
if cls.ge is not None:
|
||||||
|
assert cls.gt is None, 'Set `gt` or `ge`, but not both'
|
||||||
|
min_value = cls.ge
|
||||||
|
elif cls.gt is not None:
|
||||||
|
min_value = cls.gt + datetime.timedelta(days=1)
|
||||||
|
else:
|
||||||
|
min_value = datetime.date.min
|
||||||
|
if cls.le is not None:
|
||||||
|
assert cls.lt is None, 'Set `lt` or `le`, but not both'
|
||||||
|
max_value = cls.le
|
||||||
|
elif cls.lt is not None:
|
||||||
|
max_value = cls.lt - datetime.timedelta(days=1)
|
||||||
|
else:
|
||||||
|
max_value = datetime.date.max
|
||||||
|
return st.dates(min_value, max_value)
|
||||||
|
|
||||||
|
|
||||||
|
@resolves(pydantic.ConstrainedStr)
|
||||||
|
def resolve_constr(cls): # type: ignore[no-untyped-def] # pragma: no cover
|
||||||
|
min_size = cls.min_length or 0
|
||||||
|
max_size = cls.max_length
|
||||||
|
|
||||||
|
if cls.regex is None and not cls.strip_whitespace:
|
||||||
|
return st.text(min_size=min_size, max_size=max_size)
|
||||||
|
|
||||||
|
if cls.regex is not None:
|
||||||
|
strategy = st.from_regex(cls.regex)
|
||||||
|
if cls.strip_whitespace:
|
||||||
|
strategy = strategy.filter(lambda s: s == s.strip())
|
||||||
|
elif cls.strip_whitespace:
|
||||||
|
repeats = '{{{},{}}}'.format(
|
||||||
|
min_size - 2 if min_size > 2 else 0,
|
||||||
|
max_size - 2 if (max_size or 0) > 2 else '',
|
||||||
|
)
|
||||||
|
if min_size >= 2:
|
||||||
|
strategy = st.from_regex(rf'\W.{repeats}\W')
|
||||||
|
elif min_size == 1:
|
||||||
|
strategy = st.from_regex(rf'\W(.{repeats}\W)?')
|
||||||
|
else:
|
||||||
|
assert min_size == 0
|
||||||
|
strategy = st.from_regex(rf'(\W(.{repeats}\W)?)?')
|
||||||
|
|
||||||
|
if min_size == 0 and max_size is None:
|
||||||
|
return strategy
|
||||||
|
elif max_size is None:
|
||||||
|
return strategy.filter(lambda s: min_size <= len(s))
|
||||||
|
return strategy.filter(lambda s: min_size <= len(s) <= max_size)
|
||||||
|
|
||||||
|
|
||||||
|
# Finally, register all previously-defined types, and patch in our new function
|
||||||
|
for typ in list(pydantic.types._DEFINED_TYPES):
|
||||||
|
_registered(typ)
|
||||||
|
pydantic.types._registered = _registered
|
||||||
|
st.register_type_strategy(pydantic.Json, resolve_json)
|
||||||
72
venv/Lib/site-packages/pydantic/v1/annotated_types.py
Normal file
72
venv/Lib/site-packages/pydantic/v1/annotated_types.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import sys
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, FrozenSet, NamedTuple, Type
|
||||||
|
|
||||||
|
from pydantic.v1.fields import Required
|
||||||
|
from pydantic.v1.main import BaseModel, create_model
|
||||||
|
from pydantic.v1.typing import is_typeddict, is_typeddict_special
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
|
if sys.version_info < (3, 11):
|
||||||
|
|
||||||
|
def is_legacy_typeddict(typeddict_cls: Type['TypedDict']) -> bool: # type: ignore[valid-type]
|
||||||
|
return is_typeddict(typeddict_cls) and type(typeddict_cls).__module__ == 'typing'
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def is_legacy_typeddict(_: Any) -> Any:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_model_from_typeddict(
|
||||||
|
# Mypy bug: `Type[TypedDict]` is resolved as `Any` https://github.com/python/mypy/issues/11030
|
||||||
|
typeddict_cls: Type['TypedDict'], # type: ignore[valid-type]
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Type['BaseModel']:
|
||||||
|
"""
|
||||||
|
Create a `BaseModel` based on the fields of a `TypedDict`.
|
||||||
|
Since `typing.TypedDict` in Python 3.8 does not store runtime information about optional keys,
|
||||||
|
we raise an error if this happens (see https://bugs.python.org/issue38834).
|
||||||
|
"""
|
||||||
|
field_definitions: Dict[str, Any]
|
||||||
|
|
||||||
|
# Best case scenario: with python 3.9+ or when `TypedDict` is imported from `typing_extensions`
|
||||||
|
if not hasattr(typeddict_cls, '__required_keys__'):
|
||||||
|
raise TypeError(
|
||||||
|
'You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` with Python < 3.9.2. '
|
||||||
|
'Without it, there is no way to differentiate required and optional fields when subclassed.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_legacy_typeddict(typeddict_cls) and any(
|
||||||
|
is_typeddict_special(t) for t in typeddict_cls.__annotations__.values()
|
||||||
|
):
|
||||||
|
raise TypeError(
|
||||||
|
'You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` with Python < 3.11. '
|
||||||
|
'Without it, there is no way to reflect Required/NotRequired keys.'
|
||||||
|
)
|
||||||
|
|
||||||
|
required_keys: FrozenSet[str] = typeddict_cls.__required_keys__ # type: ignore[attr-defined]
|
||||||
|
field_definitions = {
|
||||||
|
field_name: (field_type, Required if field_name in required_keys else None)
|
||||||
|
for field_name, field_type in typeddict_cls.__annotations__.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
return create_model(typeddict_cls.__name__, **kwargs, **field_definitions)
|
||||||
|
|
||||||
|
|
||||||
|
def create_model_from_namedtuple(namedtuple_cls: Type['NamedTuple'], **kwargs: Any) -> Type['BaseModel']:
|
||||||
|
"""
|
||||||
|
Create a `BaseModel` based on the fields of a named tuple.
|
||||||
|
A named tuple can be created with `typing.NamedTuple` and declared annotations
|
||||||
|
but also with `collections.namedtuple`, in this case we consider all fields
|
||||||
|
to have type `Any`.
|
||||||
|
"""
|
||||||
|
# With python 3.10+, `__annotations__` always exists but can be empty hence the `getattr... or...` logic
|
||||||
|
namedtuple_annotations: Dict[str, Type[Any]] = getattr(namedtuple_cls, '__annotations__', None) or {
|
||||||
|
k: Any for k in namedtuple_cls._fields
|
||||||
|
}
|
||||||
|
field_definitions: Dict[str, Any] = {
|
||||||
|
field_name: (field_type, Required) for field_name, field_type in namedtuple_annotations.items()
|
||||||
|
}
|
||||||
|
return create_model(namedtuple_cls.__name__, **kwargs, **field_definitions)
|
||||||
361
venv/Lib/site-packages/pydantic/v1/class_validators.py
Normal file
361
venv/Lib/site-packages/pydantic/v1/class_validators.py
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
import warnings
|
||||||
|
from collections import ChainMap
|
||||||
|
from functools import partial, partialmethod, wraps
|
||||||
|
from itertools import chain
|
||||||
|
from types import FunctionType
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, Union, overload
|
||||||
|
|
||||||
|
from pydantic.v1.errors import ConfigError
|
||||||
|
from pydantic.v1.typing import AnyCallable
|
||||||
|
from pydantic.v1.utils import ROOT_KEY, in_ipython
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pydantic.v1.typing import AnyClassMethod
|
||||||
|
|
||||||
|
|
||||||
|
class Validator:
|
||||||
|
__slots__ = 'func', 'pre', 'each_item', 'always', 'check_fields', 'skip_on_failure'
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
func: AnyCallable,
|
||||||
|
pre: bool = False,
|
||||||
|
each_item: bool = False,
|
||||||
|
always: bool = False,
|
||||||
|
check_fields: bool = False,
|
||||||
|
skip_on_failure: bool = False,
|
||||||
|
):
|
||||||
|
self.func = func
|
||||||
|
self.pre = pre
|
||||||
|
self.each_item = each_item
|
||||||
|
self.always = always
|
||||||
|
self.check_fields = check_fields
|
||||||
|
self.skip_on_failure = skip_on_failure
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from inspect import Signature
|
||||||
|
|
||||||
|
from pydantic.v1.config import BaseConfig
|
||||||
|
from pydantic.v1.fields import ModelField
|
||||||
|
from pydantic.v1.types import ModelOrDc
|
||||||
|
|
||||||
|
ValidatorCallable = Callable[[Optional[ModelOrDc], Any, Dict[str, Any], ModelField, Type[BaseConfig]], Any]
|
||||||
|
ValidatorsList = List[ValidatorCallable]
|
||||||
|
ValidatorListDict = Dict[str, List[Validator]]
|
||||||
|
|
||||||
|
_FUNCS: Set[str] = set()
|
||||||
|
VALIDATOR_CONFIG_KEY = '__validator_config__'
|
||||||
|
ROOT_VALIDATOR_CONFIG_KEY = '__root_validator_config__'
|
||||||
|
|
||||||
|
|
||||||
|
def validator(
|
||||||
|
*fields: str,
|
||||||
|
pre: bool = False,
|
||||||
|
each_item: bool = False,
|
||||||
|
always: bool = False,
|
||||||
|
check_fields: bool = True,
|
||||||
|
whole: Optional[bool] = None,
|
||||||
|
allow_reuse: bool = False,
|
||||||
|
) -> Callable[[AnyCallable], 'AnyClassMethod']:
|
||||||
|
"""
|
||||||
|
Decorate methods on the class indicating that they should be used to validate fields
|
||||||
|
:param fields: which field(s) the method should be called on
|
||||||
|
:param pre: whether or not this validator should be called before the standard validators (else after)
|
||||||
|
:param each_item: for complex objects (sets, lists etc.) whether to validate individual elements rather than the
|
||||||
|
whole object
|
||||||
|
:param always: whether this method and other validators should be called even if the value is missing
|
||||||
|
:param check_fields: whether to check that the fields actually exist on the model
|
||||||
|
:param allow_reuse: whether to track and raise an error if another validator refers to the decorated function
|
||||||
|
"""
|
||||||
|
if not fields:
|
||||||
|
raise ConfigError('validator with no fields specified')
|
||||||
|
elif isinstance(fields[0], FunctionType):
|
||||||
|
raise ConfigError(
|
||||||
|
"validators should be used with fields and keyword arguments, not bare. " # noqa: Q000
|
||||||
|
"E.g. usage should be `@validator('<field_name>', ...)`"
|
||||||
|
)
|
||||||
|
elif not all(isinstance(field, str) for field in fields):
|
||||||
|
raise ConfigError(
|
||||||
|
"validator fields should be passed as separate string args. " # noqa: Q000
|
||||||
|
"E.g. usage should be `@validator('<field_name_1>', '<field_name_2>', ...)`"
|
||||||
|
)
|
||||||
|
|
||||||
|
if whole is not None:
|
||||||
|
warnings.warn(
|
||||||
|
'The "whole" keyword argument is deprecated, use "each_item" (inverse meaning, default False) instead',
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
assert each_item is False, '"each_item" and "whole" conflict, remove "whole"'
|
||||||
|
each_item = not whole
|
||||||
|
|
||||||
|
def dec(f: AnyCallable) -> 'AnyClassMethod':
|
||||||
|
f_cls = _prepare_validator(f, allow_reuse)
|
||||||
|
setattr(
|
||||||
|
f_cls,
|
||||||
|
VALIDATOR_CONFIG_KEY,
|
||||||
|
(
|
||||||
|
fields,
|
||||||
|
Validator(func=f_cls.__func__, pre=pre, each_item=each_item, always=always, check_fields=check_fields),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return f_cls
|
||||||
|
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def root_validator(_func: AnyCallable) -> 'AnyClassMethod':
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def root_validator(
|
||||||
|
*, pre: bool = False, allow_reuse: bool = False, skip_on_failure: bool = False
|
||||||
|
) -> Callable[[AnyCallable], 'AnyClassMethod']:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def root_validator(
|
||||||
|
_func: Optional[AnyCallable] = None, *, pre: bool = False, allow_reuse: bool = False, skip_on_failure: bool = False
|
||||||
|
) -> Union['AnyClassMethod', Callable[[AnyCallable], 'AnyClassMethod']]:
|
||||||
|
"""
|
||||||
|
Decorate methods on a model indicating that they should be used to validate (and perhaps modify) data either
|
||||||
|
before or after standard model parsing/validation is performed.
|
||||||
|
"""
|
||||||
|
if _func:
|
||||||
|
f_cls = _prepare_validator(_func, allow_reuse)
|
||||||
|
setattr(
|
||||||
|
f_cls, ROOT_VALIDATOR_CONFIG_KEY, Validator(func=f_cls.__func__, pre=pre, skip_on_failure=skip_on_failure)
|
||||||
|
)
|
||||||
|
return f_cls
|
||||||
|
|
||||||
|
def dec(f: AnyCallable) -> 'AnyClassMethod':
|
||||||
|
f_cls = _prepare_validator(f, allow_reuse)
|
||||||
|
setattr(
|
||||||
|
f_cls, ROOT_VALIDATOR_CONFIG_KEY, Validator(func=f_cls.__func__, pre=pre, skip_on_failure=skip_on_failure)
|
||||||
|
)
|
||||||
|
return f_cls
|
||||||
|
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_validator(function: AnyCallable, allow_reuse: bool) -> 'AnyClassMethod':
|
||||||
|
"""
|
||||||
|
Avoid validators with duplicated names since without this, validators can be overwritten silently
|
||||||
|
which generally isn't the intended behaviour, don't run in ipython (see #312) or if allow_reuse is False.
|
||||||
|
"""
|
||||||
|
f_cls = function if isinstance(function, classmethod) else classmethod(function)
|
||||||
|
if not in_ipython() and not allow_reuse:
|
||||||
|
ref = (
|
||||||
|
getattr(f_cls.__func__, '__module__', '<No __module__>')
|
||||||
|
+ '.'
|
||||||
|
+ getattr(f_cls.__func__, '__qualname__', f'<No __qualname__: id:{id(f_cls.__func__)}>')
|
||||||
|
)
|
||||||
|
if ref in _FUNCS:
|
||||||
|
raise ConfigError(f'duplicate validator function "{ref}"; if this is intended, set `allow_reuse=True`')
|
||||||
|
_FUNCS.add(ref)
|
||||||
|
return f_cls
|
||||||
|
|
||||||
|
|
||||||
|
class ValidatorGroup:
|
||||||
|
def __init__(self, validators: 'ValidatorListDict') -> None:
|
||||||
|
self.validators = validators
|
||||||
|
self.used_validators = {'*'}
|
||||||
|
|
||||||
|
def get_validators(self, name: str) -> Optional[Dict[str, Validator]]:
|
||||||
|
self.used_validators.add(name)
|
||||||
|
validators = self.validators.get(name, [])
|
||||||
|
if name != ROOT_KEY:
|
||||||
|
validators += self.validators.get('*', [])
|
||||||
|
if validators:
|
||||||
|
return {getattr(v.func, '__name__', f'<No __name__: id:{id(v.func)}>'): v for v in validators}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check_for_unused(self) -> None:
|
||||||
|
unused_validators = set(
|
||||||
|
chain.from_iterable(
|
||||||
|
(
|
||||||
|
getattr(v.func, '__name__', f'<No __name__: id:{id(v.func)}>')
|
||||||
|
for v in self.validators[f]
|
||||||
|
if v.check_fields
|
||||||
|
)
|
||||||
|
for f in (self.validators.keys() - self.used_validators)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unused_validators:
|
||||||
|
fn = ', '.join(unused_validators)
|
||||||
|
raise ConfigError(
|
||||||
|
f"Validators defined with incorrect fields: {fn} " # noqa: Q000
|
||||||
|
f"(use check_fields=False if you're inheriting from the model and intended this)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_validators(namespace: Dict[str, Any]) -> Dict[str, List[Validator]]:
|
||||||
|
validators: Dict[str, List[Validator]] = {}
|
||||||
|
for var_name, value in namespace.items():
|
||||||
|
validator_config = getattr(value, VALIDATOR_CONFIG_KEY, None)
|
||||||
|
if validator_config:
|
||||||
|
fields, v = validator_config
|
||||||
|
for field in fields:
|
||||||
|
if field in validators:
|
||||||
|
validators[field].append(v)
|
||||||
|
else:
|
||||||
|
validators[field] = [v]
|
||||||
|
return validators
|
||||||
|
|
||||||
|
|
||||||
|
def extract_root_validators(namespace: Dict[str, Any]) -> Tuple[List[AnyCallable], List[Tuple[bool, AnyCallable]]]:
|
||||||
|
from inspect import signature
|
||||||
|
|
||||||
|
pre_validators: List[AnyCallable] = []
|
||||||
|
post_validators: List[Tuple[bool, AnyCallable]] = []
|
||||||
|
for name, value in namespace.items():
|
||||||
|
validator_config: Optional[Validator] = getattr(value, ROOT_VALIDATOR_CONFIG_KEY, None)
|
||||||
|
if validator_config:
|
||||||
|
sig = signature(validator_config.func)
|
||||||
|
args = list(sig.parameters.keys())
|
||||||
|
if args[0] == 'self':
|
||||||
|
raise ConfigError(
|
||||||
|
f'Invalid signature for root validator {name}: {sig}, "self" not permitted as first argument, '
|
||||||
|
f'should be: (cls, values).'
|
||||||
|
)
|
||||||
|
if len(args) != 2:
|
||||||
|
raise ConfigError(f'Invalid signature for root validator {name}: {sig}, should be: (cls, values).')
|
||||||
|
# check function signature
|
||||||
|
if validator_config.pre:
|
||||||
|
pre_validators.append(validator_config.func)
|
||||||
|
else:
|
||||||
|
post_validators.append((validator_config.skip_on_failure, validator_config.func))
|
||||||
|
return pre_validators, post_validators
|
||||||
|
|
||||||
|
|
||||||
|
def inherit_validators(base_validators: 'ValidatorListDict', validators: 'ValidatorListDict') -> 'ValidatorListDict':
|
||||||
|
for field, field_validators in base_validators.items():
|
||||||
|
if field not in validators:
|
||||||
|
validators[field] = []
|
||||||
|
validators[field] += field_validators
|
||||||
|
return validators
|
||||||
|
|
||||||
|
|
||||||
|
def make_generic_validator(validator: AnyCallable) -> 'ValidatorCallable':
|
||||||
|
"""
|
||||||
|
Make a generic function which calls a validator with the right arguments.
|
||||||
|
|
||||||
|
Unfortunately other approaches (eg. return a partial of a function that builds the arguments) is slow,
|
||||||
|
hence this laborious way of doing things.
|
||||||
|
|
||||||
|
It's done like this so validators don't all need **kwargs in their signature, eg. any combination of
|
||||||
|
the arguments "values", "fields" and/or "config" are permitted.
|
||||||
|
"""
|
||||||
|
from inspect import signature
|
||||||
|
|
||||||
|
if not isinstance(validator, (partial, partialmethod)):
|
||||||
|
# This should be the default case, so overhead is reduced
|
||||||
|
sig = signature(validator)
|
||||||
|
args = list(sig.parameters.keys())
|
||||||
|
else:
|
||||||
|
# Fix the generated argument lists of partial methods
|
||||||
|
sig = signature(validator.func)
|
||||||
|
args = [
|
||||||
|
k
|
||||||
|
for k in signature(validator.func).parameters.keys()
|
||||||
|
if k not in validator.args | validator.keywords.keys()
|
||||||
|
]
|
||||||
|
|
||||||
|
first_arg = args.pop(0)
|
||||||
|
if first_arg == 'self':
|
||||||
|
raise ConfigError(
|
||||||
|
f'Invalid signature for validator {validator}: {sig}, "self" not permitted as first argument, '
|
||||||
|
f'should be: (cls, value, values, config, field), "values", "config" and "field" are all optional.'
|
||||||
|
)
|
||||||
|
elif first_arg == 'cls':
|
||||||
|
# assume the second argument is value
|
||||||
|
return wraps(validator)(_generic_validator_cls(validator, sig, set(args[1:])))
|
||||||
|
else:
|
||||||
|
# assume the first argument was value which has already been removed
|
||||||
|
return wraps(validator)(_generic_validator_basic(validator, sig, set(args)))
|
||||||
|
|
||||||
|
|
||||||
|
def prep_validators(v_funcs: Iterable[AnyCallable]) -> 'ValidatorsList':
|
||||||
|
return [make_generic_validator(f) for f in v_funcs if f]
|
||||||
|
|
||||||
|
|
||||||
|
all_kwargs = {'values', 'field', 'config'}
|
||||||
|
|
||||||
|
|
||||||
|
def _generic_validator_cls(validator: AnyCallable, sig: 'Signature', args: Set[str]) -> 'ValidatorCallable':
|
||||||
|
# assume the first argument is value
|
||||||
|
has_kwargs = False
|
||||||
|
if 'kwargs' in args:
|
||||||
|
has_kwargs = True
|
||||||
|
args -= {'kwargs'}
|
||||||
|
|
||||||
|
if not args.issubset(all_kwargs):
|
||||||
|
raise ConfigError(
|
||||||
|
f'Invalid signature for validator {validator}: {sig}, should be: '
|
||||||
|
f'(cls, value, values, config, field), "values", "config" and "field" are all optional.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if has_kwargs:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, values=values, field=field, config=config)
|
||||||
|
elif args == set():
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v)
|
||||||
|
elif args == {'values'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, values=values)
|
||||||
|
elif args == {'field'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, field=field)
|
||||||
|
elif args == {'config'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, config=config)
|
||||||
|
elif args == {'values', 'field'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, values=values, field=field)
|
||||||
|
elif args == {'values', 'config'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, values=values, config=config)
|
||||||
|
elif args == {'field', 'config'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, field=field, config=config)
|
||||||
|
else:
|
||||||
|
# args == {'values', 'field', 'config'}
|
||||||
|
return lambda cls, v, values, field, config: validator(cls, v, values=values, field=field, config=config)
|
||||||
|
|
||||||
|
|
||||||
|
def _generic_validator_basic(validator: AnyCallable, sig: 'Signature', args: Set[str]) -> 'ValidatorCallable':
|
||||||
|
has_kwargs = False
|
||||||
|
if 'kwargs' in args:
|
||||||
|
has_kwargs = True
|
||||||
|
args -= {'kwargs'}
|
||||||
|
|
||||||
|
if not args.issubset(all_kwargs):
|
||||||
|
raise ConfigError(
|
||||||
|
f'Invalid signature for validator {validator}: {sig}, should be: '
|
||||||
|
f'(value, values, config, field), "values", "config" and "field" are all optional.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if has_kwargs:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, values=values, field=field, config=config)
|
||||||
|
elif args == set():
|
||||||
|
return lambda cls, v, values, field, config: validator(v)
|
||||||
|
elif args == {'values'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, values=values)
|
||||||
|
elif args == {'field'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, field=field)
|
||||||
|
elif args == {'config'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, config=config)
|
||||||
|
elif args == {'values', 'field'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, values=values, field=field)
|
||||||
|
elif args == {'values', 'config'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, values=values, config=config)
|
||||||
|
elif args == {'field', 'config'}:
|
||||||
|
return lambda cls, v, values, field, config: validator(v, field=field, config=config)
|
||||||
|
else:
|
||||||
|
# args == {'values', 'field', 'config'}
|
||||||
|
return lambda cls, v, values, field, config: validator(v, values=values, field=field, config=config)
|
||||||
|
|
||||||
|
|
||||||
|
def gather_all_validators(type_: 'ModelOrDc') -> Dict[str, 'AnyClassMethod']:
|
||||||
|
all_attributes = ChainMap(*[cls.__dict__ for cls in type_.__mro__]) # type: ignore[arg-type,var-annotated]
|
||||||
|
return {
|
||||||
|
k: v
|
||||||
|
for k, v in all_attributes.items()
|
||||||
|
if hasattr(v, VALIDATOR_CONFIG_KEY) or hasattr(v, ROOT_VALIDATOR_CONFIG_KEY)
|
||||||
|
}
|
||||||
494
venv/Lib/site-packages/pydantic/v1/color.py
Normal file
494
venv/Lib/site-packages/pydantic/v1/color.py
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
"""
|
||||||
|
Color definitions are used as per CSS3 specification:
|
||||||
|
http://www.w3.org/TR/css3-color/#svg-color
|
||||||
|
|
||||||
|
A few colors have multiple names referring to the sames colors, eg. `grey` and `gray` or `aqua` and `cyan`.
|
||||||
|
|
||||||
|
In these cases the LAST color when sorted alphabetically takes preferences,
|
||||||
|
eg. Color((0, 255, 255)).as_named() == 'cyan' because "cyan" comes after "aqua".
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
from colorsys import hls_to_rgb, rgb_to_hls
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union, cast
|
||||||
|
|
||||||
|
from pydantic.v1.errors import ColorError
|
||||||
|
from pydantic.v1.utils import Representation, almost_equal_floats
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pydantic.v1.typing import CallableGenerator, ReprArgs
|
||||||
|
|
||||||
|
ColorTuple = Union[Tuple[int, int, int], Tuple[int, int, int, float]]
|
||||||
|
ColorType = Union[ColorTuple, str]
|
||||||
|
HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, float]]
|
||||||
|
|
||||||
|
|
||||||
|
class RGBA:
|
||||||
|
"""
|
||||||
|
Internal use only as a representation of a color.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = 'r', 'g', 'b', 'alpha', '_tuple'
|
||||||
|
|
||||||
|
def __init__(self, r: float, g: float, b: float, alpha: Optional[float]):
|
||||||
|
self.r = r
|
||||||
|
self.g = g
|
||||||
|
self.b = b
|
||||||
|
self.alpha = alpha
|
||||||
|
|
||||||
|
self._tuple: Tuple[float, float, float, Optional[float]] = (r, g, b, alpha)
|
||||||
|
|
||||||
|
def __getitem__(self, item: Any) -> Any:
|
||||||
|
return self._tuple[item]
|
||||||
|
|
||||||
|
|
||||||
|
# these are not compiled here to avoid import slowdown, they'll be compiled the first time they're used, then cached
|
||||||
|
r_hex_short = r'\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*'
|
||||||
|
r_hex_long = r'\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*'
|
||||||
|
_r_255 = r'(\d{1,3}(?:\.\d+)?)'
|
||||||
|
_r_comma = r'\s*,\s*'
|
||||||
|
r_rgb = fr'\s*rgb\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}\)\s*'
|
||||||
|
_r_alpha = r'(\d(?:\.\d+)?|\.\d+|\d{1,2}%)'
|
||||||
|
r_rgba = fr'\s*rgba\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_alpha}\s*\)\s*'
|
||||||
|
_r_h = r'(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?'
|
||||||
|
_r_sl = r'(\d{1,3}(?:\.\d+)?)%'
|
||||||
|
r_hsl = fr'\s*hsl\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}\s*\)\s*'
|
||||||
|
r_hsla = fr'\s*hsl\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}{_r_comma}{_r_alpha}\s*\)\s*'
|
||||||
|
|
||||||
|
# colors where the two hex characters are the same, if all colors match this the short version of hex colors can be used
|
||||||
|
repeat_colors = {int(c * 2, 16) for c in '0123456789abcdef'}
|
||||||
|
rads = 2 * math.pi
|
||||||
|
|
||||||
|
|
||||||
|
class Color(Representation):
|
||||||
|
__slots__ = '_original', '_rgba'
|
||||||
|
|
||||||
|
def __init__(self, value: ColorType) -> None:
|
||||||
|
self._rgba: RGBA
|
||||||
|
self._original: ColorType
|
||||||
|
if isinstance(value, (tuple, list)):
|
||||||
|
self._rgba = parse_tuple(value)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
self._rgba = parse_str(value)
|
||||||
|
elif isinstance(value, Color):
|
||||||
|
self._rgba = value._rgba
|
||||||
|
value = value._original
|
||||||
|
else:
|
||||||
|
raise ColorError(reason='value must be a tuple, list or string')
|
||||||
|
|
||||||
|
# if we've got here value must be a valid color
|
||||||
|
self._original = value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
|
||||||
|
field_schema.update(type='string', format='color')
|
||||||
|
|
||||||
|
def original(self) -> ColorType:
|
||||||
|
"""
|
||||||
|
Original value passed to Color
|
||||||
|
"""
|
||||||
|
return self._original
|
||||||
|
|
||||||
|
def as_named(self, *, fallback: bool = False) -> str:
|
||||||
|
if self._rgba.alpha is None:
|
||||||
|
rgb = cast(Tuple[int, int, int], self.as_rgb_tuple())
|
||||||
|
try:
|
||||||
|
return COLORS_BY_VALUE[rgb]
|
||||||
|
except KeyError as e:
|
||||||
|
if fallback:
|
||||||
|
return self.as_hex()
|
||||||
|
else:
|
||||||
|
raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e
|
||||||
|
else:
|
||||||
|
return self.as_hex()
|
||||||
|
|
||||||
|
def as_hex(self) -> str:
|
||||||
|
"""
|
||||||
|
Hex string representing the color can be 3, 4, 6 or 8 characters depending on whether the string
|
||||||
|
a "short" representation of the color is possible and whether there's an alpha channel.
|
||||||
|
"""
|
||||||
|
values = [float_to_255(c) for c in self._rgba[:3]]
|
||||||
|
if self._rgba.alpha is not None:
|
||||||
|
values.append(float_to_255(self._rgba.alpha))
|
||||||
|
|
||||||
|
as_hex = ''.join(f'{v:02x}' for v in values)
|
||||||
|
if all(c in repeat_colors for c in values):
|
||||||
|
as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2))
|
||||||
|
return '#' + as_hex
|
||||||
|
|
||||||
|
def as_rgb(self) -> str:
|
||||||
|
"""
|
||||||
|
Color as an rgb(<r>, <g>, <b>) or rgba(<r>, <g>, <b>, <a>) string.
|
||||||
|
"""
|
||||||
|
if self._rgba.alpha is None:
|
||||||
|
return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})'
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
f'rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, '
|
||||||
|
f'{round(self._alpha_float(), 2)})'
|
||||||
|
)
|
||||||
|
|
||||||
|
def as_rgb_tuple(self, *, alpha: Optional[bool] = None) -> ColorTuple:
|
||||||
|
"""
|
||||||
|
Color as an RGB or RGBA tuple; red, green and blue are in the range 0 to 255, alpha if included is
|
||||||
|
in the range 0 to 1.
|
||||||
|
|
||||||
|
:param alpha: whether to include the alpha channel, options are
|
||||||
|
None - (default) include alpha only if it's set (e.g. not None)
|
||||||
|
True - always include alpha,
|
||||||
|
False - always omit alpha,
|
||||||
|
"""
|
||||||
|
r, g, b = (float_to_255(c) for c in self._rgba[:3])
|
||||||
|
if alpha is None:
|
||||||
|
if self._rgba.alpha is None:
|
||||||
|
return r, g, b
|
||||||
|
else:
|
||||||
|
return r, g, b, self._alpha_float()
|
||||||
|
elif alpha:
|
||||||
|
return r, g, b, self._alpha_float()
|
||||||
|
else:
|
||||||
|
# alpha is False
|
||||||
|
return r, g, b
|
||||||
|
|
||||||
|
def as_hsl(self) -> str:
|
||||||
|
"""
|
||||||
|
Color as an hsl(<h>, <s>, <l>) or hsl(<h>, <s>, <l>, <a>) string.
|
||||||
|
"""
|
||||||
|
if self._rgba.alpha is None:
|
||||||
|
h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore
|
||||||
|
return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})'
|
||||||
|
else:
|
||||||
|
h, s, li, a = self.as_hsl_tuple(alpha=True) # type: ignore
|
||||||
|
return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})'
|
||||||
|
|
||||||
|
def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple:
|
||||||
|
"""
|
||||||
|
Color as an HSL or HSLA tuple, e.g. hue, saturation, lightness and optionally alpha; all elements are in
|
||||||
|
the range 0 to 1.
|
||||||
|
|
||||||
|
NOTE: this is HSL as used in HTML and most other places, not HLS as used in python's colorsys.
|
||||||
|
|
||||||
|
:param alpha: whether to include the alpha channel, options are
|
||||||
|
None - (default) include alpha only if it's set (e.g. not None)
|
||||||
|
True - always include alpha,
|
||||||
|
False - always omit alpha,
|
||||||
|
"""
|
||||||
|
h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b)
|
||||||
|
if alpha is None:
|
||||||
|
if self._rgba.alpha is None:
|
||||||
|
return h, s, l
|
||||||
|
else:
|
||||||
|
return h, s, l, self._alpha_float()
|
||||||
|
if alpha:
|
||||||
|
return h, s, l, self._alpha_float()
|
||||||
|
else:
|
||||||
|
# alpha is False
|
||||||
|
return h, s, l
|
||||||
|
|
||||||
|
def _alpha_float(self) -> float:
|
||||||
|
return 1 if self._rgba.alpha is None else self._rgba.alpha
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __get_validators__(cls) -> 'CallableGenerator':
|
||||||
|
yield cls
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.as_named(fallback=True)
|
||||||
|
|
||||||
|
def __repr_args__(self) -> 'ReprArgs':
|
||||||
|
return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] # type: ignore
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple()
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.as_rgb_tuple())
|
||||||
|
|
||||||
|
|
||||||
|
def parse_tuple(value: Tuple[Any, ...]) -> RGBA:
|
||||||
|
"""
|
||||||
|
Parse a tuple or list as a color.
|
||||||
|
"""
|
||||||
|
if len(value) == 3:
|
||||||
|
r, g, b = (parse_color_value(v) for v in value)
|
||||||
|
return RGBA(r, g, b, None)
|
||||||
|
elif len(value) == 4:
|
||||||
|
r, g, b = (parse_color_value(v) for v in value[:3])
|
||||||
|
return RGBA(r, g, b, parse_float_alpha(value[3]))
|
||||||
|
else:
|
||||||
|
raise ColorError(reason='tuples must have length 3 or 4')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_str(value: str) -> RGBA:
|
||||||
|
"""
|
||||||
|
Parse a string to an RGBA tuple, trying the following formats (in this order):
|
||||||
|
* named color, see COLORS_BY_NAME below
|
||||||
|
* hex short eg. `<prefix>fff` (prefix can be `#`, `0x` or nothing)
|
||||||
|
* hex long eg. `<prefix>ffffff` (prefix can be `#`, `0x` or nothing)
|
||||||
|
* `rgb(<r>, <g>, <b>) `
|
||||||
|
* `rgba(<r>, <g>, <b>, <a>)`
|
||||||
|
"""
|
||||||
|
value_lower = value.lower()
|
||||||
|
try:
|
||||||
|
r, g, b = COLORS_BY_NAME[value_lower]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ints_to_rgba(r, g, b, None)
|
||||||
|
|
||||||
|
m = re.fullmatch(r_hex_short, value_lower)
|
||||||
|
if m:
|
||||||
|
*rgb, a = m.groups()
|
||||||
|
r, g, b = (int(v * 2, 16) for v in rgb)
|
||||||
|
if a:
|
||||||
|
alpha: Optional[float] = int(a * 2, 16) / 255
|
||||||
|
else:
|
||||||
|
alpha = None
|
||||||
|
return ints_to_rgba(r, g, b, alpha)
|
||||||
|
|
||||||
|
m = re.fullmatch(r_hex_long, value_lower)
|
||||||
|
if m:
|
||||||
|
*rgb, a = m.groups()
|
||||||
|
r, g, b = (int(v, 16) for v in rgb)
|
||||||
|
if a:
|
||||||
|
alpha = int(a, 16) / 255
|
||||||
|
else:
|
||||||
|
alpha = None
|
||||||
|
return ints_to_rgba(r, g, b, alpha)
|
||||||
|
|
||||||
|
m = re.fullmatch(r_rgb, value_lower)
|
||||||
|
if m:
|
||||||
|
return ints_to_rgba(*m.groups(), None) # type: ignore
|
||||||
|
|
||||||
|
m = re.fullmatch(r_rgba, value_lower)
|
||||||
|
if m:
|
||||||
|
return ints_to_rgba(*m.groups()) # type: ignore
|
||||||
|
|
||||||
|
m = re.fullmatch(r_hsl, value_lower)
|
||||||
|
if m:
|
||||||
|
h, h_units, s, l_ = m.groups()
|
||||||
|
return parse_hsl(h, h_units, s, l_)
|
||||||
|
|
||||||
|
m = re.fullmatch(r_hsla, value_lower)
|
||||||
|
if m:
|
||||||
|
h, h_units, s, l_, a = m.groups()
|
||||||
|
return parse_hsl(h, h_units, s, l_, parse_float_alpha(a))
|
||||||
|
|
||||||
|
raise ColorError(reason='string not recognised as a valid color')
|
||||||
|
|
||||||
|
|
||||||
|
def ints_to_rgba(r: Union[int, str], g: Union[int, str], b: Union[int, str], alpha: Optional[float]) -> RGBA:
|
||||||
|
return RGBA(parse_color_value(r), parse_color_value(g), parse_color_value(b), parse_float_alpha(alpha))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_color_value(value: Union[int, str], max_val: int = 255) -> float:
|
||||||
|
"""
|
||||||
|
Parse a value checking it's a valid int in the range 0 to max_val and divide by max_val to give a number
|
||||||
|
in the range 0 to 1
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
color = float(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ColorError(reason='color values must be a valid number')
|
||||||
|
if 0 <= color <= max_val:
|
||||||
|
return color / max_val
|
||||||
|
else:
|
||||||
|
raise ColorError(reason=f'color values must be in the range 0 to {max_val}')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_float_alpha(value: Union[None, str, float, int]) -> Optional[float]:
|
||||||
|
"""
|
||||||
|
Parse a value checking it's a valid float in the range 0 to 1
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if isinstance(value, str) and value.endswith('%'):
|
||||||
|
alpha = float(value[:-1]) / 100
|
||||||
|
else:
|
||||||
|
alpha = float(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ColorError(reason='alpha values must be a valid float')
|
||||||
|
|
||||||
|
if almost_equal_floats(alpha, 1):
|
||||||
|
return None
|
||||||
|
elif 0 <= alpha <= 1:
|
||||||
|
return alpha
|
||||||
|
else:
|
||||||
|
raise ColorError(reason='alpha values must be in the range 0 to 1')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: Optional[float] = None) -> RGBA:
|
||||||
|
"""
|
||||||
|
Parse raw hue, saturation, lightness and alpha values and convert to RGBA.
|
||||||
|
"""
|
||||||
|
s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100)
|
||||||
|
|
||||||
|
h_value = float(h)
|
||||||
|
if h_units in {None, 'deg'}:
|
||||||
|
h_value = h_value % 360 / 360
|
||||||
|
elif h_units == 'rad':
|
||||||
|
h_value = h_value % rads / rads
|
||||||
|
else:
|
||||||
|
# turns
|
||||||
|
h_value = h_value % 1
|
||||||
|
|
||||||
|
r, g, b = hls_to_rgb(h_value, l_value, s_value)
|
||||||
|
return RGBA(r, g, b, alpha)
|
||||||
|
|
||||||
|
|
||||||
|
def float_to_255(c: float) -> int:
|
||||||
|
return int(round(c * 255))
|
||||||
|
|
||||||
|
|
||||||
|
COLORS_BY_NAME = {
|
||||||
|
'aliceblue': (240, 248, 255),
|
||||||
|
'antiquewhite': (250, 235, 215),
|
||||||
|
'aqua': (0, 255, 255),
|
||||||
|
'aquamarine': (127, 255, 212),
|
||||||
|
'azure': (240, 255, 255),
|
||||||
|
'beige': (245, 245, 220),
|
||||||
|
'bisque': (255, 228, 196),
|
||||||
|
'black': (0, 0, 0),
|
||||||
|
'blanchedalmond': (255, 235, 205),
|
||||||
|
'blue': (0, 0, 255),
|
||||||
|
'blueviolet': (138, 43, 226),
|
||||||
|
'brown': (165, 42, 42),
|
||||||
|
'burlywood': (222, 184, 135),
|
||||||
|
'cadetblue': (95, 158, 160),
|
||||||
|
'chartreuse': (127, 255, 0),
|
||||||
|
'chocolate': (210, 105, 30),
|
||||||
|
'coral': (255, 127, 80),
|
||||||
|
'cornflowerblue': (100, 149, 237),
|
||||||
|
'cornsilk': (255, 248, 220),
|
||||||
|
'crimson': (220, 20, 60),
|
||||||
|
'cyan': (0, 255, 255),
|
||||||
|
'darkblue': (0, 0, 139),
|
||||||
|
'darkcyan': (0, 139, 139),
|
||||||
|
'darkgoldenrod': (184, 134, 11),
|
||||||
|
'darkgray': (169, 169, 169),
|
||||||
|
'darkgreen': (0, 100, 0),
|
||||||
|
'darkgrey': (169, 169, 169),
|
||||||
|
'darkkhaki': (189, 183, 107),
|
||||||
|
'darkmagenta': (139, 0, 139),
|
||||||
|
'darkolivegreen': (85, 107, 47),
|
||||||
|
'darkorange': (255, 140, 0),
|
||||||
|
'darkorchid': (153, 50, 204),
|
||||||
|
'darkred': (139, 0, 0),
|
||||||
|
'darksalmon': (233, 150, 122),
|
||||||
|
'darkseagreen': (143, 188, 143),
|
||||||
|
'darkslateblue': (72, 61, 139),
|
||||||
|
'darkslategray': (47, 79, 79),
|
||||||
|
'darkslategrey': (47, 79, 79),
|
||||||
|
'darkturquoise': (0, 206, 209),
|
||||||
|
'darkviolet': (148, 0, 211),
|
||||||
|
'deeppink': (255, 20, 147),
|
||||||
|
'deepskyblue': (0, 191, 255),
|
||||||
|
'dimgray': (105, 105, 105),
|
||||||
|
'dimgrey': (105, 105, 105),
|
||||||
|
'dodgerblue': (30, 144, 255),
|
||||||
|
'firebrick': (178, 34, 34),
|
||||||
|
'floralwhite': (255, 250, 240),
|
||||||
|
'forestgreen': (34, 139, 34),
|
||||||
|
'fuchsia': (255, 0, 255),
|
||||||
|
'gainsboro': (220, 220, 220),
|
||||||
|
'ghostwhite': (248, 248, 255),
|
||||||
|
'gold': (255, 215, 0),
|
||||||
|
'goldenrod': (218, 165, 32),
|
||||||
|
'gray': (128, 128, 128),
|
||||||
|
'green': (0, 128, 0),
|
||||||
|
'greenyellow': (173, 255, 47),
|
||||||
|
'grey': (128, 128, 128),
|
||||||
|
'honeydew': (240, 255, 240),
|
||||||
|
'hotpink': (255, 105, 180),
|
||||||
|
'indianred': (205, 92, 92),
|
||||||
|
'indigo': (75, 0, 130),
|
||||||
|
'ivory': (255, 255, 240),
|
||||||
|
'khaki': (240, 230, 140),
|
||||||
|
'lavender': (230, 230, 250),
|
||||||
|
'lavenderblush': (255, 240, 245),
|
||||||
|
'lawngreen': (124, 252, 0),
|
||||||
|
'lemonchiffon': (255, 250, 205),
|
||||||
|
'lightblue': (173, 216, 230),
|
||||||
|
'lightcoral': (240, 128, 128),
|
||||||
|
'lightcyan': (224, 255, 255),
|
||||||
|
'lightgoldenrodyellow': (250, 250, 210),
|
||||||
|
'lightgray': (211, 211, 211),
|
||||||
|
'lightgreen': (144, 238, 144),
|
||||||
|
'lightgrey': (211, 211, 211),
|
||||||
|
'lightpink': (255, 182, 193),
|
||||||
|
'lightsalmon': (255, 160, 122),
|
||||||
|
'lightseagreen': (32, 178, 170),
|
||||||
|
'lightskyblue': (135, 206, 250),
|
||||||
|
'lightslategray': (119, 136, 153),
|
||||||
|
'lightslategrey': (119, 136, 153),
|
||||||
|
'lightsteelblue': (176, 196, 222),
|
||||||
|
'lightyellow': (255, 255, 224),
|
||||||
|
'lime': (0, 255, 0),
|
||||||
|
'limegreen': (50, 205, 50),
|
||||||
|
'linen': (250, 240, 230),
|
||||||
|
'magenta': (255, 0, 255),
|
||||||
|
'maroon': (128, 0, 0),
|
||||||
|
'mediumaquamarine': (102, 205, 170),
|
||||||
|
'mediumblue': (0, 0, 205),
|
||||||
|
'mediumorchid': (186, 85, 211),
|
||||||
|
'mediumpurple': (147, 112, 219),
|
||||||
|
'mediumseagreen': (60, 179, 113),
|
||||||
|
'mediumslateblue': (123, 104, 238),
|
||||||
|
'mediumspringgreen': (0, 250, 154),
|
||||||
|
'mediumturquoise': (72, 209, 204),
|
||||||
|
'mediumvioletred': (199, 21, 133),
|
||||||
|
'midnightblue': (25, 25, 112),
|
||||||
|
'mintcream': (245, 255, 250),
|
||||||
|
'mistyrose': (255, 228, 225),
|
||||||
|
'moccasin': (255, 228, 181),
|
||||||
|
'navajowhite': (255, 222, 173),
|
||||||
|
'navy': (0, 0, 128),
|
||||||
|
'oldlace': (253, 245, 230),
|
||||||
|
'olive': (128, 128, 0),
|
||||||
|
'olivedrab': (107, 142, 35),
|
||||||
|
'orange': (255, 165, 0),
|
||||||
|
'orangered': (255, 69, 0),
|
||||||
|
'orchid': (218, 112, 214),
|
||||||
|
'palegoldenrod': (238, 232, 170),
|
||||||
|
'palegreen': (152, 251, 152),
|
||||||
|
'paleturquoise': (175, 238, 238),
|
||||||
|
'palevioletred': (219, 112, 147),
|
||||||
|
'papayawhip': (255, 239, 213),
|
||||||
|
'peachpuff': (255, 218, 185),
|
||||||
|
'peru': (205, 133, 63),
|
||||||
|
'pink': (255, 192, 203),
|
||||||
|
'plum': (221, 160, 221),
|
||||||
|
'powderblue': (176, 224, 230),
|
||||||
|
'purple': (128, 0, 128),
|
||||||
|
'red': (255, 0, 0),
|
||||||
|
'rosybrown': (188, 143, 143),
|
||||||
|
'royalblue': (65, 105, 225),
|
||||||
|
'saddlebrown': (139, 69, 19),
|
||||||
|
'salmon': (250, 128, 114),
|
||||||
|
'sandybrown': (244, 164, 96),
|
||||||
|
'seagreen': (46, 139, 87),
|
||||||
|
'seashell': (255, 245, 238),
|
||||||
|
'sienna': (160, 82, 45),
|
||||||
|
'silver': (192, 192, 192),
|
||||||
|
'skyblue': (135, 206, 235),
|
||||||
|
'slateblue': (106, 90, 205),
|
||||||
|
'slategray': (112, 128, 144),
|
||||||
|
'slategrey': (112, 128, 144),
|
||||||
|
'snow': (255, 250, 250),
|
||||||
|
'springgreen': (0, 255, 127),
|
||||||
|
'steelblue': (70, 130, 180),
|
||||||
|
'tan': (210, 180, 140),
|
||||||
|
'teal': (0, 128, 128),
|
||||||
|
'thistle': (216, 191, 216),
|
||||||
|
'tomato': (255, 99, 71),
|
||||||
|
'turquoise': (64, 224, 208),
|
||||||
|
'violet': (238, 130, 238),
|
||||||
|
'wheat': (245, 222, 179),
|
||||||
|
'white': (255, 255, 255),
|
||||||
|
'whitesmoke': (245, 245, 245),
|
||||||
|
'yellow': (255, 255, 0),
|
||||||
|
'yellowgreen': (154, 205, 50),
|
||||||
|
}
|
||||||
|
|
||||||
|
COLORS_BY_VALUE = {v: k for k, v in COLORS_BY_NAME.items()}
|
||||||
Reference in New Issue
Block a user