lib-detect-testenv 3.0.4__tar.gz → 3.0.6__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.
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/.gitignore +8 -5
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/CHANGELOG.md +34 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/PKG-INFO +50 -17
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/README.md +38 -8
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/pyproject.toml +31 -11
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/__init__conf__.py +1 -1
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/safe_console.py +9 -4
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/conftest.py +18 -0
- lib_detect_testenv-3.0.6/tests/test_packaging.py +127 -0
- lib_detect_testenv-3.0.6/tests/test_platform_markers.py +31 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/test_safe_console.py +25 -0
- lib_detect_testenv-3.0.4/.devcontainer/devcontainer.json +0 -22
- lib_detect_testenv-3.0.4/.devcontainer/settings.json +0 -6
- lib_detect_testenv-3.0.4/.env.example +0 -11
- lib_detect_testenv-3.0.4/.github/actions/extract-metadata/action.yml +0 -108
- lib_detect_testenv-3.0.4/.github/dependabot.yml +0 -22
- lib_detect_testenv-3.0.4/.github/workflows/codeql.yml +0 -39
- lib_detect_testenv-3.0.4/.github/workflows/default_cicd_public.yml +0 -436
- lib_detect_testenv-3.0.4/.github/workflows/default_release_public.yml +0 -95
- lib_detect_testenv-3.0.4/.qlty/qlty.toml +0 -2
- lib_detect_testenv-3.0.4/.snyk +0 -5
- lib_detect_testenv-3.0.4/CLAUDE.md +0 -177
- lib_detect_testenv-3.0.4/CONTRIBUTING.md +0 -48
- lib_detect_testenv-3.0.4/DEVELOPMENT.md +0 -134
- lib_detect_testenv-3.0.4/INSTALL.md +0 -193
- lib_detect_testenv-3.0.4/Makefile +0 -453
- lib_detect_testenv-3.0.4/codecov.yml +0 -28
- lib_detect_testenv-3.0.4/concept.md +0 -312
- lib_detect_testenv-3.0.4/notebooks/Quickstart.ipynb +0 -165
- lib_detect_testenv-3.0.4/reset_git_history.sh +0 -55
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/LICENSE +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/docs/systemdesign/module_reference.md +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/__init__.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/__main__.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/behaviors.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/cli.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/lib_detect_testenv.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/py.typed +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/typed_click.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/test_behaviors.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/test_cli.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/test_lib_detect_testenv.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/test_metadata.py +0 -0
- {lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/tests/test_module_entry.py +0 -0
|
@@ -47,7 +47,8 @@ tmp_*
|
|
|
47
47
|
# *.py[cod]: catches .pyc, .pyo, .pyd
|
|
48
48
|
__pycache__/
|
|
49
49
|
*.py[cod]
|
|
50
|
-
|
|
50
|
+
# other compiled artifacts
|
|
51
|
+
*$py.class
|
|
51
52
|
|
|
52
53
|
# pytest, coverage, MyPy, Hypothesis
|
|
53
54
|
.pytest_cache/
|
|
@@ -73,12 +74,15 @@ coverage.xml
|
|
|
73
74
|
# source/package builds
|
|
74
75
|
build/
|
|
75
76
|
dist/
|
|
76
|
-
|
|
77
|
+
# metadata for pip installs
|
|
78
|
+
*.egg-info/
|
|
77
79
|
*.egg
|
|
78
80
|
*.whl
|
|
79
81
|
pip-wheel-metadata/
|
|
80
|
-
|
|
81
|
-
*.
|
|
82
|
+
# PyInstaller metadata
|
|
83
|
+
*.manifest
|
|
84
|
+
# PyInstaller spec files
|
|
85
|
+
*.spec
|
|
82
86
|
|
|
83
87
|
# Eggs, parts, wheels, sdist, etc.
|
|
84
88
|
develop-eggs/
|
|
@@ -178,7 +182,6 @@ result
|
|
|
178
182
|
# Optional lockfiles (for alternative managers)
|
|
179
183
|
poetry.lock
|
|
180
184
|
pdm.lock
|
|
181
|
-
uv.lock
|
|
182
185
|
|
|
183
186
|
# Local, per-developer settings (Claude Code etc.) - never commit
|
|
184
187
|
*.local.json
|
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.0.6] 2026-10-15 16:35:25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The source distribution ships only the package, its tests, its docs, `README.md`, `LICENSE`,
|
|
15
|
+
`CHANGELOG.md` and `pyproject.toml` (an include list). Earlier sdists also carried every other
|
|
16
|
+
root file and, in a local build, anything hidden only through `.git/info/exclude` or a nested
|
|
17
|
+
`.gitignore`. `tests/test_packaging.py` builds the sdist and wheel and checks both; `hatchling`
|
|
18
|
+
joins the dev extra for it.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Ruff selects `C90` (mccabe), so a function over cyclomatic complexity 10, the house hard limit,
|
|
23
|
+
now fails `make test`; before, nothing enforced it.
|
|
24
|
+
|
|
25
|
+
### Tests
|
|
26
|
+
|
|
27
|
+
- The `os_windows`, `os_macos`, `os_linux` and `os_posix` markers now skip a test off its platform
|
|
28
|
+
(`pytest_runtest_setup` in `tests/conftest.py`); registered in `pyproject.toml` alone they skipped
|
|
29
|
+
nothing, so a test trusting its marker ran everywhere. `tests/test_platform_markers.py` pins it.
|
|
30
|
+
|
|
31
|
+
## [3.0.5] 2026-10-15 14:11:33
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Python 3.15 is a supported version: declared in the classifiers, so CI and `make test-all` run it.
|
|
35
|
+
- `uv.lock` is committed, and `[tool.uv] exclude-newer` sets a cooldown that keeps releases younger
|
|
36
|
+
than it out of the lock. CI installs exactly the locked tree on push and pull request; the nightly
|
|
37
|
+
scheduled run still resolves afresh to catch upstream breakage early.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- The install section explains why installing with uv protects against supply-chain attacks and why
|
|
41
|
+
a plain `pip install` from PyPI does not.
|
|
42
|
+
- `rtoml` floor raised to 0.14.0, the first release with Python 3.15 wheels.
|
|
43
|
+
|
|
10
44
|
## [3.0.4] 2026-08-01 00:14:59
|
|
11
45
|
### Fixed
|
|
12
46
|
- **Console output no longer crashes on a legacy codepage.** A Windows console at codepage 1252
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_detect_testenv
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.6
|
|
4
4
|
Summary: Detect test environment - pytest, doctest, unittest, or regular execution
|
|
5
5
|
Project-URL: Homepage, https://github.com/bitranox/lib_detect_testenv
|
|
6
6
|
Project-URL: Repository, https://github.com/bitranox/lib_detect_testenv.git
|
|
@@ -18,24 +18,27 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
23
|
Classifier: Typing :: Typed
|
|
22
24
|
Requires-Python: >=3.10
|
|
23
|
-
Requires-Dist: rich-click>=1.9.
|
|
25
|
+
Requires-Dist: rich-click>=1.9.9
|
|
24
26
|
Provides-Extra: dev
|
|
25
27
|
Requires-Dist: bandit<1.9,>=1.8.6; (python_version < '3.10') and extra == 'dev'
|
|
26
28
|
Requires-Dist: bandit>=1.9.4; (python_version >= '3.10') and extra == 'dev'
|
|
27
|
-
Requires-Dist: build>=1.
|
|
28
|
-
Requires-Dist: click>=8.
|
|
29
|
+
Requires-Dist: build>=1.6.1; extra == 'dev'
|
|
30
|
+
Requires-Dist: click>=8.5.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: hatchling>=1.32.4; extra == 'dev'
|
|
29
32
|
Requires-Dist: import-linter<2.6,>=2.5.2; (python_version < '3.10') and extra == 'dev'
|
|
30
|
-
Requires-Dist: import-linter>=2.
|
|
33
|
+
Requires-Dist: import-linter>=2.15; (python_version >= '3.10') and extra == 'dev'
|
|
31
34
|
Requires-Dist: pip-audit>=2.10.1; extra == 'dev'
|
|
32
|
-
Requires-Dist: pyright[nodejs]>=1.1.
|
|
35
|
+
Requires-Dist: pyright[nodejs]>=1.1.414; extra == 'dev'
|
|
33
36
|
Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
|
|
34
37
|
Requires-Dist: pytest<9,>=8.4.2; (python_version < '3.10') and extra == 'dev'
|
|
35
38
|
Requires-Dist: pytest>=9.1.1; (python_version >= '3.10') and extra == 'dev'
|
|
36
|
-
Requires-Dist: rtoml>=0.
|
|
37
|
-
Requires-Dist: ruff>=0.
|
|
38
|
-
Requires-Dist: setuptools>=
|
|
39
|
+
Requires-Dist: rtoml>=0.14.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.17.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: setuptools>=84.0.0; extra == 'dev'
|
|
39
42
|
Requires-Dist: textual>=8.2.8; extra == 'dev'
|
|
40
43
|
Requires-Dist: twine>=7.0.0; extra == 'dev'
|
|
41
44
|
Description-Content-Type: text/markdown
|
|
@@ -72,7 +75,7 @@ This library provides utility functions to detect whether your code is running i
|
|
|
72
75
|
|
|
73
76
|
### Recommended: Install via UV
|
|
74
77
|
|
|
75
|
-
UV is an ultrafast Python package installer written in Rust (10
|
|
78
|
+
UV is an ultrafast Python package installer written in Rust (10-20× faster than pip/poetry):
|
|
76
79
|
|
|
77
80
|
```bash
|
|
78
81
|
# Install uv
|
|
@@ -107,9 +110,39 @@ For more installation methods (pipx, source builds, etc.), see [INSTALL.md](INST
|
|
|
107
110
|
|
|
108
111
|
### Python Version Requirements
|
|
109
112
|
|
|
110
|
-
- **Python 3.
|
|
113
|
+
- **Python 3.10+** is required
|
|
111
114
|
- Tested on Linux, macOS, and Windows
|
|
112
|
-
- CI runs on CPython 3.
|
|
115
|
+
- CI runs on CPython 3.10 to 3.15
|
|
116
|
+
|
|
117
|
+
### Why we recommend uv: supply-chain protection
|
|
118
|
+
|
|
119
|
+
This repository commits a `uv.lock`. It pins every package the project needs, transitive
|
|
120
|
+
dependencies included, to an exact version and to the hashes of that version's files. The
|
|
121
|
+
`exclude-newer` setting under `[tool.uv]` in `pyproject.toml` adds a cooldown: uv leaves any
|
|
122
|
+
release younger than that out of the lock. CI installs from the lock, so a hijacked release that
|
|
123
|
+
gets pulled again within the cooldown never reaches our builds, and a file whose hash does not
|
|
124
|
+
match is refused.
|
|
125
|
+
|
|
126
|
+
That protection stays in this repository. The installable package declares only lower bounds
|
|
127
|
+
(`>=`) for its dependencies, so a plain `pip install` takes the newest release of every transitive
|
|
128
|
+
dependency at the moment you run it. If one of them was hijacked an hour earlier, you get the
|
|
129
|
+
hijacked version. `pipx install` and `uv tool install` without options do the same.
|
|
130
|
+
|
|
131
|
+
To install the command line tool with a cooldown of your own (uv then skips any release younger
|
|
132
|
+
than 7 days, transitive ones included), or to run the exact tree our CI tests:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv tool install --exclude-newer "7 days" lib_detect_testenv
|
|
136
|
+
git clone https://github.com/bitranox/lib_detect_testenv.git && cd lib_detect_testenv && uv sync --locked && uv run lib_detect_testenv --help
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If you depend on lib_detect_testenv in your own project, give that project the same cooldown (7 days here, as
|
|
140
|
+
an example) and commit the `uv.lock` that `uv add lib_detect_testenv` writes:
|
|
141
|
+
|
|
142
|
+
```toml
|
|
143
|
+
[tool.uv]
|
|
144
|
+
exclude-newer = "7 days"
|
|
145
|
+
```
|
|
113
146
|
|
|
114
147
|
## Usage
|
|
115
148
|
|
|
@@ -121,11 +154,11 @@ The package provides a CLI for shell scripting and CI/CD integration. All comman
|
|
|
121
154
|
|
|
122
155
|
All detection commands use a **3-level exit code system**:
|
|
123
156
|
|
|
124
|
-
| Code
|
|
125
|
-
|
|
126
|
-
| **0** | Test environment **detected**
|
|
127
|
-
| **1** | Test environment **not detected** | Normal negative result (not an error)
|
|
128
|
-
| **2** | **Error occurred**
|
|
157
|
+
| Code | Meaning | Use Case |
|
|
158
|
+
|-------|-----------------------------------|-------------------------------------------|
|
|
159
|
+
| **0** | Test environment **detected** | Success - test env found |
|
|
160
|
+
| **1** | Test environment **not detected** | Normal negative result (not an error) |
|
|
161
|
+
| **2** | **Error occurred** | Command failed (bad arguments, exception) |
|
|
129
162
|
|
|
130
163
|
This design follows Unix conventions and makes shell scripting intuitive:
|
|
131
164
|
|
|
@@ -30,7 +30,7 @@ This library provides utility functions to detect whether your code is running i
|
|
|
30
30
|
|
|
31
31
|
### Recommended: Install via UV
|
|
32
32
|
|
|
33
|
-
UV is an ultrafast Python package installer written in Rust (10
|
|
33
|
+
UV is an ultrafast Python package installer written in Rust (10-20× faster than pip/poetry):
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
# Install uv
|
|
@@ -65,9 +65,39 @@ For more installation methods (pipx, source builds, etc.), see [INSTALL.md](INST
|
|
|
65
65
|
|
|
66
66
|
### Python Version Requirements
|
|
67
67
|
|
|
68
|
-
- **Python 3.
|
|
68
|
+
- **Python 3.10+** is required
|
|
69
69
|
- Tested on Linux, macOS, and Windows
|
|
70
|
-
- CI runs on CPython 3.
|
|
70
|
+
- CI runs on CPython 3.10 to 3.15
|
|
71
|
+
|
|
72
|
+
### Why we recommend uv: supply-chain protection
|
|
73
|
+
|
|
74
|
+
This repository commits a `uv.lock`. It pins every package the project needs, transitive
|
|
75
|
+
dependencies included, to an exact version and to the hashes of that version's files. The
|
|
76
|
+
`exclude-newer` setting under `[tool.uv]` in `pyproject.toml` adds a cooldown: uv leaves any
|
|
77
|
+
release younger than that out of the lock. CI installs from the lock, so a hijacked release that
|
|
78
|
+
gets pulled again within the cooldown never reaches our builds, and a file whose hash does not
|
|
79
|
+
match is refused.
|
|
80
|
+
|
|
81
|
+
That protection stays in this repository. The installable package declares only lower bounds
|
|
82
|
+
(`>=`) for its dependencies, so a plain `pip install` takes the newest release of every transitive
|
|
83
|
+
dependency at the moment you run it. If one of them was hijacked an hour earlier, you get the
|
|
84
|
+
hijacked version. `pipx install` and `uv tool install` without options do the same.
|
|
85
|
+
|
|
86
|
+
To install the command line tool with a cooldown of your own (uv then skips any release younger
|
|
87
|
+
than 7 days, transitive ones included), or to run the exact tree our CI tests:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv tool install --exclude-newer "7 days" lib_detect_testenv
|
|
91
|
+
git clone https://github.com/bitranox/lib_detect_testenv.git && cd lib_detect_testenv && uv sync --locked && uv run lib_detect_testenv --help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If you depend on lib_detect_testenv in your own project, give that project the same cooldown (7 days here, as
|
|
95
|
+
an example) and commit the `uv.lock` that `uv add lib_detect_testenv` writes:
|
|
96
|
+
|
|
97
|
+
```toml
|
|
98
|
+
[tool.uv]
|
|
99
|
+
exclude-newer = "7 days"
|
|
100
|
+
```
|
|
71
101
|
|
|
72
102
|
## Usage
|
|
73
103
|
|
|
@@ -79,11 +109,11 @@ The package provides a CLI for shell scripting and CI/CD integration. All comman
|
|
|
79
109
|
|
|
80
110
|
All detection commands use a **3-level exit code system**:
|
|
81
111
|
|
|
82
|
-
| Code
|
|
83
|
-
|
|
84
|
-
| **0** | Test environment **detected**
|
|
85
|
-
| **1** | Test environment **not detected** | Normal negative result (not an error)
|
|
86
|
-
| **2** | **Error occurred**
|
|
112
|
+
| Code | Meaning | Use Case |
|
|
113
|
+
|-------|-----------------------------------|-------------------------------------------|
|
|
114
|
+
| **0** | Test environment **detected** | Success - test env found |
|
|
115
|
+
| **1** | Test environment **not detected** | Normal negative result (not an error) |
|
|
116
|
+
| **2** | **Error occurred** | Command failed (bad arguments, exception) |
|
|
87
117
|
|
|
88
118
|
This design follows Unix conventions and makes shell scripting intuitive:
|
|
89
119
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lib_detect_testenv"
|
|
3
|
-
version = "3.0.
|
|
3
|
+
version = "3.0.6"
|
|
4
4
|
description = "Detect test environment - pytest, doctest, unittest, or regular execution"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"rich-click>=1.9.
|
|
8
|
+
"rich-click>=1.9.9",
|
|
9
9
|
]
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
authors = [{ name = "bitranox", email = "bitranox@gmail.com" }]
|
|
@@ -17,6 +17,8 @@ classifiers = [
|
|
|
17
17
|
"Programming Language :: Python :: 3.11",
|
|
18
18
|
"Programming Language :: Python :: 3.12",
|
|
19
19
|
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Programming Language :: Python :: 3.15",
|
|
20
22
|
"License :: OSI Approved :: MIT License",
|
|
21
23
|
"Environment :: Console",
|
|
22
24
|
"Operating System :: OS Independent",
|
|
@@ -33,13 +35,12 @@ dev = [
|
|
|
33
35
|
"pytest>=8.4.2,<9; python_version<'3.10'",
|
|
34
36
|
"pytest>=9.1.1; python_version>='3.10'",
|
|
35
37
|
"pytest-cov>=7.1.0",
|
|
36
|
-
"ruff>=0.
|
|
37
|
-
"pyright[nodejs]>=1.1.
|
|
38
|
+
"ruff>=0.17.0",
|
|
39
|
+
"pyright[nodejs]>=1.1.414",
|
|
38
40
|
"bandit>=1.8.6,<1.9; python_version<'3.10'",
|
|
39
41
|
"bandit>=1.9.4; python_version>='3.10'",
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"build>=1.5.0",
|
|
42
|
+
"build>=1.6.1",
|
|
43
|
+
"hatchling>=1.32.4",
|
|
43
44
|
"twine>=7.0.0",
|
|
44
45
|
# codecov-cli is disabled, not dropped: it requires click<8.3.0, which drags click back to
|
|
45
46
|
# 8.2.1 - the version with CVE-2026-7246 (command injection in click.edit(), fixed in 8.3.3).
|
|
@@ -49,13 +50,13 @@ dev = [
|
|
|
49
50
|
# Re-enable once codecov-cli relaxes its click<8.3.0 pin.
|
|
50
51
|
# "codecov-cli>=11.2.8",
|
|
51
52
|
# Transitive CVE pins
|
|
52
|
-
"click>=8.
|
|
53
|
+
"click>=8.5.0", # CVE-2026-7246
|
|
53
54
|
"pip-audit>=2.10.1",
|
|
54
55
|
"textual>=8.2.8",
|
|
55
56
|
"import-linter>=2.5.2,<2.6; python_version<'3.10'",
|
|
56
|
-
"import-linter>=2.
|
|
57
|
-
"setuptools>=
|
|
58
|
-
"rtoml>=0.
|
|
57
|
+
"import-linter>=2.15; python_version>='3.10'",
|
|
58
|
+
"setuptools>=84.0.0",
|
|
59
|
+
"rtoml>=0.14.0",
|
|
59
60
|
]
|
|
60
61
|
|
|
61
62
|
[project.scripts]
|
|
@@ -71,6 +72,21 @@ build-backend = "hatchling.build"
|
|
|
71
72
|
packages = ["src/lib_detect_testenv"]
|
|
72
73
|
include = ["src/lib_detect_testenv/py.typed"]
|
|
73
74
|
|
|
75
|
+
# An allowlist, never a denylist: hatchling reads only the root .gitignore, so tracked working
|
|
76
|
+
# files and files hidden through .git/info/exclude or a nested .gitignore (session records,
|
|
77
|
+
# review notes, helper scripts) would ship. tests/test_packaging.py builds the sdist and
|
|
78
|
+
# refuses any other top-level entry.
|
|
79
|
+
[tool.hatch.build.targets.sdist]
|
|
80
|
+
include = [
|
|
81
|
+
"/src/lib_detect_testenv",
|
|
82
|
+
"/tests/*.py",
|
|
83
|
+
"/docs",
|
|
84
|
+
"/README.md",
|
|
85
|
+
"/LICENSE",
|
|
86
|
+
"/CHANGELOG.md",
|
|
87
|
+
"/pyproject.toml",
|
|
88
|
+
]
|
|
89
|
+
|
|
74
90
|
[tool.ruff]
|
|
75
91
|
line-length = 160
|
|
76
92
|
target-version = "py39"
|
|
@@ -85,6 +101,7 @@ select = [
|
|
|
85
101
|
"B", # bugbear
|
|
86
102
|
"A", # builtins shadowing
|
|
87
103
|
"C4", # comprehension simplification
|
|
104
|
+
"C90", # mccabe: cyclomatic complexity <= 10 (the house hard limit)
|
|
88
105
|
"SIM", # simplification
|
|
89
106
|
"TCH", # type-checking imports
|
|
90
107
|
"RUF", # ruff-specific
|
|
@@ -200,3 +217,6 @@ ignore-vulns = ["GHSA-4xh5-x5gv-qwph", "PYSEC-2022-42969", "CVE-2026-25990", "CV
|
|
|
200
217
|
[tool.bashate]
|
|
201
218
|
max-line-length = 120
|
|
202
219
|
ignores = ["E003"]
|
|
220
|
+
|
|
221
|
+
[tool.uv]
|
|
222
|
+
exclude-newer = "1 day"
|
{lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/__init__conf__.py
RENAMED
|
@@ -26,7 +26,7 @@ name = "lib_detect_testenv"
|
|
|
26
26
|
#: Human-readable summary shown in CLI help output.
|
|
27
27
|
title = "Detect test environment - pytest, doctest, unittest, or regular execution"
|
|
28
28
|
#: Current release version pulled from ``pyproject.toml`` by automation.
|
|
29
|
-
version = "3.0.
|
|
29
|
+
version = "3.0.6"
|
|
30
30
|
#: Repository homepage presented to users.
|
|
31
31
|
homepage = "https://github.com/bitranox/lib_detect_testenv"
|
|
32
32
|
#: Author attribution surfaced in CLI output.
|
{lib_detect_testenv-3.0.4 → lib_detect_testenv-3.0.6}/src/lib_detect_testenv/safe_console.py
RENAMED
|
@@ -64,13 +64,19 @@ ASCII_FALLBACKS: Final[dict[str, str]] = {
|
|
|
64
64
|
_UNIVERSAL_ENCODINGS: Final[frozenset[str]] = frozenset({"utf-8", "utf8", "utf-16", "utf16", "utf-32", "utf32"})
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def _stream_encoding(file: IO[Any] | None) -> str | None:
|
|
67
|
+
def _stream_encoding(file: IO[Any] | None, *, err: bool = False) -> str | None:
|
|
68
68
|
"""Return the target stream's encoding, or None when it cannot be determined.
|
|
69
69
|
|
|
70
70
|
An unknown encoding means the caller gets the original text: guessing would
|
|
71
71
|
degrade output that may well have been fine.
|
|
72
|
+
|
|
73
|
+
With no explicit `file` the answer comes from ``sys.stdout``/``sys.stderr``,
|
|
74
|
+
which is what :func:`click.echo` resolves its own default target from. click
|
|
75
|
+
exposes no supported way to ask for that stream: ``get_text_stream`` was
|
|
76
|
+
deprecated in click 8.5.0 and is removed in 9.0, its documented replacement
|
|
77
|
+
being to let ``echo`` resolve the stream itself.
|
|
72
78
|
"""
|
|
73
|
-
stream = file if file is not None else
|
|
79
|
+
stream = file if file is not None else (sys.stderr if err else sys.stdout)
|
|
74
80
|
encoding = getattr(stream, "encoding", None)
|
|
75
81
|
return encoding if isinstance(encoding, str) else None
|
|
76
82
|
|
|
@@ -135,8 +141,7 @@ def echo(message: object = "", *, file: IO[Any] | None = None, err: bool = False
|
|
|
135
141
|
Writes to the given stream.
|
|
136
142
|
"""
|
|
137
143
|
text = message if isinstance(message, str) else str(message)
|
|
138
|
-
|
|
139
|
-
click.echo(encode_safe(text, _stream_encoding(target)), file=target, nl=nl)
|
|
144
|
+
click.echo(encode_safe(text, _stream_encoding(file, err=err)), file=file, err=err, nl=nl)
|
|
140
145
|
|
|
141
146
|
|
|
142
147
|
class _SafeWriter:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
+
import sys
|
|
6
7
|
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
import pytest
|
|
@@ -48,3 +49,20 @@ def strip_ansi() -> Callable[[str], str]:
|
|
|
48
49
|
return _remove_ansi_codes(value)
|
|
49
50
|
|
|
50
51
|
return _strip
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
#: Which platforms each ``os_*`` marker lets a test run on. Registering a marker in pyproject only
|
|
55
|
+
#: silences pytest's unknown-marker warning; without this table a marked test runs everywhere.
|
|
56
|
+
_PLATFORM_MARKERS: dict[str, Callable[[str], bool]] = {
|
|
57
|
+
"os_windows": lambda platform: platform == "win32",
|
|
58
|
+
"os_macos": lambda platform: platform == "darwin",
|
|
59
|
+
"os_linux": lambda platform: platform.startswith("linux"),
|
|
60
|
+
"os_posix": lambda platform: platform != "win32",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def pytest_runtest_setup(item: pytest.Item) -> None:
|
|
65
|
+
"""Skip a test whose ``os_*`` marker names a platform other than the running one."""
|
|
66
|
+
for name, runs_on in _PLATFORM_MARKERS.items():
|
|
67
|
+
if item.get_closest_marker(name) is not None and not runs_on(sys.platform):
|
|
68
|
+
pytest.skip(f"marked {name}; this is {sys.platform}")
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""The source distribution carries the package, its tests and its docs, and nothing else.
|
|
2
|
+
|
|
3
|
+
A working tree holds files that must never be published: session records, backlogs, scratch
|
|
4
|
+
review notes and helper scripts. Some are tracked, some are hidden only through
|
|
5
|
+
``.git/info/exclude`` or a nested ``.gitignore``, which hatchling does not read. The sdist is
|
|
6
|
+
therefore built from an allowlist, and this test builds one from a copy of the tree that also
|
|
7
|
+
holds such decoys. The wheel must still carry the package data the runtime loads.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import tarfile
|
|
14
|
+
import zipfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
from hatchling.builders.sdist import SdistBuilder
|
|
19
|
+
from hatchling.builders.wheel import WheelBuilder
|
|
20
|
+
|
|
21
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
22
|
+
PACKAGE = "lib_detect_testenv"
|
|
23
|
+
|
|
24
|
+
# hatchling force-includes the root .gitignore into every sdist (so a build from the unpacked
|
|
25
|
+
# sdist applies the same exclusions); no include/exclude setting removes it.
|
|
26
|
+
ALLOWED_TOP_LEVEL = frozenset(
|
|
27
|
+
{
|
|
28
|
+
"PKG-INFO",
|
|
29
|
+
"pyproject.toml",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"CHANGELOG.md",
|
|
33
|
+
".gitignore",
|
|
34
|
+
"src",
|
|
35
|
+
"tests",
|
|
36
|
+
"docs",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
REQUIRED_TOP_LEVEL = frozenset({"PKG-INFO", "pyproject.toml", "src", "tests"})
|
|
40
|
+
|
|
41
|
+
# Files and directories that exist in working trees and must never ship. Tracked ones are
|
|
42
|
+
# named in the repository; the others are hidden locally through .git/info/exclude.
|
|
43
|
+
DECOY_FILES = ("handover.md", "OPEN-WORK.md", "EXECUTION-USER-REVIEW.md", "reset_git_history.sh", ".private/notes.txt")
|
|
44
|
+
|
|
45
|
+
REQUIRED_WHEEL_DATA = ("lib_detect_testenv/py.typed",)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _copy_tree_with_decoys(destination: Path) -> Path:
|
|
49
|
+
"""Copy the buildable tree and plant the files a working tree carries but must not ship."""
|
|
50
|
+
root = destination / "tree"
|
|
51
|
+
root.mkdir()
|
|
52
|
+
for name in ("pyproject.toml", "README.md", "LICENSE", "CHANGELOG.md", ".gitignore"):
|
|
53
|
+
shutil.copy2(PROJECT_ROOT / name, root / name)
|
|
54
|
+
for name in ("src", "docs", "tests"):
|
|
55
|
+
shutil.copytree(PROJECT_ROOT / name, root / name, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
|
|
56
|
+
for decoy in DECOY_FILES:
|
|
57
|
+
path = root / decoy
|
|
58
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
path.write_text("decoy\n", encoding="utf-8")
|
|
60
|
+
return root
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _build_sdist(root: Path, directory: Path) -> Path:
|
|
64
|
+
built = list(SdistBuilder(str(root)).build(directory=str(directory), versions=["standard"]))
|
|
65
|
+
assert len(built) == 1, f"expected one sdist, got {built}"
|
|
66
|
+
return Path(built[0])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _strip_root(names: list[str]) -> list[str]:
|
|
70
|
+
"""Drop the ``<name>-<version>/`` prefix every member carries."""
|
|
71
|
+
return [name.split("/", 1)[1] for name in names if "/" in name]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture(scope="module")
|
|
75
|
+
def built_tree(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
|
76
|
+
return _copy_tree_with_decoys(tmp_path_factory.mktemp("packaging"))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.fixture(scope="module")
|
|
80
|
+
def sdist_members(built_tree: Path, tmp_path_factory: pytest.TempPathFactory) -> list[str]:
|
|
81
|
+
sdist = _build_sdist(built_tree, tmp_path_factory.mktemp("sdist"))
|
|
82
|
+
with tarfile.open(sdist, "r:gz") as archive:
|
|
83
|
+
members = _strip_root(archive.getnames())
|
|
84
|
+
assert members, "the sdist is empty, so nothing below would be judged"
|
|
85
|
+
return members
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.fixture(scope="module")
|
|
89
|
+
def wheel_members(built_tree: Path, tmp_path_factory: pytest.TempPathFactory) -> list[str]:
|
|
90
|
+
built = list(WheelBuilder(str(built_tree)).build(directory=str(tmp_path_factory.mktemp("wheel")), versions=["standard"]))
|
|
91
|
+
assert len(built) == 1, f"expected one wheel, got {built}"
|
|
92
|
+
with zipfile.ZipFile(built[0]) as archive:
|
|
93
|
+
members = archive.namelist()
|
|
94
|
+
assert members, "the wheel is empty, so nothing below would be judged"
|
|
95
|
+
return members
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.os_agnostic
|
|
99
|
+
def test_the_sdist_holds_only_allowlisted_top_level_entries(sdist_members: list[str]) -> None:
|
|
100
|
+
top_level = {member.split("/", 1)[0] for member in sdist_members}
|
|
101
|
+
|
|
102
|
+
unexpected = sorted(top_level - ALLOWED_TOP_LEVEL)
|
|
103
|
+
missing = sorted(REQUIRED_TOP_LEVEL - top_level)
|
|
104
|
+
|
|
105
|
+
assert not unexpected, f"the sdist ships files outside the allowlist: {unexpected}"
|
|
106
|
+
assert not missing, f"the sdist lacks required entries: {missing}"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.os_agnostic
|
|
110
|
+
def test_the_sdist_ships_only_the_package_under_src(sdist_members: list[str]) -> None:
|
|
111
|
+
under_src = {member.split("/")[1] for member in sdist_members if member.startswith("src/") and member.count("/") >= 1}
|
|
112
|
+
|
|
113
|
+
assert under_src == {PACKAGE}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.os_agnostic
|
|
117
|
+
def test_the_sdist_carries_no_compiled_bytecode(sdist_members: list[str]) -> None:
|
|
118
|
+
compiled = [member for member in sdist_members if "__pycache__" in member or member.endswith(".pyc")]
|
|
119
|
+
|
|
120
|
+
assert not compiled, f"the sdist ships bytecode: {compiled[:5]}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.mark.os_agnostic
|
|
124
|
+
def test_the_wheel_carries_the_package_data_the_runtime_loads(wheel_members: list[str]) -> None:
|
|
125
|
+
missing = [name for name in REQUIRED_WHEEL_DATA if name not in wheel_members]
|
|
126
|
+
|
|
127
|
+
assert not missing, f"the wheel lacks package data: {missing}"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The ``os_*`` markers skip a test off its platform; registering them alone would skip nothing.
|
|
2
|
+
|
|
3
|
+
Each test asserts the platform its own marker names, so it fails wherever conftest lets it run by
|
|
4
|
+
mistake, and passes where the marker lets it run.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.os_windows
|
|
15
|
+
def test_an_os_windows_test_runs_only_on_windows() -> None:
|
|
16
|
+
assert sys.platform == "win32"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.os_macos
|
|
20
|
+
def test_an_os_macos_test_runs_only_on_macos() -> None:
|
|
21
|
+
assert sys.platform == "darwin"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.os_linux
|
|
25
|
+
def test_an_os_linux_test_runs_only_on_linux() -> None:
|
|
26
|
+
assert sys.platform.startswith("linux")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.os_posix
|
|
30
|
+
def test_an_os_posix_test_never_runs_on_windows() -> None:
|
|
31
|
+
assert sys.platform != "win32"
|
|
@@ -8,6 +8,7 @@ keeps the next output line someone adds covered by construction.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import io
|
|
11
|
+
import sys
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
|
|
13
14
|
import pytest
|
|
@@ -78,6 +79,30 @@ class TestEchoOnAUtf8Console:
|
|
|
78
79
|
assert "\u2713" in buffer.getvalue().decode("utf-8")
|
|
79
80
|
|
|
80
81
|
|
|
82
|
+
class TestTheDefaultTargetFollowsTheStreamEchoWritesTo:
|
|
83
|
+
"""With no `file`, the encoding must come from the stream `echo` lands on.
|
|
84
|
+
|
|
85
|
+
click resolves that target itself from ``sys.stdout``/``sys.stderr``. Judge
|
|
86
|
+
the wrong stream and a legacy-codepage console gets exactly the crash this
|
|
87
|
+
module exists to prevent, while every test passing an explicit `file` stays
|
|
88
|
+
green and says nothing about it.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def test_a_cp1252_stdout_degrades_the_glyph(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
92
|
+
stream = _cp1252_stream()
|
|
93
|
+
monkeypatch.setattr(sys, "stdout", stream)
|
|
94
|
+
safe_console.echo("✓ deployed")
|
|
95
|
+
assert "[OK]" in _read_back(stream)
|
|
96
|
+
|
|
97
|
+
def test_err_is_judged_against_stderr_not_stdout(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
98
|
+
"""A stdout that could take the glyph must not excuse a cp1252 stderr."""
|
|
99
|
+
stream = _cp1252_stream()
|
|
100
|
+
monkeypatch.setattr(sys, "stdout", io.TextIOWrapper(io.BytesIO(), encoding="utf-8", newline=""))
|
|
101
|
+
monkeypatch.setattr(sys, "stderr", stream)
|
|
102
|
+
safe_console.echo("✓ deployed", err=True)
|
|
103
|
+
assert "[OK]" in _read_back(stream)
|
|
104
|
+
|
|
105
|
+
|
|
81
106
|
class TestSafeStreamProtectsRich:
|
|
82
107
|
"""Rich raises on a legacy codepage too; it renders through its own writer."""
|
|
83
108
|
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"image": "mcr.microsoft.com/devcontainers/python:3.13",
|
|
3
|
-
|
|
4
|
-
"postCreateCommand": "bash -lc '\nPY=/usr/local/bin/python;\n$PY -m pip install -U pip && \\\n$PY -m pip install ipykernel && \\\n$PY -m pip install -e . && \\\n# register kernel using this exact interpreter\n$PY -m ipykernel install --name=python3 --display-name=\"Python 3 (3.13)\" --user && \\\n# patch the notebook to point to the python3 kernelspec (VS Code will bind to /usr/local/bin/python)\n$PY - <<\"PY\"\nimport json, pathlib\np = pathlib.Path(\"notebooks/Quickstart.ipynb\")\nif p.exists():\n nb = json.loads(p.read_text(encoding=\"utf-8\"))\n md = nb.setdefault(\"metadata\", {})\n md[\"kernelspec\"] = {\n \"name\": \"python3\",\n \"display_name\": \"Python 3 (3.13)\",\n \"language\": \"python\"\n }\n md[\"language_info\"] = {\"name\": \"python\"}\n p.write_text(json.dumps(nb, ensure_ascii=False, indent=1), encoding=\"utf-8\")\n print(\"Pinned kernelspec to python3 in\", p)\nelse:\n print(\"Notebook not found:\", p)\nPY'\n",
|
|
5
|
-
|
|
6
|
-
"customizations": {
|
|
7
|
-
"vscode": {
|
|
8
|
-
"extensions": ["ms-toolsai.jupyter", "ms-python.python"],
|
|
9
|
-
"settings": {
|
|
10
|
-
"workbench.startupEditor": "none",
|
|
11
|
-
"jupyter.alwaysTrustNotebooks": true,
|
|
12
|
-
"jupyter.kernelPickerType": "OnlyRecommended",
|
|
13
|
-
"python.defaultInterpreterPath": "/usr/local/bin/python"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"codespaces": {
|
|
17
|
-
"openFiles": ["README.md"]
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
"postAttachCommand": "bash -lc 'if [ -f notebooks/Quickstart.ipynb ]; then code -r notebooks/Quickstart.ipynb; fi'"
|
|
22
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Copy to .env and fill values as needed.
|
|
2
|
-
|
|
3
|
-
# Codecov upload token (required for private repos; optional for public repos)
|
|
4
|
-
CODECOV_TOKEN=
|
|
5
|
-
|
|
6
|
-
# PyPI API token for release workflow (.github/workflows/release.yml)
|
|
7
|
-
# Format: pypi-AgENdGVzdC5weXBpLm9yZwIk...
|
|
8
|
-
PYPI_API_TOKEN=
|
|
9
|
-
|
|
10
|
-
# Optional: GitHub token for API-limited tasks
|
|
11
|
-
GITHUB_TOKEN=
|