From afc607cfd81458d4e4f3b1f3cf8cc931b933907e Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 17 Dec 2017 15:19:01 +0100 Subject: [PATCH 1/2] move node base classes from main to nodes --- _pytest/fixtures.py | 3 +- _pytest/hookspec.py | 2 +- _pytest/main.py | 370 +------------------------------------ _pytest/nodes.py | 361 ++++++++++++++++++++++++++++++++++++ _pytest/python.py | 8 +- doc/en/writing_plugins.rst | 8 +- pytest.py | 3 +- testing/python/collect.py | 7 +- testing/test_resultlog.py | 2 +- tox.ini | 1 + 10 files changed, 386 insertions(+), 379 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index a7a63f505..a2b6f7d94 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -26,11 +26,12 @@ from _pytest.outcomes import fail, TEST_OUTCOME def pytest_sessionstart(session): import _pytest.python + import _pytest.nodes scopename2class.update({ 'class': _pytest.python.Class, 'module': _pytest.python.Module, - 'function': _pytest.main.Item, + 'function': _pytest.nodes.Item, 'session': _pytest.main.Session, }) session._fixturemanager = FixtureManager(session) diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index f004dd097..ec4659159 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -153,7 +153,7 @@ def pytest_collection_modifyitems(session, config, items): :param _pytest.main.Session session: the pytest session object :param _pytest.config.Config config: pytest config object - :param List[_pytest.main.Item] items: list of item objects + :param List[_pytest.nodes.Item] items: list of item objects """ diff --git a/_pytest/main.py b/_pytest/main.py index 142240c99..fce4f35f3 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -12,16 +12,11 @@ import _pytest from _pytest import nodes import _pytest._code import py -try: - from collections import MutableMapping as MappingMixin -except ImportError: - from UserDict import DictMixin as MappingMixin from _pytest.config import directory_arg, UsageError, hookimpl from _pytest.outcomes import exit from _pytest.runner import collect_one_node -tracebackcutdir = py.path.local(_pytest.__file__).dirpath() # exitcodes for the command line EXIT_OK = 0 @@ -260,356 +255,6 @@ class FSHookProxy: return x -class _CompatProperty(object): - def __init__(self, name): - self.name = name - - def __get__(self, obj, owner): - if obj is None: - return self - - # TODO: reenable in the features branch - # warnings.warn( - # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( - # name=self.name, owner=type(owner).__name__), - # PendingDeprecationWarning, stacklevel=2) - return getattr(__import__('pytest'), self.name) - - -class NodeKeywords(MappingMixin): - def __init__(self, node): - self.node = node - self.parent = node.parent - self._markers = {node.name: True} - - def __getitem__(self, key): - try: - return self._markers[key] - except KeyError: - if self.parent is None: - raise - return self.parent.keywords[key] - - def __setitem__(self, key, value): - self._markers[key] = value - - def __delitem__(self, key): - raise ValueError("cannot delete key in keywords dict") - - def __iter__(self): - seen = set(self._markers) - if self.parent is not None: - seen.update(self.parent.keywords) - return iter(seen) - - def __len__(self): - return len(self.__iter__()) - - def keys(self): - return list(self) - - def __repr__(self): - return "" % (self.node, ) - - -class Node(object): - """ base class for Collector and Item the test collection tree. - Collector subclasses have children, Items are terminal nodes.""" - - def __init__(self, name, parent=None, config=None, session=None): - #: a unique name within the scope of the parent node - self.name = name - - #: the parent collector node. - self.parent = parent - - #: the pytest config object - self.config = config or parent.config - - #: the session this node is part of - self.session = session or parent.session - - #: filesystem path where this node was collected from (can be None) - self.fspath = getattr(parent, 'fspath', None) - - #: keywords/markers collected from all scopes - self.keywords = NodeKeywords(self) - - #: allow adding of extra keywords to use for matching - self.extra_keyword_matches = set() - - # used for storing artificial fixturedefs for direct parametrization - self._name2pseudofixturedef = {} - - @property - def ihook(self): - """ fspath sensitive hook proxy used to call pytest hooks""" - return self.session.gethookproxy(self.fspath) - - Module = _CompatProperty("Module") - Class = _CompatProperty("Class") - Instance = _CompatProperty("Instance") - Function = _CompatProperty("Function") - File = _CompatProperty("File") - Item = _CompatProperty("Item") - - def _getcustomclass(self, name): - maybe_compatprop = getattr(type(self), name) - if isinstance(maybe_compatprop, _CompatProperty): - return getattr(__import__('pytest'), name) - else: - cls = getattr(self, name) - # TODO: reenable in the features branch - # warnings.warn("use of node.%s is deprecated, " - # "use pytest_pycollect_makeitem(...) to create custom " - # "collection nodes" % name, category=DeprecationWarning) - return cls - - def __repr__(self): - return "<%s %r>" % (self.__class__.__name__, - getattr(self, 'name', None)) - - def warn(self, code, message): - """ generate a warning with the given code and message for this - item. """ - assert isinstance(code, str) - fslocation = getattr(self, "location", None) - if fslocation is None: - fslocation = getattr(self, "fspath", None) - self.ihook.pytest_logwarning.call_historic(kwargs=dict( - code=code, message=message, - nodeid=self.nodeid, fslocation=fslocation)) - - # methods for ordering nodes - @property - def nodeid(self): - """ a ::-separated string denoting its collection tree address. """ - try: - return self._nodeid - except AttributeError: - self._nodeid = x = self._makeid() - return x - - def _makeid(self): - return self.parent.nodeid + "::" + self.name - - def __hash__(self): - return hash(self.nodeid) - - def setup(self): - pass - - def teardown(self): - pass - - def listchain(self): - """ return list of all parent collectors up to self, - starting from root of collection tree. """ - chain = [] - item = self - while item is not None: - chain.append(item) - item = item.parent - chain.reverse() - return chain - - def add_marker(self, marker): - """ dynamically add a marker object to the node. - - ``marker`` can be a string or pytest.mark.* instance. - """ - from _pytest.mark import MarkDecorator, MARK_GEN - if isinstance(marker, six.string_types): - marker = getattr(MARK_GEN, marker) - elif not isinstance(marker, MarkDecorator): - raise ValueError("is not a string or pytest.mark.* Marker") - self.keywords[marker.name] = marker - - def get_marker(self, name): - """ get a marker object from this node or None if - the node doesn't have a marker with that name. """ - val = self.keywords.get(name, None) - if val is not None: - from _pytest.mark import MarkInfo, MarkDecorator - if isinstance(val, (MarkDecorator, MarkInfo)): - return val - - def listextrakeywords(self): - """ Return a set of all extra keywords in self and any parents.""" - extra_keywords = set() - item = self - for item in self.listchain(): - extra_keywords.update(item.extra_keyword_matches) - return extra_keywords - - def listnames(self): - return [x.name for x in self.listchain()] - - def addfinalizer(self, fin): - """ register a function to be called when this node is finalized. - - This method can only be called when this node is active - in a setup chain, for example during self.setup(). - """ - self.session._setupstate.addfinalizer(fin, self) - - def getparent(self, cls): - """ get the next parent node (including ourself) - which is an instance of the given class""" - current = self - while current and not isinstance(current, cls): - current = current.parent - return current - - def _prunetraceback(self, excinfo): - pass - - def _repr_failure_py(self, excinfo, style=None): - fm = self.session._fixturemanager - if excinfo.errisinstance(fm.FixtureLookupError): - return excinfo.value.formatrepr() - tbfilter = True - if self.config.option.fulltrace: - style = "long" - else: - tb = _pytest._code.Traceback([excinfo.traceback[-1]]) - self._prunetraceback(excinfo) - if len(excinfo.traceback) == 0: - excinfo.traceback = tb - tbfilter = False # prunetraceback already does it - if style == "auto": - style = "long" - # XXX should excinfo.getrepr record all data and toterminal() process it? - if style is None: - if self.config.option.tbstyle == "short": - style = "short" - else: - style = "long" - - try: - os.getcwd() - abspath = False - except OSError: - abspath = True - - return excinfo.getrepr(funcargs=True, abspath=abspath, - showlocals=self.config.option.showlocals, - style=style, tbfilter=tbfilter) - - repr_failure = _repr_failure_py - - -class Collector(Node): - """ Collector instances create children through collect() - and thus iteratively build a tree. - """ - - class CollectError(Exception): - """ an error during collection, contains a custom message. """ - - def collect(self): - """ returns a list of children (items and collectors) - for this collection node. - """ - raise NotImplementedError("abstract") - - def repr_failure(self, excinfo): - """ represent a collection failure. """ - if excinfo.errisinstance(self.CollectError): - exc = excinfo.value - return str(exc.args[0]) - return self._repr_failure_py(excinfo, style="short") - - def _prunetraceback(self, excinfo): - if hasattr(self, 'fspath'): - traceback = excinfo.traceback - ntraceback = traceback.cut(path=self.fspath) - if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - excinfo.traceback = ntraceback.filter() - - -class FSCollector(Collector): - def __init__(self, fspath, parent=None, config=None, session=None): - fspath = py.path.local(fspath) # xxx only for test_resultlog.py? - name = fspath.basename - if parent is not None: - rel = fspath.relto(parent.fspath) - if rel: - name = rel - name = name.replace(os.sep, nodes.SEP) - super(FSCollector, self).__init__(name, parent, config, session) - self.fspath = fspath - - def _check_initialpaths_for_relpath(self): - for initialpath in self.session._initialpaths: - if self.fspath.common(initialpath) == initialpath: - return self.fspath.relto(initialpath.dirname) - - def _makeid(self): - relpath = self.fspath.relto(self.config.rootdir) - - if not relpath: - relpath = self._check_initialpaths_for_relpath() - if os.sep != nodes.SEP: - relpath = relpath.replace(os.sep, nodes.SEP) - return relpath - - -class File(FSCollector): - """ base class for collecting tests from a file. """ - - -class Item(Node): - """ a basic test invocation item. Note that for a single function - there might be multiple test invocation items. - """ - nextitem = None - - def __init__(self, name, parent=None, config=None, session=None): - super(Item, self).__init__(name, parent, config, session) - self._report_sections = [] - - def add_report_section(self, when, key, content): - """ - Adds a new report section, similar to what's done internally to add stdout and - stderr captured output:: - - item.add_report_section("call", "stdout", "report section contents") - - :param str when: - One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. - :param str key: - Name of the section, can be customized at will. Pytest uses ``"stdout"`` and - ``"stderr"`` internally. - - :param str content: - The full contents as a string. - """ - if content: - self._report_sections.append((when, key, content)) - - def reportinfo(self): - return self.fspath, None, "" - - @property - def location(self): - try: - return self._location - except AttributeError: - location = self.reportinfo() - # bestrelpath is a quite slow function - cache = self.config.__dict__.setdefault("_bestrelpathcache", {}) - try: - fspath = cache[location[0]] - except KeyError: - fspath = self.session.fspath.bestrelpath(location[0]) - cache[location[0]] = fspath - location = (fspath, location[1], str(location[2])) - self._location = location - return location - - class NoMatch(Exception): """ raised if matching cannot locate a matching names. """ @@ -623,13 +268,14 @@ class Failed(Exception): """ signals an stop as failed test run. """ -class Session(FSCollector): +class Session(nodes.FSCollector): Interrupted = Interrupted Failed = Failed def __init__(self, config): - FSCollector.__init__(self, config.rootdir, parent=None, - config=config, session=self) + nodes.FSCollector.__init__( + self, config.rootdir, parent=None, + config=config, session=self) self.testsfailed = 0 self.testscollected = 0 self.shouldstop = False @@ -826,11 +472,11 @@ class Session(FSCollector): nextnames = names[1:] resultnodes = [] for node in matching: - if isinstance(node, Item): + if isinstance(node, nodes.Item): if not names: resultnodes.append(node) continue - assert isinstance(node, Collector) + assert isinstance(node, nodes.Collector) rep = collect_one_node(node) if rep.passed: has_matched = False @@ -852,11 +498,11 @@ class Session(FSCollector): def genitems(self, node): self.trace("genitems", node) - if isinstance(node, Item): + if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) yield node else: - assert isinstance(node, Collector) + assert isinstance(node, nodes.Collector) rep = collect_one_node(node) if rep.passed: for subnode in rep.result: diff --git a/_pytest/nodes.py b/_pytest/nodes.py index ad3af2ce6..9b41664c1 100644 --- a/_pytest/nodes.py +++ b/_pytest/nodes.py @@ -1,5 +1,16 @@ +from __future__ import absolute_import, division, print_function +from collections import MutableMapping as MappingMixin + +import os + +import six +import py +import _pytest + SEP = "/" +tracebackcutdir = py.path.local(_pytest.__file__).dirpath() + def _splitnode(nodeid): """Split a nodeid into constituent 'parts'. @@ -35,3 +46,353 @@ def ischildnode(baseid, nodeid): if len(node_parts) < len(base_parts): return False return node_parts[:len(base_parts)] == base_parts + + +class _CompatProperty(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, owner): + if obj is None: + return self + + # TODO: reenable in the features branch + # warnings.warn( + # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( + # name=self.name, owner=type(owner).__name__), + # PendingDeprecationWarning, stacklevel=2) + return getattr(__import__('pytest'), self.name) + + +class NodeKeywords(MappingMixin): + def __init__(self, node): + self.node = node + self.parent = node.parent + self._markers = {node.name: True} + + def __getitem__(self, key): + try: + return self._markers[key] + except KeyError: + if self.parent is None: + raise + return self.parent.keywords[key] + + def __setitem__(self, key, value): + self._markers[key] = value + + def __delitem__(self, key): + raise ValueError("cannot delete key in keywords dict") + + def __iter__(self): + seen = set(self._markers) + if self.parent is not None: + seen.update(self.parent.keywords) + return iter(seen) + + def __len__(self): + return len(self.__iter__()) + + def keys(self): + return list(self) + + def __repr__(self): + return "" % (self.node, ) + + +class Node(object): + """ base class for Collector and Item the test collection tree. + Collector subclasses have children, Items are terminal nodes.""" + + def __init__(self, name, parent=None, config=None, session=None): + #: a unique name within the scope of the parent node + self.name = name + + #: the parent collector node. + self.parent = parent + + #: the pytest config object + self.config = config or parent.config + + #: the session this node is part of + self.session = session or parent.session + + #: filesystem path where this node was collected from (can be None) + self.fspath = getattr(parent, 'fspath', None) + + #: keywords/markers collected from all scopes + self.keywords = NodeKeywords(self) + + #: allow adding of extra keywords to use for matching + self.extra_keyword_matches = set() + + # used for storing artificial fixturedefs for direct parametrization + self._name2pseudofixturedef = {} + + @property + def ihook(self): + """ fspath sensitive hook proxy used to call pytest hooks""" + return self.session.gethookproxy(self.fspath) + + Module = _CompatProperty("Module") + Class = _CompatProperty("Class") + Instance = _CompatProperty("Instance") + Function = _CompatProperty("Function") + File = _CompatProperty("File") + Item = _CompatProperty("Item") + + def _getcustomclass(self, name): + maybe_compatprop = getattr(type(self), name) + if isinstance(maybe_compatprop, _CompatProperty): + return getattr(__import__('pytest'), name) + else: + cls = getattr(self, name) + # TODO: reenable in the features branch + # warnings.warn("use of node.%s is deprecated, " + # "use pytest_pycollect_makeitem(...) to create custom " + # "collection nodes" % name, category=DeprecationWarning) + return cls + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, + getattr(self, 'name', None)) + + def warn(self, code, message): + """ generate a warning with the given code and message for this + item. """ + assert isinstance(code, str) + fslocation = getattr(self, "location", None) + if fslocation is None: + fslocation = getattr(self, "fspath", None) + self.ihook.pytest_logwarning.call_historic(kwargs=dict( + code=code, message=message, + nodeid=self.nodeid, fslocation=fslocation)) + + # methods for ordering nodes + @property + def nodeid(self): + """ a ::-separated string denoting its collection tree address. """ + try: + return self._nodeid + except AttributeError: + self._nodeid = x = self._makeid() + return x + + def _makeid(self): + return self.parent.nodeid + "::" + self.name + + def __hash__(self): + return hash(self.nodeid) + + def setup(self): + pass + + def teardown(self): + pass + + def listchain(self): + """ return list of all parent collectors up to self, + starting from root of collection tree. """ + chain = [] + item = self + while item is not None: + chain.append(item) + item = item.parent + chain.reverse() + return chain + + def add_marker(self, marker): + """ dynamically add a marker object to the node. + + ``marker`` can be a string or pytest.mark.* instance. + """ + from _pytest.mark import MarkDecorator, MARK_GEN + if isinstance(marker, six.string_types): + marker = getattr(MARK_GEN, marker) + elif not isinstance(marker, MarkDecorator): + raise ValueError("is not a string or pytest.mark.* Marker") + self.keywords[marker.name] = marker + + def get_marker(self, name): + """ get a marker object from this node or None if + the node doesn't have a marker with that name. """ + val = self.keywords.get(name, None) + if val is not None: + from _pytest.mark import MarkInfo, MarkDecorator + if isinstance(val, (MarkDecorator, MarkInfo)): + return val + + def listextrakeywords(self): + """ Return a set of all extra keywords in self and any parents.""" + extra_keywords = set() + item = self + for item in self.listchain(): + extra_keywords.update(item.extra_keyword_matches) + return extra_keywords + + def listnames(self): + return [x.name for x in self.listchain()] + + def addfinalizer(self, fin): + """ register a function to be called when this node is finalized. + + This method can only be called when this node is active + in a setup chain, for example during self.setup(). + """ + self.session._setupstate.addfinalizer(fin, self) + + def getparent(self, cls): + """ get the next parent node (including ourself) + which is an instance of the given class""" + current = self + while current and not isinstance(current, cls): + current = current.parent + return current + + def _prunetraceback(self, excinfo): + pass + + def _repr_failure_py(self, excinfo, style=None): + fm = self.session._fixturemanager + if excinfo.errisinstance(fm.FixtureLookupError): + return excinfo.value.formatrepr() + tbfilter = True + if self.config.option.fulltrace: + style = "long" + else: + tb = _pytest._code.Traceback([excinfo.traceback[-1]]) + self._prunetraceback(excinfo) + if len(excinfo.traceback) == 0: + excinfo.traceback = tb + tbfilter = False # prunetraceback already does it + if style == "auto": + style = "long" + # XXX should excinfo.getrepr record all data and toterminal() process it? + if style is None: + if self.config.option.tbstyle == "short": + style = "short" + else: + style = "long" + + try: + os.getcwd() + abspath = False + except OSError: + abspath = True + + return excinfo.getrepr(funcargs=True, abspath=abspath, + showlocals=self.config.option.showlocals, + style=style, tbfilter=tbfilter) + + repr_failure = _repr_failure_py + + +class Collector(Node): + """ Collector instances create children through collect() + and thus iteratively build a tree. + """ + + class CollectError(Exception): + """ an error during collection, contains a custom message. """ + + def collect(self): + """ returns a list of children (items and collectors) + for this collection node. + """ + raise NotImplementedError("abstract") + + def repr_failure(self, excinfo): + """ represent a collection failure. """ + if excinfo.errisinstance(self.CollectError): + exc = excinfo.value + return str(exc.args[0]) + return self._repr_failure_py(excinfo, style="short") + + def _prunetraceback(self, excinfo): + if hasattr(self, 'fspath'): + traceback = excinfo.traceback + ntraceback = traceback.cut(path=self.fspath) + if ntraceback == traceback: + ntraceback = ntraceback.cut(excludepath=tracebackcutdir) + excinfo.traceback = ntraceback.filter() + + +class FSCollector(Collector): + def __init__(self, fspath, parent=None, config=None, session=None): + fspath = py.path.local(fspath) # xxx only for test_resultlog.py? + name = fspath.basename + if parent is not None: + rel = fspath.relto(parent.fspath) + if rel: + name = rel + name = name.replace(os.sep, SEP) + super(FSCollector, self).__init__(name, parent, config, session) + self.fspath = fspath + + def _check_initialpaths_for_relpath(self): + for initialpath in self.session._initialpaths: + if self.fspath.common(initialpath) == initialpath: + return self.fspath.relto(initialpath.dirname) + + def _makeid(self): + relpath = self.fspath.relto(self.config.rootdir) + + if not relpath: + relpath = self._check_initialpaths_for_relpath() + if os.sep != SEP: + relpath = relpath.replace(os.sep, SEP) + return relpath + + +class File(FSCollector): + """ base class for collecting tests from a file. """ + + +class Item(Node): + """ a basic test invocation item. Note that for a single function + there might be multiple test invocation items. + """ + nextitem = None + + def __init__(self, name, parent=None, config=None, session=None): + super(Item, self).__init__(name, parent, config, session) + self._report_sections = [] + + def add_report_section(self, when, key, content): + """ + Adds a new report section, similar to what's done internally to add stdout and + stderr captured output:: + + item.add_report_section("call", "stdout", "report section contents") + + :param str when: + One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. + :param str key: + Name of the section, can be customized at will. Pytest uses ``"stdout"`` and + ``"stderr"`` internally. + + :param str content: + The full contents as a string. + """ + if content: + self._report_sections.append((when, key, content)) + + def reportinfo(self): + return self.fspath, None, "" + + @property + def location(self): + try: + return self._location + except AttributeError: + location = self.reportinfo() + # bestrelpath is a quite slow function + cache = self.config.__dict__.setdefault("_bestrelpathcache", {}) + try: + fspath = cache[location[0]] + except KeyError: + fspath = self.session.fspath.bestrelpath(location[0]) + cache[location[0]] = fspath + location = (fspath, location[1], str(location[2])) + self._location = location + return location diff --git a/_pytest/python.py b/_pytest/python.py index 57ebcfbb3..9a89ae10f 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -19,7 +19,7 @@ from _pytest.config import hookimpl import _pytest import pluggy from _pytest import fixtures -from _pytest import main +from _pytest import nodes from _pytest import deprecated from _pytest.compat import ( isclass, isfunction, is_generator, ascii_escaped, @@ -261,7 +261,7 @@ class PyobjMixin(PyobjContext): return fspath, lineno, modpath -class PyCollector(PyobjMixin, main.Collector): +class PyCollector(PyobjMixin, nodes.Collector): def funcnamefilter(self, name): return self._matches_prefix_or_glob_option('python_functions', name) @@ -386,7 +386,7 @@ class PyCollector(PyobjMixin, main.Collector): ) -class Module(main.File, PyCollector): +class Module(nodes.File, PyCollector): """ Collector for test classes and functions. """ def _getobj(self): @@ -1090,7 +1090,7 @@ def write_docstring(tw, doc): tw.write(INDENT + line + "\n") -class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr): +class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): """ a Function Item is responsible for setting up and executing a Python test function. """ diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 8320d2c6a..0b3bafa37 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -681,14 +681,14 @@ Reference of objects involved in hooks .. autoclass:: _pytest.config.Parser() :members: -.. autoclass:: _pytest.main.Node() +.. autoclass:: _pytest.nodes.Node() :members: -.. autoclass:: _pytest.main.Collector() +.. autoclass:: _pytest.nodes.Collector() :members: :show-inheritance: -.. autoclass:: _pytest.main.FSCollector() +.. autoclass:: _pytest.nodes.FSCollector() :members: :show-inheritance: @@ -696,7 +696,7 @@ Reference of objects involved in hooks :members: :show-inheritance: -.. autoclass:: _pytest.main.Item() +.. autoclass:: _pytest.nodes.Item() :members: :show-inheritance: diff --git a/pytest.py b/pytest.py index 2b681b64b..d3aebbff9 100644 --- a/pytest.py +++ b/pytest.py @@ -18,7 +18,8 @@ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call from _pytest.outcomes import fail, skip, importorskip, exit, xfail from _pytest.mark import MARK_GEN as mark, param -from _pytest.main import Item, Collector, File, Session +from _pytest.main import Session +from _pytest.nodes import Item, Collector, File from _pytest.fixtures import fillfixtures as _fillfuncargs from _pytest.python import ( Module, Class, Instance, Function, Generator, diff --git a/testing/python/collect.py b/testing/python/collect.py index 16c2154b8..815fb5467 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -6,11 +6,8 @@ from textwrap import dedent import _pytest._code import py import pytest -from _pytest.main import ( - Collector, - EXIT_NOTESTSCOLLECTED -) - +from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.nodes import Collector ignore_parametrized_marks = pytest.mark.filterwarnings('ignore:Applying marks directly to parameters') diff --git a/testing/test_resultlog.py b/testing/test_resultlog.py index b7dd2687c..45fed7078 100644 --- a/testing/test_resultlog.py +++ b/testing/test_resultlog.py @@ -4,7 +4,7 @@ import os import _pytest._code import py import pytest -from _pytest.main import Node, Item, FSCollector +from _pytest.nodes import Node, Item, FSCollector from _pytest.resultlog import generic_path, ResultLog, \ pytest_configure, pytest_unconfigure diff --git a/tox.ini b/tox.ini index 38ebaf69f..f9ae03739 100644 --- a/tox.ini +++ b/tox.ini @@ -129,6 +129,7 @@ basepython = python changedir = doc/en deps = sphinx + attrs PyYAML commands = From 94608c6110ac45e52c752501c3c50d9df977436a Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 18 Dec 2017 10:20:15 +0100 Subject: [PATCH 2/2] port _Compatproperty to attrs --- _pytest/nodes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/_pytest/nodes.py b/_pytest/nodes.py index 9b41664c1..e836cd4d6 100644 --- a/_pytest/nodes.py +++ b/_pytest/nodes.py @@ -1,12 +1,14 @@ from __future__ import absolute_import, division, print_function from collections import MutableMapping as MappingMixin - import os import six import py +import attr + import _pytest + SEP = "/" tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -48,9 +50,9 @@ def ischildnode(baseid, nodeid): return node_parts[:len(base_parts)] == base_parts +@attr.s class _CompatProperty(object): - def __init__(self, name): - self.name = name + name = attr.ib() def __get__(self, obj, owner): if obj is None: