1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
from __future__ import annotations
from enum import EnumMeta
from typing import TYPE_CHECKING, Any, Callable, Literal, Mapping, TypeVar
from polyfactory.exceptions import ParameterException
if TYPE_CHECKING:
from polyfactory.factories.base import BaseFactory, BuildContext
from polyfactory.field_meta import FieldMeta
T = TypeVar("T", list, set, frozenset)
def handle_constrained_collection( # noqa: C901
collection_type: Callable[..., T],
factory: type[BaseFactory[Any]],
field_meta: FieldMeta,
item_type: Any,
max_items: int | None = None,
min_items: int | None = None,
unique_items: bool = False,
field_build_parameters: Any | None = None,
build_context: BuildContext | None = None,
) -> T:
"""Generate a constrained list or set.
:param collection_type: A type that can accept type arguments.
:param factory: A factory.
:param field_meta: A field meta instance.
:param item_type: Type of the collection items.
:param max_items: Maximal number of items.
:param min_items: Minimal number of items.
:param unique_items: Whether the items should be unique.
:param field_build_parameters: Any build parameters passed to the factory as kwarg values.
:param build_context: BuildContext data for current build.
:returns: A collection value.
"""
build_context = factory._get_build_context(build_context)
if field_meta.annotation in build_context["seen_models"]:
return collection_type()
min_items = abs(min_items if min_items is not None else (max_items or 0))
max_items = abs(max_items) if max_items is not None else min_items
if max_items < min_items:
msg = "max_items must be larger or equal to min_items"
raise ParameterException(msg)
if collection_type in (frozenset, set) or unique_items:
max_field_values = max_items
if hasattr(field_meta.annotation, "__origin__") and field_meta.annotation.__origin__ is Literal:
if field_meta.children is not None:
max_field_values = len(field_meta.children)
elif isinstance(field_meta.annotation, EnumMeta):
max_field_values = len(field_meta.annotation)
min_items = min(min_items, max_field_values)
max_items = min(max_items, max_field_values)
collection: set[T] | list[T] = set() if (collection_type in (frozenset, set) or unique_items) else []
try:
length = factory.__random__.randint(min_items, max_items)
while (i := len(collection)) < length:
if field_build_parameters and len(field_build_parameters) > i:
build_params = field_build_parameters[i]
else:
build_params = None
value = factory.get_field_value(
field_meta,
field_build_parameters=build_params,
build_context=build_context,
)
if isinstance(collection, set):
collection.add(value)
else:
collection.append(value)
return collection_type(collection)
except TypeError as e:
msg = f"cannot generate a constrained collection of type: {item_type}"
raise ParameterException(msg) from e
def handle_constrained_mapping(
factory: type[BaseFactory[Any]],
field_meta: FieldMeta,
max_items: int | None = None,
min_items: int | None = None,
field_build_parameters: Any | None = None,
build_context: BuildContext | None = None,
) -> Mapping[Any, Any]:
"""Generate a constrained mapping.
:param factory: A factory.
:param field_meta: A field meta instance.
:param max_items: Maximal number of items.
:param min_items: Minimal number of items.
:param field_build_parameters: Any build parameters passed to the factory as kwarg values.
:param build_context: BuildContext data for current build.
:returns: A mapping instance.
"""
build_context = factory._get_build_context(build_context)
if field_meta.children is None or any(
child_meta.annotation in build_context["seen_models"] for child_meta in field_meta.children
):
return {}
min_items = abs(min_items if min_items is not None else (max_items or 0))
max_items = abs(max_items) if max_items is not None else min_items
if max_items < min_items:
msg = "max_items must be larger or equal to min_items"
raise ParameterException(msg)
length = factory.__random__.randint(min_items, max_items)
collection: dict[Any, Any] = {}
children = field_meta.children
key_field_meta = children[0]
value_field_meta = children[1]
while len(collection) < length:
key = factory.get_field_value(
key_field_meta, field_build_parameters=field_build_parameters, build_context=build_context
)
value = factory.get_field_value(
value_field_meta, field_build_parameters=field_build_parameters, build_context=build_context
)
collection[key] = value
return collection
|