From 2586decb8c989ae7ee6a5074e2be822a7a02d404 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 1 Aug 2017 11:52:09 +0200 Subject: [PATCH] introduce attrs as dependency and use it for FixtureFunctionMarker and marks --- _pytest/fixtures.py | 23 ++++++++++++++------- _pytest/mark.py | 50 ++++++++++++++++++++++++++------------------- setup.py | 7 ++++++- 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 5ac93b1a921..4a5fc4fcfe3 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -7,6 +7,7 @@ import warnings import inspect +import attr import _pytest from _pytest._code.code import TerminalRepr from _pytest.compat import ( @@ -820,13 +821,21 @@ def pytest_fixture_setup(fixturedef, request): return result -class FixtureFunctionMarker: - def __init__(self, scope, params, autouse=False, ids=None, name=None): - self.scope = scope - self.params = params - self.autouse = autouse - self.ids = ids - self.name = name +def _ensure_immutable_ids(ids): + if ids is None: + return + if callable(ids): + return ids + return tuple(ids) + + +@attr.s(frozen=True) +class FixtureFunctionMarker(object): + scope = attr.ib() + params = attr.ib(convert=attr.converters.optional(tuple)) + autouse = attr.ib(default=False) + ids = attr.ib(default=None, convert=_ensure_immutable_ids) + name = attr.ib(default=None) def __call__(self, function): if isclass(function): diff --git a/_pytest/mark.py b/_pytest/mark.py index 547a94e4ba4..d3830a620a3 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -3,6 +3,7 @@ import inspect import warnings +import attr from collections import namedtuple from operator import attrgetter from .compat import imap @@ -160,22 +161,26 @@ def pytest_collection_modifyitems(items, config): items[:] = remaining -class MarkMapping: +@attr.s +class MarkMapping(object): """Provides a local mapping for markers where item access resolves to True if the marker is present. """ - def __init__(self, keywords): - mymarks = set() + own_mark_names = attr.ib() + + @classmethod + def from_keywords(cls, keywords): + mark_names = set() for key, value in keywords.items(): if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator): - mymarks.add(key) - self._mymarks = mymarks + mark_names.add(key) + return cls(mark_names) def __getitem__(self, name): - return name in self._mymarks + return name in self.own_mark_names -class KeywordMapping: +class KeywordMapping(object): """Provides a local mapping for keywords. Given a list of names, map any substring of one of these names to True. """ @@ -192,7 +197,7 @@ def __getitem__(self, subname): def matchmark(colitem, markexpr): """Tries to match on any marker names, attached to the given colitem.""" - return eval(markexpr, {}, MarkMapping(colitem.keywords)) + return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords)) def matchkeyword(colitem, keywordexpr): @@ -280,7 +285,21 @@ def istestfunc(func): getattr(func, "__name__", "") != "" -class MarkDecorator: +@attr.s(frozen=True) +class Mark(object): + name = attr.ib() + args = attr.ib() + kwargs = attr.ib() + + def combined_with(self, other): + assert self.name == other.name + return Mark( + self.name, self.args + other.args, + dict(self.kwargs, **other.kwargs)) + + +@attr.s +class MarkDecorator(object): """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be :ref:`retrieved by hooks as item keywords `. @@ -314,9 +333,7 @@ def test_function(): """ - def __init__(self, mark): - assert isinstance(mark, Mark), repr(mark) - self.mark = mark + mark = attr.ib(validator=attr.validators.instance_of(Mark)) name = alias('mark.name') args = alias('mark.args') @@ -396,15 +413,6 @@ def store_legacy_markinfo(func, mark): holder.add_mark(mark) -class Mark(namedtuple('Mark', 'name, args, kwargs')): - - def combined_with(self, other): - assert self.name == other.name - return Mark( - self.name, self.args + other.args, - dict(self.kwargs, **other.kwargs)) - - class MarkInfo(object): """ Marking object created by :class:`MarkDecorator` instances. """ diff --git a/setup.py b/setup.py index 61ae1587f25..c4fc48920fa 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,13 @@ def has_environment_marker_support(): def main(): - install_requires = ['py>=1.4.34', 'six>=1.10.0', 'setuptools'] extras_require = {} + install_requires = [ + 'py>=1.4.33', + 'six>=1.10.0', + 'setuptools', + 'attrs>=17.2.0', + ] # if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy; # used by tox.ini to test with pluggy master if '_PYTEST_SETUP_SKIP_PLUGGY_DEP' not in os.environ: