Skip to content

Commit c33074c

Browse files
authored
Merge pull request #2641 from RonnyPfannschmidt/introduce-attrs
[RFC] Introduce attrs
2 parents b18a9de + e351976 commit c33074c

File tree

4 files changed

+52
-29
lines changed

4 files changed

+52
-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
@@ -185,22 +186,26 @@ def pytest_collection_modifyitems(items, config):
185186
items[:] = remaining
186187

187188

188-
class MarkMapping:
189+
@attr.s
190+
class MarkMapping(object):
189191
"""Provides a local mapping for markers where item access
190192
resolves to True if the marker is present. """
191193

192-
def __init__(self, keywords):
193-
mymarks = set()
194+
own_mark_names = attr.ib()
195+
196+
@classmethod
197+
def from_keywords(cls, keywords):
198+
mark_names = set()
194199
for key, value in keywords.items():
195200
if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
196-
mymarks.add(key)
197-
self._mymarks = mymarks
201+
mark_names.add(key)
202+
return cls(mark_names)
198203

199204
def __getitem__(self, name):
200-
return name in self._mymarks
205+
return name in self.own_mark_names
201206

202207

203-
class KeywordMapping:
208+
class KeywordMapping(object):
204209
"""Provides a local mapping for keywords.
205210
Given a list of names, map any substring of one of these names to True.
206211
"""
@@ -217,7 +222,7 @@ def __getitem__(self, subname):
217222

218223
def matchmark(colitem, markexpr):
219224
"""Tries to match on any marker names, attached to the given colitem."""
220-
return eval(markexpr, {}, MarkMapping(colitem.keywords))
225+
return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords))
221226

222227

223228
def matchkeyword(colitem, keywordexpr):
@@ -306,7 +311,21 @@ def istestfunc(func):
306311
getattr(func, "__name__", "<lambda>") != "<lambda>"
307312

308313

309-
class MarkDecorator:
314+
@attr.s(frozen=True)
315+
class Mark(object):
316+
name = attr.ib()
317+
args = attr.ib()
318+
kwargs = attr.ib()
319+
320+
def combined_with(self, other):
321+
assert self.name == other.name
322+
return Mark(
323+
self.name, self.args + other.args,
324+
dict(self.kwargs, **other.kwargs))
325+
326+
327+
@attr.s
328+
class MarkDecorator(object):
310329
""" A decorator for test functions and test classes. When applied
311330
it will create :class:`MarkInfo` objects which may be
312331
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
@@ -340,9 +359,7 @@ def test_function():
340359
341360
"""
342361

343-
def __init__(self, mark):
344-
assert isinstance(mark, Mark), repr(mark)
345-
self.mark = mark
362+
mark = attr.ib(validator=attr.validators.instance_of(Mark))
346363

347364
name = alias('mark.name')
348365
args = alias('mark.args')
@@ -422,15 +439,6 @@ def store_legacy_markinfo(func, mark):
422439
holder.add_mark(mark)
423440

424441

425-
class Mark(namedtuple('Mark', 'name, args, kwargs')):
426-
427-
def combined_with(self, other):
428-
assert self.name == other.name
429-
return Mark(
430-
self.name, self.args + other.args,
431-
dict(self.kwargs, **other.kwargs))
432-
433-
434442
class MarkInfo(object):
435443
""" Marking object created by :class:`MarkDecorator` instances. """
436444

changelog/2641.trivial

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal structures to ease code maintainability.

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)