扫码登录,获取cookies
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# This file is part of Hypothesis, which may be found at
|
||||
# https://github.com/HypothesisWorks/hypothesis/
|
||||
#
|
||||
# Copyright the Hypothesis Authors.
|
||||
# Individual contributors are listed in AUTHORS.rst and the git log.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
# obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
from hypothesis.extra.django._fields import from_field, register_field_strategy
|
||||
from hypothesis.extra.django._impl import (
|
||||
LiveServerTestCase,
|
||||
StaticLiveServerTestCase,
|
||||
TestCase,
|
||||
TransactionTestCase,
|
||||
from_form,
|
||||
from_model,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"LiveServerTestCase",
|
||||
"StaticLiveServerTestCase",
|
||||
"TestCase",
|
||||
"TransactionTestCase",
|
||||
"from_field",
|
||||
"from_model",
|
||||
"register_field_strategy",
|
||||
"from_form",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,343 @@
|
||||
# This file is part of Hypothesis, which may be found at
|
||||
# https://github.com/HypothesisWorks/hypothesis/
|
||||
#
|
||||
# Copyright the Hypothesis Authors.
|
||||
# Individual contributors are listed in AUTHORS.rst and the git log.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
# obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import re
|
||||
import string
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from functools import lru_cache
|
||||
from typing import Any, Callable, Dict, Type, TypeVar, Union
|
||||
|
||||
import django
|
||||
from django import forms as df
|
||||
from django.contrib.auth.forms import UsernameField
|
||||
from django.core.validators import (
|
||||
validate_ipv4_address,
|
||||
validate_ipv6_address,
|
||||
validate_ipv46_address,
|
||||
)
|
||||
from django.db import models as dm
|
||||
|
||||
from hypothesis import strategies as st
|
||||
from hypothesis.errors import InvalidArgument, ResolutionFailed
|
||||
from hypothesis.internal.validation import check_type
|
||||
from hypothesis.provisional import urls
|
||||
from hypothesis.strategies import emails
|
||||
|
||||
AnyField = Union[dm.Field, df.Field]
|
||||
F = TypeVar("F", bound=AnyField)
|
||||
|
||||
|
||||
def numeric_bounds_from_validators(
|
||||
field, min_value=float("-inf"), max_value=float("inf")
|
||||
):
|
||||
for v in field.validators:
|
||||
if isinstance(v, django.core.validators.MinValueValidator):
|
||||
min_value = max(min_value, v.limit_value)
|
||||
elif isinstance(v, django.core.validators.MaxValueValidator):
|
||||
max_value = min(max_value, v.limit_value)
|
||||
return min_value, max_value
|
||||
|
||||
|
||||
def integers_for_field(min_value, max_value):
|
||||
def inner(field):
|
||||
return st.integers(*numeric_bounds_from_validators(field, min_value, max_value))
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@lru_cache
|
||||
def timezones():
|
||||
# From Django 4.0, the default is to use zoneinfo instead of pytz.
|
||||
assert getattr(django.conf.settings, "USE_TZ", False)
|
||||
if getattr(django.conf.settings, "USE_DEPRECATED_PYTZ", True):
|
||||
from hypothesis.extra.pytz import timezones
|
||||
else:
|
||||
from hypothesis.strategies import timezones
|
||||
|
||||
return timezones()
|
||||
|
||||
|
||||
# Mapping of field types, to strategy objects or functions of (type) -> strategy
|
||||
_FieldLookUpType = Dict[
|
||||
Type[AnyField],
|
||||
Union[st.SearchStrategy, Callable[[Any], st.SearchStrategy]],
|
||||
]
|
||||
_global_field_lookup: _FieldLookUpType = {
|
||||
dm.SmallIntegerField: integers_for_field(-32768, 32767),
|
||||
dm.IntegerField: integers_for_field(-2147483648, 2147483647),
|
||||
dm.BigIntegerField: integers_for_field(-9223372036854775808, 9223372036854775807),
|
||||
dm.PositiveIntegerField: integers_for_field(0, 2147483647),
|
||||
dm.PositiveSmallIntegerField: integers_for_field(0, 32767),
|
||||
dm.BooleanField: st.booleans(),
|
||||
dm.DateField: st.dates(),
|
||||
dm.EmailField: emails(),
|
||||
dm.FloatField: st.floats(),
|
||||
dm.NullBooleanField: st.one_of(st.none(), st.booleans()),
|
||||
dm.URLField: urls(),
|
||||
dm.UUIDField: st.uuids(),
|
||||
df.DateField: st.dates(),
|
||||
df.DurationField: st.timedeltas(),
|
||||
df.EmailField: emails(),
|
||||
df.FloatField: lambda field: st.floats(
|
||||
*numeric_bounds_from_validators(field), allow_nan=False, allow_infinity=False
|
||||
),
|
||||
df.IntegerField: integers_for_field(-2147483648, 2147483647),
|
||||
df.NullBooleanField: st.one_of(st.none(), st.booleans()),
|
||||
df.URLField: urls(),
|
||||
df.UUIDField: st.uuids(),
|
||||
}
|
||||
|
||||
_ipv6_strings = st.one_of(
|
||||
st.ip_addresses(v=6).map(str),
|
||||
st.ip_addresses(v=6).map(lambda addr: addr.exploded),
|
||||
)
|
||||
|
||||
|
||||
def register_for(field_type):
|
||||
def inner(func):
|
||||
_global_field_lookup[field_type] = func
|
||||
return func
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@register_for(dm.DateTimeField)
|
||||
@register_for(df.DateTimeField)
|
||||
def _for_datetime(field):
|
||||
if getattr(django.conf.settings, "USE_TZ", False):
|
||||
return st.datetimes(timezones=timezones())
|
||||
return st.datetimes()
|
||||
|
||||
|
||||
def using_sqlite():
|
||||
try:
|
||||
return (
|
||||
getattr(django.conf.settings, "DATABASES", {})
|
||||
.get("default", {})
|
||||
.get("ENGINE", "")
|
||||
.endswith(".sqlite3")
|
||||
)
|
||||
except django.core.exceptions.ImproperlyConfigured:
|
||||
return None
|
||||
|
||||
|
||||
@register_for(dm.TimeField)
|
||||
def _for_model_time(field):
|
||||
# SQLITE supports TZ-aware datetimes, but not TZ-aware times.
|
||||
if getattr(django.conf.settings, "USE_TZ", False) and not using_sqlite():
|
||||
return st.times(timezones=timezones())
|
||||
return st.times()
|
||||
|
||||
|
||||
@register_for(df.TimeField)
|
||||
def _for_form_time(field):
|
||||
if getattr(django.conf.settings, "USE_TZ", False):
|
||||
return st.times(timezones=timezones())
|
||||
return st.times()
|
||||
|
||||
|
||||
@register_for(dm.DurationField)
|
||||
def _for_duration(field):
|
||||
# SQLite stores timedeltas as six bytes of microseconds
|
||||
if using_sqlite():
|
||||
delta = timedelta(microseconds=2**47 - 1)
|
||||
return st.timedeltas(-delta, delta)
|
||||
return st.timedeltas()
|
||||
|
||||
|
||||
@register_for(dm.SlugField)
|
||||
@register_for(df.SlugField)
|
||||
def _for_slug(field):
|
||||
min_size = 1
|
||||
if getattr(field, "blank", False) or not getattr(field, "required", True):
|
||||
min_size = 0
|
||||
return st.text(
|
||||
alphabet=string.ascii_letters + string.digits,
|
||||
min_size=min_size,
|
||||
max_size=field.max_length,
|
||||
)
|
||||
|
||||
|
||||
@register_for(dm.GenericIPAddressField)
|
||||
def _for_model_ip(field):
|
||||
return {
|
||||
"ipv4": st.ip_addresses(v=4).map(str),
|
||||
"ipv6": _ipv6_strings,
|
||||
"both": st.ip_addresses(v=4).map(str) | _ipv6_strings,
|
||||
}[field.protocol.lower()]
|
||||
|
||||
|
||||
@register_for(df.GenericIPAddressField)
|
||||
def _for_form_ip(field):
|
||||
# the IP address form fields have no direct indication of which type
|
||||
# of address they want, so direct comparison with the validator
|
||||
# function has to be used instead. Sorry for the potato logic here
|
||||
if validate_ipv46_address in field.default_validators:
|
||||
return st.ip_addresses(v=4).map(str) | _ipv6_strings
|
||||
if validate_ipv4_address in field.default_validators:
|
||||
return st.ip_addresses(v=4).map(str)
|
||||
if validate_ipv6_address in field.default_validators:
|
||||
return _ipv6_strings
|
||||
raise ResolutionFailed(f"No IP version validator on {field=}")
|
||||
|
||||
|
||||
@register_for(dm.DecimalField)
|
||||
@register_for(df.DecimalField)
|
||||
def _for_decimal(field):
|
||||
min_value, max_value = numeric_bounds_from_validators(field)
|
||||
bound = Decimal(10**field.max_digits - 1) / (10**field.decimal_places)
|
||||
return st.decimals(
|
||||
min_value=max(min_value, -bound),
|
||||
max_value=min(max_value, bound),
|
||||
places=field.decimal_places,
|
||||
)
|
||||
|
||||
|
||||
def length_bounds_from_validators(field):
|
||||
min_size = 1
|
||||
max_size = field.max_length
|
||||
for v in field.validators:
|
||||
if isinstance(v, django.core.validators.MinLengthValidator):
|
||||
min_size = max(min_size, v.limit_value)
|
||||
elif isinstance(v, django.core.validators.MaxLengthValidator):
|
||||
max_size = min(max_size or v.limit_value, v.limit_value)
|
||||
return min_size, max_size
|
||||
|
||||
|
||||
@register_for(dm.BinaryField)
|
||||
def _for_binary(field):
|
||||
min_size, max_size = length_bounds_from_validators(field)
|
||||
if getattr(field, "blank", False) or not getattr(field, "required", True):
|
||||
return st.just(b"") | st.binary(min_size=min_size, max_size=max_size)
|
||||
return st.binary(min_size=min_size, max_size=max_size)
|
||||
|
||||
|
||||
@register_for(dm.CharField)
|
||||
@register_for(dm.TextField)
|
||||
@register_for(df.CharField)
|
||||
@register_for(df.RegexField)
|
||||
@register_for(UsernameField)
|
||||
def _for_text(field):
|
||||
# We can infer a vastly more precise strategy by considering the
|
||||
# validators as well as the field type. This is a minimal proof of
|
||||
# concept, but we intend to leverage the idea much more heavily soon.
|
||||
# See https://github.com/HypothesisWorks/hypothesis-python/issues/1116
|
||||
regexes = [
|
||||
re.compile(v.regex, v.flags) if isinstance(v.regex, str) else v.regex
|
||||
for v in field.validators
|
||||
if isinstance(v, django.core.validators.RegexValidator) and not v.inverse_match
|
||||
]
|
||||
if regexes:
|
||||
# This strategy generates according to one of the regexes, and
|
||||
# filters using the others. It can therefore learn to generate
|
||||
# from the most restrictive and filter with permissive patterns.
|
||||
# Not maximally efficient, but it makes pathological cases rarer.
|
||||
# If you want a challenge: extend https://qntm.org/greenery to
|
||||
# compute intersections of the full Python regex language.
|
||||
return st.one_of(*(st.from_regex(r) for r in regexes))
|
||||
# If there are no (usable) regexes, we use a standard text strategy.
|
||||
min_size, max_size = length_bounds_from_validators(field)
|
||||
strategy = st.text(
|
||||
alphabet=st.characters(exclude_characters="\x00", exclude_categories=("Cs",)),
|
||||
min_size=min_size,
|
||||
max_size=max_size,
|
||||
).filter(lambda s: min_size <= len(s.strip()))
|
||||
if getattr(field, "blank", False) or not getattr(field, "required", True):
|
||||
return st.just("") | strategy
|
||||
return strategy
|
||||
|
||||
|
||||
@register_for(df.BooleanField)
|
||||
def _for_form_boolean(field):
|
||||
if field.required:
|
||||
return st.just(True)
|
||||
return st.booleans()
|
||||
|
||||
|
||||
def register_field_strategy(
|
||||
field_type: Type[AnyField], strategy: st.SearchStrategy
|
||||
) -> None:
|
||||
"""Add an entry to the global field-to-strategy lookup used by
|
||||
:func:`~hypothesis.extra.django.from_field`.
|
||||
|
||||
``field_type`` must be a subtype of :class:`django.db.models.Field` or
|
||||
:class:`django.forms.Field`, which must not already be registered.
|
||||
``strategy`` must be a :class:`~hypothesis.strategies.SearchStrategy`.
|
||||
"""
|
||||
if not issubclass(field_type, (dm.Field, df.Field)):
|
||||
raise InvalidArgument(f"{field_type=} must be a subtype of Field")
|
||||
check_type(st.SearchStrategy, strategy, "strategy")
|
||||
if field_type in _global_field_lookup:
|
||||
raise InvalidArgument(
|
||||
f"{field_type=} already has a registered "
|
||||
f"strategy ({_global_field_lookup[field_type]!r})"
|
||||
)
|
||||
if issubclass(field_type, dm.AutoField):
|
||||
raise InvalidArgument("Cannot register a strategy for an AutoField")
|
||||
_global_field_lookup[field_type] = strategy
|
||||
|
||||
|
||||
def from_field(field: F) -> st.SearchStrategy[Union[F, None]]:
|
||||
"""Return a strategy for values that fit the given field.
|
||||
|
||||
This function is used by :func:`~hypothesis.extra.django.from_form` and
|
||||
:func:`~hypothesis.extra.django.from_model` for any fields that require
|
||||
a value, or for which you passed ``...`` (:obj:`python:Ellipsis`) to infer
|
||||
a strategy from an annotation.
|
||||
|
||||
It's pretty similar to the core :func:`~hypothesis.strategies.from_type`
|
||||
function, with a subtle but important difference: ``from_field`` takes a
|
||||
Field *instance*, rather than a Field *subtype*, so that it has access to
|
||||
instance attributes such as string length and validators.
|
||||
"""
|
||||
check_type((dm.Field, df.Field), field, "field")
|
||||
if getattr(field, "choices", False):
|
||||
choices: list = []
|
||||
for value, name_or_optgroup in field.choices:
|
||||
if isinstance(name_or_optgroup, (list, tuple)):
|
||||
choices.extend(key for key, _ in name_or_optgroup)
|
||||
else:
|
||||
choices.append(value)
|
||||
# form fields automatically include an empty choice, strip it out
|
||||
if "" in choices:
|
||||
choices.remove("")
|
||||
min_size = 1
|
||||
if isinstance(field, (dm.CharField, dm.TextField)) and field.blank:
|
||||
choices.insert(0, "")
|
||||
elif isinstance(field, (df.Field)) and not field.required:
|
||||
choices.insert(0, "")
|
||||
min_size = 0
|
||||
strategy = st.sampled_from(choices)
|
||||
if isinstance(field, (df.MultipleChoiceField, df.TypedMultipleChoiceField)):
|
||||
strategy = st.lists(st.sampled_from(choices), min_size=min_size)
|
||||
else:
|
||||
if type(field) not in _global_field_lookup:
|
||||
if getattr(field, "null", False):
|
||||
return st.none()
|
||||
raise ResolutionFailed(f"Could not infer a strategy for {field!r}")
|
||||
strategy = _global_field_lookup[type(field)] # type: ignore
|
||||
if not isinstance(strategy, st.SearchStrategy):
|
||||
strategy = strategy(field)
|
||||
assert isinstance(strategy, st.SearchStrategy)
|
||||
if field.validators:
|
||||
|
||||
def validate(value):
|
||||
try:
|
||||
field.run_validators(value)
|
||||
return True
|
||||
except django.core.exceptions.ValidationError:
|
||||
return False
|
||||
|
||||
strategy = strategy.filter(validate)
|
||||
|
||||
if getattr(field, "null", False):
|
||||
return st.none() | strategy
|
||||
return strategy
|
||||
217
backend/venv/Lib/site-packages/hypothesis/extra/django/_impl.py
Normal file
217
backend/venv/Lib/site-packages/hypothesis/extra/django/_impl.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# This file is part of Hypothesis, which may be found at
|
||||
# https://github.com/HypothesisWorks/hypothesis/
|
||||
#
|
||||
# Copyright the Hypothesis Authors.
|
||||
# Individual contributors are listed in AUTHORS.rst and the git log.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
# obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Optional, Type, TypeVar, Union
|
||||
|
||||
from django import forms as df, test as dt
|
||||
from django.contrib.staticfiles import testing as dst
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError, models as dm
|
||||
|
||||
from hypothesis import reject, strategies as st
|
||||
from hypothesis.errors import InvalidArgument
|
||||
from hypothesis.extra.django._fields import from_field
|
||||
from hypothesis.strategies._internal.utils import defines_strategy
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import EllipsisType as EllipsisType
|
||||
elif TYPE_CHECKING:
|
||||
from builtins import ellipsis as EllipsisType
|
||||
else:
|
||||
EllipsisType = type(Ellipsis)
|
||||
|
||||
ModelT = TypeVar("ModelT", bound=dm.Model)
|
||||
|
||||
|
||||
class HypothesisTestCase:
|
||||
def setup_example(self):
|
||||
self._pre_setup()
|
||||
|
||||
def teardown_example(self, example):
|
||||
self._post_teardown()
|
||||
|
||||
def __call__(self, result=None):
|
||||
testMethod = getattr(self, self._testMethodName)
|
||||
if getattr(testMethod, "is_hypothesis_test", False):
|
||||
return unittest.TestCase.__call__(self, result)
|
||||
else:
|
||||
return dt.SimpleTestCase.__call__(self, result)
|
||||
|
||||
|
||||
class TestCase(HypothesisTestCase, dt.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TransactionTestCase(HypothesisTestCase, dt.TransactionTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class LiveServerTestCase(HypothesisTestCase, dt.LiveServerTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class StaticLiveServerTestCase(HypothesisTestCase, dst.StaticLiveServerTestCase):
|
||||
pass
|
||||
|
||||
|
||||
@defines_strategy()
|
||||
def from_model(
|
||||
model: Type[ModelT], /, **field_strategies: Union[st.SearchStrategy, EllipsisType]
|
||||
) -> st.SearchStrategy[ModelT]:
|
||||
"""Return a strategy for examples of ``model``.
|
||||
|
||||
.. warning::
|
||||
Hypothesis creates saved models. This will run inside your testing
|
||||
transaction when using the test runner, but if you use the dev console
|
||||
this will leave debris in your database.
|
||||
|
||||
``model`` must be an subclass of :class:`~django:django.db.models.Model`.
|
||||
Strategies for fields may be passed as keyword arguments, for example
|
||||
``is_staff=st.just(False)``. In order to support models with fields named
|
||||
"model", this is a positional-only parameter.
|
||||
|
||||
Hypothesis can often infer a strategy based the field type and validators,
|
||||
and will attempt to do so for any required fields. No strategy will be
|
||||
inferred for an :class:`~django:django.db.models.AutoField`, nullable field,
|
||||
foreign key, or field for which a keyword
|
||||
argument is passed to ``from_model()``. For example,
|
||||
a Shop type with a foreign key to Company could be generated with::
|
||||
|
||||
shop_strategy = from_model(Shop, company=from_model(Company))
|
||||
|
||||
Like for :func:`~hypothesis.strategies.builds`, you can pass
|
||||
``...`` (:obj:`python:Ellipsis`) as a keyword argument to infer a strategy for
|
||||
a field which has a default value instead of using the default.
|
||||
"""
|
||||
if not issubclass(model, dm.Model):
|
||||
raise InvalidArgument(f"{model=} must be a subtype of Model")
|
||||
|
||||
fields_by_name = {f.name: f for f in model._meta.concrete_fields}
|
||||
for name, value in sorted(field_strategies.items()):
|
||||
if value is ...:
|
||||
field_strategies[name] = from_field(fields_by_name[name])
|
||||
for name, field in sorted(fields_by_name.items()):
|
||||
if (
|
||||
name not in field_strategies
|
||||
and not field.auto_created
|
||||
and field.default is dm.fields.NOT_PROVIDED
|
||||
):
|
||||
field_strategies[name] = from_field(field)
|
||||
|
||||
for field in field_strategies:
|
||||
if model._meta.get_field(field).primary_key:
|
||||
# The primary key is generated as part of the strategy. We
|
||||
# want to find any existing row with this primary key and
|
||||
# overwrite its contents.
|
||||
kwargs = {field: field_strategies.pop(field)}
|
||||
kwargs["defaults"] = st.fixed_dictionaries(field_strategies) # type: ignore
|
||||
return _models_impl(st.builds(model.objects.update_or_create, **kwargs))
|
||||
|
||||
# The primary key is not generated as part of the strategy, so we
|
||||
# just match against any row that has the same value for all
|
||||
# fields.
|
||||
return _models_impl(st.builds(model.objects.get_or_create, **field_strategies))
|
||||
|
||||
|
||||
@st.composite
|
||||
def _models_impl(draw, strat):
|
||||
"""Handle the nasty part of drawing a value for models()"""
|
||||
try:
|
||||
return draw(strat)[0]
|
||||
except IntegrityError:
|
||||
reject()
|
||||
|
||||
|
||||
@defines_strategy()
|
||||
def from_form(
|
||||
form: Type[df.Form],
|
||||
form_kwargs: Optional[dict] = None,
|
||||
**field_strategies: Union[st.SearchStrategy, EllipsisType],
|
||||
) -> st.SearchStrategy[df.Form]:
|
||||
"""Return a strategy for examples of ``form``.
|
||||
|
||||
``form`` must be an subclass of :class:`~django:django.forms.Form`.
|
||||
Strategies for fields may be passed as keyword arguments, for example
|
||||
``is_staff=st.just(False)``.
|
||||
|
||||
Hypothesis can often infer a strategy based the field type and validators,
|
||||
and will attempt to do so for any required fields. No strategy will be
|
||||
inferred for a disabled field or field for which a keyword argument
|
||||
is passed to ``from_form()``.
|
||||
|
||||
This function uses the fields of an unbound ``form`` instance to determine
|
||||
field strategies, any keyword arguments needed to instantiate the unbound
|
||||
``form`` instance can be passed into ``from_form()`` as a dict with the
|
||||
keyword ``form_kwargs``. E.g.::
|
||||
|
||||
shop_strategy = from_form(Shop, form_kwargs={"company_id": 5})
|
||||
|
||||
Like for :func:`~hypothesis.strategies.builds`, you can pass
|
||||
``...`` (:obj:`python:Ellipsis`) as a keyword argument to infer a strategy for
|
||||
a field which has a default value instead of using the default.
|
||||
"""
|
||||
# currently unsupported:
|
||||
# ComboField
|
||||
# FilePathField
|
||||
# FileField
|
||||
# ImageField
|
||||
form_kwargs = form_kwargs or {}
|
||||
if not issubclass(form, df.BaseForm):
|
||||
raise InvalidArgument(f"{form=} must be a subtype of Form")
|
||||
|
||||
# Forms are a little bit different from models. Model classes have
|
||||
# all their fields defined, whereas forms may have different fields
|
||||
# per-instance. So, we ought to instantiate the form and get the
|
||||
# fields from the instance, thus we need to accept the kwargs for
|
||||
# instantiation as well as the explicitly defined strategies
|
||||
|
||||
unbound_form = form(**form_kwargs)
|
||||
fields_by_name = {}
|
||||
for name, field in unbound_form.fields.items():
|
||||
if isinstance(field, df.MultiValueField):
|
||||
# PS: So this is a little strange, but MultiValueFields must
|
||||
# have their form data encoded in a particular way for the
|
||||
# values to actually be picked up by the widget instances'
|
||||
# ``value_from_datadict``.
|
||||
# E.g. if a MultiValueField named 'mv_field' has 3
|
||||
# sub-fields then the ``value_from_datadict`` will look for
|
||||
# 'mv_field_0', 'mv_field_1', and 'mv_field_2'. Here I'm
|
||||
# decomposing the individual sub-fields into the names that
|
||||
# the form validation process expects
|
||||
for i, _field in enumerate(field.fields):
|
||||
fields_by_name[f"{name}_{i}"] = _field
|
||||
else:
|
||||
fields_by_name[name] = field
|
||||
for name, value in sorted(field_strategies.items()):
|
||||
if value is ...:
|
||||
field_strategies[name] = from_field(fields_by_name[name])
|
||||
|
||||
for name, field in sorted(fields_by_name.items()):
|
||||
if name not in field_strategies and not field.disabled:
|
||||
field_strategies[name] = from_field(field)
|
||||
|
||||
return _forms_impl(
|
||||
st.builds(
|
||||
partial(form, **form_kwargs),
|
||||
data=st.fixed_dictionaries(field_strategies), # type: ignore
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@st.composite
|
||||
def _forms_impl(draw, strat):
|
||||
"""Handle the nasty part of drawing a value for from_form()"""
|
||||
try:
|
||||
return draw(strat)
|
||||
except ValidationError:
|
||||
reject()
|
||||
Reference in New Issue
Block a user