Async-first Python testing framework
Project description
Write your tests and your LLM evals in one async-first framework.
An eval is just a test that returns a value - scored, not asserted. Your evals get real fixtures, dependency injection and parallelism, and live right next to the tests they ship with. Python 3.10+, installs lean (Rich UI optional).
Why Apte?
Explicit Injection (IDE-Ready)
Ctrl+Click works. Your IDE knows every type. No guessing where fixtures come from.
def test_user(db: Annotated[Database, Use(database)]): ...
Native Async & Parallelism
Tests run as coroutines on a single event loop. No plugin needed.
apte run tests:session -n 10
Smart Tagging (Tag Propagation)
Tag a fixture once, every test using it inherits the tag automatically.
@fixture(tags=["database"])
def db(): ...
session.bind(db)
@session.test()
def test_users(repo: Annotated[Repo, Use(user_repo)]): ... # Also tagged "database"
apte run tests:session --no-tag database # Skips ALL tests touching DB
Infra vs Code Errors (Error ≠ Fail)
✗ test_create_user: AssertionError # Your bug - TEST FAILED
⚠ test_with_db: [FIXTURE] ConnectionError # Infra issue - SETUP ERROR
Typed Parameterization
CODES = ForEach([200, 201])
@session.test()
def test_status(code: Annotated[int, From(CODES)]): ...
Native LLM Evals
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.
from typing import Annotated
from apte import ForEach, From, ApteSession
from apte.evals import EvalCase, EvalSuite
from apte.evals.evaluators import contains_keywords
session = ApteSession()
chatbot_suite = EvalSuite("chatbot")
session.add_suite(chatbot_suite)
cases = ForEach([
EvalCase(name="capital_fr", inputs="Capital of France?", expected="Paris"),
])
@chatbot_suite.eval(evaluators=[contains_keywords(keywords=["paris"])])
async def chatbot(case: Annotated[EvalCase, From(cases)]) -> str:
return await my_agent(case.inputs) # your LLM call
apte eval evals.session:session # runs are recorded to .apte/history.jsonl
See Evals docs for evaluators, judges, and scoring.
Quick Start
from apte import ApteSession
session = ApteSession()
def inc(x):
return x + 1
@session.test()
def test_answer():
assert inc(3) == 4
apte run test_sample:session
Installation
Apte is not yet on PyPI. Install directly from GitHub:
# With uv (recommended)
uv add git+https://github.com/renaudcepre/apte.git
# With pip
pip install git+https://github.com/renaudcepre/apte.git
CLI
apte run module:session # Run tests
apte run module:session -n 4 # Parallel (4 workers)
apte run module:session --lf # Re-run failed tests only
apte run module:session --collect-only # List tests without running
apte run module:session --cache-clear # Clear cache before run
apte run module:session --app-dir src # Look for module in src/
apte run module:session --ctrf-output r.json # CTRF report for CI/CD
apte eval module:session # Run LLM evals
apte eval module:session --tag safety # Filter by case tag
apte eval module:session --last-failed # Re-run failed cases only
Features
- Explicit DI - No guessing which fixture you're using
- Async native - No plugin needed, just
async def - Parallel execution - Built-in with
-n 4 - Scoped fixtures -
SESSION,SUITE,TEST - Mix sync/async - They just work together
- Factory fixtures - Callables to create instances on-demand
- Plugin system - Custom reporters, filters
- Last-failed mode - Re-run only failed tests with
--lf - CTRF reports - Standardized JSON for CI/CD integration
- Native LLM evals - Scored cases, JSONL history,
apte eval(see evals docs)
Why Not pytest?
| pytest | Apte | |
|---|---|---|
| Fixtures | Implicit (by name) | Explicit (Use(fixture)) |
| Params | Hidden in fixture | Visible in test (From() + factory) |
| Async | Plugin required | Native |
| Parallel | Plugin required | Built-in |
| Cycles | Runtime error | Prevented at registration |
| Evals | External (deepeval, pydantic-…) | Native (apte eval, JSONL history) |
pytest has a large ecosystem and extensive community. Apte is an alternative if you prefer FastAPI-style explicit dependencies and native async in your tests.
Documentation
Full API reference, guides, and examples: renaudcepre.github.io/apte
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file apte-0.3.0.tar.gz.
File metadata
- Download URL: apte-0.3.0.tar.gz
- Upload date:
- Size: 110.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c48a5015b0693fc764d4b6a2aa57e07202385ea214ef53cbf86450e2ee46a068
|
|
| MD5 |
40c4f0e11f98ddd9142064a3966aaa2a
|
|
| BLAKE2b-256 |
45e8bc66519a592f506937f5559deb3cae2c57cc6832e40eb3a0d7d43b902842
|
Provenance
The following attestation bundles were made for apte-0.3.0.tar.gz:
Publisher:
publish.yml on renaudcepre/apte
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
apte-0.3.0.tar.gz -
Subject digest:
c48a5015b0693fc764d4b6a2aa57e07202385ea214ef53cbf86450e2ee46a068 - Sigstore transparency entry: 1946513642
- Sigstore integration time:
-
Permalink:
renaudcepre/apte@8fab49ffb20253a28d229b0c0de99175cb72d24a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/renaudcepre
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8fab49ffb20253a28d229b0c0de99175cb72d24a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file apte-0.3.0-py3-none-any.whl.
File metadata
- Download URL: apte-0.3.0-py3-none-any.whl
- Upload date:
- Size: 131.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2000993adc018efd5111d1b10f48b4f944aad772fd0b2182d0b932f7bd344883
|
|
| MD5 |
c036bcf41fae75ca1bbc9b2fbce06130
|
|
| BLAKE2b-256 |
fc0a6dacc6afa0256f881824523effd0727caba12bf0204ea9d0e406cd9e1253
|
Provenance
The following attestation bundles were made for apte-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on renaudcepre/apte
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
apte-0.3.0-py3-none-any.whl -
Subject digest:
2000993adc018efd5111d1b10f48b4f944aad772fd0b2182d0b932f7bd344883 - Sigstore transparency entry: 1946513690
- Sigstore integration time:
-
Permalink:
renaudcepre/apte@8fab49ffb20253a28d229b0c0de99175cb72d24a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/renaudcepre
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8fab49ffb20253a28d229b0c0de99175cb72d24a -
Trigger Event:
workflow_dispatch
-
Statement type: