File: pytest_plugin.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 (126 lines) | stat: -rw-r--r-- 3,326 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
from __future__ import annotations

import inspect
import re
from typing import (
    Any,
    Callable,
    Literal,
    Type,
    TypeVar,
    Union,
    cast,
    overload,
)

import pytest

from polyfactory.exceptions import ParameterException
from polyfactory.factories.base import BaseFactory
from polyfactory.utils.predicates import is_safe_subclass

Scope = Union[
    Literal["session", "package", "module", "class", "function"],
    Callable[[str, pytest.Config], Literal["session", "package", "module", "class", "function"]],
]
T = TypeVar("T", bound=BaseFactory[Any])

split_pattern_1 = re.compile(r"([A-Z]+)([A-Z][a-z])")
split_pattern_2 = re.compile(r"([a-z\d])([A-Z])")


def _get_fixture_name(name: str) -> str:
    """From inflection.underscore.

    :param name: str: A name.

    :returns: Normalized fixture name.

    """
    name = re.sub(split_pattern_1, r"\1_\2", name)
    name = re.sub(split_pattern_2, r"\1_\2", name)
    name = name.replace("-", "_")
    return name.lower()


class FactoryFixture:
    """Decorator that creates a pytest fixture from a factory"""

    __slots__ = ("autouse", "name", "scope")

    def __init__(
        self,
        scope: Scope = "function",
        autouse: bool = False,
        name: str | None = None,
    ) -> None:
        """Create a factory fixture decorator

        :param scope: Fixture scope
        :param autouse: Autouse the fixture
        :param name: Fixture name
        """
        self.scope = scope
        self.autouse = autouse
        self.name = name

    def __call__(self, factory: type[T], depth: int = 1) -> type[T]:
        if not is_safe_subclass(factory, BaseFactory):
            msg = f"{factory.__name__} is not a BaseFactory subclass."
            raise ParameterException(msg)

        fixture_name = self.name or _get_fixture_name(factory.__name__)
        fixture_register = pytest.fixture(
            scope=self.scope,  # pyright: ignore[reportArgumentType]
            name=fixture_name,
            autouse=self.autouse,
        )

        def _factory_fixture() -> type[T]:
            """The wrapped factory"""
            return cast("Type[T]", factory)

        caller_globals = inspect.stack()[depth][0].f_globals
        caller_globals[fixture_name] = fixture_register(_factory_fixture)

        return factory  # type: ignore[return-value]


@overload
def register_fixture(
    factory: None = None,
    *,
    scope: Scope = "function",
    autouse: bool = False,
    name: str | None = None,
) -> FactoryFixture: ...


@overload
def register_fixture(
    factory: type[T],
    *,
    scope: Scope = "function",
    autouse: bool = False,
    name: str | None = None,
) -> type[T]: ...


def register_fixture(
    factory: type[T] | None = None,
    *,
    scope: Scope = "function",
    autouse: bool = False,
    name: str | None = None,
) -> type[T] | FactoryFixture:
    """A decorator that allows registering model factories as fixtures.

    :param factory: An optional factory class to decorate.
    :param scope: Pytest scope.
    :param autouse: Auto use fixture.
    :param name: Fixture name.

    :returns: A fixture factory instance.
    """
    factory_fixture = FactoryFixture(scope=scope, autouse=autouse, name=name)
    return factory_fixture(factory, depth=2) if factory else factory_fixture