pytest-threadpool 0.3.3__tar.gz → 0.3.5__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 (162) hide show
  1. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/.github/workflows/ci.yml +18 -3
  2. pytest_threadpool-0.3.5/.pre-commit-config.yaml +17 -0
  3. pytest_threadpool-0.3.5/CHANGELOG.md +19 -0
  4. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/CONTRIBUTING.md +3 -4
  5. pytest_threadpool-0.3.5/PKG-INFO +228 -0
  6. pytest_threadpool-0.3.3/PKG-INFO → pytest_threadpool-0.3.5/README.md +74 -56
  7. pytest_threadpool-0.3.5/ROADMAP.md +83 -0
  8. pytest_threadpool-0.3.5/codecov.yml +14 -0
  9. pytest_threadpool-0.3.5/examples/__init__.py +3 -0
  10. pytest_threadpool-0.3.5/examples/test_di/__init__.py +1 -0
  11. pytest_threadpool-0.3.5/examples/test_di/conftest.py +28 -0
  12. pytest_threadpool-0.3.5/examples/test_di/container.py +44 -0
  13. pytest_threadpool-0.3.5/examples/test_di/providers.py +104 -0
  14. pytest_threadpool-0.3.5/examples/test_di/services.py +53 -0
  15. pytest_threadpool-0.3.5/examples/test_di/test_factory.py +19 -0
  16. pytest_threadpool-0.3.5/examples/test_di/test_local.py +99 -0
  17. pytest_threadpool-0.3.5/examples/test_di/test_singleton.py +20 -0
  18. pytest_threadpool-0.3.5/examples/test_di/test_thread_local.py +77 -0
  19. pytest_threadpool-0.3.5/examples/test_event_bus/event_bus.py +85 -0
  20. pytest_threadpool-0.3.5/examples/test_event_bus/test_event_bus.py +112 -0
  21. pytest_threadpool-0.3.5/examples/test_logging/test_log_capture.py +34 -0
  22. pytest_threadpool-0.3.5/examples/test_queue/__init__.py +0 -0
  23. pytest_threadpool-0.3.5/examples/test_queue/conftest.py +16 -0
  24. pytest_threadpool-0.3.5/examples/test_queue/test_queue.py +30 -0
  25. pytest_threadpool-0.3.5/examples/test_queue/user_pool.py +26 -0
  26. pytest_threadpool-0.3.5/examples/test_shared_state/__init__.py +0 -0
  27. pytest_threadpool-0.3.5/examples/test_shared_state/test_barrier.py +47 -0
  28. pytest_threadpool-0.3.5/examples/test_shared_state/test_counter.py +43 -0
  29. pytest_threadpool-0.3.5/examples/test_shared_state/test_counter_async.py +42 -0
  30. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/pyproject.toml +45 -5
  31. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/scripts/setup-dev +1 -1
  32. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_grouping.py +59 -2
  33. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_markers.py +24 -4
  34. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_runner.py +212 -30
  35. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_version.py +2 -2
  36. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/plugin.py +9 -3
  37. pytest_threadpool-0.3.5/src/pytest_threadpool/py.typed +0 -0
  38. pytest_threadpool-0.3.5/tests/__init__.py +0 -0
  39. pytest_threadpool-0.3.5/tests/integration_tests/cases/__init__.py +0 -0
  40. pytest_threadpool-0.3.5/tests/integration_tests/cases/capture_print_parallel.py +13 -0
  41. pytest_threadpool-0.3.5/tests/integration_tests/cases/capture_two_groups.py +30 -0
  42. pytest_threadpool-0.3.5/tests/integration_tests/cases/edge_first_item_setup_fail.py +32 -0
  43. pytest_threadpool-0.3.5/tests/integration_tests/cases/edge_maxfail_parallel.py +24 -0
  44. pytest_threadpool-0.3.5/tests/integration_tests/cases/edge_teardown_exception.py +32 -0
  45. pytest_threadpool-0.3.5/tests/integration_tests/cases/edge_worker_exception.py +23 -0
  46. pytest_threadpool-0.3.5/tests/integration_tests/cases/edge_worker_exception_conftest.py +14 -0
  47. pytest_threadpool-0.3.5/tests/integration_tests/cases/fixture_func_from_conftest.py +38 -0
  48. pytest_threadpool-0.3.5/tests/integration_tests/cases/fixture_func_from_conftest_conftest.py +9 -0
  49. pytest_threadpool-0.3.5/tests/integration_tests/cases/fixture_func_implicit_scope.py +56 -0
  50. pytest_threadpool-0.3.5/tests/integration_tests/cases/fixture_func_mixed_parallel.py +45 -0
  51. pytest_threadpool-0.3.5/tests/integration_tests/cases/fixture_func_setup_parallel.py +48 -0
  52. pytest_threadpool-0.3.5/tests/integration_tests/cases/reporting_single_file_params.py +12 -0
  53. pytest_threadpool-0.3.5/tests/integration_tests/cases/reporting_stdout_during_parallel.py +13 -0
  54. pytest_threadpool-0.3.5/tests/integration_tests/cases/runner_collect_only.py +16 -0
  55. pytest_threadpool-0.3.5/tests/integration_tests/cases/runner_setup_only.py +20 -0
  56. pytest_threadpool-0.3.5/tests/integration_tests/cases/runner_setup_show.py +17 -0
  57. pytest_threadpool-0.3.5/tests/integration_tests/cases/runner_single_parallel_item.py +23 -0
  58. pytest_threadpool-0.3.5/tests/integration_tests/cases/runner_single_worker.py +25 -0
  59. pytest_threadpool-0.3.5/tests/integration_tests/cases/scope_children_on_function.py +15 -0
  60. pytest_threadpool-0.3.5/tests/integration_tests/cases/scope_mixed_fail_skip.py +38 -0
  61. pytest_threadpool-0.3.5/tests/integration_tests/cases/scope_two_classes_mixed.py +48 -0
  62. pytest_threadpool-0.3.5/tests/integration_tests/cases/xunit_function_mixed_parallel.py +60 -0
  63. pytest_threadpool-0.3.5/tests/integration_tests/cases/xunit_method_mixed_parallel.py +49 -0
  64. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/conftest.py +22 -1
  65. pytest_threadpool-0.3.5/tests/integration_tests/test_capture.py +172 -0
  66. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_edge_cases.py +149 -8
  67. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_func_fixtures.py +29 -1
  68. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_package.py +78 -0
  69. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_reporting.py +127 -0
  70. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_scopes.py +12 -0
  71. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_xunit.py +12 -0
  72. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/unit_tests/test_unit_grouping.py +120 -0
  73. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/unit_tests/test_unit_markers.py +70 -0
  74. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/unit_tests/test_unit_plugin.py +21 -0
  75. pytest_threadpool-0.3.5/tests/unit_tests/test_unit_stream_proxy.py +122 -0
  76. pytest_threadpool-0.3.3/README.md +0 -170
  77. pytest_threadpool-0.3.3/hooks/pre-commit +0 -13
  78. pytest_threadpool-0.3.3/tests/integration_tests/cases/fixture_func_setup_parallel.py +0 -51
  79. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/.github/workflows/publish.yml +0 -0
  80. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/.gitignore +0 -0
  81. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/LICENSE +0 -0
  82. {pytest_threadpool-0.3.3/tests → pytest_threadpool-0.3.5/examples/test_event_bus}/__init__.py +0 -0
  83. {pytest_threadpool-0.3.3/tests/integration_tests/cases → pytest_threadpool-0.3.5/examples/test_logging}/__init__.py +0 -0
  84. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/__init__.py +0 -0
  85. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_api.py +0 -0
  86. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_constants.py +0 -0
  87. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/src/pytest_threadpool/_fixtures.py +0 -0
  88. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/__init__.py +0 -0
  89. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/_templates.py +0 -0
  90. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/class_barrier_concurrency.py +0 -0
  91. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/class_single_method.py +0 -0
  92. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/class_thread_verification.py +0 -0
  93. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/collected_count.py +0 -0
  94. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/edge_cross_module_group.py +0 -0
  95. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/edge_keyboard_interrupt.py +0 -0
  96. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/edge_nested_threads.py +0 -0
  97. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/edge_sigint.py +0 -0
  98. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/edge_sigint_many.py +0 -0
  99. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/edge_system_exit.py +0 -0
  100. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_autouse_function.py +0 -0
  101. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_class_scoped_once.py +0 -0
  102. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_class_yield.py +0 -0
  103. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_addfinalizer.py +0 -0
  104. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_chain.py +0 -0
  105. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_multiple_per_test.py +0 -0
  106. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_with_shared_deps.py +0 -0
  107. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_with_tmp_path.py +0 -0
  108. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_with_xunit.py +0 -0
  109. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_func_yield_teardown.py +0 -0
  110. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_function_scoped.py +0 -0
  111. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_interdependent_finalizers.py +0 -0
  112. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_multiple_scopes.py +0 -0
  113. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_parameterized.py +0 -0
  114. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_teardown_exception.py +0 -0
  115. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/fixture_yield_cleanup.py +0 -0
  116. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/marks_custom.py +0 -0
  117. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/marks_standard.py +0 -0
  118. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/parallel_only_skip.py +0 -0
  119. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/reporting_cross_module.py +0 -0
  120. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/reporting_incremental.py +0 -0
  121. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/reporting_incremental_conftest.py +0 -0
  122. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/reporting_package_children.py +0 -0
  123. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_all_merged.py +0 -0
  124. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_all_not_parallelizable.py +0 -0
  125. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_children_separate_params.py +0 -0
  126. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_dynamic_parametrize.py +0 -0
  127. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_method_overrides_class.py +0 -0
  128. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_module_children.py +0 -0
  129. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_not_parallelizable_function.py +0 -0
  130. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_not_parallelizable_method.py +0 -0
  131. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/scope_parameters.py +0 -0
  132. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/sequential_bare_functions.py +0 -0
  133. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/sequential_unmarked_class.py +0 -0
  134. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/setup_all_fail.py +0 -0
  135. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/setup_mixed_pass_fail.py +0 -0
  136. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/shared_counter.py +0 -0
  137. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/shared_cross_group_non_pickleable.py +0 -0
  138. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/shared_dict_mutation.py +0 -0
  139. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/shared_non_pickleable.py +0 -0
  140. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/shared_two_phase_barrier.py +0 -0
  141. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/teardown_after_test_failure.py +0 -0
  142. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/teardown_parallel_timing.py +0 -0
  143. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/teardown_same_thread.py +0 -0
  144. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/teardown_xunit_function.py +0 -0
  145. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/teardown_xunit_method_thread.py +0 -0
  146. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/validate_threadpool.py +0 -0
  147. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/validate_threadpool_conftest.py +0 -0
  148. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/xunit_class_setup.py +0 -0
  149. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/xunit_combined_setup.py +0 -0
  150. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/xunit_function_setup.py +0 -0
  151. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/xunit_method_setup.py +0 -0
  152. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/xunit_method_teardown_runs.py +0 -0
  153. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/cases/xunit_module_setup.py +0 -0
  154. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_class.py +0 -0
  155. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_fixtures.py +0 -0
  156. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_marks.py +0 -0
  157. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_parallel_teardown.py +0 -0
  158. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_sequential.py +0 -0
  159. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/integration_tests/test_pytester_shared.py +0 -0
  160. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/unit_tests/__init__.py +0 -0
  161. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/unit_tests/test_unit_api.py +0 -0
  162. {pytest_threadpool-0.3.3 → pytest_threadpool-0.3.5}/tests/unit_tests/test_unit_fixtures.py +0 -0
@@ -3,6 +3,7 @@ name: CI
3
3
  on:
4
4
  push:
5
5
  branches: [main]
6
+ tags-ignore: ["v*"]
6
7
  pull_request:
7
8
  branches: [main]
8
9
  workflow_call:
@@ -25,7 +26,7 @@ jobs:
25
26
  strategy:
26
27
  fail-fast: false
27
28
  matrix:
28
- python: ["3.13t", "3.14t", "3.15t"]
29
+ python: ["3.13", "3.13t", "3.14", "3.14t", "3.15", "3.15t"]
29
30
  steps:
30
31
  - uses: actions/checkout@v4
31
32
  with:
@@ -33,9 +34,23 @@ jobs:
33
34
  - uses: astral-sh/setup-uv@v6
34
35
  - run: uv python install ${{ matrix.python }}
35
36
  - run: uv sync --python ${{ matrix.python }} --group dev
36
- - run: uv run --python ${{ matrix.python }} pytest -v --tb=short --cov --cov-branch --cov-report=xml
37
+ - name: Run tests
38
+ id: tests
39
+ env:
40
+ COVERAGE_PROCESS_START: pyproject.toml
41
+ run: uv run --python ${{ matrix.python }} coverage run -m pytest -v --tb=short
42
+ - name: Generate coverage report
43
+ if: always() && steps.tests.outcome != 'cancelled'
44
+ run: |
45
+ uv run --python ${{ matrix.python }} coverage combine
46
+ uv run --python ${{ matrix.python }} coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY"
47
+ uv run --python ${{ matrix.python }} coverage xml
48
+ sed -i 's|<source></source>|<source>.</source>|' coverage.xml
37
49
  - name: Upload coverage reports to Codecov
38
- if: matrix.python == '3.14t'
50
+ if: always() && matrix.python == '3.14t' && steps.tests.outcome != 'cancelled'
39
51
  uses: codecov/codecov-action@v5
40
52
  with:
41
53
  token: ${{ secrets.CODECOV_TOKEN }}
54
+ files: coverage.xml
55
+ fail_ci_if_error: true
56
+ verbose: true
@@ -0,0 +1,17 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.6
4
+ hooks:
5
+ - id: ruff-format
6
+ args: [--check, src/, tests/]
7
+ - id: ruff
8
+ args: [src/, tests/]
9
+
10
+ - repo: local
11
+ hooks:
12
+ - id: pyright
13
+ name: pyright
14
+ entry: uv run pyright src/
15
+ language: system
16
+ types: [python]
17
+ pass_filenames: false
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.3.5
4
+
5
+ ### Fixes
6
+
7
+ - Fixed crash (KeyError) when a worker thread throws an unexpected exception
8
+ outside the normal test lifecycle — failures are now reported gracefully
9
+ instead of taking down the entire test run.
10
+ - `--threadpool` with a non-numeric value (e.g. `--threadpool foo`) now raises
11
+ a clean `pytest.UsageError` instead of an unhandled `ValueError` traceback.
12
+ - `@parallelizable("children")` applied directly to a test function now emits
13
+ a warning explaining that functions have no children, instead of being
14
+ silently ignored.
15
+
16
+ ### Cleanup
17
+
18
+ - Moved `ParallelScope` import to module top in `_markers.py`, removed
19
+ misleading circular-import comment.
@@ -13,7 +13,9 @@ This installs a free-threaded Python via uv, syncs all dependencies (including
13
13
  dev tools), and sets up a pre-commit hook that runs `ruff format`, `ruff check`,
14
14
  and `pyright` before each commit.
15
15
 
16
- > Python 3.13t and 3.14t ship with the GIL disabled by default.
16
+ > Free-threaded builds (3.13t, 3.14t, 3.15t) ship with the GIL disabled and
17
+ > provide true parallelism. Standard builds (3.13, 3.14, 3.15) are also
18
+ > supported — threads work, but the GIL serializes CPU-bound code.
17
19
 
18
20
  ## Architecture rules
19
21
 
@@ -125,9 +127,6 @@ src/pytest_threadpool/
125
127
  _runner.py # ParallelRunner: parallel execution orchestration
126
128
  plugin.py # pytest hook implementations (wiring only)
127
129
 
128
- hooks/
129
- pre-commit # Git pre-commit hook (ruff + pyright)
130
-
131
130
  scripts/
132
131
  setup-dev # One-command dev environment setup
133
132
  ```
@@ -0,0 +1,228 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-threadpool
3
+ Version: 0.3.5
4
+ Summary: Parallel test execution for free-threaded Python builds
5
+ Project-URL: Homepage, https://github.com/pytest-threadpool/pytest-threadpool
6
+ Project-URL: Source, https://github.com/pytest-threadpool/pytest-threadpool
7
+ Project-URL: Issues, https://github.com/pytest-threadpool/pytest-threadpool/issues
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Keywords: free-threaded,nogil,parallel,pytest,pytest parallel,testing,threadpool
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: pytest<=9.0.2,>=9.0.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # pytest-threadpool
25
+
26
+ [![PyPI - Version](https://img.shields.io/pypi/v/pytest-threadpool)](https://pypi.org/project/pytest-threadpool/)
27
+ [![Python](https://img.shields.io/badge/python-3.13%20%7C%203.13t%20%7C%203.14%20%7C%203.14t%20%7C%203.15%20%7C%203.15t-blue?logo=python&logoColor=white)](https://pypi.org/project/pytest-threadpool/)
28
+ [![License](https://img.shields.io/github/license/pytest-threadpool/pytest-threadpool)](https://github.com/pytest-threadpool/pytest-threadpool/blob/main/LICENSE)
29
+ [![GitHub](https://img.shields.io/badge/GitHub-pytest--threadpool-blue?logo=github)](https://github.com/pytest-threadpool/pytest-threadpool)
30
+ [![CI](https://github.com/pytest-threadpool/pytest-threadpool/actions/workflows/ci.yml/badge.svg)](https://github.com/pytest-threadpool/pytest-threadpool/actions/workflows/ci.yml)
31
+ [![codecov](https://codecov.io/gh/pytest-threadpool/pytest-threadpool/branch/main/graph/badge.svg)](https://codecov.io/gh/pytest-threadpool/pytest-threadpool)
32
+
33
+ **Status: Beta** · Parallel test execution using threads.
34
+
35
+ Runs test *bodies*, function-scoped fixture setup, and function-scoped fixture
36
+ teardown concurrently in a thread pool while keeping shared fixtures
37
+ (module/class/session scope) sequential.
38
+
39
+ Works on any Python 3.13+. Free-threaded builds (3.13t, 3.14t, 3.15t) get true
40
+ parallelism for CPU-bound tests. Standard builds still benefit from parallel
41
+ execution of I/O-bound tests (network, database, file operations).
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install pytest-threadpool
47
+ ```
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ pytest --threadpool auto
53
+ ```
54
+
55
+ Mark tests for parallel execution:
56
+
57
+ ```python
58
+ from pytest_threadpool import parallelizable, not_parallelizable
59
+
60
+ import pytest
61
+
62
+
63
+ @parallelizable("children") # all nested tests run in parallel
64
+ class TestMyFeature:
65
+ def test_a(self): ...
66
+
67
+ def test_b(self): ...
68
+
69
+
70
+ @parallelizable("parameters") # parametrized variants run in parallel
71
+ @pytest.mark.parametrize("x", [1, 2, 3])
72
+ def test_with_params(x): ...
73
+
74
+
75
+ @parallelizable("all") # children + parameters combined
76
+ class TestEverything:
77
+ @pytest.mark.parametrize("n", [1, 2])
78
+ def test_param(self, n): ...
79
+
80
+ def test_plain(self): ...
81
+
82
+
83
+ @not_parallelizable # opt out of inherited parallelism
84
+ def test_must_be_sequential(): ...
85
+ ```
86
+
87
+ ## Scopes
88
+
89
+ | Scope | Effect |
90
+ |--------------|-------------------------------------------------------------------|
91
+ | `children` | All nested tests run concurrently (children, grandchildren, etc.) |
92
+ | `parameters` | Parametrized variants of the same test run concurrently |
93
+ | `all` | Combines `children` + `parameters` |
94
+
95
+ ## Fixture handling
96
+
97
+ Function-scoped fixtures are cloned per-item and set up in parallel alongside
98
+ test calls. Each worker gets independent fixture instances — no shared mutable
99
+ state between concurrent fixture setups.
100
+
101
+ Shared fixtures (module, class, and session scope) are resolved once
102
+ sequentially before workers launch and served from cache to all items.
103
+
104
+ | Scope | Behavior |
105
+ |------------|---------------------------------------------------------|
106
+ | `function` | Cloned per-item, setup and teardown in parallel workers |
107
+ | `class` | Resolved once, cached, shared across items |
108
+ | `module` | Resolved once, cached, shared across items |
109
+ | `session` | Resolved once, cached, shared across items |
110
+
111
+ Shared fixture teardown runs sequentially after the parallel group completes.
112
+
113
+ ## Marker levels
114
+
115
+ Markers can be applied at function, class, module (`pytestmark`), or
116
+ package (`__init__.py` `pytestmark`) level. Priority (most specific wins):
117
+
118
+ ```
119
+ not_parallelizable > own marker > class > module > package
120
+ ```
121
+
122
+ ## Shared state between tests
123
+
124
+ Unlike `pytest-xdist`, which uses subprocesses and requires all test data to
125
+ be pickleable, `pytest-threadpool` runs tests in threads within a **single
126
+ process**. This means tests can share common non-pickleable, thread-safe
127
+ objects — both within a parallel group and across sequential groups:
128
+
129
+ ```python
130
+ import threading
131
+ import pytest
132
+
133
+
134
+ class SharedState:
135
+ lock = threading.Lock() # not pickleable
136
+ event = threading.Event() # not pickleable
137
+ results = {}
138
+
139
+
140
+ @pytest.mark.parallelizable("children")
141
+ class TestGroupA:
142
+ def test_a1(self):
143
+ with SharedState.lock:
144
+ SharedState.results["a1"] = True
145
+
146
+ def test_a2(self):
147
+ with SharedState.lock:
148
+ SharedState.results["a2"] = True
149
+
150
+
151
+ @pytest.mark.parallelizable("children")
152
+ class TestGroupB:
153
+ def test_b1(self):
154
+ SharedState.event.set()
155
+ with SharedState.lock:
156
+ SharedState.results["b1"] = True
157
+
158
+ def test_b2(self):
159
+ assert SharedState.event.wait(timeout=10)
160
+ with SharedState.lock:
161
+ SharedState.results["b2"] = True
162
+ ```
163
+
164
+ Objects like `threading.Lock`, `threading.Event`, `logging.Logger`, database
165
+ connections, and other non-pickleable resources can live as class attributes
166
+ and be accessed freely from any test — parallel or sequential — without
167
+ serialization overhead or workarounds.
168
+
169
+ ## Usage
170
+
171
+ ```bash
172
+ # Auto-detect thread count
173
+ pytest --threadpool auto
174
+
175
+ # Fixed thread count
176
+ pytest --threadpool 8
177
+
178
+ # Normal sequential run (no flag)
179
+ pytest
180
+ ```
181
+
182
+ ## Tested versions
183
+
184
+ | Component | Versions |
185
+ |-----------|---------------------------------------|
186
+ | Python | 3.13, 3.13t, 3.14, 3.14t, 3.15, 3.15t |
187
+ | pytest | 9.0.2 |
188
+
189
+ > **Note:** On standard (GIL-enabled) builds, the GIL limits parallel speedup
190
+ > for CPU-bound tests. I/O-bound tests still run concurrently.
191
+
192
+ ## Examples
193
+
194
+ The [`examples/`](examples/) directory contains runnable usage patterns:
195
+
196
+ - **DI container** — dependency injection with Singleton, ThreadLocal, ContextLocal, and Factory scopes
197
+ - **Event bus** — shared in-memory test double with concurrent producers and aggregate verification
198
+ - **Parallel logging** — shared thread-safe log collector (caplog alternative)
199
+ - **Shared state** — barriers, atomic counters, and cross-group coordination
200
+ - **User pool** — custom thread pool with LIFO queue recycling
201
+
202
+ The [`tests/integration_tests/cases/`](tests/integration_tests/cases/) and
203
+ [`tests/integration_tests/`](tests/integration_tests/) directories are also
204
+ worth browsing for real-world grouping, fixture, and reporting scenarios.
205
+
206
+ ## Known limitations
207
+
208
+ - **Private pytest API usage** — The plugin relies on internal `_pytest` APIs
209
+ (fixture finalizers, setup state, terminal writer) that have no public
210
+ equivalents. These may break across pytest releases without warning.
211
+ - **No plugin compatibility guarantees** — Interactions with other pytest
212
+ plugins (e.g. `pytest-xdist`, `pytest-timeout`, `pytest-randomly`) are
213
+ untested and may conflict.
214
+ - **No `capsys`/`capfd`/`caplog` in parallel** — These fixtures are not
215
+ thread-safe. `capsys`/`capfd` fail with "cannot use capsys and capsys at
216
+ the same time" when requested by parallel tests. `caplog` leaks records
217
+ across fixtures and its `at_level()` context managers race on the shared
218
+ root logger. Alternatives:
219
+ - **`print()`** — Worker output is suppressed by default (buffered by
220
+ thread-local stream proxies). Pass `-s` (`--capture=no`) to disable
221
+ suppression and see interleaved output.
222
+ - **Logging** — Use a per-test `FileHandler` writing to `tmp_path`, or
223
+ collect structured records in a shared thread-safe list (see
224
+ [`examples/test_logging/`](examples/test_logging/)).
225
+
226
+ ## License
227
+
228
+ [Apache 2.0](LICENSE)
@@ -1,35 +1,22 @@
1
- Metadata-Version: 2.4
2
- Name: pytest-threadpool
3
- Version: 0.3.3
4
- Summary: Parallel test execution for free-threaded Python builds
5
- License-Expression: MIT
6
- License-File: LICENSE
7
- Keywords: free-threaded,nogil,parallel,pytest,pytest parallel,testing,threadpool
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Framework :: Pytest
10
- Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Topic :: Software Development :: Testing
14
- Requires-Python: >=3.13
15
- Requires-Dist: pytest>=9.0.2
16
- Description-Content-Type: text/markdown
17
-
18
1
  # pytest-threadpool
19
2
 
20
3
  [![PyPI - Version](https://img.shields.io/pypi/v/pytest-threadpool)](https://pypi.org/project/pytest-threadpool/)
21
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytest-threadpool)](https://pypi.org/project/pytest-threadpool/)
4
+ [![Python](https://img.shields.io/badge/python-3.13%20%7C%203.13t%20%7C%203.14%20%7C%203.14t%20%7C%203.15%20%7C%203.15t-blue?logo=python&logoColor=white)](https://pypi.org/project/pytest-threadpool/)
22
5
  [![License](https://img.shields.io/github/license/pytest-threadpool/pytest-threadpool)](https://github.com/pytest-threadpool/pytest-threadpool/blob/main/LICENSE)
23
6
  [![GitHub](https://img.shields.io/badge/GitHub-pytest--threadpool-blue?logo=github)](https://github.com/pytest-threadpool/pytest-threadpool)
24
7
  [![CI](https://github.com/pytest-threadpool/pytest-threadpool/actions/workflows/ci.yml/badge.svg)](https://github.com/pytest-threadpool/pytest-threadpool/actions/workflows/ci.yml)
25
8
  [![codecov](https://codecov.io/gh/pytest-threadpool/pytest-threadpool/branch/main/graph/badge.svg)](https://codecov.io/gh/pytest-threadpool/pytest-threadpool)
26
9
 
27
- **Status: Beta** · Parallel test execution for free-threaded Python builds (3.13t+).
10
+ **Status: Beta** · Parallel test execution using threads.
28
11
 
29
12
  Runs test *bodies*, function-scoped fixture setup, and function-scoped fixture
30
13
  teardown concurrently in a thread pool while keeping shared fixtures
31
14
  (module/class/session scope) sequential.
32
15
 
16
+ Works on any Python 3.13+. Free-threaded builds (3.13t, 3.14t, 3.15t) get true
17
+ parallelism for CPU-bound tests. Standard builds still benefit from parallel
18
+ execution of I/O-bound tests (network, database, file operations).
19
+
33
20
  ## Installation
34
21
 
35
22
  ```bash
@@ -49,22 +36,28 @@ from pytest_threadpool import parallelizable, not_parallelizable
49
36
 
50
37
  import pytest
51
38
 
52
- @parallelizable("children") # all nested tests run in parallel
39
+
40
+ @parallelizable("children") # all nested tests run in parallel
53
41
  class TestMyFeature:
54
- def test_a(self): ...
55
- def test_b(self): ...
42
+ def test_a(self): ...
43
+
44
+ def test_b(self): ...
56
45
 
57
- @parallelizable("parameters") # parametrized variants run in parallel
46
+
47
+ @parallelizable("parameters") # parametrized variants run in parallel
58
48
  @pytest.mark.parametrize("x", [1, 2, 3])
59
49
  def test_with_params(x): ...
60
50
 
61
- @parallelizable("all") # children + parameters combined
51
+
52
+ @parallelizable("all") # children + parameters combined
62
53
  class TestEverything:
63
- @pytest.mark.parametrize("n", [1, 2])
64
- def test_param(self, n): ...
65
- def test_plain(self): ...
54
+ @pytest.mark.parametrize("n", [1, 2])
55
+ def test_param(self, n): ...
56
+
57
+ def test_plain(self): ...
66
58
 
67
- @not_parallelizable # opt out of inherited parallelism
59
+
60
+ @not_parallelizable # opt out of inherited parallelism
68
61
  def test_must_be_sequential(): ...
69
62
  ```
70
63
 
@@ -85,8 +78,8 @@ state between concurrent fixture setups.
85
78
  Shared fixtures (module, class, and session scope) are resolved once
86
79
  sequentially before workers launch and served from cache to all items.
87
80
 
88
- | Scope | Behavior |
89
- |------------|-------------------------------------------------|
81
+ | Scope | Behavior |
82
+ |------------|---------------------------------------------------------|
90
83
  | `function` | Cloned per-item, setup and teardown in parallel workers |
91
84
  | `class` | Resolved once, cached, shared across items |
92
85
  | `module` | Resolved once, cached, shared across items |
@@ -116,33 +109,33 @@ import pytest
116
109
 
117
110
 
118
111
  class SharedState:
119
- lock = threading.Lock() # not pickleable
120
- event = threading.Event() # not pickleable
121
- results = {}
112
+ lock = threading.Lock() # not pickleable
113
+ event = threading.Event() # not pickleable
114
+ results = {}
122
115
 
123
116
 
124
117
  @pytest.mark.parallelizable("children")
125
118
  class TestGroupA:
126
- def test_a1(self):
127
- with SharedState.lock:
128
- SharedState.results["a1"] = True
119
+ def test_a1(self):
120
+ with SharedState.lock:
121
+ SharedState.results["a1"] = True
129
122
 
130
- def test_a2(self):
131
- with SharedState.lock:
132
- SharedState.results["a2"] = True
123
+ def test_a2(self):
124
+ with SharedState.lock:
125
+ SharedState.results["a2"] = True
133
126
 
134
127
 
135
128
  @pytest.mark.parallelizable("children")
136
129
  class TestGroupB:
137
- def test_b1(self):
138
- SharedState.event.set()
139
- with SharedState.lock:
140
- SharedState.results["b1"] = True
141
-
142
- def test_b2(self):
143
- assert SharedState.event.wait(timeout=10)
144
- with SharedState.lock:
145
- SharedState.results["b2"] = True
130
+ def test_b1(self):
131
+ SharedState.event.set()
132
+ with SharedState.lock:
133
+ SharedState.results["b1"] = True
134
+
135
+ def test_b2(self):
136
+ assert SharedState.event.wait(timeout=10)
137
+ with SharedState.lock:
138
+ SharedState.results["b2"] = True
146
139
  ```
147
140
 
148
141
  Objects like `threading.Lock`, `threading.Event`, `logging.Logger`, database
@@ -165,10 +158,27 @@ pytest
165
158
 
166
159
  ## Tested versions
167
160
 
168
- | Component | Versions |
169
- |-----------|----------|
170
- | Python | 3.13t, 3.14t, 3.15t |
171
- | pytest | >=9.0.2 |
161
+ | Component | Versions |
162
+ |-----------|---------------------------------------|
163
+ | Python | 3.13, 3.13t, 3.14, 3.14t, 3.15, 3.15t |
164
+ | pytest | 9.0.2 |
165
+
166
+ > **Note:** On standard (GIL-enabled) builds, the GIL limits parallel speedup
167
+ > for CPU-bound tests. I/O-bound tests still run concurrently.
168
+
169
+ ## Examples
170
+
171
+ The [`examples/`](examples/) directory contains runnable usage patterns:
172
+
173
+ - **DI container** — dependency injection with Singleton, ThreadLocal, ContextLocal, and Factory scopes
174
+ - **Event bus** — shared in-memory test double with concurrent producers and aggregate verification
175
+ - **Parallel logging** — shared thread-safe log collector (caplog alternative)
176
+ - **Shared state** — barriers, atomic counters, and cross-group coordination
177
+ - **User pool** — custom thread pool with LIFO queue recycling
178
+
179
+ The [`tests/integration_tests/cases/`](tests/integration_tests/cases/) and
180
+ [`tests/integration_tests/`](tests/integration_tests/) directories are also
181
+ worth browsing for real-world grouping, fixture, and reporting scenarios.
172
182
 
173
183
  ## Known limitations
174
184
 
@@ -178,10 +188,18 @@ pytest
178
188
  - **No plugin compatibility guarantees** — Interactions with other pytest
179
189
  plugins (e.g. `pytest-xdist`, `pytest-timeout`, `pytest-randomly`) are
180
190
  untested and may conflict.
181
- - **No captured stdout in parallel** — Standard output from tests running
182
- concurrently is written directly to the terminal. Pytest's built-in capture
183
- (`capsys`/`capfd`) is not supported during parallel execution.
191
+ - **No `capsys`/`capfd`/`caplog` in parallel** — These fixtures are not
192
+ thread-safe. `capsys`/`capfd` fail with "cannot use capsys and capsys at
193
+ the same time" when requested by parallel tests. `caplog` leaks records
194
+ across fixtures and its `at_level()` context managers race on the shared
195
+ root logger. Alternatives:
196
+ - **`print()`** — Worker output is suppressed by default (buffered by
197
+ thread-local stream proxies). Pass `-s` (`--capture=no`) to disable
198
+ suppression and see interleaved output.
199
+ - **Logging** — Use a per-test `FileHandler` writing to `tmp_path`, or
200
+ collect structured records in a shared thread-safe list (see
201
+ [`examples/test_logging/`](examples/test_logging/)).
184
202
 
185
203
  ## License
186
204
 
187
- MIT
205
+ [Apache 2.0](LICENSE)
@@ -0,0 +1,83 @@
1
+ # Roadmap
2
+
3
+ ## Capture fixture support (capsys / capfd / caplog)
4
+
5
+ **Goal:** Make `capsys`, `capfd`, and `caplog` work transparently in parallel tests.
6
+
7
+ **Problem:** These fixtures assume exclusive ownership of global state (`sys.stdout`,
8
+ root logger) during a test's lifetime. When multiple tests run concurrently, output
9
+ interleaves and pytest raises "cannot use capsys and capsys at the same time".
10
+ `caplog` leaks records across tests, and `at_level()` context managers race on the
11
+ shared root logger.
12
+
13
+ **Approach:** Delayed replay — collect output per-worker during parallel execution,
14
+ then feed it back one item at a time during the sequential reporting phase.
15
+
16
+ - **capsys/capfd:** The `_ThreadLocalStream` proxy already buffers worker output
17
+ into per-thread `StringIO`. Instead of discarding on `deactivate()`, associate
18
+ each buffer with its test item and flush to the real stream before
19
+ `pytest_runtest_makereport` reads the capture.
20
+
21
+ - **caplog:** Install a per-thread `logging.Handler` that collects records into a
22
+ thread-local list. During sequential reporting, replay records into caplog's
23
+ handler so the fixture sees the correct records for each test.
24
+
25
+ **Edge cases to handle:**
26
+ - Nested captures (fixtures that call `capsys.readouterr()` mid-test)
27
+ - `caplog.at_level()` context managers across threads
28
+ - Fixtures that mix capsys with print output
29
+ - `capfd` (file descriptor level) vs `capsys` (sys.stdout level)
30
+
31
+ **Priority:** High — this is the most common friction point for adoption.
32
+
33
+ ## Harden FixtureDef cloning
34
+
35
+ **Goal:** Make function-scoped fixture cloning more robust against pytest internals
36
+ changes and more transparent to developers.
37
+
38
+ **Current approach:** `clone_function_fixturedefs` uses `__new__` + `__dict__.update`
39
+ for a shallow copy, then resets `cached_result` and `_finalizers`. This works because
40
+ those are the only two fields mutated during fixture lifecycle — but it's fragile.
41
+
42
+ **Improvements:**
43
+
44
+ - **Deep-copy guard:** Validate at clone time that the only mutable fields are the
45
+ ones we reset. If a future pytest version adds mutable state to `FixtureDef`,
46
+ the clone should fail loudly (assertion or warning) rather than silently sharing
47
+ state across workers.
48
+
49
+ - **Document fixture chain isolation:** When fixture A (function-scoped) depends on
50
+ fixture B (also function-scoped), both get cloned independently. Chain resolution
51
+ works because `_arg2fixturedefs` is replaced as a whole, so `getfixturevalue("B")`
52
+ hits B's clone. This is correct but non-obvious — add inline comments explaining
53
+ the chain resolution path through the cloned map.
54
+
55
+ - **`object.__setattr__` on `_arg2fixturedefs`:** Currently bypasses the `Final`
56
+ typing annotation. If pytest ever enforces immutability at runtime (frozen
57
+ dataclass, `__slots__`), this breaks. Track whether upstream adds a setter or
58
+ public mutation API.
59
+
60
+ **Priority:** Medium — current implementation works, but one pytest release could
61
+ break it silently.
62
+
63
+ ## Plugin compatibility testing
64
+
65
+ Validate and document interactions with commonly used pytest plugins:
66
+
67
+ - `pytest-randomly` — test reordering vs parallel group formation
68
+ - `pytest-timeout` — per-test timeouts in worker threads
69
+ - `pytest-repeat` — repeated test execution in parallel groups
70
+ - `pytest-cov` — coverage collection across worker threads
71
+
72
+ ## Public pytest API migration
73
+
74
+ Track pytest releases for public equivalents of internal APIs currently used:
75
+
76
+ - `_pytest.fixtures.FixtureDef._finalizers`
77
+ - `_pytest.fixtures.FixtureDef.cached_result`
78
+ - `_pytest.setupplan.SetupState.stack`
79
+ - `_pytest.scope.Scope`
80
+ - `callspec._arg2scope`
81
+
82
+ Migrate to public APIs as they become available to reduce breakage risk
83
+ across pytest releases.
@@ -0,0 +1,14 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ default:
5
+ target: auto
6
+ threshold: 1%
7
+ patch:
8
+ default:
9
+ target: auto
10
+
11
+ ignore:
12
+ - "src/pytest_threadpool/_version.py"
13
+
14
+ comment: false
@@ -0,0 +1,3 @@
1
+ from pytest_threadpool import parallelizable
2
+
3
+ pytestmark = parallelizable("all")
@@ -0,0 +1,28 @@
1
+ """Fixtures resolving from the static DI container.
2
+
3
+ - ``request_handler`` injects a fresh RequestHandler per test (Factory).
4
+ - ``test_context`` injects a per-test TestContext (ContextLocal, reset per test).
5
+ """
6
+
7
+ import pytest
8
+
9
+ from examples.test_di.container import Container
10
+
11
+
12
+ @pytest.fixture
13
+ def request_handler():
14
+ """Fresh RequestHandler per test (Factory provider)."""
15
+ return Container.request_handler()
16
+
17
+
18
+ @pytest.fixture
19
+ def test_context():
20
+ """Per-test TestContext — ContextLocal + reset = test-local scope.
21
+
22
+ ContextLocal uses contextvars, so the instance follows the execution
23
+ context — not the OS thread. Safe across await boundaries.
24
+ The reset() in teardown gives the next test a fresh instance.
25
+ """
26
+ ctx = Container.test_context()
27
+ yield ctx
28
+ Container.test_context.reset()