diff options
Diffstat (limited to 'venv/lib/python3.11/site-packages/polyfactory/utils')
12 files changed, 637 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__init__.py b/venv/lib/python3.11/site-packages/polyfactory/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__init__.py diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..6297806 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/deprecation.cpython-311.pyc b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/deprecation.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..8f43f83 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/deprecation.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/helpers.cpython-311.pyc b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/helpers.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..982e068 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/helpers.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/model_coverage.cpython-311.pyc b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/model_coverage.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..22c475b --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/model_coverage.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/predicates.cpython-311.pyc b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/predicates.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..c1908b6 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/predicates.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/types.cpython-311.pyc b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/types.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..59b2b6a --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/__pycache__/types.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/deprecation.py b/venv/lib/python3.11/site-packages/polyfactory/utils/deprecation.py new file mode 100644 index 0000000..576c4ac --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/deprecation.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +import inspect +from functools import wraps +from typing import Any, Callable, Literal, TypeVar +from warnings import warn + +from typing_extensions import ParamSpec + +__all__ = ("deprecated", "warn_deprecation", "check_for_deprecated_parameters") + + +T = TypeVar("T") +P = ParamSpec("P") +DeprecatedKind = Literal["function", "method", "classmethod", "attribute", "property", "class", "parameter", "import"] + + +def warn_deprecation( + version: str, + deprecated_name: str, + kind: DeprecatedKind, + *, + removal_in: str | None = None, + alternative: str | None = None, + info: str | None = None, + pending: bool = False, +) -> None: + """Warn about a call to a (soon to be) deprecated function. + + Args: + version: Polyfactory version where the deprecation will occur. + deprecated_name: Name of the deprecated function. + removal_in: Polyfactory version where the deprecated function will be removed. + alternative: Name of a function that should be used instead. + info: Additional information. + pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``. + kind: Type of the deprecated thing. + """ + parts = [] + + if kind == "import": + access_type = "Import of" + elif kind in {"function", "method"}: + access_type = "Call to" + else: + access_type = "Use of" + + if pending: + parts.append(f"{access_type} {kind} awaiting deprecation {deprecated_name!r}") + else: + parts.append(f"{access_type} deprecated {kind} {deprecated_name!r}") + + parts.extend( + ( + f"Deprecated in polyfactory {version}", + f"This {kind} will be removed in {removal_in or 'the next major version'}", + ) # noqa: COM812 + ) + if alternative: + parts.append(f"Use {alternative!r} instead") + + if info: + parts.append(info) + + text = ". ".join(parts) + warning_class = PendingDeprecationWarning if pending else DeprecationWarning + + warn(text, warning_class, stacklevel=2) + + +def deprecated( + version: str, + *, + removal_in: str | None = None, + alternative: str | None = None, + info: str | None = None, + pending: bool = False, + kind: Literal["function", "method", "classmethod", "property"] | None = None, +) -> Callable[[Callable[P, T]], Callable[P, T]]: + """Create a decorator wrapping a function, method or property with a warning call about a (pending) deprecation. + + Args: + version: Polyfactory version where the deprecation will occur. + removal_in: Polyfactory version where the deprecated function will be removed. + alternative: Name of a function that should be used instead. + info: Additional information. + pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``. + kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure + out if it's a function or method. + + Returns: + A decorator wrapping the function call with a warning + """ + + def decorator(func: Callable[P, T]) -> Callable[P, T]: + @wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> T: + warn_deprecation( + version=version, + deprecated_name=func.__name__, + info=info, + alternative=alternative, + pending=pending, + removal_in=removal_in, + kind=kind or ("method" if inspect.ismethod(func) else "function"), + ) + return func(*args, **kwargs) + + return wrapped + + return decorator + + +def check_for_deprecated_parameters( + version: str, + *, + parameters: tuple[tuple[str, Any], ...], + default_value: Any = None, + removal_in: str | None = None, + alternative: str | None = None, + info: str | None = None, + pending: bool = False, +) -> None: + """Warn about a call to a (soon to be) deprecated argument to a function. + + Args: + version: Polyfactory version where the deprecation will occur. + parameters: Parameters to trigger warning if used. + default_value: Default value for parameter to detect if supplied or not. + removal_in: Polyfactory version where the deprecated function will be removed. + alternative: Name of a function that should be used instead. + info: Additional information. + pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``. + kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure + out if it's a function or method. + """ + for parameter_name, value in parameters: + if value == default_value: + continue + + warn_deprecation( + version=version, + deprecated_name=parameter_name, + info=info, + alternative=alternative, + pending=pending, + removal_in=removal_in, + kind="parameter", + ) diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/helpers.py b/venv/lib/python3.11/site-packages/polyfactory/utils/helpers.py new file mode 100644 index 0000000..f9924bb --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/helpers.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING, Any, Mapping + +from typing_extensions import TypeAliasType, get_args, get_origin + +from polyfactory.constants import TYPE_MAPPING +from polyfactory.utils.predicates import is_annotated, is_new_type, is_optional, is_safe_subclass, is_union +from polyfactory.utils.types import NoneType + +if TYPE_CHECKING: + from random import Random + from typing import Sequence + + +def unwrap_new_type(annotation: Any) -> Any: + """Return base type if given annotation is a type derived with NewType, otherwise annotation. + + :param annotation: A type annotation, possibly one created using 'types.NewType' + + :returns: The unwrapped annotation. + """ + while is_new_type(annotation): + annotation = annotation.__supertype__ + + return annotation + + +def unwrap_union(annotation: Any, random: Random) -> Any: + """Unwraps union types - recursively. + + :param annotation: A type annotation, possibly a type union. + :param random: An instance of random.Random. + :returns: A type annotation + """ + while is_union(annotation): + args = list(get_args(annotation)) + annotation = random.choice(args) + return annotation + + +def unwrap_optional(annotation: Any) -> Any: + """Unwraps optional union types - recursively. + + :param annotation: A type annotation, possibly an optional union. + + :returns: A type annotation + """ + while is_optional(annotation): + annotation = next(arg for arg in get_args(annotation) if arg not in (NoneType, None)) + return annotation + + +def unwrap_annotation(annotation: Any, random: Random) -> Any: + """Unwraps an annotation. + + :param annotation: A type annotation. + :param random: An instance of random.Random. + + :returns: The unwrapped annotation. + + """ + while ( + is_optional(annotation) + or is_union(annotation) + or is_new_type(annotation) + or is_annotated(annotation) + or isinstance(annotation, TypeAliasType) + ): + if is_new_type(annotation): + annotation = unwrap_new_type(annotation) + elif is_optional(annotation): + annotation = unwrap_optional(annotation) + elif is_annotated(annotation): + annotation = unwrap_annotated(annotation, random=random)[0] + elif isinstance(annotation, TypeAliasType): + annotation = annotation.__value__ + else: + annotation = unwrap_union(annotation, random=random) + + return annotation + + +def flatten_annotation(annotation: Any) -> list[Any]: + """Flattens an annotation. + + :param annotation: A type annotation. + + :returns: The flattened annotations. + """ + flat = [] + if is_new_type(annotation): + flat.extend(flatten_annotation(unwrap_new_type(annotation))) + elif is_optional(annotation): + for a in get_args(annotation): + flat.extend(flatten_annotation(a)) + elif is_annotated(annotation): + flat.extend(flatten_annotation(get_args(annotation)[0])) + elif is_union(annotation): + for a in get_args(annotation): + flat.extend(flatten_annotation(a)) + else: + flat.append(annotation) + + return flat + + +def unwrap_args(annotation: Any, random: Random) -> tuple[Any, ...]: + """Unwrap the annotation and return any type args. + + :param annotation: A type annotation + :param random: An instance of random.Random. + + :returns: A tuple of type args. + + """ + + return get_args(unwrap_annotation(annotation=annotation, random=random)) + + +def unwrap_annotated(annotation: Any, random: Random) -> tuple[Any, list[Any]]: + """Unwrap an annotated type and return a tuple of type args and optional metadata. + + :param annotation: An annotated type annotation + :param random: An instance of random.Random. + + :returns: A tuple of type args. + + """ + args = [arg for arg in get_args(annotation) if arg is not None] + return unwrap_annotation(args[0], random=random), args[1:] + + +def normalize_annotation(annotation: Any, random: Random) -> Any: + """Normalize an annotation. + + :param annotation: A type annotation. + + :returns: A normalized type annotation. + + """ + if is_new_type(annotation): + annotation = unwrap_new_type(annotation) + + if is_annotated(annotation): + annotation = unwrap_annotated(annotation, random=random)[0] + + # we have to maintain compatibility with the older non-subscriptable typings. + if sys.version_info <= (3, 9): # pragma: no cover + return annotation + + origin = get_origin(annotation) or annotation + + if origin in TYPE_MAPPING: + origin = TYPE_MAPPING[origin] + + if args := get_args(annotation): + args = tuple(normalize_annotation(arg, random=random) for arg in args) + return origin[args] if origin is not type else annotation + + return origin + + +def get_annotation_metadata(annotation: Any) -> Sequence[Any]: + """Get the metadata in the annotation. + + :param annotation: A type annotation. + + :returns: The metadata. + """ + + return get_args(annotation)[1:] + + +def get_collection_type(annotation: Any) -> type[list | tuple | set | frozenset | dict]: + """Get the collection type from the annotation. + + :param annotation: A type annotation. + + :returns: The collection type. + """ + + if is_safe_subclass(annotation, list): + return list + if is_safe_subclass(annotation, Mapping): + return dict + if is_safe_subclass(annotation, tuple): + return tuple + if is_safe_subclass(annotation, set): + return set + if is_safe_subclass(annotation, frozenset): + return frozenset + + msg = f"Unknown collection type - {annotation}" + raise ValueError(msg) diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/model_coverage.py b/venv/lib/python3.11/site-packages/polyfactory/utils/model_coverage.py new file mode 100644 index 0000000..6fc3971 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/model_coverage.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable, Iterable, Iterator, Mapping, MutableSequence +from typing import AbstractSet, Any, Generic, Set, TypeVar, cast + +from typing_extensions import ParamSpec + +from polyfactory.exceptions import ParameterException + + +class CoverageContainerBase(ABC): + """Base class for coverage container implementations. + + A coverage container is a wrapper providing values for a particular field. Coverage containers return field values and + track a "done" state to indicate that all coverage examples have been generated. + """ + + @abstractmethod + def next_value(self) -> Any: + """Provide the next value""" + ... + + @abstractmethod + def is_done(self) -> bool: + """Indicate if this container has provided every coverage example it has""" + ... + + +T = TypeVar("T") + + +class CoverageContainer(CoverageContainerBase, Generic[T]): + """A coverage container that wraps a collection of values. + + When calling ``next_value()`` a greater number of times than the length of the given collection will cause duplicate + examples to be returned (wraps around). + + If there are any coverage containers within the given collection, the values from those containers are essentially merged + into the parent container. + """ + + def __init__(self, instances: Iterable[T]) -> None: + self._pos = 0 + self._instances = list(instances) + if not self._instances: + msg = "CoverageContainer must have at least one instance" + raise ValueError(msg) + + def next_value(self) -> T: + value = self._instances[self._pos % len(self._instances)] + if isinstance(value, CoverageContainerBase): + result = value.next_value() + if value.is_done(): + # Only move onto the next instance if the sub-container is done + self._pos += 1 + return cast(T, result) + + self._pos += 1 + return value + + def is_done(self) -> bool: + return self._pos >= len(self._instances) + + def __repr__(self) -> str: + return f"CoverageContainer(instances={self._instances}, is_done={self.is_done()})" + + +P = ParamSpec("P") + + +class CoverageContainerCallable(CoverageContainerBase, Generic[T]): + """A coverage container that wraps a callable. + + When calling ``next_value()`` the wrapped callable is called to provide a value. + """ + + def __init__(self, func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None: + self._func = func + self._args = args + self._kwargs = kwargs + + def next_value(self) -> T: + try: + return self._func(*self._args, **self._kwargs) + except Exception as e: # noqa: BLE001 + msg = f"Unsupported type: {self._func!r}\n\nEither extend the providers map or add a factory function for this type." + raise ParameterException(msg) from e + + def is_done(self) -> bool: + return True + + +def _resolve_next(unresolved: Any) -> tuple[Any, bool]: # noqa: C901 + if isinstance(unresolved, CoverageContainerBase): + result, done = _resolve_next(unresolved.next_value()) + return result, unresolved.is_done() and done + + if isinstance(unresolved, Mapping): + result = {} + done_status = True + for key, value in unresolved.items(): + val_resolved, val_done = _resolve_next(value) + key_resolved, key_done = _resolve_next(key) + result[key_resolved] = val_resolved + done_status = done_status and val_done and key_done + return result, done_status + + if isinstance(unresolved, (tuple, MutableSequence)): + result = [] + done_status = True + for value in unresolved: + resolved, done = _resolve_next(value) + result.append(resolved) + done_status = done_status and done + if isinstance(unresolved, tuple): + result = tuple(result) + return result, done_status + + if isinstance(unresolved, Set): + result = type(unresolved)() + done_status = True + for value in unresolved: + resolved, done = _resolve_next(value) + result.add(resolved) + done_status = done_status and done + return result, done_status + + if issubclass(type(unresolved), AbstractSet): + result = type(unresolved)() + done_status = True + resolved_values = [] + for value in unresolved: + resolved, done = _resolve_next(value) + resolved_values.append(resolved) + done_status = done_status and done + return result.union(resolved_values), done_status + + return unresolved, True + + +def resolve_kwargs_coverage(kwargs: dict[str, Any]) -> Iterator[dict[str, Any]]: + done = False + while not done: + resolved, done = _resolve_next(kwargs) + yield resolved diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/predicates.py b/venv/lib/python3.11/site-packages/polyfactory/utils/predicates.py new file mode 100644 index 0000000..895e380 --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/predicates.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +from inspect import isclass +from typing import Any, Literal, NewType, Optional, TypeVar, get_args + +from typing_extensions import Annotated, NotRequired, ParamSpec, Required, TypeGuard, _AnnotatedAlias, get_origin + +from polyfactory.constants import TYPE_MAPPING +from polyfactory.utils.types import UNION_TYPES, NoneType + +P = ParamSpec("P") +T = TypeVar("T") + + +def is_safe_subclass(annotation: Any, class_or_tuple: type[T] | tuple[type[T], ...]) -> "TypeGuard[type[T]]": + """Determine whether a given annotation is a subclass of a give type + + :param annotation: A type annotation. + :param class_or_tuple: A potential super class or classes. + + :returns: A typeguard + """ + origin = get_type_origin(annotation) + if not origin and not isclass(annotation): + return False + try: + return issubclass(origin or annotation, class_or_tuple) + except TypeError: # pragma: no cover + return False + + +def is_any(annotation: Any) -> "TypeGuard[Any]": + """Determine whether a given annotation is 'typing.Any'. + + :param annotation: A type annotation. + + :returns: A typeguard. + """ + return ( + annotation is Any + or getattr(annotation, "_name", "") == "typing.Any" + or (get_origin(annotation) in UNION_TYPES and Any in get_args(annotation)) + ) + + +def is_dict_key_or_value_type(annotation: Any) -> "TypeGuard[Any]": + """Determine whether a given annotation is a valid dict key or value type: + ``typing.KT`` or ``typing.VT``. + + :returns: A typeguard. + """ + return str(annotation) in {"~KT", "~VT"} + + +def is_union(annotation: Any) -> "TypeGuard[Any]": + """Determine whether a given annotation is 'typing.Union'. + + :param annotation: A type annotation. + + :returns: A typeguard. + """ + return get_type_origin(annotation) in UNION_TYPES + + +def is_optional(annotation: Any) -> "TypeGuard[Any | None]": + """Determine whether a given annotation is 'typing.Optional'. + + :param annotation: A type annotation. + + :returns: A typeguard. + """ + origin = get_type_origin(annotation) + return origin is Optional or (get_origin(annotation) in UNION_TYPES and NoneType in get_args(annotation)) + + +def is_literal(annotation: Any) -> bool: + """Determine whether a given annotation is 'typing.Literal'. + + :param annotation: A type annotation. + + :returns: A boolean. + """ + return ( + get_type_origin(annotation) is Literal + or repr(annotation).startswith("typing.Literal") + or repr(annotation).startswith("typing_extensions.Literal") + ) + + +def is_new_type(annotation: Any) -> "TypeGuard[type[NewType]]": + """Determine whether a given annotation is 'typing.NewType'. + + :param annotation: A type annotation. + + :returns: A typeguard. + """ + return hasattr(annotation, "__supertype__") + + +def is_annotated(annotation: Any) -> bool: + """Determine whether a given annotation is 'typing.Annotated'. + + :param annotation: A type annotation. + + :returns: A boolean. + """ + return get_origin(annotation) is Annotated or ( + isinstance(annotation, _AnnotatedAlias) and getattr(annotation, "__args__", None) is not None + ) + + +def is_any_annotated(annotation: Any) -> bool: + """Determine whether any of the types in the given annotation is + `typing.Annotated`. + + :param annotation: A type annotation. + + :returns: A boolean + """ + + return any(is_annotated(arg) or hasattr(arg, "__args__") and is_any_annotated(arg) for arg in get_args(annotation)) + + +def get_type_origin(annotation: Any) -> Any: + """Get the type origin of an annotation - safely. + + :param annotation: A type annotation. + + :returns: A type annotation. + """ + origin = get_origin(annotation) + if origin in (Annotated, Required, NotRequired): + origin = get_args(annotation)[0] + return mapped_type if (mapped_type := TYPE_MAPPING.get(origin)) else origin diff --git a/venv/lib/python3.11/site-packages/polyfactory/utils/types.py b/venv/lib/python3.11/site-packages/polyfactory/utils/types.py new file mode 100644 index 0000000..413f5dd --- /dev/null +++ b/venv/lib/python3.11/site-packages/polyfactory/utils/types.py @@ -0,0 +1,12 @@ +from typing import Union + +try: + from types import NoneType, UnionType + + UNION_TYPES = {UnionType, Union} +except ImportError: + UNION_TYPES = {Union} + + NoneType = type(None) # type: ignore[misc,assignment] + +__all__ = ("NoneType", "UNION_TYPES") |