wexample-wex-addon-dev-python 0.0.53__tar.gz → 0.0.60__tar.gz

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.
Files changed (45) hide show
  1. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/PKG-INFO +6 -6
  2. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/README.md +2 -2
  3. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/pyproject.toml +38 -9
  4. wexample_wex_addon_dev_python-0.0.60/src/wexample_wex_addon_dev_python/const/python.py +7 -0
  5. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/file/python_package_toml_file.py +97 -56
  6. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/resources/readme_templates/tests.md.j2 +1 -1
  7. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/workdir/python_package_workdir.py +226 -164
  8. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/workdir/python_packages_suite_workdir.py +6 -1
  9. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/workdir/python_workdir.py +273 -127
  10. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/__init__.py +0 -0
  11. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/__pycache__/__init__.py +0 -0
  12. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/__init__.py +0 -0
  13. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/__init__.py +0 -0
  14. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/check/__init__.py +0 -0
  15. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/check/mypy.py +0 -0
  16. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/check/pylint.py +0 -0
  17. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/check/pyright.py +0 -0
  18. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/check.py +0 -0
  19. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/format/__init__.py +0 -0
  20. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/format/black.py +0 -0
  21. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/format/isort.py +0 -0
  22. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/code/format.py +0 -0
  23. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/examples/__init__.py +0 -0
  24. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/examples/utils/__init__.py +0 -0
  25. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/examples/utils/some_example_type.py +0 -0
  26. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/examples/validate.py +0 -0
  27. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/commands/release/__init__.py +0 -0
  28. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/config_value/__init__.py +0 -0
  29. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/config_value/__pycache__/__init__.py +0 -0
  30. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/config_value/python_package_readme_config_value.py +0 -0
  31. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/const/__init__.py +0 -0
  32. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/const/__pycache__/__init__.py +0 -0
  33. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/const/package.py +0 -0
  34. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/file/__init__.py +0 -0
  35. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/file/__pycache__/__init__.py +0 -0
  36. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/middleware/__init__.py +0 -0
  37. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/middleware/__pycache__/__init__.py +0 -0
  38. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/middleware/each_python_file_middleware.py +0 -0
  39. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/py.typed +0 -0
  40. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/python_addon_manager.py +0 -0
  41. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/resources/__init__.py +0 -0
  42. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/resources/readme_templates/__init__.py +0 -0
  43. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/resources/readme_templates/title.md.j2 +0 -0
  44. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/workdir/__init__.py +0 -0
  45. {wexample_wex_addon_dev_python-0.0.53 → wexample_wex_addon_dev_python-0.0.60}/src/wexample_wex_addon_dev_python/workdir/__pycache__/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wexample-wex-addon-dev-python
3
- Version: 0.0.53
3
+ Version: 0.0.60
4
4
  Summary: Python dev addon for wex
5
5
  Author-Email: weeger <contact@wexample.com>
6
6
  License: MIT
@@ -15,9 +15,9 @@ Requires-Dist: jinja2
15
15
  Requires-Dist: networkx
16
16
  Requires-Dist: pylint
17
17
  Requires-Dist: pyright
18
- Requires-Dist: wexample-filestate-python==0.0.48
19
- Requires-Dist: wexample-wex-addon-app==0.0.49
20
- Requires-Dist: wexample-wex-core==6.0.55
18
+ Requires-Dist: wexample-filestate-python==0.0.54
19
+ Requires-Dist: wexample-wex-addon-app==0.0.51
20
+ Requires-Dist: wexample-wex-core==6.0.63
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: pytest; extra == "dev"
23
23
  Requires-Dist: pytest-cov; extra == "dev"
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  # wexample-wex-addon-dev-python
27
27
 
28
- Version: 0.0.53
28
+ Version: 0.0.60
29
29
 
30
30
  Python dev addon for wex
31
31
 
@@ -44,7 +44,7 @@ First, install the required testing dependencies:
44
44
 
45
45
  Run all tests with coverage:
46
46
  ```bash
47
- .venv/bin/python -m pytest --cov
47
+ .venv/bin/python -m pytest --cov --cov-report=html
48
48
  ```
49
49
 
50
50
  ### Common Commands
@@ -1,6 +1,6 @@
1
1
  # wexample-wex-addon-dev-python
2
2
 
3
- Version: 0.0.53
3
+ Version: 0.0.60
4
4
 
5
5
  Python dev addon for wex
6
6
 
@@ -19,7 +19,7 @@ First, install the required testing dependencies:
19
19
 
20
20
  Run all tests with coverage:
21
21
  ```bash
22
- .venv/bin/python -m pytest --cov
22
+ .venv/bin/python -m pytest --cov --cov-report=html
23
23
  ```
24
24
 
25
25
  ### Common Commands
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-wex-addon-dev-python"
9
- version = "0.0.53"
9
+ version = "0.0.60"
10
10
  description = "Python dev addon for wex"
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
@@ -24,9 +24,9 @@ dependencies = [
24
24
  "networkx",
25
25
  "pylint",
26
26
  "pyright",
27
- "wexample-filestate-python==0.0.48",
28
- "wexample-wex-addon-app==0.0.49",
29
- "wexample-wex-core==6.0.55",
27
+ "wexample-filestate-python==0.0.54",
28
+ "wexample-wex-addon-app==0.0.51",
29
+ "wexample-wex-core==6.0.63",
30
30
  ]
31
31
 
32
32
  [project.readme]
@@ -45,6 +45,14 @@ dev = [
45
45
  "pytest-cov",
46
46
  ]
47
47
 
48
+ [tool.setuptools.packages.find]
49
+ include = [
50
+ "*",
51
+ ]
52
+ exclude = [
53
+ "wexample_wex_addon_dev_python.testing*",
54
+ ]
55
+
48
56
  [tool.pdm]
49
57
  distribution = true
50
58
 
@@ -54,10 +62,31 @@ packages = [
54
62
  { include = "wexample_wex_addon_dev_python", from = "src" },
55
63
  ]
56
64
 
57
- [tool.setuptools.packages.find]
58
- include = [
59
- "*",
65
+ [tool.pytest.ini_options]
66
+ testpaths = [
67
+ "tests",
60
68
  ]
61
- exclude = [
62
- "wexample_wex_addon_dev_python.testing*",
69
+ pythonpath = [
70
+ "src",
71
+ ]
72
+
73
+ [tool.coverage.run]
74
+ source = [
75
+ "wexample_wex_addon_dev_python",
76
+ ]
77
+ omit = [
78
+ "*/tests/*",
79
+ "*/.venv/*",
80
+ "*/venv/*",
81
+ ]
82
+
83
+ [tool.coverage.report]
84
+ exclude_lines = [
85
+ "pragma: no cover",
86
+ "def __repr__",
87
+ "raise AssertionError",
88
+ "raise NotImplementedError",
89
+ "if __name__ == .__main__.:",
90
+ "if TYPE_CHECKING:",
91
+ "@abstractmethod",
63
92
  ]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ PYTHON_PYTEST_COV_REPORT_DIR: Path = Path("htmlcov")
6
+ PYTHON_PYTEST_COV_FORMAT_HTML: str = "html"
7
+ PYTHON_PYTEST_COV_FORMAT_JSON: str = "json"
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from wexample_filestate.item.file.toml_file import TomlFile
6
6
  from wexample_helpers.decorator.base_class import base_class
7
+ from wexample_wex_addon_app.const.path import APP_PATH_README
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from tomlkit import TOMLDocument
@@ -22,19 +23,21 @@ class PythonPackageTomlFile(TomlFile):
22
23
  from wexample_filestate_python.helpers.toml import toml_sort_string_array
23
24
 
24
25
  deps = self._get_deps_array(optional=optional, group=group)
25
- # Remove existing entries for the same package name before adding the new spec.
26
- new_name = canonicalize_name(Requirement(spec).name)
27
- removed = self.remove_dependency_by_name(
28
- new_name, optional=optional, group=group
29
- )
26
+ new_req = Requirement(spec)
27
+ new_name = canonicalize_name(new_req.name)
30
28
 
31
- # Append (or re-append) the new spec if it is not already present verbatim
32
- if spec not in deps:
33
- deps.append(spec)
34
- toml_sort_string_array(deps)
35
- return True
29
+ old_spec = None
30
+ for dep in deps:
31
+ if canonicalize_name(Requirement(dep).name) == new_name:
32
+ old_spec = dep
33
+ break
34
+
35
+ self.remove_dependency_by_name(new_name, optional=optional, group=group)
36
36
 
37
- return removed
37
+ deps.append(spec)
38
+ toml_sort_string_array(deps)
39
+
40
+ return old_spec != spec
38
41
 
39
42
  def dumps(self, content: TOMLDocument | dict | None = None) -> str:
40
43
  """Serialize a TOMLDocument (preferred) or a plain dict to TOML.
@@ -54,6 +57,7 @@ class PythonPackageTomlFile(TomlFile):
54
57
  self._enforce_project_metadata(content, project_name, project_version)
55
58
  self._normalize_dependencies(content)
56
59
  self._ensure_dev_dependencies(content)
60
+ self._enforce_pytest_coverage_config(content, import_name)
57
61
  self._reorder_toml_sections(content)
58
62
 
59
63
  result = dumps(content)
@@ -96,6 +100,19 @@ class PythonPackageTomlFile(TomlFile):
96
100
  continue
97
101
  return names
98
102
 
103
+ def optional_group_array(self, group: str):
104
+ """Ensure and return project.optional-dependencies[group] as multi-line array."""
105
+ from wexample_filestate_python.helpers.toml import (
106
+ toml_ensure_array,
107
+ toml_ensure_table,
108
+ )
109
+
110
+ project = self._project_table()
111
+ opt, _ = toml_ensure_table(project, ["optional-dependencies"])
112
+ arr, _ = toml_ensure_array(opt, group)
113
+ arr.multiline(True)
114
+ return arr
115
+
99
116
  def remove_dependency_by_name(
100
117
  self, package_name: str, optional: bool = False, group: str = "dev"
101
118
  ) -> bool:
@@ -218,20 +235,55 @@ class PythonPackageTomlFile(TomlFile):
218
235
 
219
236
  # Add README if it exists
220
237
  if package:
221
- from wexample_wex_addon_app.workdir.mixin.with_readme_workdir_mixin import (
222
- WithReadmeWorkdirMixin,
223
- )
238
+ pass
224
239
 
225
- readme_file = package.find_by_name(WithReadmeWorkdirMixin.README_FILENAME)
240
+ readme_file = package.find_by_name(APP_PATH_README)
226
241
  if readme_file:
227
242
  readme_tbl, _ = toml_ensure_table(project_tbl, ["readme"])
228
- readme_tbl["file"] = WithReadmeWorkdirMixin.README_FILENAME
243
+ readme_tbl["file"] = str(APP_PATH_README)
229
244
  readme_tbl["content-type"] = "text/markdown"
230
245
 
231
246
  # Add MIT license
232
247
  license_tbl, _ = toml_ensure_table(project_tbl, ["license"])
233
248
  license_tbl["text"] = "MIT"
234
249
 
250
+ def _enforce_pytest_coverage_config(
251
+ self, content: dict, import_name: str | None
252
+ ) -> None:
253
+ """Add pytest and coverage configuration to limit coverage to the package only."""
254
+ if not import_name:
255
+ return
256
+
257
+ from wexample_filestate_python.helpers.toml import toml_ensure_table
258
+
259
+ tool_tbl, _ = toml_ensure_table(content, ["tool"])
260
+
261
+ # Add pytest configuration
262
+ pytest_tbl, _ = toml_ensure_table(tool_tbl, ["pytest", "ini_options"])
263
+ pytest_tbl["testpaths"] = ["tests"]
264
+ pytest_tbl["pythonpath"] = ["src"]
265
+
266
+ # Add coverage.run configuration to limit source to the package
267
+ coverage_run_tbl, _ = toml_ensure_table(tool_tbl, ["coverage", "run"])
268
+ coverage_run_tbl["source"] = [import_name]
269
+ coverage_run_tbl["omit"] = [
270
+ "*/tests/*",
271
+ "*/.venv/*",
272
+ "*/venv/*",
273
+ ]
274
+
275
+ # Add coverage.report configuration
276
+ coverage_report_tbl, _ = toml_ensure_table(tool_tbl, ["coverage", "report"])
277
+ coverage_report_tbl["exclude_lines"] = [
278
+ "pragma: no cover",
279
+ "def __repr__",
280
+ "raise AssertionError",
281
+ "raise NotImplementedError",
282
+ "if __name__ == .__main__.:",
283
+ "if TYPE_CHECKING:",
284
+ "@abstractmethod",
285
+ ]
286
+
235
287
  def _ensure_dev_dependencies(self, content: dict) -> None:
236
288
  from wexample_filestate_python.helpers.package import package_normalize_name
237
289
  from wexample_filestate_python.helpers.toml import (
@@ -239,7 +291,7 @@ class PythonPackageTomlFile(TomlFile):
239
291
  toml_sort_string_array,
240
292
  )
241
293
 
242
- dev_arr = self._optional_group_array("dev")
294
+ dev_arr = self.optional_group_array("dev")
243
295
  deps_arr = self._dependencies_array()
244
296
 
245
297
  runtime_pkgs = {
@@ -257,9 +309,7 @@ class PythonPackageTomlFile(TomlFile):
257
309
  def _get_deps_array(self, optional: bool = False, group: str = "dev"):
258
310
  """Return TOML array for runtime deps or optional group (multiline)."""
259
311
  return (
260
- self._optional_group_array(group)
261
- if optional
262
- else self._dependencies_array()
312
+ self.optional_group_array(group) if optional else self._dependencies_array()
263
313
  )
264
314
 
265
315
  def _normalize_dependencies(self, content: dict) -> None:
@@ -276,9 +326,24 @@ class PythonPackageTomlFile(TomlFile):
276
326
  deps_arr = self._dependencies_array()
277
327
  toml_sort_string_array(deps_arr)
278
328
 
329
+ # Read the keep list from [tool.filestate].keep
330
+ keep_packages: set[str] = set()
331
+ if "tool" in content and isinstance(content["tool"], dict):
332
+ tool_tbl = content["tool"]
333
+ if "filestate" in tool_tbl and isinstance(tool_tbl["filestate"], dict):
334
+ filestate_tbl = tool_tbl["filestate"]
335
+ if "keep" in filestate_tbl and isinstance(filestate_tbl["keep"], list):
336
+ keep_packages = {
337
+ package_normalize_name(str(pkg))
338
+ for pkg in filestate_tbl["keep"]
339
+ }
340
+
279
341
  # filter unwanted deps
280
342
  def _should_remove(item: object) -> bool:
281
343
  name = package_normalize_name(toml_get_string_value(item))
344
+ # Don't remove if in keep list
345
+ if name in keep_packages:
346
+ return False
282
347
  return name in RUNTIME_DEPENDENCY_REMOVE_NAMES or (
283
348
  name == "typing-extensions"
284
349
  )
@@ -288,29 +353,6 @@ class PythonPackageTomlFile(TomlFile):
288
353
  deps_arr.extend(filtered)
289
354
  toml_sort_string_array(deps_arr)
290
355
 
291
- # normalize attrs/cattrs
292
- normalized = []
293
- for it in list(deps_arr):
294
- base = package_normalize_name(toml_get_string_value(it).strip())
295
- if base == "attrs":
296
- normalized.append("attrs>=23.1.0")
297
- elif base == "cattrs":
298
- normalized.append("cattrs>=23.1.0")
299
- else:
300
- normalized.append(it)
301
- if normalized:
302
- deps_arr.clear()
303
- deps_arr.extend(normalized)
304
- toml_sort_string_array(deps_arr)
305
-
306
- # ensure they are present
307
- names = {package_normalize_name(toml_get_string_value(it)) for it in deps_arr}
308
- if "attrs" not in names:
309
- deps_arr.append("attrs>=23.1.0")
310
- if "cattrs" not in names:
311
- deps_arr.append("cattrs>=23.1.0")
312
- toml_sort_string_array(deps_arr)
313
-
314
356
  def _normalize_toml_formatting(self, content: str) -> str:
315
357
  """Normalize TOML formatting:
316
358
  - No empty lines at the beginning
@@ -330,19 +372,6 @@ class PythonPackageTomlFile(TomlFile):
330
372
 
331
373
  return content
332
374
 
333
- def _optional_group_array(self, group: str):
334
- """Ensure and return project.optional-dependencies[group] as multi-line array."""
335
- from wexample_filestate_python.helpers.toml import (
336
- toml_ensure_array,
337
- toml_ensure_table,
338
- )
339
-
340
- project = self._project_table()
341
- opt, _ = toml_ensure_table(project, ["optional-dependencies"])
342
- arr, _ = toml_ensure_array(opt, group)
343
- arr.multiline(True)
344
- return arr
345
-
346
375
  def _project_table(self):
347
376
  """Ensure and return the [project] table."""
348
377
  from wexample_filestate_python.helpers.toml import toml_ensure_table
@@ -392,9 +421,21 @@ class PythonPackageTomlFile(TomlFile):
392
421
  "optional-dependencies",
393
422
  ]
394
423
 
424
+ # Define the desired order for keys within [tool]
425
+ tool_key_order = [
426
+ "setuptools",
427
+ "pdm",
428
+ "pytest",
429
+ "coverage",
430
+ ]
431
+
395
432
  # Reorder top-level sections
396
433
  self._reorder_dict_keys(content, section_order)
397
434
 
398
435
  # Reorder keys within [project] if it exists
399
436
  if "project" in content:
400
437
  self._reorder_dict_keys(content["project"], project_key_order)
438
+
439
+ # Reorder keys within [tool] if it exists
440
+ if "tool" in content:
441
+ self._reorder_dict_keys(content["tool"], tool_key_order)
@@ -13,7 +13,7 @@ First, install the required testing dependencies:
13
13
 
14
14
  Run all tests with coverage:
15
15
  ```bash
16
- .venv/bin/python -m pytest --cov
16
+ .venv/bin/python -m pytest --cov --cov-report=html
17
17
  ```
18
18
 
19
19
  ### Common Commands