File: attrs_factory.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 (81 lines) | stat: -rw-r--r-- 2,439 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
from __future__ import annotations

from inspect import isclass
from typing import TYPE_CHECKING, Generic, TypeVar

from polyfactory.exceptions import MissingDependencyException
from polyfactory.factories.base import BaseFactory
from polyfactory.field_meta import FieldMeta, Null

if TYPE_CHECKING:
    from typing import Any, TypeGuard


try:
    import attrs
    from attr._make import Factory
    from attrs import AttrsInstance
except ImportError as ex:
    msg = "attrs is not installed"
    raise MissingDependencyException(msg) from ex


T = TypeVar("T", bound=AttrsInstance)


class AttrsFactory(Generic[T], BaseFactory[T]):
    """Base factory for attrs classes."""

    __model__: type[T]

    __is_base_factory__ = True

    @classmethod
    def is_supported_type(cls, value: Any) -> TypeGuard[type[T]]:
        return isclass(value) and hasattr(value, "__attrs_attrs__")

    @classmethod
    def get_model_fields(cls) -> list[FieldMeta]:
        field_metas: list[FieldMeta] = []
        none_type = type(None)

        cls.resolve_types(cls.__model__)
        fields = attrs.fields(cls.__model__)

        for field in fields:
            if not field.init:
                continue

            annotation = none_type if field.type is None else field.type

            default = field.default
            if isinstance(default, Factory):
                # The default value is not currently being used when generating
                # the field values. When that is implemented, this would need
                # to be handled differently since the `default.factory` could
                # take a `self` argument.
                default_value = default.factory
            elif default is None:
                default_value = Null
            else:
                default_value = default

            field_metas.append(
                FieldMeta.from_type(
                    annotation=annotation,
                    name=field.alias,
                    default=default_value,
                ),
            )

        return field_metas

    @classmethod
    def resolve_types(cls, model: type[T], **kwargs: Any) -> None:
        """Resolve any strings and forward annotations in type annotations.

        :param model: The model to resolve the type annotations for.
        :param kwargs: Any parameters that need to be passed to `attrs.resolve_types`.
        """

        attrs.resolve_types(model, **kwargs)