pytest-pyspec 0.4.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,50 +1,89 @@
1
1
  from types import ModuleType
2
+ from typing import Dict, List
3
+
4
+ import re
5
+
2
6
  import pytest
3
7
 
4
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
+
5
20
  def __init__(self, item: pytest.Item) -> None:
6
21
  self._item = item
7
22
 
8
23
  @property
9
- def description(self):
10
- description = self._item.obj.__doc__
11
-
12
- if not description:
13
- description = self._item.name
14
- description = self._parse_description(description)
24
+ def description(self) -> str:
25
+ """
26
+ Return a human-readable description for the wrapped pytest item.
15
27
 
16
- description = description.splitlines()[0]
17
- description = description.capitalize()
18
- return description
19
-
20
- def _parse_description(self, description: str):
21
- split = False
22
- if description.lower().startswith("it_"):
23
- description = description.replace('it_', '')
24
- split = True
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)
25
36
 
26
- if description.lower().startswith("test_it_"):
27
- description = description.replace('test_it_', '')
28
- 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()
29
43
 
30
- if description.lower().startswith("test_describe_"):
31
- description = description.replace('test_describe_', '')
32
- split = True
44
+ def _parse_name(self, name: str) -> str:
45
+ """
46
+ Normalize a Python identifier into words.
33
47
 
34
- if description.lower().startswith("describe_"):
35
- description = description.replace('describe_', '')
36
- 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
37
63
 
38
- if split:
39
- description = description.replace('_', ' ')
64
+ def _parse_itemname(self, name: str) -> str:
65
+ """
66
+ Create a description from a pytest test function name.
40
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
+ )
41
77
  return description
42
78
 
43
- def __repr__(self):
79
+ def __repr__(self) -> str:
44
80
  return self._item.__repr__()
45
81
 
46
82
 
47
83
  class Test(Item):
84
+ """
85
+ Represents a single test item and its relationship to a container.
86
+ """
48
87
  def __init__(self, item: pytest.Item) -> None:
49
88
  super().__init__(item)
50
89
  self.container: Container = None
@@ -52,25 +91,47 @@ class Test(Item):
52
91
 
53
92
  @property
54
93
  def level(self) -> int:
94
+ """
95
+ Depth in the container hierarchy (root container is 0).
96
+ """
97
+ if not self.container:
98
+ return 0
55
99
  return self.container.level +1
56
100
 
57
101
 
58
102
  class Container(Item):
59
- 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:
60
112
  super().__init__(item)
61
- self.tests = list[Test]()
62
- self.containers = list[Container]()
113
+ self.tests: List[Test] = list()
114
+ self.containers: List[Container] = list()
63
115
  self.parent = None
64
116
 
65
- 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
+ """
66
121
  self.tests.append(test)
67
122
  test.container = self
68
123
 
69
- 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
+ """
70
128
  self.containers.append(container)
71
129
  container.parent = self
72
130
 
73
- def flat_list(self):
131
+ def flat_list(self) -> list:
132
+ """
133
+ Return a list of containers from root to self.
134
+ """
74
135
  containers = []
75
136
  container = self
76
137
  while container:
@@ -80,51 +141,118 @@ class Container(Item):
80
141
 
81
142
  @property
82
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
+ """
83
149
  level = 0
84
150
 
85
151
  item = self._item
152
+ # Climb the pytest node hierarchy until we reach the module node
86
153
  while item.parent and not isinstance(item.parent.obj, ModuleType):
87
154
  level += 1
88
155
  item = item.parent
89
156
 
90
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
91
185
 
92
186
 
93
187
  class ItemFactory:
188
+ """
189
+ Factory responsible for creating Test items and wiring them into containers.
190
+ """
94
191
  def __init__(self) -> None:
95
192
  self.container_factory = ContainerFactory()
96
193
 
97
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
+ """
98
201
  test_item = Test(item)
99
202
 
100
203
  container_item = item.parent
101
204
  container = self.container_factory.create(container_item)
102
- container.add(test_item)
205
+ if container:
206
+ container.add(test_item)
103
207
 
208
+ item.name = test_item.description
104
209
  return test_item
105
210
 
106
211
 
107
212
  class ContainerFactory:
213
+ """
214
+ Factory that ensures containers are unique per pytest node and linked
215
+ properly.
216
+ """
108
217
  def __init__(self) -> None:
109
- self.containers = dict[str, Container]()
218
+ self.containers: Dict[str, Container] = dict()
110
219
 
111
- 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
+ """
112
224
  containers = self._create_containers(item)
225
+ if not containers:
226
+ return None
227
+
113
228
  return containers[-1]
114
229
 
115
- 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
116
237
  if item not in self.containers:
117
238
  container = Container(item)
118
239
  self.containers[item] = container
119
240
 
120
241
  container = self.containers.get(item)
242
+ # Mutate node's visible name to our human-friendly description
243
+ item.name = container.description
121
244
  return container
122
245
 
123
- 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
+ """
124
250
  containers = []
125
251
  child_container = None
252
+ # Walk upwards creating/collecting containers until the module level
126
253
  while item and not isinstance(item.obj, ModuleType):
127
254
  container = self._create_unique_container(item)
255
+ # Link the previous (deeper) container as a child of the current one
128
256
  if child_container:
129
257
  container.add_container(child_container)
130
258
 
pytest_pyspec/output.py CHANGED
@@ -2,25 +2,26 @@ 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
- for container in container.flat_list():
8
- output += print_parent_container(container)
9
- container = container.parent
7
+ if container:
8
+ for container in container.flat_list():
9
+ output += print_parent_container(container)
10
+ container = container.parent
10
11
  return output
11
12
 
12
- def print_parent_container(container: Container):
13
+ def print_parent_container(container: Container) -> str:
13
14
  ident = " " * container.level
14
15
  output = f"\n{ident}{container.description}"
15
16
  return output
16
17
 
17
- def print_test(test: Test):
18
+ def print_test(test: Test) -> str:
18
19
  ident = " " * test.level
19
20
  status = print_test_status(test)
20
21
  output = f"\n{ident}{status} {test.description}"
21
22
  return output
22
23
 
23
- def print_test_status(test: Test):
24
+ def print_test_status(test: Test) -> str:
24
25
  if test.outcome == 'passed':
25
26
  return '✓'
26
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,50 +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
55
+
56
+ if config.getoption('pyspec'):
57
+ # Extend discovery to match RSpec-like naming.
58
+ python_functions = config.getini("python_functions")
59
+ python_functions.append('it_')
60
+ config.option.python_functions = python_functions
61
+
62
+ python_classes = config.getini("python_classes")
63
+ python_classes.append('Describe')
64
+ python_classes.append('With')
65
+ config.option.python_classes = python_classes
66
+
22
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.
23
80
 
24
- test_key = pytest.StashKey[Test]()
25
- prev_test_key = pytest.StashKey[Test]()
26
- 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)
27
85
  if enabled:
28
86
  factory = ItemFactory()
29
87
  prev_test = None
30
88
  for i, item in enumerate(items):
31
89
  test = factory.create(item)
32
- item.stash[test_key] = test
33
- item.stash[prev_test_key] = prev_test
90
+ item.stash[TEST_KEY] = test
91
+ item.stash[PREV_TEST_KEY] = prev_test
34
92
  prev_test = test
35
93
 
36
94
 
37
95
  @pytest.hookimpl(hookwrapper=True)
38
- def pytest_runtest_makereport(item, call):
39
- outcome = yield
40
- if enabled:
41
- report: pytest.Report = outcome.get_result()
42
- #TODO Check whether the report has a stash
43
- #TODO move previous test to test class
44
- report.test = item.stash[test_key]
45
- 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.
46
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]
47
116
 
48
- def pytest_report_teststatus(report: pytest.TestReport, config: pytest.Config):
49
- if enabled:
50
- test = report.test
51
- prev_test = report.prev_test
52
-
53
- if report.when == 'setup':
54
- if not prev_test \
55
- or test.container != prev_test.container:
56
- # Show container
57
- output = print_container(test.container)
58
- return '', output, ('', {'white': True})
59
-
60
- if report.when == 'call':
61
- test.outcome = report.outcome
62
- output = print_test(test)
63
- 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.4.0
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