apte 0.3.0__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 (100) hide show
  1. apte-0.3.0/LICENSE +21 -0
  2. apte-0.3.0/PKG-INFO +211 -0
  3. apte-0.3.0/README.md +180 -0
  4. apte-0.3.0/apte/__init__.py +55 -0
  5. apte-0.3.0/apte/__main__.py +5 -0
  6. apte-0.3.0/apte/api.py +194 -0
  7. apte-0.3.0/apte/assertions.py +152 -0
  8. apte-0.3.0/apte/cache/__init__.py +6 -0
  9. apte-0.3.0/apte/cache/plugin.py +94 -0
  10. apte-0.3.0/apte/cache/storage.py +132 -0
  11. apte-0.3.0/apte/cli/__init__.py +0 -0
  12. apte-0.3.0/apte/cli/main.py +342 -0
  13. apte-0.3.0/apte/compat.py +15 -0
  14. apte-0.3.0/apte/console.py +85 -0
  15. apte-0.3.0/apte/core/__init__.py +0 -0
  16. apte-0.3.0/apte/core/collector.py +242 -0
  17. apte-0.3.0/apte/core/execution/__init__.py +7 -0
  18. apte-0.3.0/apte/core/execution/parallel.py +304 -0
  19. apte-0.3.0/apte/core/execution/suite_manager.py +93 -0
  20. apte-0.3.0/apte/core/execution/test_executor.py +371 -0
  21. apte-0.3.0/apte/core/fixture.py +14 -0
  22. apte-0.3.0/apte/core/outcome.py +137 -0
  23. apte-0.3.0/apte/core/runner.py +206 -0
  24. apte-0.3.0/apte/core/session.py +382 -0
  25. apte-0.3.0/apte/core/suite.py +236 -0
  26. apte-0.3.0/apte/core/tracker.py +50 -0
  27. apte-0.3.0/apte/di/__init__.py +0 -0
  28. apte-0.3.0/apte/di/container.py +851 -0
  29. apte-0.3.0/apte/di/decorators.py +220 -0
  30. apte-0.3.0/apte/di/factory.py +79 -0
  31. apte-0.3.0/apte/di/hashable.py +57 -0
  32. apte-0.3.0/apte/di/hints.py +163 -0
  33. apte-0.3.0/apte/di/markers.py +79 -0
  34. apte-0.3.0/apte/di/proxy.py +81 -0
  35. apte-0.3.0/apte/di/validation.py +38 -0
  36. apte-0.3.0/apte/entities/__init__.py +70 -0
  37. apte-0.3.0/apte/entities/core.py +158 -0
  38. apte-0.3.0/apte/entities/events.py +171 -0
  39. apte-0.3.0/apte/entities/log_capture.py +28 -0
  40. apte-0.3.0/apte/entities/retry.py +31 -0
  41. apte-0.3.0/apte/entities/skip.py +63 -0
  42. apte-0.3.0/apte/entities/suite_path.py +70 -0
  43. apte-0.3.0/apte/entities/xfail.py +24 -0
  44. apte-0.3.0/apte/evals/__init__.py +45 -0
  45. apte-0.3.0/apte/evals/evaluator.py +420 -0
  46. apte-0.3.0/apte/evals/evaluators.py +199 -0
  47. apte-0.3.0/apte/evals/hashing.py +109 -0
  48. apte-0.3.0/apte/evals/results_writer.py +175 -0
  49. apte-0.3.0/apte/evals/suite.py +98 -0
  50. apte-0.3.0/apte/evals/types.py +356 -0
  51. apte-0.3.0/apte/evals/wrapper.py +309 -0
  52. apte-0.3.0/apte/events/__init__.py +0 -0
  53. apte-0.3.0/apte/events/bus.py +231 -0
  54. apte-0.3.0/apte/events/types.py +38 -0
  55. apte-0.3.0/apte/exceptions.py +188 -0
  56. apte-0.3.0/apte/execution/__init__.py +0 -0
  57. apte-0.3.0/apte/execution/async_bridge.py +36 -0
  58. apte-0.3.0/apte/execution/capture.py +264 -0
  59. apte-0.3.0/apte/execution/context.py +73 -0
  60. apte-0.3.0/apte/execution/interrupt.py +118 -0
  61. apte-0.3.0/apte/execution/runner.py +0 -0
  62. apte-0.3.0/apte/filters/__init__.py +4 -0
  63. apte-0.3.0/apte/filters/keyword.py +52 -0
  64. apte-0.3.0/apte/filters/kind.py +37 -0
  65. apte-0.3.0/apte/filters/suite.py +43 -0
  66. apte-0.3.0/apte/fixtures/__init__.py +0 -0
  67. apte-0.3.0/apte/fixtures/builtins.py +38 -0
  68. apte-0.3.0/apte/fixtures/mocker.py +145 -0
  69. apte-0.3.0/apte/history/__init__.py +17 -0
  70. apte-0.3.0/apte/history/collector.py +80 -0
  71. apte-0.3.0/apte/history/plugin.py +254 -0
  72. apte-0.3.0/apte/history/storage.py +295 -0
  73. apte-0.3.0/apte/loader.py +85 -0
  74. apte-0.3.0/apte/plugin.py +221 -0
  75. apte-0.3.0/apte/py.typed +0 -0
  76. apte-0.3.0/apte/reporting/__init__.py +10 -0
  77. apte-0.3.0/apte/reporting/ascii.py +419 -0
  78. apte-0.3.0/apte/reporting/ctrf.py +252 -0
  79. apte-0.3.0/apte/reporting/factory.py +31 -0
  80. apte-0.3.0/apte/reporting/format.py +39 -0
  81. apte-0.3.0/apte/reporting/log_file.py +111 -0
  82. apte-0.3.0/apte/reporting/rich_reporter.py +523 -0
  83. apte-0.3.0/apte/reporting/verbosity.py +18 -0
  84. apte-0.3.0/apte/reporting/web.py +347 -0
  85. apte-0.3.0/apte/shell.py +200 -0
  86. apte-0.3.0/apte/tags/__init__.py +5 -0
  87. apte-0.3.0/apte/tags/plugin.py +77 -0
  88. apte-0.3.0/apte/utils.py +26 -0
  89. apte-0.3.0/apte.egg-info/PKG-INFO +211 -0
  90. apte-0.3.0/apte.egg-info/SOURCES.txt +98 -0
  91. apte-0.3.0/apte.egg-info/dependency_links.txt +1 -0
  92. apte-0.3.0/apte.egg-info/entry_points.txt +2 -0
  93. apte-0.3.0/apte.egg-info/requires.txt +7 -0
  94. apte-0.3.0/apte.egg-info/top_level.txt +1 -0
  95. apte-0.3.0/pyproject.toml +179 -0
  96. apte-0.3.0/setup.cfg +4 -0
  97. apte-0.3.0/tests/test_assertions.py +141 -0
  98. apte-0.3.0/tests/test_console_print.py +171 -0
  99. apte-0.3.0/tests/test_normalizers.py +73 -0
  100. apte-0.3.0/tests/test_shell.py +252 -0
apte-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Renaud Cepre
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
apte-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: apte
3
+ Version: 0.3.0
4
+ Summary: Async-first Python testing framework
5
+ Author-email: Renaud Cepre <renaudcepre@protonmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/renaudcepre/apte
8
+ Project-URL: Documentation, https://renaudcepre.github.io/apte/
9
+ Project-URL: Repository, https://github.com/renaudcepre/apte
10
+ Project-URL: Changelog, https://github.com/renaudcepre/apte/blob/main/CHANGELOG.md
11
+ Project-URL: Issues, https://github.com/renaudcepre/apte/issues
12
+ Keywords: testing,async,dependency-injection,test-framework,asyncio,fixtures
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Classifier: Framework :: AsyncIO
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: typing-extensions>=4.15.0
26
+ Provides-Extra: rich
27
+ Requires-Dist: rich>=15.0.0; extra == "rich"
28
+ Provides-Extra: web
29
+ Requires-Dist: websockets>=16.0; extra == "web"
30
+ Dynamic: license-file
31
+
32
+ ![Apte](assets/logo-with-text-animate.svg)
33
+
34
+ [![CI](https://github.com/renaudcepre/apte/actions/workflows/ci.yml/badge.svg)](https://github.com/renaudcepre/apte/actions/workflows/ci.yml)
35
+ [![codecov](https://codecov.io/gh/renaudcepre/apte/graph/badge.svg?token=V0MLGEE5UZ)](https://codecov.io/gh/renaudcepre/apte)
36
+ [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://renaudcepre.github.io/apte/)
37
+
38
+ **Write your tests and your LLM evals in one async-first framework.**
39
+
40
+ An eval is just a test that returns a value - scored, not asserted. Your evals get
41
+ real fixtures, dependency injection and parallelism, and live right next to the
42
+ tests they ship with. Python 3.10+, installs lean (Rich UI optional).
43
+
44
+ ---
45
+
46
+ ## Why Apte?
47
+
48
+ ### Explicit Injection (IDE-Ready)
49
+
50
+ **Ctrl+Click works.** Your IDE knows every type. No guessing where fixtures come from.
51
+
52
+ ```python
53
+ def test_user(db: Annotated[Database, Use(database)]): ...
54
+ ```
55
+
56
+ ### Native Async & Parallelism
57
+
58
+ Tests run as coroutines on a single event loop. No plugin needed.
59
+
60
+ ```bash
61
+ apte run tests:session -n 10
62
+ ```
63
+
64
+ ### Smart Tagging (Tag Propagation)
65
+
66
+ Tag a fixture once, every test using it inherits the tag automatically.
67
+
68
+ ```python
69
+ @fixture(tags=["database"])
70
+ def db(): ...
71
+
72
+ session.bind(db)
73
+
74
+ @session.test()
75
+ def test_users(repo: Annotated[Repo, Use(user_repo)]): ... # Also tagged "database"
76
+ ```
77
+
78
+ ```bash
79
+ apte run tests:session --no-tag database # Skips ALL tests touching DB
80
+ ```
81
+
82
+ ### Infra vs Code Errors (Error ≠ Fail)
83
+
84
+ ```
85
+ ✗ test_create_user: AssertionError # Your bug - TEST FAILED
86
+ ⚠ test_with_db: [FIXTURE] ConnectionError # Infra issue - SETUP ERROR
87
+ ```
88
+
89
+ ### Typed Parameterization
90
+
91
+ ```python
92
+ CODES = ForEach([200, 201])
93
+
94
+ @session.test()
95
+ def test_status(code: Annotated[int, From(CODES)]): ...
96
+ ```
97
+
98
+ ### Native LLM Evals
99
+
100
+ Score model outputs alongside your tests - same fixtures, same parallelism, same `apte` CLI. Cases get pass/fail + numeric metrics, persisted to JSONL for run-over-run comparison.
101
+
102
+ ```python
103
+ from typing import Annotated
104
+ from apte import ForEach, From, ApteSession
105
+ from apte.evals import EvalCase, EvalSuite
106
+ from apte.evals.evaluators import contains_keywords
107
+
108
+ session = ApteSession()
109
+ chatbot_suite = EvalSuite("chatbot")
110
+ session.add_suite(chatbot_suite)
111
+
112
+ cases = ForEach([
113
+ EvalCase(name="capital_fr", inputs="Capital of France?", expected="Paris"),
114
+ ])
115
+
116
+ @chatbot_suite.eval(evaluators=[contains_keywords(keywords=["paris"])])
117
+ async def chatbot(case: Annotated[EvalCase, From(cases)]) -> str:
118
+ return await my_agent(case.inputs) # your LLM call
119
+ ```
120
+
121
+ ```bash
122
+ apte eval evals.session:session # runs are recorded to .apte/history.jsonl
123
+ ```
124
+
125
+ See [Evals docs](https://renaudcepre.github.io/apte/evals/) for evaluators, judges, and scoring.
126
+
127
+ ---
128
+
129
+ ## Quick Start
130
+
131
+ ```python
132
+ from apte import ApteSession
133
+
134
+ session = ApteSession()
135
+
136
+
137
+ def inc(x):
138
+ return x + 1
139
+
140
+
141
+ @session.test()
142
+ def test_answer():
143
+ assert inc(3) == 4
144
+ ```
145
+
146
+ ```bash
147
+ apte run test_sample:session
148
+ ```
149
+
150
+ ## Installation
151
+
152
+ Apte is not yet on PyPI. Install directly from GitHub:
153
+
154
+ ```bash
155
+ # With uv (recommended)
156
+ uv add git+https://github.com/renaudcepre/apte.git
157
+
158
+ # With pip
159
+ pip install git+https://github.com/renaudcepre/apte.git
160
+ ```
161
+
162
+ ## CLI
163
+
164
+ ```bash
165
+ apte run module:session # Run tests
166
+ apte run module:session -n 4 # Parallel (4 workers)
167
+ apte run module:session --lf # Re-run failed tests only
168
+ apte run module:session --collect-only # List tests without running
169
+ apte run module:session --cache-clear # Clear cache before run
170
+ apte run module:session --app-dir src # Look for module in src/
171
+ apte run module:session --ctrf-output r.json # CTRF report for CI/CD
172
+
173
+ apte eval module:session # Run LLM evals
174
+ apte eval module:session --tag safety # Filter by case tag
175
+ apte eval module:session --last-failed # Re-run failed cases only
176
+ ```
177
+
178
+ ## Features
179
+
180
+ - **Explicit DI** - No guessing which fixture you're using
181
+ - **Async native** - No plugin needed, just `async def`
182
+ - **Parallel execution** - Built-in with `-n 4`
183
+ - **Scoped fixtures** - `SESSION`, `SUITE`, `TEST`
184
+ - **Mix sync/async** - They just work together
185
+ - **Factory fixtures** - Callables to create instances on-demand
186
+ - **Plugin system** - Custom reporters, filters
187
+ - **Last-failed mode** - Re-run only failed tests with `--lf`
188
+ - **CTRF reports** - Standardized JSON for CI/CD integration
189
+ - **Native LLM evals** - Scored cases, JSONL history, `apte eval` (see [evals docs](https://renaudcepre.github.io/apte/evals/))
190
+
191
+ ## Why Not pytest?
192
+
193
+ | | pytest | Apte |
194
+ |----------|---------------------------------|--------------------------------------|
195
+ | Fixtures | Implicit (by name) | Explicit (`Use(fixture)`) |
196
+ | Params | Hidden in fixture | Visible in test (`From()` + factory) |
197
+ | Async | Plugin required | Native |
198
+ | Parallel | Plugin required | Built-in |
199
+ | Cycles | Runtime error | Prevented at registration |
200
+ | Evals | External (deepeval, pydantic-…) | Native (`apte eval`, JSONL history) |
201
+
202
+ pytest has a large ecosystem and extensive community. Apte is an alternative if you
203
+ prefer FastAPI-style explicit dependencies and native async in your tests.
204
+
205
+ ## Documentation
206
+
207
+ Full API reference, guides, and examples: [renaudcepre.github.io/apte](https://renaudcepre.github.io/apte/)
208
+
209
+ ## License
210
+
211
+ MIT
apte-0.3.0/README.md ADDED
@@ -0,0 +1,180 @@
1
+ ![Apte](assets/logo-with-text-animate.svg)
2
+
3
+ [![CI](https://github.com/renaudcepre/apte/actions/workflows/ci.yml/badge.svg)](https://github.com/renaudcepre/apte/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/renaudcepre/apte/graph/badge.svg?token=V0MLGEE5UZ)](https://codecov.io/gh/renaudcepre/apte)
5
+ [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://renaudcepre.github.io/apte/)
6
+
7
+ **Write your tests and your LLM evals in one async-first framework.**
8
+
9
+ An eval is just a test that returns a value - scored, not asserted. Your evals get
10
+ real fixtures, dependency injection and parallelism, and live right next to the
11
+ tests they ship with. Python 3.10+, installs lean (Rich UI optional).
12
+
13
+ ---
14
+
15
+ ## Why Apte?
16
+
17
+ ### Explicit Injection (IDE-Ready)
18
+
19
+ **Ctrl+Click works.** Your IDE knows every type. No guessing where fixtures come from.
20
+
21
+ ```python
22
+ def test_user(db: Annotated[Database, Use(database)]): ...
23
+ ```
24
+
25
+ ### Native Async & Parallelism
26
+
27
+ Tests run as coroutines on a single event loop. No plugin needed.
28
+
29
+ ```bash
30
+ apte run tests:session -n 10
31
+ ```
32
+
33
+ ### Smart Tagging (Tag Propagation)
34
+
35
+ Tag a fixture once, every test using it inherits the tag automatically.
36
+
37
+ ```python
38
+ @fixture(tags=["database"])
39
+ def db(): ...
40
+
41
+ session.bind(db)
42
+
43
+ @session.test()
44
+ def test_users(repo: Annotated[Repo, Use(user_repo)]): ... # Also tagged "database"
45
+ ```
46
+
47
+ ```bash
48
+ apte run tests:session --no-tag database # Skips ALL tests touching DB
49
+ ```
50
+
51
+ ### Infra vs Code Errors (Error ≠ Fail)
52
+
53
+ ```
54
+ ✗ test_create_user: AssertionError # Your bug - TEST FAILED
55
+ ⚠ test_with_db: [FIXTURE] ConnectionError # Infra issue - SETUP ERROR
56
+ ```
57
+
58
+ ### Typed Parameterization
59
+
60
+ ```python
61
+ CODES = ForEach([200, 201])
62
+
63
+ @session.test()
64
+ def test_status(code: Annotated[int, From(CODES)]): ...
65
+ ```
66
+
67
+ ### Native LLM Evals
68
+
69
+ Score model outputs alongside your tests - same fixtures, same parallelism, same `apte` CLI. Cases get pass/fail + numeric metrics, persisted to JSONL for run-over-run comparison.
70
+
71
+ ```python
72
+ from typing import Annotated
73
+ from apte import ForEach, From, ApteSession
74
+ from apte.evals import EvalCase, EvalSuite
75
+ from apte.evals.evaluators import contains_keywords
76
+
77
+ session = ApteSession()
78
+ chatbot_suite = EvalSuite("chatbot")
79
+ session.add_suite(chatbot_suite)
80
+
81
+ cases = ForEach([
82
+ EvalCase(name="capital_fr", inputs="Capital of France?", expected="Paris"),
83
+ ])
84
+
85
+ @chatbot_suite.eval(evaluators=[contains_keywords(keywords=["paris"])])
86
+ async def chatbot(case: Annotated[EvalCase, From(cases)]) -> str:
87
+ return await my_agent(case.inputs) # your LLM call
88
+ ```
89
+
90
+ ```bash
91
+ apte eval evals.session:session # runs are recorded to .apte/history.jsonl
92
+ ```
93
+
94
+ See [Evals docs](https://renaudcepre.github.io/apte/evals/) for evaluators, judges, and scoring.
95
+
96
+ ---
97
+
98
+ ## Quick Start
99
+
100
+ ```python
101
+ from apte import ApteSession
102
+
103
+ session = ApteSession()
104
+
105
+
106
+ def inc(x):
107
+ return x + 1
108
+
109
+
110
+ @session.test()
111
+ def test_answer():
112
+ assert inc(3) == 4
113
+ ```
114
+
115
+ ```bash
116
+ apte run test_sample:session
117
+ ```
118
+
119
+ ## Installation
120
+
121
+ Apte is not yet on PyPI. Install directly from GitHub:
122
+
123
+ ```bash
124
+ # With uv (recommended)
125
+ uv add git+https://github.com/renaudcepre/apte.git
126
+
127
+ # With pip
128
+ pip install git+https://github.com/renaudcepre/apte.git
129
+ ```
130
+
131
+ ## CLI
132
+
133
+ ```bash
134
+ apte run module:session # Run tests
135
+ apte run module:session -n 4 # Parallel (4 workers)
136
+ apte run module:session --lf # Re-run failed tests only
137
+ apte run module:session --collect-only # List tests without running
138
+ apte run module:session --cache-clear # Clear cache before run
139
+ apte run module:session --app-dir src # Look for module in src/
140
+ apte run module:session --ctrf-output r.json # CTRF report for CI/CD
141
+
142
+ apte eval module:session # Run LLM evals
143
+ apte eval module:session --tag safety # Filter by case tag
144
+ apte eval module:session --last-failed # Re-run failed cases only
145
+ ```
146
+
147
+ ## Features
148
+
149
+ - **Explicit DI** - No guessing which fixture you're using
150
+ - **Async native** - No plugin needed, just `async def`
151
+ - **Parallel execution** - Built-in with `-n 4`
152
+ - **Scoped fixtures** - `SESSION`, `SUITE`, `TEST`
153
+ - **Mix sync/async** - They just work together
154
+ - **Factory fixtures** - Callables to create instances on-demand
155
+ - **Plugin system** - Custom reporters, filters
156
+ - **Last-failed mode** - Re-run only failed tests with `--lf`
157
+ - **CTRF reports** - Standardized JSON for CI/CD integration
158
+ - **Native LLM evals** - Scored cases, JSONL history, `apte eval` (see [evals docs](https://renaudcepre.github.io/apte/evals/))
159
+
160
+ ## Why Not pytest?
161
+
162
+ | | pytest | Apte |
163
+ |----------|---------------------------------|--------------------------------------|
164
+ | Fixtures | Implicit (by name) | Explicit (`Use(fixture)`) |
165
+ | Params | Hidden in fixture | Visible in test (`From()` + factory) |
166
+ | Async | Plugin required | Native |
167
+ | Parallel | Plugin required | Built-in |
168
+ | Cycles | Runtime error | Prevented at registration |
169
+ | Evals | External (deepeval, pydantic-…) | Native (`apte eval`, JSONL history) |
170
+
171
+ pytest has a large ecosystem and extensive community. Apte is an alternative if you
172
+ prefer FastAPI-style explicit dependencies and native async in your tests.
173
+
174
+ ## Documentation
175
+
176
+ Full API reference, guides, and examples: [renaudcepre.github.io/apte](https://renaudcepre.github.io/apte/)
177
+
178
+ ## License
179
+
180
+ MIT
@@ -0,0 +1,55 @@
1
+ from apte import console
2
+ from apte.api import collect_tests, list_tags, run_session
3
+ from apte.assertions import ExceptionInfo, RaisesContext, raises, warns
4
+ from apte.core.session import ApteSession
5
+ from apte.core.suite import ApteSuite
6
+ from apte.di.decorators import factory, fixture
7
+ from apte.di.factory import FixtureFactory
8
+ from apte.di.markers import ForEach, From, Use
9
+ from apte.entities import FixtureCallable, Retry, Skip, Xfail
10
+ from apte.exceptions import ApteError, CircularDependencyError, FixtureError
11
+ from apte.fixtures.builtins import caplog, mocker, tmp_path
12
+ from apte.fixtures.mocker import AsyncMockType, Mocker, MockType
13
+ from apte.loader import LoadError, load_session
14
+ from apte.plugin import PluginBase
15
+ from apte.shell import CommandResult, Shell
16
+
17
+ __version__ = "0.3.0"
18
+
19
+ __all__ = [
20
+ "ApteError",
21
+ "ApteSession",
22
+ "ApteSuite",
23
+ "AsyncMockType",
24
+ "CircularDependencyError",
25
+ "CommandResult",
26
+ "ExceptionInfo",
27
+ "FixtureCallable",
28
+ "FixtureError",
29
+ "FixtureFactory",
30
+ "ForEach",
31
+ "From",
32
+ "LoadError",
33
+ "MockType",
34
+ "Mocker",
35
+ "PluginBase",
36
+ "RaisesContext",
37
+ "Retry",
38
+ "Shell",
39
+ "Skip",
40
+ "Use",
41
+ "Xfail",
42
+ "__version__",
43
+ "caplog",
44
+ "collect_tests",
45
+ "console",
46
+ "factory",
47
+ "fixture",
48
+ "list_tags",
49
+ "load_session",
50
+ "mocker",
51
+ "raises",
52
+ "run_session",
53
+ "tmp_path",
54
+ "warns",
55
+ ]
@@ -0,0 +1,5 @@
1
+ """Allow running apte as: python -m apte / coverage run -m apte."""
2
+
3
+ from apte.cli.main import main
4
+
5
+ main()
apte-0.3.0/apte/api.py ADDED
@@ -0,0 +1,194 @@
1
+ """Public API for running Apte sessions.
2
+
3
+ This module provides the main entry points for running tests programmatically,
4
+ independent of any CLI or adapter implementation.
5
+
6
+ Example:
7
+ from apte import ApteSession
8
+ from apte.api import run_session
9
+
10
+ session = ApteSession()
11
+
12
+ @session.test()
13
+ def test_example():
14
+ assert True
15
+
16
+ success = run_session(session)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ from typing import TYPE_CHECKING
23
+
24
+ from apte.core.collector import Collector
25
+ from apte.core.runner import TestRunner
26
+ from apte.core.suite import (
27
+ ApteSuite, # noqa: TC001 - used at runtime in list_tags
28
+ )
29
+ from apte.events.types import Event
30
+ from apte.filters.keyword import KeywordFilterPlugin
31
+ from apte.filters.kind import KindFilterPlugin
32
+ from apte.filters.suite import SuiteFilterPlugin
33
+ from apte.plugin import PluginBase, PluginContext
34
+ from apte.tags.plugin import TagFilterPlugin
35
+
36
+ if TYPE_CHECKING:
37
+ from apte.core.session import ApteSession
38
+ from apte.entities import RunResult, TestItem
39
+
40
+
41
+ def run_session( # noqa: PLR0913 - public API with many optional params
42
+ session: ApteSession,
43
+ concurrency: int | None = None,
44
+ exitfirst: bool = False,
45
+ last_failed: bool = False,
46
+ cache_clear: bool = False,
47
+ include_tags: set[str] | None = None,
48
+ exclude_tags: set[str] | None = None,
49
+ capture: bool = True,
50
+ suite_filter: str | None = None,
51
+ keyword_patterns: list[str] | None = None,
52
+ log_file: bool = True,
53
+ force_no_color: bool = False,
54
+ *,
55
+ ctx: PluginContext | None = None,
56
+ ) -> RunResult:
57
+ """Run a test session and return result with success and interrupted status.
58
+
59
+ This is the main entry point for running tests programmatically.
60
+
61
+ Args:
62
+ session: The ApteSession to run.
63
+ concurrency: Number of concurrent workers (None = use session default).
64
+ exitfirst: Stop after first failure.
65
+ last_failed: Only run tests that failed in the last run.
66
+ cache_clear: Clear the cache before running.
67
+ include_tags: Only run tests with these tags (OR logic).
68
+ exclude_tags: Exclude tests with these tags.
69
+ capture: Capture stdout/stderr during tests (default: True).
70
+ suite_filter: Only run tests in this suite (::SuiteName syntax).
71
+ keyword_patterns: Only run tests matching these patterns (-k flag).
72
+ log_file: Write output to .apte/last_run.log (default: True).
73
+ force_no_color: Disable colors (--no-color flag).
74
+ ctx: Plugin context (if provided, overrides individual params above).
75
+
76
+ Returns:
77
+ RunResult with success status and interrupted flag.
78
+ """
79
+ # Apply session-level settings from ctx or params
80
+ if ctx is not None:
81
+ if ctx.get("concurrency") is not None:
82
+ session.concurrency = ctx.get("concurrency")
83
+ session.exitfirst = ctx.get("exitfirst", False)
84
+ session.capture = not ctx.get("no_capture", False)
85
+ else:
86
+ if concurrency is not None:
87
+ session.concurrency = concurrency
88
+ session.exitfirst = exitfirst
89
+ session.capture = capture
90
+
91
+ # Register default plugins if none registered
92
+ if not session.plugin_classes:
93
+ session.register_default_plugins()
94
+
95
+ # Build context from parameters if not provided
96
+ if ctx is None:
97
+ ctx = PluginContext(
98
+ args={
99
+ "last_failed": last_failed,
100
+ "cache_clear": cache_clear,
101
+ "tags": list(include_tags) if include_tags else [],
102
+ "exclude_tags": list(exclude_tags) if exclude_tags else [],
103
+ "target_suite": suite_filter,
104
+ "keywords": keyword_patterns or [],
105
+ "no_log_file": not log_file,
106
+ "no_color": force_no_color,
107
+ }
108
+ )
109
+
110
+ session.activate_plugins(ctx)
111
+
112
+ runner = TestRunner(session)
113
+ return runner.run()
114
+
115
+
116
+ def collect_tests( # noqa: PLR0913 - public API with many optional params
117
+ session: ApteSession,
118
+ include_tags: set[str] | None = None,
119
+ exclude_tags: set[str] | None = None,
120
+ suite_filter: str | None = None,
121
+ keyword_patterns: list[str] | None = None,
122
+ *,
123
+ ctx: PluginContext | None = None,
124
+ ) -> list[TestItem]:
125
+ """Collect tests from a session without running them.
126
+
127
+ Args:
128
+ session: The ApteSession to collect from.
129
+ include_tags: Only include tests with these tags.
130
+ exclude_tags: Exclude tests with these tags.
131
+ suite_filter: Only include tests in this suite.
132
+ keyword_patterns: Only include tests matching these patterns.
133
+ ctx: Plugin context (if provided, overrides individual params above).
134
+
135
+ Returns:
136
+ List of collected TestItem objects.
137
+ """
138
+ # Build context from parameters if not provided
139
+ if ctx is None:
140
+ ctx = PluginContext(
141
+ args={
142
+ "tags": list(include_tags) if include_tags else [],
143
+ "exclude_tags": list(exclude_tags) if exclude_tags else [],
144
+ "target_suite": suite_filter,
145
+ "keywords": keyword_patterns or [],
146
+ }
147
+ )
148
+
149
+ # Activate filter plugins
150
+ filter_plugins: list[type[PluginBase]] = [
151
+ TagFilterPlugin,
152
+ SuiteFilterPlugin,
153
+ KeywordFilterPlugin,
154
+ KindFilterPlugin,
155
+ ]
156
+ for plugin_class in filter_plugins:
157
+ instance = plugin_class.activate(ctx)
158
+ if instance is not None:
159
+ session.register_plugin(instance)
160
+
161
+ collector = Collector()
162
+ items = collector.collect(session)
163
+ return asyncio.run(session.events.emit_and_collect(Event.COLLECTION_FINISH, items))
164
+
165
+
166
+ def list_tags(session: ApteSession) -> set[str]:
167
+ """List all declared tags in a session.
168
+
169
+ Args:
170
+ session: The ApteSession to inspect.
171
+
172
+ Returns:
173
+ Set of all tag names declared on fixtures, suites, and tests.
174
+ """
175
+ all_tags: set[str] = set()
176
+
177
+ for fixture_reg in session.fixtures:
178
+ all_tags.update(fixture_reg.tags)
179
+
180
+ for test_reg in session.tests:
181
+ all_tags.update(test_reg.tags)
182
+
183
+ def collect_from_suites(suites: list[ApteSuite]) -> None:
184
+ for suite in suites:
185
+ all_tags.update(suite.tags)
186
+ for fixture_reg in suite.fixtures:
187
+ all_tags.update(fixture_reg.tags)
188
+ for test_reg in suite.tests:
189
+ all_tags.update(test_reg.tags)
190
+ collect_from_suites(suite.suites)
191
+
192
+ collect_from_suites(session.suites)
193
+
194
+ return all_tags