diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/pytest/py3/_pytest/mark/structures.py | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/mark/structures.py')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/mark/structures.py | 812 |
1 files changed, 406 insertions, 406 deletions
diff --git a/contrib/python/pytest/py3/_pytest/mark/structures.py b/contrib/python/pytest/py3/_pytest/mark/structures.py index f5736a4c1c..fb87c0a774 100644 --- a/contrib/python/pytest/py3/_pytest/mark/structures.py +++ b/contrib/python/pytest/py3/_pytest/mark/structures.py @@ -1,197 +1,197 @@ -import collections.abc +import collections.abc import inspect import warnings -from typing import Any -from typing import Callable -from typing import Collection -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Mapping -from typing import MutableMapping -from typing import NamedTuple -from typing import Optional -from typing import overload -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union +from typing import Any +from typing import Callable +from typing import Collection +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import MutableMapping +from typing import NamedTuple +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union import attr -from .._code import getfslineno -from ..compat import ascii_escaped -from ..compat import final +from .._code import getfslineno +from ..compat import ascii_escaped +from ..compat import final from ..compat import NOTSET -from ..compat import NotSetType -from _pytest.config import Config +from ..compat import NotSetType +from _pytest.config import Config from _pytest.outcomes import fail -from _pytest.warning_types import PytestUnknownMarkWarning - -if TYPE_CHECKING: - from ..nodes import Node - +from _pytest.warning_types import PytestUnknownMarkWarning +if TYPE_CHECKING: + from ..nodes import Node + + EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" -def istestfunc(func) -> bool: +def istestfunc(func) -> bool: return ( hasattr(func, "__call__") and getattr(func, "__name__", "<lambda>") != "<lambda>" ) -def get_empty_parameterset_mark( - config: Config, argnames: Sequence[str], func -) -> "MarkDecorator": +def get_empty_parameterset_mark( + config: Config, argnames: Sequence[str], func +) -> "MarkDecorator": from ..nodes import Collector - fs, lineno = getfslineno(func) - reason = "got empty parameter set %r, function %s at %s:%d" % ( - argnames, - func.__name__, - fs, - lineno, - ) - + fs, lineno = getfslineno(func) + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, + func.__name__, + fs, + lineno, + ) + requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) if requested_mark in ("", None, "skip"): - mark = MARK_GEN.skip(reason=reason) + mark = MARK_GEN.skip(reason=reason) elif requested_mark == "xfail": - mark = MARK_GEN.xfail(reason=reason, run=False) + mark = MARK_GEN.xfail(reason=reason, run=False) elif requested_mark == "fail_at_collect": f_name = func.__name__ _, lineno = getfslineno(func) raise Collector.CollectError( - "Empty parameter set in '%s' at line %d" % (f_name, lineno + 1) + "Empty parameter set in '%s' at line %d" % (f_name, lineno + 1) ) else: raise LookupError(requested_mark) - return mark - - -class ParameterSet( - NamedTuple( - "ParameterSet", - [ - ("values", Sequence[Union[object, NotSetType]]), - ("marks", Collection[Union["MarkDecorator", "Mark"]]), - ("id", Optional[str]), - ], - ) -): + return mark + + +class ParameterSet( + NamedTuple( + "ParameterSet", + [ + ("values", Sequence[Union[object, NotSetType]]), + ("marks", Collection[Union["MarkDecorator", "Mark"]]), + ("id", Optional[str]), + ], + ) +): @classmethod - def param( - cls, - *values: object, - marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), - id: Optional[str] = None, - ) -> "ParameterSet": + def param( + cls, + *values: object, + marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), + id: Optional[str] = None, + ) -> "ParameterSet": if isinstance(marks, MarkDecorator): marks = (marks,) else: - assert isinstance(marks, collections.abc.Collection) + assert isinstance(marks, collections.abc.Collection) - if id is not None: - if not isinstance(id, str): - raise TypeError( - "Expected id to be a string, got {}: {!r}".format(type(id), id) - ) - id = ascii_escaped(id) - return cls(values, marks, id) + if id is not None: + if not isinstance(id, str): + raise TypeError( + "Expected id to be a string, got {}: {!r}".format(type(id), id) + ) + id = ascii_escaped(id) + return cls(values, marks, id) @classmethod - def extract_from( - cls, - parameterset: Union["ParameterSet", Sequence[object], object], - force_tuple: bool = False, - ) -> "ParameterSet": - """Extract from an object or objects. - + def extract_from( + cls, + parameterset: Union["ParameterSet", Sequence[object], object], + force_tuple: bool = False, + ) -> "ParameterSet": + """Extract from an object or objects. + :param parameterset: - A legacy style parameterset that may or may not be a tuple, - and may or may not be wrapped into a mess of mark objects. + A legacy style parameterset that may or may not be a tuple, + and may or may not be wrapped into a mess of mark objects. - :param force_tuple: - Enforce tuple wrapping so single argument tuple values - don't get decomposed and break tests. + :param force_tuple: + Enforce tuple wrapping so single argument tuple values + don't get decomposed and break tests. """ if isinstance(parameterset, cls): return parameterset - if force_tuple: + if force_tuple: return cls.param(parameterset) - else: - # TODO: Refactor to fix this type-ignore. Currently the following - # passes type-checking but crashes: - # - # @pytest.mark.parametrize(('x', 'y'), [1, 2]) - # def test_foo(x, y): pass - return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] - - @staticmethod - def _parse_parametrize_args( - argnames: Union[str, List[str], Tuple[str, ...]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], - *args, - **kwargs, - ) -> Tuple[Union[List[str], Tuple[str, ...]], bool]: + else: + # TODO: Refactor to fix this type-ignore. Currently the following + # passes type-checking but crashes: + # + # @pytest.mark.parametrize(('x', 'y'), [1, 2]) + # def test_foo(x, y): pass + return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] + + @staticmethod + def _parse_parametrize_args( + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + *args, + **kwargs, + ) -> Tuple[Union[List[str], Tuple[str, ...]], bool]: if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 else: force_tuple = False - return argnames, force_tuple - - @staticmethod - def _parse_parametrize_parameters( - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], - force_tuple: bool, - ) -> List["ParameterSet"]: - return [ - ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues + return argnames, force_tuple + + @staticmethod + def _parse_parametrize_parameters( + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + force_tuple: bool, + ) -> List["ParameterSet"]: + return [ + ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] - - @classmethod - def _for_parametrize( - cls, - argnames: Union[str, List[str], Tuple[str, ...]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], - func, - config: Config, - nodeid: str, - ) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]: - argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) - parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) + + @classmethod + def _for_parametrize( + cls, + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + func, + config: Config, + nodeid: str, + ) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]: + argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) + parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues if parameters: - # Check all parameter sets have the correct number of values. + # Check all parameter sets have the correct number of values. for param in parameters: if len(param.values) != len(argnames): - msg = ( - '{nodeid}: in "parametrize" the number of names ({names_len}):\n' - " {names}\n" - "must be equal to the number of values ({values_len}):\n" - " {values}" - ) - fail( - msg.format( - nodeid=nodeid, - values=param.values, - names=argnames, - names_len=len(argnames), - values_len=len(param.values), - ), - pytrace=False, + msg = ( + '{nodeid}: in "parametrize" the number of names ({names_len}):\n' + " {names}\n" + "must be equal to the number of values ({values_len}):\n" + " {values}" ) + fail( + msg.format( + nodeid=nodeid, + values=param.values, + names=argnames, + names_len=len(argnames), + values_len=len(param.values), + ), + pytrace=False, + ) else: - # Empty parameter set (likely computed at runtime): create a single - # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. + # Empty parameter set (likely computed at runtime): create a single + # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. mark = get_empty_parameterset_mark(config, argnames, func) parameters.append( ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) @@ -199,339 +199,339 @@ class ParameterSet( return argnames, parameters -@final +@final @attr.s(frozen=True) -class Mark: - #: Name of the mark. +class Mark: + #: Name of the mark. name = attr.ib(type=str) - #: Positional arguments of the mark decorator. - args = attr.ib(type=Tuple[Any, ...]) - #: Keyword arguments of the mark decorator. - kwargs = attr.ib(type=Mapping[str, Any]) - - #: Source Mark for ids with parametrize Marks. - _param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False) - #: Resolved/generated ids with parametrize Marks. - _param_ids_generated = attr.ib( - type=Optional[Sequence[str]], default=None, repr=False - ) - - def _has_param_ids(self) -> bool: - return "ids" in self.kwargs or len(self.args) >= 4 - - def combined_with(self, other: "Mark") -> "Mark": - """Return a new Mark which is a combination of this - Mark and another Mark. - - Combines by appending args and merging kwargs. - - :param Mark other: The mark to combine with. + #: Positional arguments of the mark decorator. + args = attr.ib(type=Tuple[Any, ...]) + #: Keyword arguments of the mark decorator. + kwargs = attr.ib(type=Mapping[str, Any]) + + #: Source Mark for ids with parametrize Marks. + _param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False) + #: Resolved/generated ids with parametrize Marks. + _param_ids_generated = attr.ib( + type=Optional[Sequence[str]], default=None, repr=False + ) + + def _has_param_ids(self) -> bool: + return "ids" in self.kwargs or len(self.args) >= 4 + + def combined_with(self, other: "Mark") -> "Mark": + """Return a new Mark which is a combination of this + Mark and another Mark. + + Combines by appending args and merging kwargs. + + :param Mark other: The mark to combine with. :rtype: Mark """ assert self.name == other.name - - # Remember source of ids with parametrize Marks. - param_ids_from: Optional[Mark] = None - if self.name == "parametrize": - if other._has_param_ids(): - param_ids_from = other - elif self._has_param_ids(): - param_ids_from = self - + + # Remember source of ids with parametrize Marks. + param_ids_from: Optional[Mark] = None + if self.name == "parametrize": + if other._has_param_ids(): + param_ids_from = other + elif self._has_param_ids(): + param_ids_from = self + return Mark( - self.name, - self.args + other.args, - dict(self.kwargs, **other.kwargs), - param_ids_from=param_ids_from, + self.name, + self.args + other.args, + dict(self.kwargs, **other.kwargs), + param_ids_from=param_ids_from, ) -# A generic parameter designating an object to which a Mark may -# be applied -- a test function (callable) or class. -# Note: a lambda is not allowed, but this can't be represented. -_Markable = TypeVar("_Markable", bound=Union[Callable[..., object], type]) - - +# A generic parameter designating an object to which a Mark may +# be applied -- a test function (callable) or class. +# Note: a lambda is not allowed, but this can't be represented. +_Markable = TypeVar("_Markable", bound=Union[Callable[..., object], type]) + + @attr.s -class MarkDecorator: - """A decorator for applying a mark on test functions and classes. +class MarkDecorator: + """A decorator for applying a mark on test functions and classes. - MarkDecorators are created with ``pytest.mark``:: - - mark1 = pytest.mark.NAME # Simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator + MarkDecorators are created with ``pytest.mark``:: + mark1 = pytest.mark.NAME # Simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator + and can then be applied as decorators to test functions:: @mark2 def test_function(): pass - When a MarkDecorator is called it does the following: - - 1. If called with a single class as its only positional argument and no - additional keyword arguments, it attaches the mark to the class so it - gets applied automatically to all test cases found in that class. - - 2. If called with a single function as its only positional argument and - no additional keyword arguments, it attaches the mark to the function, - containing all the arguments already stored internally in the - MarkDecorator. - - 3. When called in any other case, it returns a new MarkDecorator instance - with the original MarkDecorator's content updated with the arguments - passed to this call. - - Note: The rules above prevent MarkDecorators from storing only a single - function or class reference as their positional argument with no - additional keyword or positional arguments. You can work around this by - using `with_args()`. + When a MarkDecorator is called it does the following: + + 1. If called with a single class as its only positional argument and no + additional keyword arguments, it attaches the mark to the class so it + gets applied automatically to all test cases found in that class. + + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches the mark to the function, + containing all the arguments already stored internally in the + MarkDecorator. + + 3. When called in any other case, it returns a new MarkDecorator instance + with the original MarkDecorator's content updated with the arguments + passed to this call. + + Note: The rules above prevent MarkDecorators from storing only a single + function or class reference as their positional argument with no + additional keyword or positional arguments. You can work around this by + using `with_args()`. """ - mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark)) - - @property - def name(self) -> str: - """Alias for mark.name.""" - return self.mark.name - - @property - def args(self) -> Tuple[Any, ...]: - """Alias for mark.args.""" - return self.mark.args + mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark)) - @property - def kwargs(self) -> Mapping[str, Any]: - """Alias for mark.kwargs.""" - return self.mark.kwargs + @property + def name(self) -> str: + """Alias for mark.name.""" + return self.mark.name @property - def markname(self) -> str: + def args(self) -> Tuple[Any, ...]: + """Alias for mark.args.""" + return self.mark.args + + @property + def kwargs(self) -> Mapping[str, Any]: + """Alias for mark.kwargs.""" + return self.mark.kwargs + + @property + def markname(self) -> str: return self.name # for backward-compat (2.4.1 had this attr) - def __repr__(self) -> str: - return f"<MarkDecorator {self.mark!r}>" + def __repr__(self) -> str: + return f"<MarkDecorator {self.mark!r}>" - def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": - """Return a MarkDecorator with extra arguments added. + def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": + """Return a MarkDecorator with extra arguments added. - Unlike calling the MarkDecorator, with_args() can be used even - if the sole argument is a callable/class. + Unlike calling the MarkDecorator, with_args() can be used even + if the sole argument is a callable/class. - :rtype: MarkDecorator + :rtype: MarkDecorator """ mark = Mark(self.name, args, kwargs) return self.__class__(self.mark.combined_with(mark)) - # Type ignored because the overloads overlap with an incompatible - # return type. Not much we can do about that. Thankfully mypy picks - # the first match so it works out even if we break the rules. - @overload - def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc] - pass - - @overload - def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": - pass - - def __call__(self, *args: object, **kwargs: object): - """Call the MarkDecorator.""" + # Type ignored because the overloads overlap with an incompatible + # return type. Not much we can do about that. Thankfully mypy picks + # the first match so it works out even if we break the rules. + @overload + def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc] + pass + + @overload + def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": + pass + + def __call__(self, *args: object, **kwargs: object): + """Call the MarkDecorator.""" if args and not kwargs: func = args[0] is_class = inspect.isclass(func) if len(args) == 1 and (istestfunc(func) or is_class): - store_mark(func, self.mark) + store_mark(func, self.mark) return func return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj) -> List[Mark]: - """Obtain the unpacked marks that are stored on an object.""" +def get_unpacked_marks(obj) -> List[Mark]: + """Obtain the unpacked marks that are stored on an object.""" mark_list = getattr(obj, "pytestmark", []) if not isinstance(mark_list, list): mark_list = [mark_list] return normalize_mark_list(mark_list) -def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]: - """Normalize marker decorating helpers to mark objects. +def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]: + """Normalize marker decorating helpers to mark objects. - :type List[Union[Mark, Markdecorator]] mark_list: + :type List[Union[Mark, Markdecorator]] mark_list: :rtype: List[Mark] """ - extracted = [ - getattr(mark, "mark", mark) for mark in mark_list - ] # unpack MarkDecorator - for mark in extracted: - if not isinstance(mark, Mark): - raise TypeError(f"got {mark!r} instead of Mark") - return [x for x in extracted if isinstance(x, Mark)] - - -def store_mark(obj, mark: Mark) -> None: - """Store a Mark on an object. - - This is used to implement the Mark declarations/decorators correctly. + extracted = [ + getattr(mark, "mark", mark) for mark in mark_list + ] # unpack MarkDecorator + for mark in extracted: + if not isinstance(mark, Mark): + raise TypeError(f"got {mark!r} instead of Mark") + return [x for x in extracted if isinstance(x, Mark)] + + +def store_mark(obj, mark: Mark) -> None: + """Store a Mark on an object. + + This is used to implement the Mark declarations/decorators correctly. """ assert isinstance(mark, Mark), mark - # Always reassign name to avoid updating pytestmark in a reference that - # was only borrowed. + # Always reassign name to avoid updating pytestmark in a reference that + # was only borrowed. obj.pytestmark = get_unpacked_marks(obj) + [mark] -# Typing for builtin pytest marks. This is cheating; it gives builtin marks -# special privilege, and breaks modularity. But practicality beats purity... -if TYPE_CHECKING: - from _pytest.fixtures import _Scope - - class _SkipMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc] - def __call__(self, arg: _Markable) -> _Markable: - ... - - @overload - def __call__(self, reason: str = ...) -> "MarkDecorator": - ... - - class _SkipifMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], - reason: str = ..., - ) -> MarkDecorator: - ... - - class _XfailMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc] - def __call__(self, arg: _Markable) -> _Markable: - ... - - @overload - def __call__( - self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], - reason: str = ..., - run: bool = ..., - raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ..., - strict: bool = ..., - ) -> MarkDecorator: - ... - - class _ParametrizeMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, - argnames: Union[str, List[str], Tuple[str, ...]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], - *, - indirect: Union[bool, Sequence[str]] = ..., - ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] - ] = ..., - scope: Optional[_Scope] = ..., - ) -> MarkDecorator: - ... - - class _UsefixturesMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, *fixtures: str - ) -> MarkDecorator: - ... - - class _FilterwarningsMarkDecorator(MarkDecorator): - def __call__( # type: ignore[override] - self, *filters: str - ) -> MarkDecorator: - ... - - -@final -class MarkGenerator: - """Factory for :class:`MarkDecorator` objects - exposed as - a ``pytest.mark`` singleton instance. - - Example:: - +# Typing for builtin pytest marks. This is cheating; it gives builtin marks +# special privilege, and breaks modularity. But practicality beats purity... +if TYPE_CHECKING: + from _pytest.fixtures import _Scope + + class _SkipMarkDecorator(MarkDecorator): + @overload # type: ignore[override,misc] + def __call__(self, arg: _Markable) -> _Markable: + ... + + @overload + def __call__(self, reason: str = ...) -> "MarkDecorator": + ... + + class _SkipifMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + condition: Union[str, bool] = ..., + *conditions: Union[str, bool], + reason: str = ..., + ) -> MarkDecorator: + ... + + class _XfailMarkDecorator(MarkDecorator): + @overload # type: ignore[override,misc] + def __call__(self, arg: _Markable) -> _Markable: + ... + + @overload + def __call__( + self, + condition: Union[str, bool] = ..., + *conditions: Union[str, bool], + reason: str = ..., + run: bool = ..., + raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ..., + strict: bool = ..., + ) -> MarkDecorator: + ... + + class _ParametrizeMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + *, + indirect: Union[bool, Sequence[str]] = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + scope: Optional[_Scope] = ..., + ) -> MarkDecorator: + ... + + class _UsefixturesMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, *fixtures: str + ) -> MarkDecorator: + ... + + class _FilterwarningsMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, *filters: str + ) -> MarkDecorator: + ... + + +@final +class MarkGenerator: + """Factory for :class:`MarkDecorator` objects - exposed as + a ``pytest.mark`` singleton instance. + + Example:: + import pytest - + @pytest.mark.slowtest def test_function(): pass - applies a 'slowtest' :class:`Mark` on ``test_function``. - """ - - _config: Optional[Config] = None - _markers: Set[str] = set() - - # See TYPE_CHECKING above. - if TYPE_CHECKING: - skip: _SkipMarkDecorator - skipif: _SkipifMarkDecorator - xfail: _XfailMarkDecorator - parametrize: _ParametrizeMarkDecorator - usefixtures: _UsefixturesMarkDecorator - filterwarnings: _FilterwarningsMarkDecorator - - def __getattr__(self, name: str) -> MarkDecorator: + applies a 'slowtest' :class:`Mark` on ``test_function``. + """ + + _config: Optional[Config] = None + _markers: Set[str] = set() + + # See TYPE_CHECKING above. + if TYPE_CHECKING: + skip: _SkipMarkDecorator + skipif: _SkipifMarkDecorator + xfail: _XfailMarkDecorator + parametrize: _ParametrizeMarkDecorator + usefixtures: _UsefixturesMarkDecorator + filterwarnings: _FilterwarningsMarkDecorator + + def __getattr__(self, name: str) -> MarkDecorator: if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") - + if self._config is not None: - # We store a set of markers as a performance optimisation - if a mark - # name is in the set we definitely know it, but a mark may be known and - # not in the set. We therefore start by updating the set! - if name not in self._markers: - for line in self._config.getini("markers"): - # example lines: "skipif(condition): skip the given test if..." - # or "hypothesis: tests which use Hypothesis", so to get the - # marker name we split on both `:` and `(`. - if line == "ya:external": - marker = line - else: - marker = line.split(":")[0].split("(")[0].strip() - self._markers.add(marker) - - # If the name is not in the set of known marks after updating, - # then it really is time to issue a warning or an error. - if name not in self._markers: - if self._config.option.strict_markers or self._config.option.strict: - fail( - f"{name!r} not found in `markers` configuration option", - pytrace=False, - ) - - # Raise a specific error for common misspellings of "parametrize". - if name in ["parameterize", "parametrise", "parameterise"]: - __tracebackhide__ = True - fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") - - warnings.warn( - "Unknown pytest.mark.%s - is this a typo? You can register " - "custom marks to avoid this warning - for details, see " - "https://docs.pytest.org/en/stable/mark.html" % name, - PytestUnknownMarkWarning, - 2, - ) - + # We store a set of markers as a performance optimisation - if a mark + # name is in the set we definitely know it, but a mark may be known and + # not in the set. We therefore start by updating the set! + if name not in self._markers: + for line in self._config.getini("markers"): + # example lines: "skipif(condition): skip the given test if..." + # or "hypothesis: tests which use Hypothesis", so to get the + # marker name we split on both `:` and `(`. + if line == "ya:external": + marker = line + else: + marker = line.split(":")[0].split("(")[0].strip() + self._markers.add(marker) + + # If the name is not in the set of known marks after updating, + # then it really is time to issue a warning or an error. + if name not in self._markers: + if self._config.option.strict_markers or self._config.option.strict: + fail( + f"{name!r} not found in `markers` configuration option", + pytrace=False, + ) + + # Raise a specific error for common misspellings of "parametrize". + if name in ["parameterize", "parametrise", "parameterise"]: + __tracebackhide__ = True + fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") + + warnings.warn( + "Unknown pytest.mark.%s - is this a typo? You can register " + "custom marks to avoid this warning - for details, see " + "https://docs.pytest.org/en/stable/mark.html" % name, + PytestUnknownMarkWarning, + 2, + ) + return MarkDecorator(Mark(name, (), {})) MARK_GEN = MarkGenerator() -@final -class NodeKeywords(MutableMapping[str, Any]): - def __init__(self, node: "Node") -> None: +@final +class NodeKeywords(MutableMapping[str, Any]): + def __init__(self, node: "Node") -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} - def __getitem__(self, key: str) -> Any: + def __getitem__(self, key: str) -> Any: try: return self._markers[key] except KeyError: @@ -539,24 +539,24 @@ class NodeKeywords(MutableMapping[str, Any]): raise return self.parent.keywords[key] - def __setitem__(self, key: str, value: Any) -> None: + def __setitem__(self, key: str, value: Any) -> None: self._markers[key] = value - def __delitem__(self, key: str) -> None: + def __delitem__(self, key: str) -> None: raise ValueError("cannot delete key in keywords dict") - def __iter__(self) -> Iterator[str]: + def __iter__(self) -> Iterator[str]: seen = self._seen() return iter(seen) - def _seen(self) -> Set[str]: + def _seen(self) -> Set[str]: seen = set(self._markers) if self.parent is not None: seen.update(self.parent.keywords) return seen - def __len__(self) -> int: + def __len__(self) -> int: return len(self._seen()) - def __repr__(self) -> str: - return f"<NodeKeywords for node {self.node}>" + def __repr__(self) -> str: + return f"<NodeKeywords for node {self.node}>" |