pytest-pyspec 0.5.2__py3-none-any.whl → 0.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pytest_pyspec/item.py CHANGED
@@ -1,51 +1,89 @@
1
1
  from types import ModuleType
2
2
  from typing import Dict, List
3
+
4
+ import re
5
+
3
6
  import pytest
4
7
 
5
8
  class Item:
9
+ """
10
+ Wrapper around a pytest.Item that provides a human-friendly description.
11
+
12
+ Responsibilities:
13
+ - Derive a readable description for a test or container from either the
14
+ docstring of the underlying object or its Python name.
15
+ - Provide helpers to normalize names (convert CamelCase/snake_case into
16
+ words), strip common pytest prefixes, and keep a consistent
17
+ representation.
18
+ """
19
+
6
20
  def __init__(self, item: pytest.Item) -> None:
7
21
  self._item = item
8
22
 
9
23
  @property
10
- def description(self):
11
- description = self._item.obj.__doc__
12
-
13
- if not description:
14
- description = self._item.name
15
- description = self._parse_description(description)
24
+ def description(self) -> str:
25
+ """
26
+ Return a human-readable description for the wrapped pytest item.
16
27
 
17
- description = description.splitlines()[0]
18
- description = description.capitalize()
19
- return description
28
+ Preference order:
29
+ 1) First non-empty line of the object's docstring, stripped.
30
+ 2) A description derived from the Python name of the item.
31
+ """
32
+ docstring = getattr(self._item.obj, "__doc__", None)
33
+ if docstring:
34
+ return self._parse_docstring(docstring)
35
+ return self._parse_itemname(self._item.name)
20
36
 
21
- def _parse_description(self, description: str):
22
- split = False
23
- if description.lower().startswith("it_"):
24
- description = description.replace('it_', '')
25
- split = True
37
+ def _parse_docstring(self, docstring: str) -> str:
38
+ """
39
+ Return the first line of a docstring, trimmed of surrounding spaces.
40
+ """
41
+ first_line = docstring.splitlines()[0]
42
+ return first_line.strip()
26
43
 
27
- if description.lower().startswith("test_it_"):
28
- description = description.replace('test_it_', '')
29
- split = True
44
+ def _parse_name(self, name: str) -> str:
45
+ """
46
+ Normalize a Python identifier into words.
30
47
 
31
- if description.lower().startswith("test_describe_"):
32
- description = description.replace('test_describe_', '')
33
- split = True
34
-
35
- if description.lower().startswith("describe_"):
36
- description = description.replace('describe_', '')
37
- split = True
48
+ Steps:
49
+ - Insert underscores before internal capital letters (turn "MyClass"
50
+ -> "My_Class").
51
+ - Lowercase everything.
52
+ - Replace underscores with single spaces and normalize multiple
53
+ underscores.
54
+ """
55
+ description = re.sub(
56
+ r'(?!^[A-Z])([A-Z])',
57
+ r'_\g<1>',
58
+ name,
59
+ )
60
+ description = description.lower()
61
+ description = ' '.join(description.split('_'))
62
+ return description
38
63
 
39
- if split:
40
- description = description.replace('_', ' ')
64
+ def _parse_itemname(self, name: str) -> str:
65
+ """
66
+ Create a description from a pytest test function name.
41
67
 
68
+ - Uses _parse_name to normalize the identifier.
69
+ - Removes common prefixes like "test"/"it".
70
+ """
71
+ description = self._parse_name(name)
72
+ description = re.sub(
73
+ r'^(test|it) ',
74
+ '',
75
+ description,
76
+ )
42
77
  return description
43
78
 
44
- def __repr__(self):
79
+ def __repr__(self) -> str:
45
80
  return self._item.__repr__()
46
81
 
47
82
 
48
83
  class Test(Item):
84
+ """
85
+ Represents a single test item and its relationship to a container.
86
+ """
49
87
  def __init__(self, item: pytest.Item) -> None:
50
88
  super().__init__(item)
51
89
  self.container: Container = None
@@ -53,27 +91,47 @@ class Test(Item):
53
91
 
54
92
  @property
55
93
  def level(self) -> int:
94
+ """
95
+ Depth in the container hierarchy (root container is 0).
96
+ """
56
97
  if not self.container:
57
98
  return 0
58
99
  return self.container.level +1
59
100
 
60
101
 
61
102
  class Container(Item):
62
- def __init__(self, item: pytest.Item):
103
+ """
104
+ Represents a grouping of tests (e.g., class/function scopes) in pytest.
105
+
106
+ Tracks:
107
+ - Nested containers (parents/children) following pytest's item tree.
108
+ - Contained tests.
109
+ Provides utilities to compute display names and levels.
110
+ """
111
+ def __init__(self, item: pytest.Item) -> None:
63
112
  super().__init__(item)
64
113
  self.tests: List[Test] = list()
65
114
  self.containers: List[Container] = list()
66
115
  self.parent = None
67
116
 
68
- def add(self, test: Test):
117
+ def add(self, test: Test) -> None:
118
+ """
119
+ Attach a Test to this container and set its back-reference.
120
+ """
69
121
  self.tests.append(test)
70
122
  test.container = self
71
123
 
72
- def add_container(self, container: 'Container'):
124
+ def add_container(self, container: 'Container') -> None:
125
+ """
126
+ Attach a child container and set parent back-reference.
127
+ """
73
128
  self.containers.append(container)
74
129
  container.parent = self
75
130
 
76
- def flat_list(self):
131
+ def flat_list(self) -> list:
132
+ """
133
+ Return a list of containers from root to self.
134
+ """
77
135
  containers = []
78
136
  container = self
79
137
  while container:
@@ -83,21 +141,63 @@ class Container(Item):
83
141
 
84
142
  @property
85
143
  def level(self) -> int:
144
+ """
145
+ Compute this container's depth relative to the module root.
146
+
147
+ Walks up pytest's internal node tree until reaching a ModuleType parent.
148
+ """
86
149
  level = 0
87
150
 
88
151
  item = self._item
152
+ # Climb the pytest node hierarchy until we reach the module node
89
153
  while item.parent and not isinstance(item.parent.obj, ModuleType):
90
154
  level += 1
91
155
  item = item.parent
92
156
 
93
157
  return level
158
+
159
+ def _parse_docstring(self, docstring: str) -> str:
160
+ """
161
+ Parse container docstring and prepend an article when appropriate.
162
+
163
+ If the first word doesn't start with "with"/"without", prefix with
164
+ "a ". This mirrors RSpec-style descriptions like "a function" vs
165
+ "with options".
166
+ """
167
+ description = super()._parse_docstring(docstring)
168
+ if not description.startswith(('with ', 'without ')):
169
+ description = f'a {description}'
170
+ return description
171
+
172
+ def _parse_itemname(self, name: str) -> str:
173
+ """
174
+ Create a container description from a pytest node name.
175
+
176
+ - Normalize the identifier into words.
177
+ - Replace leading "test"/"describe" with the article "a ".
178
+ """
179
+ description = self._parse_name(name)
180
+ description = re.sub(
181
+ r'^([Tt]est|[Dd]escribe) ',
182
+ 'a ',
183
+ description)
184
+ return description
94
185
 
95
186
 
96
187
  class ItemFactory:
188
+ """
189
+ Factory responsible for creating Test items and wiring them into containers.
190
+ """
97
191
  def __init__(self) -> None:
98
192
  self.container_factory = ContainerFactory()
99
193
 
100
194
  def create(self, item: pytest.Item) -> Test:
195
+ """
196
+ Wrap a pytest item as a Test and attach it to its Container.
197
+
198
+ Also mutates pytest's item name to the human-readable description for
199
+ reporting.
200
+ """
101
201
  test_item = Test(item)
102
202
 
103
203
  container_item = item.parent
@@ -105,33 +205,54 @@ class ItemFactory:
105
205
  if container:
106
206
  container.add(test_item)
107
207
 
208
+ item.name = test_item.description
108
209
  return test_item
109
210
 
110
211
 
111
212
  class ContainerFactory:
213
+ """
214
+ Factory that ensures containers are unique per pytest node and linked
215
+ properly.
216
+ """
112
217
  def __init__(self) -> None:
113
218
  self.containers: Dict[str, Container] = dict()
114
219
 
115
- def create(self, item) -> Container:
220
+ def create(self, item: pytest.Item) -> 'Container':
221
+ """
222
+ Create (or fetch) the deepest Container for a given pytest node.
223
+ """
116
224
  containers = self._create_containers(item)
117
225
  if not containers:
118
226
  return None
119
-
227
+
120
228
  return containers[-1]
121
229
 
122
- def _create_unique_container(self, item):
230
+ def _create_unique_container(self, item: pytest.Item) -> 'Container':
231
+ """
232
+ Return a unique Container for a pytest node, creating if absent.
233
+
234
+ Also sets the pytest node's display name to the container description.
235
+ """
236
+ # Cache containers per pytest node to keep uniqueness
123
237
  if item not in self.containers:
124
238
  container = Container(item)
125
239
  self.containers[item] = container
126
240
 
127
241
  container = self.containers.get(item)
242
+ # Mutate node's visible name to our human-friendly description
243
+ item.name = container.description
128
244
  return container
129
245
 
130
- def _create_containers(self, item):
246
+ def _create_containers(self, item: pytest.Item) -> list:
247
+ """
248
+ Build the chain of containers from a pytest node up to the module root.
249
+ """
131
250
  containers = []
132
251
  child_container = None
252
+ # Walk upwards creating/collecting containers until the module level
133
253
  while item and not isinstance(item.obj, ModuleType):
134
254
  container = self._create_unique_container(item)
255
+ # Link the previous (deeper) container as a child of the current one
135
256
  if child_container:
136
257
  container.add_container(child_container)
137
258
 
pytest_pyspec/output.py CHANGED
@@ -2,7 +2,7 @@ import pytest
2
2
  from .item import Container, Test
3
3
 
4
4
 
5
- def print_container(container: Container):
5
+ def print_container(container: Container) -> str:
6
6
  output = "\n"
7
7
  if container:
8
8
  for container in container.flat_list():
@@ -10,18 +10,18 @@ def print_container(container: Container):
10
10
  container = container.parent
11
11
  return output
12
12
 
13
- def print_parent_container(container: Container):
13
+ def print_parent_container(container: Container) -> str:
14
14
  ident = " " * container.level
15
15
  output = f"\n{ident}{container.description}"
16
16
  return output
17
17
 
18
- def print_test(test: Test):
18
+ def print_test(test: Test) -> str:
19
19
  ident = " " * test.level
20
20
  status = print_test_status(test)
21
21
  output = f"\n{ident}{status} {test.description}"
22
22
  return output
23
23
 
24
- def print_test_status(test: Test):
24
+ def print_test_status(test: Test) -> str:
25
25
  if test.outcome == 'passed':
26
26
  return '✓'
27
27
  elif test.outcome == 'failed':
pytest_pyspec/plugin.py CHANGED
@@ -1,10 +1,34 @@
1
+
2
+ """
3
+ Pytest plugin that prints test output in an RSpec-like, readable format.
4
+
5
+ When enabled with the ``--pyspec`` flag, this plugin:
6
+ - Extends test discovery to also match ``it_`` functions and classes whose
7
+ names start with ``Describe`` or ``With``.
8
+ - Builds a tree of containers/tests and renders them with friendly
9
+ descriptions (favoring docstrings when present).
10
+ - Uses pytest's stash to pass the current/previous Test objects between
11
+ hooks, so we can decide when to print a container header.
12
+ """
13
+
14
+ from typing import Any, Tuple, Generator
15
+
1
16
  import pytest
2
17
  from pytest_pyspec.item import ItemFactory, Test
3
18
  from pytest_pyspec.output import print_container, print_test
4
19
 
5
20
 
6
- def pytest_addoption(parser: pytest.Parser,
7
- pluginmanager: pytest.PytestPluginManager):
21
+ def pytest_addoption(
22
+ parser: pytest.Parser,
23
+ pluginmanager: pytest.PytestPluginManager
24
+ ) -> None:
25
+ """
26
+ Register the ``--pyspec`` command-line flag.
27
+
28
+ The flag toggles all formatting and discovery behavior provided by this
29
+ plugin. We keep discovery changes behind the flag to avoid surprises in
30
+ normal pytest runs.
31
+ """
8
32
  group = parser.getgroup('general')
9
33
  group.addoption(
10
34
  '--pyspec',
@@ -14,60 +38,129 @@ def pytest_addoption(parser: pytest.Parser,
14
38
  )
15
39
 
16
40
 
17
- enabled = False
18
- def pytest_configure(config: pytest.Config):
19
- global enabled
20
- if config.getoption('pyspec') and not config.getoption('verbose'):
21
- enabled = True
41
+ ENABLED_KEY = pytest.StashKey[bool]()
42
+ def pytest_configure(config: pytest.Config) -> None:
43
+ """
44
+ Initialize plugin state and, when enabled, extend test discovery rules.
45
+
46
+ - ``enabled`` is set only when ``--pyspec`` is present and ``-v`` is not
47
+ used (verbose mode already prints enough, so we defer to pytest's
48
+ default output in that case).
49
+ - Appends discovery patterns for ``it_`` functions and ``Describe``/``With``
50
+ classes when pyspec is on.
51
+ """
52
+ # Store enabled state in config.stash for access in all hooks.
53
+ enabled = config.getoption('pyspec') and not config.getoption('verbose')
54
+ config.stash[ENABLED_KEY] = enabled
22
55
 
56
+ if config.getoption('pyspec'):
57
+ # Extend discovery to match RSpec-like naming.
23
58
  python_functions = config.getini("python_functions")
24
59
  python_functions.append('it_')
25
60
  config.option.python_functions = python_functions
26
61
 
27
62
  python_classes = config.getini("python_classes")
28
- python_classes.append('describe_')
29
- python_classes.append('with_')
63
+ python_classes.append('Describe')
64
+ python_classes.append('With')
30
65
  config.option.python_classes = python_classes
31
66
 
32
67
 
68
+ # Stash keys used to share Test objects between hooks.
69
+ # These allow the report hook to know the current and previous test, so we can
70
+ # decide when to print a container header (on container change).
71
+ TEST_KEY = pytest.StashKey[Test]()
72
+ PREV_TEST_KEY = pytest.StashKey[Test]()
73
+ def pytest_collection_modifyitems(
74
+ session: pytest.Session,
75
+ config: pytest.Config,
76
+ items: list[pytest.Item],
77
+ ) -> None:
78
+ """
79
+ After collection, wrap each pytest item with our Test model and stash it.
33
80
 
34
- test_key = pytest.StashKey[Test]()
35
- prev_test_key = pytest.StashKey[Test]()
36
- def pytest_collection_modifyitems(session, config, items):
81
+ The previous Test is also stashed so later hooks can determine container
82
+ boundaries and print headers only when the container changes.
83
+ """
84
+ enabled = config.stash.get(ENABLED_KEY, False)
37
85
  if enabled:
38
86
  factory = ItemFactory()
39
87
  prev_test = None
40
88
  for i, item in enumerate(items):
41
89
  test = factory.create(item)
42
- item.stash[test_key] = test
43
- item.stash[prev_test_key] = prev_test
90
+ item.stash[TEST_KEY] = test
91
+ item.stash[PREV_TEST_KEY] = prev_test
44
92
  prev_test = test
45
93
 
46
94
 
47
95
  @pytest.hookimpl(hookwrapper=True)
48
- def pytest_runtest_makereport(item, call):
49
- outcome = yield
50
- if enabled:
51
- report: pytest.Report = outcome.get_result()
52
- #TODO Check whether the report has a stash
53
- #TODO move previous test to test class
54
- report.test = item.stash[test_key]
55
- report.prev_test = item.stash[prev_test_key]
96
+ def pytest_runtest_makereport(
97
+ item: pytest.Item,
98
+ call: pytest.CallInfo
99
+ ) -> Generator[Any, Any, None]:
100
+ """
101
+ Inject Test metadata into the report object for later formatting.
56
102
 
103
+ As a hookwrapper we yield to let pytest create the report, then attach our
104
+ Test and prev_test (from stash) so ``pytest_report_teststatus`` can render
105
+ the right lines.
106
+ """
107
+ outcome = yield
108
+ enabled = item.config.stash.get(ENABLED_KEY, False)
109
+ if enabled:
110
+ report: pytest.Report = outcome.get_result()
111
+ # TODO Check whether the report has a stash
112
+ # TODO move previous test to test class
113
+ if TEST_KEY in item.stash:
114
+ report.test = item.stash[TEST_KEY]
115
+ report.prev_test = item.stash[PREV_TEST_KEY]
57
116
 
58
- def pytest_report_teststatus(report: pytest.TestReport, config: pytest.Config):
59
- if enabled:
60
- test = report.test
61
- prev_test = report.prev_test
62
-
63
- if report.when == 'setup':
64
- if not prev_test \
65
- or test.container != prev_test.container:
66
- # Show container
67
- output = print_container(test.container)
68
- return '', output, ('', {'white': True})
69
-
70
- if report.when == 'call':
71
- test.outcome = report.outcome
72
- output = print_test(test)
73
- return report.outcome, output, ''
117
+
118
+ from typing import Any, Tuple
119
+
120
+ def pytest_report_teststatus(
121
+ report: pytest.TestReport,
122
+ config: pytest.Config
123
+ ) -> Any:
124
+ """
125
+ Produce short status and a human-friendly text line for each test event.
126
+
127
+ Behavior (only when pyspec is enabled and a Test is attached):
128
+ - On setup: if the container changed, print the container header.
129
+ - On call (or skipped during setup): print the test line with its status
130
+ mark. We reuse pytest's status tuple format.
131
+ """
132
+ enabled = config.stash.get(ENABLED_KEY, False)
133
+ if enabled and hasattr(report, 'test'):
134
+ test = report.test
135
+ prev_test = report.prev_test
136
+
137
+ if report.when == 'setup':
138
+ if not prev_test or test.container != prev_test.container:
139
+ # Show container
140
+ output = print_container(test.container)
141
+ return '', output, ('', {'white': True})
142
+
143
+ # Determine if this is the last test in the container
144
+ is_last_in_container = False
145
+ if hasattr(test.container, 'tests'):
146
+ # Find the index of this test in the container's test list
147
+ try:
148
+ idx = test.container.tests.index(test)
149
+ is_last_in_container = idx == len(test.container.tests) - 1
150
+ except (ValueError, AttributeError):
151
+ pass
152
+
153
+ if report.when == 'call':
154
+ test.outcome = report.outcome
155
+ output = print_test(test)
156
+ if is_last_in_container:
157
+ output += '\n'
158
+ return report.outcome, output, ''
159
+
160
+ if report.when == 'setup' and report.skipped:
161
+ test.outcome = report.outcome
162
+ output = print_test(test)
163
+ if is_last_in_container:
164
+ output += '\n'
165
+ return report.outcome, output, ''
166
+
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-pyspec
3
+ Version: 0.10.1
4
+ Summary: A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it".
5
+ Project-URL: Repository, https://github.com/felipecrp/pytest-pyspec
6
+ Author-email: Felipe Curty <felipecrp@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: <4,>=3.10
10
+ Requires-Dist: pytest<10,>=9
11
+ Description-Content-Type: text/markdown
12
+
13
+ [![](https://github.com/felipecrp/pytest-pyspec/actions/workflows/pytest.yml/badge.svg)](https://github.com/felipecrp/pytest-pyspec/actions/workflows/pytest.yml)
14
+
15
+ The **pytest-pyspec** plugin provides semantics to the pytest output. It
16
+ transforms the pytest's output into a result similar to the RSpec.
17
+
18
+ The default pytest output is like the following:
19
+
20
+ ```
21
+ test/test_pytest.py::TestFunction::test_use_test_name PASSED
22
+ test/test_pytest.py::TestFunction::test_use_the_prefix_test PASSED
23
+ test/test_pytest.py::TestFunction::test_use_the_prefix_it PASSED
24
+ test/test_pytest.py::TestFunction::WithDocstring::test_use_docstring PASSED
25
+ ```
26
+ The **pytest-pyspec** transforms the output into the following:
27
+
28
+ ```
29
+ A function
30
+ ✓ use test name
31
+ ✓ use the prefix test
32
+ ✓ use the prefix it
33
+
34
+ A function
35
+ with docstring
36
+ ✓ use docstring
37
+ ```
38
+
39
+ You just need to prefix your test case classes with:
40
+
41
+ - _describe / test_ to represent objects
42
+ - _with / without_ to represent context
43
+
44
+ And prefix your tests with:
45
+
46
+ - _it / test_ to represent objects
47
+
48
+ The following is a sample test that generates the previous tests` output.
49
+
50
+ ```python
51
+ class TestFunction:
52
+ def test_use_test_name(self):
53
+ assert 1 == 1
54
+
55
+ def test_use_the_prefix_test(self):
56
+ assert 1 == 1
57
+
58
+ def test_use_the_prefix_it(self):
59
+ assert 1 == 1
60
+
61
+ class WithDocstring:
62
+ def test_use_docstring(self):
63
+ assert 1 == 1
64
+ ```
65
+
66
+ Moreover, you can use a docstring to overwrite the test description. The
67
+ following tests have the same output as the previous tests:
68
+
69
+ ```python
70
+ class TestA:
71
+ """ Function """
72
+ def test_1(self):
73
+ """ use test name """
74
+ assert 1 == 1
75
+
76
+ def test_2(self):
77
+ """ use the prefix test """
78
+ assert 1 == 1
79
+
80
+ def test_3(self):
81
+ """ use the prefix it """
82
+ assert 1 == 1
83
+
84
+ class TestB:
85
+ """ with docstring """
86
+ def test_4(self):
87
+ """ use docstring """
88
+ assert 1 == 1
89
+ ```
90
+
91
+ The following test sample:
92
+
93
+ ```python
94
+ import pytest
95
+
96
+ class DescribeHouse:
97
+ def it_has_door(self):
98
+ assert 1 == 1
99
+
100
+ class WithTwoFloors:
101
+ def it_has_stairs(self):
102
+ assert 1 == 1
103
+
104
+ def it_has_second_floor(self):
105
+ assert 1 == 1
106
+
107
+ def it_has_third_floor(self):
108
+ assert 1 == 2
109
+ ```
110
+
111
+ Generates the following output:
112
+
113
+ ```
114
+ test/test_sample.py
115
+
116
+ A house
117
+ ✓ has door
118
+
119
+ A house
120
+ with two floors
121
+ ✓ has stairs
122
+ ✓ has second floor
123
+ ✗ has third floor
124
+ ```
125
+
126
+ ## Installing and running **pySpec**
127
+
128
+ ```bash
129
+ pip install pytest pytest-pyspec
130
+ pytest --pyspec
131
+ ```
@@ -0,0 +1,9 @@
1
+ pytest_pyspec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pytest_pyspec/item.py,sha256=FCVWRd1O8EQ01eDYd7bh2v0jO-O3BERyZKIxs3WFSPo,8173
3
+ pytest_pyspec/output.py,sha256=VJAqNhd5695Dn5G6Jow3cfQkYXbfbcSquAf1IAivUVs,816
4
+ pytest_pyspec/plugin.py,sha256=_vx7g_2wq1AFV4mnC5tkYLbaK8xYAvlh1vstJRx35EQ,5664
5
+ pytest_pyspec-0.10.1.dist-info/METADATA,sha256=cWocxgEZURwKPat6Yn6Qrb5gS7iZp5wcd9Kf7RTcBL0,3148
6
+ pytest_pyspec-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ pytest_pyspec-0.10.1.dist-info/entry_points.txt,sha256=6YScWbxnkpduYs2Ef3gKOe6rV8Cjbj-OxCGMDNdV4Kc,48
8
+ pytest_pyspec-0.10.1.dist-info/licenses/LICENSE,sha256=PXKqh3rDWRsfhWsjBQ27qqYzE5E05n-eFRH95LsKRT4,1094
9
+ pytest_pyspec-0.10.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.5.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ pytest_pyspec = pytest_pyspec.plugin