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
|