Skip to content

Commit 07b2b18

Browse files
introduce attrs as dependency and use it
for FixtureFunctionMarker and marks
1 parent cb30848 commit 07b2b18

File tree

3 files changed

+51
-29
lines changed

3 files changed

+51
-29
lines changed

_pytest/fixtures.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import py
88
from py._code.code import FormattedExcinfo
99

10+
import attr
1011
import _pytest
1112
from _pytest import nodes
1213
from _pytest._code.code import TerminalRepr
@@ -822,13 +823,21 @@ def pytest_fixture_setup(fixturedef, request):
822823
return result
823824

824825

825-
class FixtureFunctionMarker:
826-
def __init__(self, scope, params, autouse=False, ids=None, name=None):
827-
self.scope = scope
828-
self.params = params
829-
self.autouse = autouse
830-
self.ids = ids
831-
self.name = name
826+
def _ensure_immutable_ids(ids):
827+
if ids is None:
828+
return
829+
if callable(ids):
830+
return ids
831+
return tuple(ids)
832+
833+
834+
@attr.s(frozen=True)
835+
class FixtureFunctionMarker(object):
836+
scope = attr.ib()
837+
params = attr.ib(convert=attr.converters.optional(tuple))
838+
autouse = attr.ib(default=False)
839+
ids = attr.ib(default=None, convert=_ensure_immutable_ids)
840+
name = attr.ib(default=None)
832841

833842
def __call__(self, function):
834843
if isclass(function):

_pytest/mark.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import inspect
55
import warnings
6+
import attr
67
from collections import namedtuple
78
from operator import attrgetter
89
from six.moves import map
@@ -160,22 +161,26 @@ def pytest_collection_modifyitems(items, config):
160161
items[:] = remaining
161162

162163

163-
class MarkMapping:
164+
@attr.s
165+
class MarkMapping(object):
164166
"""Provides a local mapping for markers where item access
165167
resolves to True if the marker is present. """
166168

167-
def __init__(self, keywords):
168-
mymarks = set()
169+
own_mark_names = attr.ib()
170+
171+
@classmethod
172+
def from_keywords(cls, keywords):
173+
mark_names = set()
169174
for key, value in keywords.items():
170175
if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
171-
mymarks.add(key)
172-
self._mymarks = mymarks
176+
mark_names.add(key)
177+
return cls(mark_names)
173178

174179
def __getitem__(self, name):
175-
return name in self._mymarks
180+
return name in self.own_mark_names
176181

177182

178-
class KeywordMapping:
183+
class KeywordMapping(object):
179184
"""Provides a local mapping for keywords.
180185
Given a list of names, map any substring of one of these names to True.
181186
"""
@@ -192,7 +197,7 @@ def __getitem__(self, subname):
192197

193198
def matchmark(colitem, markexpr):
194199
"""Tries to match on any marker names, attached to the given colitem."""
195-
return eval(markexpr, {}, MarkMapping(colitem.keywords))
200+
return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords))
196201

197202

198203
def matchkeyword(colitem, keywordexpr):
@@ -280,7 +285,21 @@ def istestfunc(func):
280285
getattr(func, "__name__", "<lambda>") != "<lambda>"
281286

282287

283-
class MarkDecorator:
288+
@attr.s(frozen=True)
289+
class Mark(object):
290+
name = attr.ib()
291+
args = attr.ib()
292+
kwargs = attr.ib()
293+
294+
def combined_with(self, other):
295+
assert self.name == other.name
296+
return Mark(
297+
self.name, self.args + other.args,
298+
dict(self.kwargs, **other.kwargs))
299+
300+
301+
@attr.s
302+
class MarkDecorator(object):
284303
""" A decorator for test functions and test classes. When applied
285304
it will create :class:`MarkInfo` objects which may be
286305
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
@@ -314,9 +333,7 @@ def test_function():
314333
315334
"""
316335

317-
def __init__(self, mark):
318-
assert isinstance(mark, Mark), repr(mark)
319-
self.mark = mark
336+
mark = attr.ib(validator=attr.validators.instance_of(Mark))
320337

321338
name = alias('mark.name')
322339
args = alias('mark.args')
@@ -396,15 +413,6 @@ def store_legacy_markinfo(func, mark):
396413
holder.add_mark(mark)
397414

398415

399-
class Mark(namedtuple('Mark', 'name, args, kwargs')):
400-
401-
def combined_with(self, other):
402-
assert self.name == other.name
403-
return Mark(
404-
self.name, self.args + other.args,
405-
dict(self.kwargs, **other.kwargs))
406-
407-
408416
class MarkInfo(object):
409417
""" Marking object created by :class:`MarkDecorator` instances. """
410418

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ def has_environment_marker_support():
4343

4444

4545
def main():
46-
install_requires = ['py>=1.4.34', 'six>=1.10.0', 'setuptools']
4746
extras_require = {}
47+
install_requires = [
48+
'py>=1.4.33',
49+
'six>=1.10.0',
50+
'setuptools',
51+
'attrs>=17.2.0',
52+
]
4853
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
4954
# used by tox.ini to test with pluggy master
5055
if '_PYTEST_SETUP_SKIP_PLUGGY_DEP' not in os.environ:

0 commit comments

Comments
 (0)