pytest-nijam 0.1.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.
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ # Cancel superseded runs on the same ref.
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: lint + types + build
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ # mypy/ruff run on a modern Python; the package itself targets 3.8+ (ruff's
21
+ # target-version in pyproject enforces 3.8-compatible syntax).
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: '3.12'
25
+ cache: pip
26
+
27
+ - name: Install (with dev extras)
28
+ run: pip install -e '.[dev]'
29
+
30
+ - name: Ruff
31
+ run: ruff check src
32
+
33
+ - name: Mypy
34
+ run: mypy
35
+
36
+ # Catch packaging errors (entry point, metadata) on every PR.
37
+ - name: Build sanity check
38
+ run: |
39
+ pip install build twine
40
+ python -m build
41
+ twine check dist/*
@@ -0,0 +1,113 @@
1
+ name: Publish
2
+
3
+ # One-click release for pytest-nijam:
4
+ # set version (in pyproject + __init__) → verify → guard → build → publish to PyPI
5
+ # via Trusted Publishing (OIDC, no token) → commit + tag (vX.Y.Z) → push.
6
+ # Run it from the Actions tab: "Publish" → "Run workflow", enter the new version.
7
+ #
8
+ # No secret needed. Set up Trusted Publishing once on PyPI BEFORE the first run:
9
+ # PyPI → (project, or "Your projects → Publishing" for a *pending* publisher) →
10
+ # Add a publisher: owner = getnijam, repo = pytest-reporter,
11
+ # workflow = publish.yml, environment = pypi.
12
+ # A pending publisher lets the first run create the project. Also create a GitHub
13
+ # environment named "pypi" (Settings → Environments) — optionally gated by reviewers.
14
+ on:
15
+ workflow_dispatch:
16
+ inputs:
17
+ version:
18
+ description: 'New version (PEP 440 — e.g. 0.1.0a2, 0.1.0, 1.0.0)'
19
+ type: string
20
+ required: true
21
+
22
+ permissions:
23
+ contents: write # push the version-bump commit + tag
24
+ id-token: write # PyPI Trusted Publishing (OIDC)
25
+
26
+ # Never let two publishes overlap.
27
+ concurrency:
28
+ group: publish
29
+ cancel-in-progress: false
30
+
31
+ jobs:
32
+ publish:
33
+ runs-on: ubuntu-latest
34
+ # Must match the environment named in the PyPI trusted-publisher config.
35
+ environment: pypi
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ with:
39
+ fetch-depth: 0 # full history so the bump commit + tag push cleanly
40
+ token: ${{ secrets.GITHUB_TOKEN }}
41
+
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: '3.12'
45
+ cache: pip
46
+
47
+ # Validate BEFORE bumping/publishing anything.
48
+ - name: Validate (lint + types)
49
+ run: |
50
+ pip install -e '.[dev]'
51
+ ruff check src
52
+ mypy
53
+
54
+ # Write the requested version into pyproject.toml + __init__ (kept in sync).
55
+ - name: Set version
56
+ env:
57
+ VERSION: ${{ inputs.version }}
58
+ run: |
59
+ python - <<'PY'
60
+ import os, re, pathlib
61
+ v = os.environ["VERSION"].strip()
62
+ if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+([abrc][0-9]+|rc[0-9]+|\.dev[0-9]+|\.post[0-9]+)?", v):
63
+ raise SystemExit(f"::error::'{v}' is not a PEP 440 version")
64
+ targets = [
65
+ ("pyproject.toml", r'(?m)^version = ".*"$', f'version = "{v}"'),
66
+ ("src/nijam_pytest/__init__.py", r'(?m)^__version__ = ".*"$', f'__version__ = "{v}"'),
67
+ ]
68
+ for path, pat, repl in targets:
69
+ p = pathlib.Path(path)
70
+ s = p.read_text()
71
+ s2, n = re.subn(pat, repl, s, count=1)
72
+ if n != 1:
73
+ raise SystemExit(f"::error::could not set version in {path}")
74
+ p.write_text(s2)
75
+ print(f"version set to {v}")
76
+ PY
77
+
78
+ # Guard: this version must not already be published (PyPI rejects re-uploads).
79
+ - name: Guard — version not already on PyPI
80
+ env:
81
+ VERSION: ${{ inputs.version }}
82
+ run: |
83
+ code=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/pytest-nijam/$VERSION/json")
84
+ if [ "$code" = "200" ]; then
85
+ echo "::error::pytest-nijam $VERSION is already on PyPI — bump again."
86
+ exit 1
87
+ fi
88
+ echo "OK — $VERSION is not on PyPI yet (HTTP $code)."
89
+
90
+ - name: Build
91
+ run: |
92
+ pip install build twine
93
+ python -m build
94
+ twine check dist/*
95
+
96
+ # Trusted Publishing: no password — authenticates via OIDC (id-token: write)
97
+ # against the PyPI publisher you configured for this repo + environment.
98
+ - name: Publish to PyPI
99
+ uses: pypa/gh-action-pypi-publish@release/v1
100
+
101
+ # Record the release in git only AFTER PyPI accepted it (publish is the one
102
+ # irreversible step). Pushes to the branch the workflow was dispatched from.
103
+ - name: Commit + tag the release
104
+ env:
105
+ VERSION: ${{ inputs.version }}
106
+ BRANCH: ${{ github.ref_name }}
107
+ run: |
108
+ git config user.name 'github-actions[bot]'
109
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
110
+ git commit -am "release: v$VERSION"
111
+ git tag "v$VERSION"
112
+ git push origin "HEAD:$BRANCH"
113
+ git push origin "v$VERSION"
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .pytest_cache/
9
+ .venv/
10
+ venv/
11
+ *.log
12
+ .DS_Store
@@ -0,0 +1,82 @@
1
+ # pytest-nijam — Claude instructions
2
+
3
+ pytest plugin for **Nijam**. Implements pytest's hooks to capture a test run and ship it
4
+ to the Nijam API. It is the Python sibling of `@nijam/pw-reporter` and reports into the
5
+ **same** ingestion endpoints (`/v1/runs`, `…/executions`, `…/source`, `PATCH /v1/runs/:id`).
6
+
7
+ **This is a public-facing artifact** — installed via `pip`, read on GitHub, pasted into
8
+ users' `pytest.ini`. Code quality, **zero runtime dependencies**, and a copy-paste-runnable
9
+ README matter more here than anywhere else.
10
+
11
+ License: **MIT** (separate from the BSL platform — must be maximally adoptable).
12
+
13
+ > This is an independent repo (`getnijam/pytest-reporter`), not part of a monorepo. It
14
+ > shares nothing with the other repos except the API's wire format.
15
+
16
+ ## The one big difference from pw-reporter
17
+ **pytest has no traces.** There is no artifact upload path at all (no trace / screenshot /
18
+ video). We capture the error log (`longrepr`), the failing line, durations, run stats, and
19
+ (opt-in) the test file source — everything the dashboard needs minus the trace viewer.
20
+ Projects created as `pytest` in the dashboard hide trace UI accordingly.
21
+
22
+ ## Stack (locked — ask before changing the public option shape)
23
+ - Python, `>=3.8`. `from __future__ import annotations` everywhere (3.8-safe typing).
24
+ - **Zero runtime dependencies.** `pytest>=7` is the host. HTTP is **stdlib `urllib`** only
25
+ (no `requests`/`httpx`).
26
+ - Build: **hatchling**, src-layout. Auto-loads via the `pytest11` entry point in `pyproject.toml`.
27
+ - Tooling: `mypy --strict` and `ruff` must stay clean. No automated test suite in v0.1
28
+ (smoke-test by `pip install -e .` into a sample suite).
29
+
30
+ ## Layout
31
+ ```
32
+ src/nijam_pytest/
33
+ plugin.py # pytest hooks (addoption/configure/sessionstart/logreport/sessionfinish)
34
+ client.py # NijamClient — HTTP to the API (urllib, soft-fail)
35
+ ci.py # detect_run_context / relative_file — CI/git metadata (port of pw-reporter ci.ts)
36
+ buffer.py # ExecutionBuffer — collect during run, flush in chunks at session finish
37
+ models.py # payload dataclasses (RunContext, TestExecution, FinalizeRunPayload, …)
38
+ log.py # [nijam]-prefixed warn/info
39
+ ```
40
+
41
+ ## Public config surface (design backward from this)
42
+ ini options in `pytest.ini` / `[tool.pytest.ini_options]`, each overridable by env (env wins):
43
+ `nijam_api_key` (`NIJAM_API_KEY`), `nijam_project_id` (`NIJAM_PROJECT_ID`),
44
+ `nijam_api_url` (`NIJAM_API_URL`), `nijam_environment` (`NIJAM_ENVIRONMENT`),
45
+ `nijam_upload_source` (bool, default true), `nijam_auto_complete` (bool, default true,
46
+ `NIJAM_AUTO_COMPLETE`), `nijam_silent` (bool). Missing key/project → warn with the docs
47
+ link + disable; no further work. Don't change these names/shape without asking.
48
+
49
+ ## Lifecycle & behavior
50
+ - `pytest_configure` → register one `NijamPlugin`.
51
+ - `pytest_sessionstart` → `detect_run_context` + `POST /v1/runs`, store `run_id`; on
52
+ failure log + disable for this run.
53
+ - `pytest_runtest_logreport` → accumulate per-attempt state keyed by nodeid (setup → call
54
+ → teardown), emit **one execution per attempt** on the teardown report. Never block on I/O.
55
+ - `pytest_sessionfinish` → drain the buffer, optionally upload sources, then
56
+ `PATCH /v1/runs/:id` to finalize with status + stats (unless `auto_complete` is off).
57
+ - **Field mapping**: `testId`=nodeid, `title`=last `::` segment, `titlePath`=`::`-split minus
58
+ the file, `file`=git-root-relative (else rootdir-relative) of `report.location[0]`,
59
+ `line`=`report.location[1] + 1` (pytest is 0-based, the API is 1-based), `errorMessage`=
60
+ `longreprtext`, `durationMs`=summed phase durations, `id`=client `uuid4`.
61
+ - **Status**: failed > skipped > passed precedence across phases. `xfail` lands as skipped.
62
+ `pytest-rerunfailures` reruns (outcome `rerun`) emit extra attempts with bumped `retry`,
63
+ which is how flaky is derived (a test that passed only after a retry). No reruns ⇒ no flaky.
64
+ - **CI detection** (`ci.py`): per-field `CI var → generic GIT_* → git shell-out → empty`.
65
+ GitHub/GitLab/CircleCI/Bitbucket/generic. Leave `branch` None when unknown (dashboard
66
+ renders "No Branch Info"). Same env-var names as pw-reporter's `ci.ts`.
67
+ - **HTTP**: Bearer `api_key`, 30s timeout, no retries, 402 → "plan limit reached" warning.
68
+
69
+ ## Guard rails — do NOT
70
+ - ❌ **Raise from any hook** — wrap every hook body in try/except, `log.warn`, continue.
71
+ The plugin MUST NOT break a user's test run. Ever.
72
+ - ❌ Add runtime dependencies (zero-dep goal) · use `requests`/`httpx` (stdlib `urllib` only).
73
+ - ❌ Block the test path (`logreport`) on the network — only append to the buffer; flush at finish.
74
+ - ❌ Add a trace/artifact upload path — pytest has no traces.
75
+ - ❌ Use Python-3.10-only syntax in runtime code without `from __future__ import annotations`.
76
+ - ❌ Change the public option names/shape, or the API wire format, without asking.
77
+ - ❌ Ternary hell / one-liners that hurt readability — prefer early returns and `if`/`else`.
78
+
79
+ ## Build & publish
80
+ - `pip install -e '.[dev]'` · `mypy` · `ruff check src` · smoke-test into a sample suite.
81
+ - Versions: `0.1.0aN` until platform launch, then `0.1.0`; semver after. Bump the alpha on
82
+ each meaningful change. Publish: `python -m build && twine upload dist/*`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nijam
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.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-nijam
3
+ Version: 0.1.0
4
+ Summary: pytest plugin for Nijam — captures test runs and ships them to the Nijam API.
5
+ Project-URL: Homepage, https://nijam.dev
6
+ Project-URL: Documentation, https://docs.nijam.dev
7
+ Project-URL: Source, https://github.com/getnijam/pytest-reporter
8
+ Author: Nijam
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ci,nijam,pytest,reporting,test-analytics
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Testing
17
+ Requires-Python: >=3.8
18
+ Requires-Dist: pytest>=7.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: mypy>=1.8; extra == 'dev'
21
+ Requires-Dist: ruff>=0.5; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # pytest-nijam
25
+
26
+ pytest plugin for [Nijam](https://nijam.dev) — captures your test runs and ships them
27
+ to the Nijam dashboard so you can track what failed, why, and where (error log +
28
+ failing line), across CI runs and over time.
29
+
30
+ > pytest has no traces, so — unlike the Playwright reporter — runs won't include a
31
+ > trace viewer. Everything else (failures, error output, the failing line, durations,
32
+ > and your test source) is captured.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install pytest-nijam
38
+ ```
39
+
40
+ The plugin auto-activates once installed (via pytest's `pytest11` entry point).
41
+
42
+ ## Configure
43
+
44
+ Add your project ID to `pytest.ini` (or `pyproject.toml`), and provide the ingest API
45
+ key via an environment variable (it's a secret — keep it out of source control):
46
+
47
+ ```ini
48
+ # pytest.ini
49
+ [pytest]
50
+ nijam_project_id = 00000000-0000-0000-0000-000000000000
51
+ ```
52
+
53
+ ```toml
54
+ # pyproject.toml
55
+ [tool.pytest.ini_options]
56
+ nijam_project_id = "00000000-0000-0000-0000-000000000000"
57
+ ```
58
+
59
+ ```bash
60
+ export NIJAM_API_KEY="nij_sk_…" # from the Nijam dashboard → Secret keys
61
+ pytest
62
+ ```
63
+
64
+ Both the project ID and the API key can come from either the ini file or an
65
+ environment variable; **the environment variable wins** when both are set.
66
+
67
+ ## Options
68
+
69
+ | ini option | env var | default | what it does |
70
+ | --------------------- | --------------------- | ------------------------ | ------------------------------------------------------------------- |
71
+ | `nijam_api_key` | `NIJAM_API_KEY` | — | Ingest API key (required). |
72
+ | `nijam_project_id` | `NIJAM_PROJECT_ID` | — | Project UUID (required). |
73
+ | `nijam_api_url` | `NIJAM_API_URL` | `https://api.nijam.dev` | API base URL. |
74
+ | `nijam_environment` | `NIJAM_ENVIRONMENT` | — | Free-form environment tag (e.g. `staging`). |
75
+ | `nijam_upload_source` | — | `true` | Upload each test file's source so the dashboard can show it. |
76
+ | `nijam_auto_complete` | `NIJAM_AUTO_COMPLETE` | `true` | Finalize the run when this process ends. Set `false` for fan-out. |
77
+ | `nijam_silent` | — | `false` | Suppress `[nijam]` log lines. |
78
+
79
+ If `nijam_api_key` or `nijam_project_id` is missing, the plugin disables itself with a
80
+ single warning — your tests run exactly as before.
81
+
82
+ ## CI metadata
83
+
84
+ Run context (commit, branch, PR number, CI provider/run URL, commit author) is detected
85
+ automatically from GitHub Actions, GitLab CI, CircleCI, Bitbucket Pipelines, or generic
86
+ `GIT_*` env vars, falling back to `git` itself. No configuration needed.
87
+
88
+ ## Fan-out across CI jobs
89
+
90
+ If you split your suite across several CI jobs (e.g. one job per test path) that all feed
91
+ one Nijam run, set `nijam_auto_complete = false` (or `NIJAM_AUTO_COMPLETE=false`) on each
92
+ job so none of them finalizes early, then complete the run once from a post-matrix step.
93
+ See the [docs](https://docs.nijam.dev/reporter/pytest/).
94
+
95
+ ## Guarantees
96
+
97
+ This plugin **never breaks your test run.** Every hook is fail-soft: a network error, a
98
+ bad key, or an unreachable API produces a `[nijam]` warning and nothing more — your tests
99
+ still pass or fail on their own merits.
100
+
101
+ ## License
102
+
103
+ MIT
@@ -0,0 +1,80 @@
1
+ # pytest-nijam
2
+
3
+ pytest plugin for [Nijam](https://nijam.dev) — captures your test runs and ships them
4
+ to the Nijam dashboard so you can track what failed, why, and where (error log +
5
+ failing line), across CI runs and over time.
6
+
7
+ > pytest has no traces, so — unlike the Playwright reporter — runs won't include a
8
+ > trace viewer. Everything else (failures, error output, the failing line, durations,
9
+ > and your test source) is captured.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install pytest-nijam
15
+ ```
16
+
17
+ The plugin auto-activates once installed (via pytest's `pytest11` entry point).
18
+
19
+ ## Configure
20
+
21
+ Add your project ID to `pytest.ini` (or `pyproject.toml`), and provide the ingest API
22
+ key via an environment variable (it's a secret — keep it out of source control):
23
+
24
+ ```ini
25
+ # pytest.ini
26
+ [pytest]
27
+ nijam_project_id = 00000000-0000-0000-0000-000000000000
28
+ ```
29
+
30
+ ```toml
31
+ # pyproject.toml
32
+ [tool.pytest.ini_options]
33
+ nijam_project_id = "00000000-0000-0000-0000-000000000000"
34
+ ```
35
+
36
+ ```bash
37
+ export NIJAM_API_KEY="nij_sk_…" # from the Nijam dashboard → Secret keys
38
+ pytest
39
+ ```
40
+
41
+ Both the project ID and the API key can come from either the ini file or an
42
+ environment variable; **the environment variable wins** when both are set.
43
+
44
+ ## Options
45
+
46
+ | ini option | env var | default | what it does |
47
+ | --------------------- | --------------------- | ------------------------ | ------------------------------------------------------------------- |
48
+ | `nijam_api_key` | `NIJAM_API_KEY` | — | Ingest API key (required). |
49
+ | `nijam_project_id` | `NIJAM_PROJECT_ID` | — | Project UUID (required). |
50
+ | `nijam_api_url` | `NIJAM_API_URL` | `https://api.nijam.dev` | API base URL. |
51
+ | `nijam_environment` | `NIJAM_ENVIRONMENT` | — | Free-form environment tag (e.g. `staging`). |
52
+ | `nijam_upload_source` | — | `true` | Upload each test file's source so the dashboard can show it. |
53
+ | `nijam_auto_complete` | `NIJAM_AUTO_COMPLETE` | `true` | Finalize the run when this process ends. Set `false` for fan-out. |
54
+ | `nijam_silent` | — | `false` | Suppress `[nijam]` log lines. |
55
+
56
+ If `nijam_api_key` or `nijam_project_id` is missing, the plugin disables itself with a
57
+ single warning — your tests run exactly as before.
58
+
59
+ ## CI metadata
60
+
61
+ Run context (commit, branch, PR number, CI provider/run URL, commit author) is detected
62
+ automatically from GitHub Actions, GitLab CI, CircleCI, Bitbucket Pipelines, or generic
63
+ `GIT_*` env vars, falling back to `git` itself. No configuration needed.
64
+
65
+ ## Fan-out across CI jobs
66
+
67
+ If you split your suite across several CI jobs (e.g. one job per test path) that all feed
68
+ one Nijam run, set `nijam_auto_complete = false` (or `NIJAM_AUTO_COMPLETE=false`) on each
69
+ job so none of them finalizes early, then complete the run once from a post-matrix step.
70
+ See the [docs](https://docs.nijam.dev/reporter/pytest/).
71
+
72
+ ## Guarantees
73
+
74
+ This plugin **never breaks your test run.** Every hook is fail-soft: a network error, a
75
+ bad key, or an unreachable API produces a `[nijam]` warning and nothing more — your tests
76
+ still pass or fail on their own merits.
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pytest-nijam"
7
+ version = "0.1.0"
8
+ description = "pytest plugin for Nijam — captures test runs and ships them to the Nijam API."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ authors = [{ name = "Nijam" }]
13
+ keywords = ["pytest", "nijam", "test-analytics", "reporting", "ci"]
14
+ classifiers = [
15
+ "Framework :: Pytest",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Testing",
20
+ ]
21
+ # Zero runtime dependencies. pytest is the host — declared so resolvers know the
22
+ # minimum, but the plugin only ever loads inside a pytest process anyway.
23
+ dependencies = ["pytest>=7.0"]
24
+
25
+ [project.urls]
26
+ Homepage = "https://nijam.dev"
27
+ Documentation = "https://docs.nijam.dev"
28
+ Source = "https://github.com/getnijam/pytest-reporter"
29
+
30
+ # This entry point is what makes pytest auto-discover and load the plugin.
31
+ [project.entry-points.pytest11]
32
+ nijam = "nijam_pytest.plugin"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["mypy>=1.8", "ruff>=0.5"]
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/nijam_pytest"]
39
+
40
+ [tool.mypy]
41
+ strict = true
42
+ files = ["src"]
43
+
44
+ [tool.ruff]
45
+ line-length = 100
46
+ target-version = "py38"
47
+ src = ["src"]
48
+
49
+ [tool.ruff.lint]
50
+ select = ["E", "F", "I", "UP", "B", "SIM"]
@@ -0,0 +1,3 @@
1
+ """pytest plugin for Nijam — captures test runs and ships them to the Nijam API."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,38 @@
1
+ """Batches test executions and flushes them in chunks.
2
+
3
+ Unlike the Playwright reporter (which fire-and-forgets over the network on a worker
4
+ thread), this buffer only *appends* during the test run — it never blocks the test
5
+ path on I/O. The accumulated executions are flushed in `FLUSH_SIZE` chunks at
6
+ `drain()` time (session finish), which keeps the hot path allocation-only and avoids
7
+ threading complexity. pytest sessions are short-lived, so a single end-of-run flush
8
+ is acceptable.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Callable
14
+
15
+ from . import log
16
+ from .models import TestExecution
17
+
18
+ FLUSH_SIZE = 50
19
+
20
+
21
+ class ExecutionBuffer:
22
+ def __init__(self, flush_fn: Callable[[list[TestExecution]], None]) -> None:
23
+ self._flush_fn = flush_fn
24
+ self._items: list[TestExecution] = []
25
+
26
+ def add(self, item: TestExecution) -> None:
27
+ self._items.append(item)
28
+
29
+ def drain(self) -> None:
30
+ """Send everything collected, in FLUSH_SIZE chunks. A failed chunk drops that
31
+ batch with a warning (the flush fn soft-fails) and we continue with the rest."""
32
+ items, self._items = self._items, []
33
+ for start in range(0, len(items), FLUSH_SIZE):
34
+ batch = items[start : start + FLUSH_SIZE]
35
+ try:
36
+ self._flush_fn(batch)
37
+ except Exception as err:
38
+ log.warn(f"dropped a batch of {len(batch)} executions: {err}")