File: constrained_collections.py

package info (click to toggle)
python-polyfactory 2.22.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,892 kB
  • sloc: python: 11,338; makefile: 103; sh: 37
file content (135 lines) | stat: -rw-r--r-- 5,062 bytes parent folder | download
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