testisolation 0.0.0__tar.gz → 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 (42) hide show
  1. testisolation-0.3.0/.github/workflows/ci.yml +33 -0
  2. testisolation-0.3.0/.github/workflows/publish.yml +195 -0
  3. testisolation-0.3.0/.gitignore +18 -0
  4. testisolation-0.3.0/.rlsbl/config.json +14 -0
  5. testisolation-0.3.0/.rlsbl/lint/python.toml +25 -0
  6. testisolation-0.3.0/.rlsbl/managed-files.json +9 -0
  7. testisolation-0.3.0/LICENSE +21 -0
  8. testisolation-0.3.0/PKG-INFO +50 -0
  9. testisolation-0.3.0/README.md +28 -0
  10. testisolation-0.3.0/pyproject.toml +60 -0
  11. testisolation-0.3.0/src/testisolation/__init__.py +18 -0
  12. testisolation-0.3.0/src/testisolation/config.py +396 -0
  13. testisolation-0.3.0/src/testisolation/env_overrides.py +155 -0
  14. testisolation-0.3.0/src/testisolation/pgcluster.py +696 -0
  15. testisolation-0.3.0/src/testisolation/plugin.py +158 -0
  16. testisolation-0.3.0/src/testisolation/pushguard.py +113 -0
  17. testisolation-0.3.0/src/testisolation/sandbox.py +79 -0
  18. testisolation-0.3.0/src/testisolation/socketguard.py +226 -0
  19. testisolation-0.3.0/tests/conftest.py +126 -0
  20. testisolation-0.3.0/tests/sample_consumer/README.md +14 -0
  21. testisolation-0.3.0/tests/sample_consumer/acme/__init__.py +48 -0
  22. testisolation-0.3.0/tests/sample_consumer/pytest.ini +15 -0
  23. testisolation-0.3.0/tests/sample_consumer/tests/conftest.py +22 -0
  24. testisolation-0.3.0/tests/sample_consumer/tests/test_git_workflow.py +49 -0
  25. testisolation-0.3.0/tests/sample_consumer/tests/test_home_isolation.py +32 -0
  26. testisolation-0.3.0/tests/sample_consumer/tests/test_network_is_off.py +20 -0
  27. testisolation-0.3.0/tests/test_chdir_isolation.py +74 -0
  28. testisolation-0.3.0/tests/test_config_keys.py +197 -0
  29. testisolation-0.3.0/tests/test_cross_language_parity.py +104 -0
  30. testisolation-0.3.0/tests/test_env_overrides.py +273 -0
  31. testisolation-0.3.0/tests/test_harness_selfcheck.py +7 -0
  32. testisolation-0.3.0/tests/test_pg_cluster.py +191 -0
  33. testisolation-0.3.0/tests/test_pg_socket_path.py +134 -0
  34. testisolation-0.3.0/tests/test_push_guard.py +250 -0
  35. testisolation-0.3.0/tests/test_sample_consumer.py +55 -0
  36. testisolation-0.3.0/tests/test_socket_guard.py +507 -0
  37. testisolation-0.3.0/tests/test_threshold.py +154 -0
  38. testisolation-0.3.0/tests/test_tmp_refusal.py +80 -0
  39. testisolation-0.3.0/uv.lock +109 -0
  40. testisolation-0.0.0/PKG-INFO +0 -5
  41. testisolation-0.0.0/pyproject.toml +0 -9
  42. testisolation-0.0.0/testisolation/__init__.py +0 -0
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ # Per-SHA group: re-runs of the same commit dedupe, but a new commit never
11
+ # cancels an earlier commit's in-flight run (release CI conclusions stay intact).
12
+ concurrency:
13
+ group: ${{ github.workflow_ref }}-${{ github.sha }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ # requires-python: >= 3.11
22
+ python-version: ["3.12", "3.13", "3.14"]
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ - uses: astral-sh/setup-uv@v7
26
+ - run: uv python install ${{ matrix.python-version }}
27
+ - run: uv sync --locked
28
+ - run: uv run python -c "import testisolation"
29
+ - name: Install gitleaks
30
+ run: |
31
+ GITLEAKS_VERSION=8.24.3
32
+ curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar xz -C /usr/local/bin gitleaks
33
+ - run: uv run pytest --rootdir .
@@ -0,0 +1,195 @@
1
+ name: Publish
2
+ on:
3
+ release:
4
+ types: [published]
5
+ workflow_dispatch:
6
+ inputs:
7
+ tag:
8
+ description: Release tag to publish (e.g. v1.2.3). Overrides the ref for
9
+ retry dispatch.
10
+ required: false
11
+ type: string
12
+
13
+ # One publish run per tag: a workflow_dispatch retry at the same tag
14
+ # queues behind the in-flight run instead of racing it. A publish is never
15
+ # cancelled mid-flight.
16
+ concurrency:
17
+ group: publish-${{ inputs.tag || github.ref_name }}
18
+ cancel-in-progress: false
19
+ permissions:
20
+ contents: read
21
+ id-token: write
22
+ jobs:
23
+ gate:
24
+ name: Gate on CI
25
+ runs-on: ubuntu-latest
26
+ permissions:
27
+ checks: read
28
+ contents: read
29
+ env:
30
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31
+ GH_REPO: ${{ github.repository }}
32
+ GATE_TIMEOUT_MINUTES: '20'
33
+ GATE_GRACE_MINUTES: '5'
34
+ GATE_POLL_SECONDS: '15'
35
+ GATE_MARKER_ATTEMPTS: '5'
36
+ GATE_MARKER_RETRY_SECONDS: '5'
37
+ CI_CHECK_REGEX: ^(test)( \(.*\))?$
38
+ steps:
39
+ - name: Wait for CI to succeed on the release commit
40
+ run: |
41
+ set -euo pipefail
42
+
43
+ # Commit resolution -- explicit marker-first order (never reads the release
44
+ # event payload; dispatch retries have none, so this path is uniform for
45
+ # release events and dispatch retries alike):
46
+ # (1) Marker: rlsbl writes the exact commit CI ran on into the GitHub
47
+ # Release body as a machine-parseable line of the form
48
+ # <!-- rlsbl-ci-sha: <40-hex> -->
49
+ # Prefer it when present -- it pins the precise commit and is immune to
50
+ # ref races. A just-created release can lag on a GitHub API read replica
51
+ # (the marker is written at release creation but may not be visible on
52
+ # the first read), so RETRY the read GATE_MARKER_ATTEMPTS times,
53
+ # GATE_MARKER_RETRY_SECONDS apart, before concluding it is absent. The
54
+ # tag is inputs.tag (TAG_INPUT) when dispatched with an explicit
55
+ # override, else GITHUB_REF_NAME (matches the router resolver).
56
+ # (2) Fallback: older releases predate the marker, so fall back to
57
+ # $GITHUB_SHA, the tag's commit for both release and dispatch-at-tag.
58
+ extract_ci_sha() {
59
+ # Emit the first rlsbl-ci-sha marker SHA found on stdin, if any.
60
+ sed -n 's/.*<!-- rlsbl-ci-sha: \([0-9a-f]\{40\}\) -->.*/\1/p' | head -n1
61
+ }
62
+ tag="${TAG_INPUT:-$GITHUB_REF_NAME}"
63
+ marker_attempts="${GATE_MARKER_ATTEMPTS:-5}"
64
+ marker_retry_seconds="${GATE_MARKER_RETRY_SECONDS:-5}"
65
+ sha=""
66
+ attempt=1
67
+ while [ "$attempt" -le "$marker_attempts" ]; do
68
+ if body="$(gh release view "$tag" --json body --jq .body 2>/dev/null)"; then
69
+ sha="$(printf '%s\n' "$body" | extract_ci_sha)"
70
+ fi
71
+ if [ -n "$sha" ]; then
72
+ break
73
+ fi
74
+ if [ "$attempt" -lt "$marker_attempts" ]; then
75
+ echo "Publish gate: rlsbl-ci-sha marker not yet visible in the '$tag' release body (attempt $attempt/$marker_attempts); retrying in ${marker_retry_seconds}s..."
76
+ sleep "$marker_retry_seconds"
77
+ fi
78
+ attempt=$(( attempt + 1 ))
79
+ done
80
+ if [ -n "$sha" ]; then
81
+ echo "Publish gate: resolved release commit from rlsbl-ci-sha marker in the '$tag' release body."
82
+ else
83
+ sha="$GITHUB_SHA"
84
+ echo "Publish gate: no rlsbl-ci-sha marker after $marker_attempts attempt(s) on the '$tag' release body; falling back to \$GITHUB_SHA."
85
+ fi
86
+ echo "Publish gate: waiting for CI on $GITHUB_REF_NAME (commit $sha)"
87
+ echo "Check-run name filter: $CI_CHECK_REGEX"
88
+ # Timeout, grace window, and poll interval come from the job env above;
89
+ # edit them there if this repository's CI needs different limits.
90
+
91
+ now() { date +%s; }
92
+ start="$(now)"
93
+ deadline=$(( start + GATE_TIMEOUT_MINUTES * 60 ))
94
+ grace_deadline=$(( start + GATE_GRACE_MINUTES * 60 ))
95
+
96
+ while :; do
97
+ if ! resp="$(gh api --paginate "repos/$GITHUB_REPOSITORY/commits/$sha/check-runs?per_page=100")"; then
98
+ echo "Checks API request failed; retrying in ${GATE_POLL_SECONDS}s..."
99
+ sleep "$GATE_POLL_SECONDS"
100
+ continue
101
+ fi
102
+ # Match this project's CI check runs by name; exclude check runs that
103
+ # belong to THIS workflow run (the gate itself and the queued publish
104
+ # jobs would otherwise deadlock the poll loop).
105
+ # A retried CI run creates a BRAND-NEW check-run with the same name as the
106
+ # old one, so a stale failure would block the gate forever. Project id and
107
+ # started_at, then reduce to the latest check-run per name (max started_at,
108
+ # numeric id as tiebreak for retries within the same second) BEFORE the
109
+ # pending / not-success logic runs. This way the newest run decides: still
110
+ # running -> wait; genuinely red -> hard fail.
111
+ runs="$(jq -s --arg re "$CI_CHECK_REGEX" --arg run_id "$GITHUB_RUN_ID" '
112
+ [ .[].check_runs[]
113
+ | select(.name | test($re))
114
+ | select((.details_url // "") | contains("/actions/runs/" + $run_id + "/") | not)
115
+ | {name, status, conclusion, id, started_at} ]
116
+ | group_by(.name)
117
+ | map(sort_by(.started_at, .id) | last)' <<< "$resp")"
118
+ total="$(jq 'length' <<< "$runs")"
119
+
120
+ if [ "$total" -eq 0 ]; then
121
+ if [ "$(now)" -ge "$grace_deadline" ]; then
122
+ echo "::error::Publish gate: no CI check runs matching $CI_CHECK_REGEX appeared on $sha within $GATE_GRACE_MINUTES minutes."
123
+ echo "A scaffolded repository always has a CI workflow, and rlsbl verifies CI on this exact commit BEFORE tagging it, so check runs must exist here."
124
+ echo "Their absence means the check runs were deleted, the commit resolution is wrong, or this tag was created outside rlsbl."
125
+ echo "If CI jobs were renamed, update CI_CHECK_REGEX in this workflow's gate job to match the new names."
126
+ exit 1
127
+ fi
128
+ echo "No matching CI check runs yet; retrying in ${GATE_POLL_SECONDS}s..."
129
+ sleep "$GATE_POLL_SECONDS"
130
+ continue
131
+ fi
132
+
133
+ pending="$(jq '[ .[] | select(.status != "completed") ] | length' <<< "$runs")"
134
+ if [ "$pending" -gt 0 ]; then
135
+ if [ "$(now)" -ge "$deadline" ]; then
136
+ echo "::error::Publish gate: timed out after $GATE_TIMEOUT_MINUTES minutes waiting for CI to complete on $sha."
137
+ jq -r '.[] | " \(.name): status=\(.status) conclusion=\(.conclusion // "none")"' <<< "$runs"
138
+ exit 1
139
+ fi
140
+ echo "$pending of $total matching CI check runs still running; retrying in ${GATE_POLL_SECONDS}s..."
141
+ sleep "$GATE_POLL_SECONDS"
142
+ continue
143
+ fi
144
+
145
+ not_success="$(jq '[ .[] | select(.conclusion != "success") ]' <<< "$runs")"
146
+ if [ "$(jq 'length' <<< "$not_success")" -gt 0 ]; then
147
+ echo "::error::Publish gate: CI did not pass on $sha -- refusing to publish."
148
+ jq -r '.[] | " \(.name): \(.conclusion)"' <<< "$not_success"
149
+ while IFS= read -r conclusion; do
150
+ case "$conclusion" in
151
+ failure|timed_out)
152
+ echo "CI concluded '$conclusion' on the release commit."
153
+ echo "rlsbl tags and releases a commit only AFTER its CI has gone green, so reaching this branch means one of: CI was re-run on an already-released commit and regressed, a required check was added after the release, or this tag/Release was created outside rlsbl."
154
+ echo "Do NOT re-dispatch this publish workflow expecting a different answer -- a failure baked into the code at this commit fails identically every time, and there is no way to make this tag green."
155
+ echo "Remedy: fix forward on the release branch and cut a NEW release with 'rlsbl release run' (its own CI gate must go green before it is tagged), then mark this one with 'rlsbl release deprecate <version>'."
156
+ ;;
157
+ cancelled)
158
+ echo "A CI check run was CANCELLED. A cancelled run proves nothing about the commit, so the gate treats it as a hard failure instead of waiting for a conclusion that will never come."
159
+ echo "Remedy: re-run the cancelled CI workflow on this exact commit (gh run rerun <run-id>). If it concludes success, re-dispatch this publish workflow at the tag ref: gh workflow run <publish workflow> --ref $GITHUB_REF_NAME"
160
+ ;;
161
+ skipped)
162
+ echo "A CI check run matching the filter was SKIPPED. The gate cannot treat a skipped check as passing: this project must actually run its own CI on the release commit."
163
+ echo "Remedy: check paths filters and job conditions so CI runs for this project, re-run CI on this commit, then re-dispatch this publish workflow at the tag ref."
164
+ ;;
165
+ *)
166
+ echo "CI check concluded '$conclusion' (not success). The gate only proceeds when every matching check concluded success."
167
+ ;;
168
+ esac
169
+ done <<< "$(jq -r '.[].conclusion' <<< "$not_success" | sort -u)"
170
+ exit 1
171
+ fi
172
+
173
+ echo "Publish gate: all $total matching CI check runs succeeded."
174
+ jq -r '.[] | " \(.name): \(.conclusion)"' <<< "$runs"
175
+ exit 0
176
+ done
177
+ pypi:
178
+ needs: gate
179
+ runs-on: ubuntu-latest
180
+ steps:
181
+ - uses: actions/checkout@v6
182
+ with:
183
+ ref: ${{ inputs.tag || github.event.release.tag_name }}
184
+ - uses: astral-sh/setup-uv@v7
185
+ - name: Install gitleaks
186
+ run: |
187
+ GITLEAKS_VERSION=8.24.3
188
+ curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar xz -C /usr/local/bin gitleaks
189
+ - run: uv build --out-dir dist
190
+ - name: Scan artifacts for secrets
191
+ run: |
192
+ gitleaks dir dist/
193
+ - uses: pypa/gh-action-pypi-publish@release/v1
194
+ with:
195
+ skip-existing: true
@@ -0,0 +1,18 @@
1
+ node_modules/
2
+ __pycache__/
3
+ *.pyc
4
+ *.log
5
+ .DS_Store
6
+ coverage/
7
+ build/
8
+ dist/
9
+ target/
10
+ *.egg-info/
11
+ .rlsbl-notes-*.tmp
12
+ .rlsbl/lock
13
+ .rlsbl-monorepo/lock
14
+ .credentials.json
15
+ .*-cache.json
16
+ .env
17
+ .env.local
18
+ *.local-only
@@ -0,0 +1,14 @@
1
+ {
2
+ "targets": [
3
+ "pypi"
4
+ ],
5
+ "publish_mode": "ci",
6
+ "pipelines": {
7
+ "pypi": {
8
+ "type": "pypi",
9
+ "local": false,
10
+ "target": "pypi"
11
+ }
12
+ },
13
+ "changelog_format_version_enforced": true
14
+ }
@@ -0,0 +1,25 @@
1
+ [forbidden-imports]
2
+ modules = [
3
+ "argparse",
4
+ "click",
5
+ "typer",
6
+ "flask",
7
+ "fastapi",
8
+ "django",
9
+ "uvicorn",
10
+ "granian",
11
+ "starlette",
12
+ "tornado",
13
+ "bottle",
14
+ ]
15
+
16
+ [stdout]
17
+ enabled = true
18
+ ignore = []
19
+
20
+ [entry-point]
21
+ enabled = true
22
+ ignore = []
23
+
24
+ [files]
25
+ exclude = []
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 1,
3
+ "files": {
4
+ ".github/workflows/ci.yml": "317dad66e40c753dfa18b5d143ea16cb1c9b4e8426eb3d8f703b67589d8ac129",
5
+ ".github/workflows/publish.yml": "bfa60b4f497b67e71b31336e0373a3730aca9b16e6b35d1f7a3e003ee406c01a",
6
+ ".gitignore": "c32366f560dbf628eb04a94d25e8d8acf719605b2ea27d31047df16e13580c04",
7
+ ".rlsbl/lint/python.toml": "9cfbcef2e010d5fad243437bf2fd8df54a1fae70ee0762f9d11da70fe207501c"
8
+ }
9
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smm-h
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,50 @@
1
+ Metadata-Version: 2.5
2
+ Name: testisolation
3
+ Version: 0.3.0
4
+ Summary: Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node
5
+ Project-URL: Homepage, https://smmh.dev/testisolation/
6
+ Project-URL: Documentation, https://smmh.dev/testisolation/
7
+ Project-URL: Repository, https://github.com/stricttools/testisolation
8
+ Project-URL: Issues, https://github.com/stricttools/testisolation/issues
9
+ Project-URL: Changelog, https://github.com/stricttools/testisolation/blob/main/CHANGELOG.md
10
+ Author-email: "S. M. Hosseini" <m.hosseini@veliu.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: credentials,isolation,node-test,pytest,rlsbl,sandbox,security,test-isolation,testing
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Framework :: Pytest
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Software Development :: Testing
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: pytest>=7.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # testisolation (Python)
24
+
25
+ Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node
26
+
27
+ This is the pytest plugin.
28
+
29
+ ```bash
30
+ pip install testisolation
31
+ ```
32
+
33
+ Installing it is adoption: the plugin loads through its `pytest11` entry point
34
+ and refuses to run a suite that has not declared its safety stance.
35
+
36
+ ```toml
37
+ [tool.pytest.ini_options]
38
+ testisolation_sockets = "deny"
39
+ testisolation_socket_allowlist = []
40
+ testisolation_unix_socket_allowlist = []
41
+ testisolation_loopback = "deny"
42
+ testisolation_sandbox_required = "false"
43
+ ```
44
+
45
+ See the [repository README](https://github.com/stricttools/testisolation) for the full
46
+ key reference and what each guard does.
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,28 @@
1
+ # testisolation (Python)
2
+
3
+ Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node
4
+
5
+ This is the pytest plugin.
6
+
7
+ ```bash
8
+ pip install testisolation
9
+ ```
10
+
11
+ Installing it is adoption: the plugin loads through its `pytest11` entry point
12
+ and refuses to run a suite that has not declared its safety stance.
13
+
14
+ ```toml
15
+ [tool.pytest.ini_options]
16
+ testisolation_sockets = "deny"
17
+ testisolation_socket_allowlist = []
18
+ testisolation_unix_socket_allowlist = []
19
+ testisolation_loopback = "deny"
20
+ testisolation_sandbox_required = "false"
21
+ ```
22
+
23
+ See the [repository README](https://github.com/stricttools/testisolation) for the full
24
+ key reference and what each guard does.
25
+
26
+ ## License
27
+
28
+ MIT
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "testisolation"
7
+ version = "0.3.0"
8
+ description = "Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "S. M. Hosseini", email = "m.hosseini@veliu.com" }]
14
+ keywords = [
15
+ "testing",
16
+ "test-isolation",
17
+ "isolation",
18
+ "sandbox",
19
+ "security",
20
+ "credentials",
21
+ "pytest",
22
+ "node-test",
23
+ "rlsbl",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 3 - Alpha",
27
+ "Framework :: Pytest",
28
+ "Intended Audience :: Developers",
29
+ "Programming Language :: Python :: 3",
30
+ "Topic :: Software Development :: Testing",
31
+ ]
32
+ dependencies = ["pytest>=7.0"]
33
+
34
+ [project.entry-points.pytest11]
35
+ testisolation = "testisolation.plugin"
36
+
37
+ [dependency-groups]
38
+ dev = ["pytest", "pytest-xdist"]
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["src/testisolation"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ # testisolation is active in its own suite through the pytest11 entry point, so
46
+ # this project declares its safety stance like any other consumer.
47
+ testisolation_sockets = "deny"
48
+ testisolation_socket_allowlist = []
49
+ testisolation_unix_socket_allowlist = []
50
+ testisolation_loopback = "deny"
51
+ # No sandbox runner exists in this repo yet; the runner ships as a scaffold
52
+ # template from rlsbl. Flip to "true" at adoption.
53
+ testisolation_sandbox_required = "false"
54
+
55
+ [project.urls]
56
+ Homepage = "https://smmh.dev/testisolation/"
57
+ Documentation = "https://smmh.dev/testisolation/"
58
+ Repository = "https://github.com/stricttools/testisolation"
59
+ Issues = "https://github.com/stricttools/testisolation/issues"
60
+ Changelog = "https://github.com/stricttools/testisolation/blob/main/CHANGELOG.md"
@@ -0,0 +1,18 @@
1
+ """testisolation -- always-on test isolation for pytest suites.
2
+
3
+ The package ships a ``pytest11`` plugin. Installing it is adoption: the plugin
4
+ loads automatically and refuses to run a suite that has not declared its safety
5
+ stance. See :mod:`testisolation.config` for the ini keys.
6
+ """
7
+
8
+ from .config import PRESERVE_VARS, REQUIRED_KEYS, Settings
9
+ from .socketguard import NetworkBlocked, Policy
10
+
11
+ __version__ = "0.3.0"
12
+
13
+ # No ``__all__`` here on purpose. Every name above is defined -- and documented
14
+ # -- on its own module's API-reference page; re-listing them here would make the
15
+ # package root claim them a second time, double-counting them in documentation
16
+ # coverage. The re-exports themselves are the public import path
17
+ # (``from testisolation import NetworkBlocked``) and are covered by the suite.
18
+ _REEXPORTS = (PRESERVE_VARS, REQUIRED_KEYS, Settings, NetworkBlocked, Policy)