ripple-effect 0.0.1__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.
- ripple_effect-0.0.1/.github/workflows/release.yml +27 -0
- ripple_effect-0.0.1/.github/workflows/tests.yml +38 -0
- ripple_effect-0.0.1/.gitignore +18 -0
- ripple_effect-0.0.1/DEVELOP.md +38 -0
- ripple_effect-0.0.1/LICENSE +21 -0
- ripple_effect-0.0.1/PKG-INFO +76 -0
- ripple_effect-0.0.1/README.rst +48 -0
- ripple_effect-0.0.1/SPEC.md +236 -0
- ripple_effect-0.0.1/pyproject.toml +169 -0
- ripple_effect-0.0.1/setup.cfg +4 -0
- ripple_effect-0.0.1/src/ripple_effect/__init__.py +5 -0
- ripple_effect-0.0.1/src/ripple_effect/cli.py +16 -0
- ripple_effect-0.0.1/src/ripple_effect.egg-info/PKG-INFO +76 -0
- ripple_effect-0.0.1/src/ripple_effect.egg-info/SOURCES.txt +18 -0
- ripple_effect-0.0.1/src/ripple_effect.egg-info/dependency_links.txt +1 -0
- ripple_effect-0.0.1/src/ripple_effect.egg-info/entry_points.txt +2 -0
- ripple_effect-0.0.1/src/ripple_effect.egg-info/requires.txt +3 -0
- ripple_effect-0.0.1/src/ripple_effect.egg-info/top_level.txt +1 -0
- ripple_effect-0.0.1/tests/conftest.py +5 -0
- ripple_effect-0.0.1/tests/test_cli.py +19 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v[0-9]*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: release
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # mandatory for trusted publishing
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v6
|
|
19
|
+
- uses: actions/setup-python@v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.14"
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v7
|
|
24
|
+
- run: uvx --with tox-uv tox -e py,style,typecheck
|
|
25
|
+
- run: uv build
|
|
26
|
+
- name: Publish to PyPI
|
|
27
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.10", "3.14"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- uses: astral-sh/setup-uv@v7
|
|
25
|
+
- run: uvx --with tox-uv tox -e py
|
|
26
|
+
|
|
27
|
+
linters:
|
|
28
|
+
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
- uses: actions/setup-python@v6
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.14"
|
|
36
|
+
|
|
37
|
+
- uses: astral-sh/setup-uv@v7
|
|
38
|
+
- run: uvx --with tox-uv tox -e style,typecheck,readme
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
## Commands
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
# Run all tests + coverage report + linters
|
|
5
|
+
tox
|
|
6
|
+
|
|
7
|
+
# Run a single test by name
|
|
8
|
+
.venv/bin/pytest -k test_help
|
|
9
|
+
|
|
10
|
+
# Run ruff linter (check only)
|
|
11
|
+
tox -e style
|
|
12
|
+
|
|
13
|
+
# Auto-fix linting/formatting with ruff
|
|
14
|
+
tox -e reformat
|
|
15
|
+
|
|
16
|
+
# Run type checking
|
|
17
|
+
tox -e typecheck
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
**`src/ripple_effect/cli.py`** — click CLI entry point. Accepts a YAML config file and
|
|
23
|
+
top-level flags (`--dryrun`, `--verbose`).
|
|
24
|
+
|
|
25
|
+
**Config format** (YAML):
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
location: /tmp/ripple-workspace # where to clone/cache downstream repos
|
|
29
|
+
projects:
|
|
30
|
+
- git+https://github.com/org/project.git@main # remote ref
|
|
31
|
+
- ~/dev/local-project # local folder
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For each project ripple-effect will:
|
|
35
|
+
1. Clone (or `git fetch --reset`) the repo into `location/<name>/`
|
|
36
|
+
2. Create a venv, install the project + the library under test in editable mode via `uv`
|
|
37
|
+
3. Run `pytest tests/` against that venv
|
|
38
|
+
4. Collect and report pass/fail per project
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zoran Simic
|
|
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,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ripple-effect
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Know your blast radius before you push
|
|
5
|
+
Author-email: Zoran Simic <zoran@simicweb.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/zsimic/ripple-effect
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/x-rst
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: click
|
|
25
|
+
Requires-Dist: PyYAML
|
|
26
|
+
Requires-Dist: runez
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
ripple-effect
|
|
30
|
+
=============
|
|
31
|
+
|
|
32
|
+
Know your blast radius before you push.
|
|
33
|
+
|
|
34
|
+
Test a Python library against the downstream projects that depend on it —
|
|
35
|
+
before you tag a release and find out the hard way.
|
|
36
|
+
|
|
37
|
+
Installation
|
|
38
|
+
------------
|
|
39
|
+
|
|
40
|
+
.. code-block:: bash
|
|
41
|
+
|
|
42
|
+
uvx ripple-effect config.yml
|
|
43
|
+
|
|
44
|
+
Usage
|
|
45
|
+
-----
|
|
46
|
+
|
|
47
|
+
Create a YAML config describing your library and the downstream projects to test against:
|
|
48
|
+
|
|
49
|
+
.. code-block:: yaml
|
|
50
|
+
|
|
51
|
+
proving-grounds: /tmp/ripple-effect-workspace
|
|
52
|
+
downstream-projects:
|
|
53
|
+
- git+https://github.com/example/project-b.git@main
|
|
54
|
+
- git+https://github.com/example/project-c.git@main
|
|
55
|
+
|
|
56
|
+
Then run:
|
|
57
|
+
|
|
58
|
+
.. code-block:: bash
|
|
59
|
+
|
|
60
|
+
ripple-effect config.yml
|
|
61
|
+
|
|
62
|
+
ripple-effect will:
|
|
63
|
+
|
|
64
|
+
1. Clone (or update) each downstream project into ``proving-grounds``
|
|
65
|
+
2. Create a venv for each, with the downstream project + your library installed in editable mode
|
|
66
|
+
3. Run each project's test suite
|
|
67
|
+
4. Report which passed, which failed
|
|
68
|
+
|
|
69
|
+
For local iteration (e.g. testing against a local checkout of a downstream project):
|
|
70
|
+
|
|
71
|
+
.. code-block:: yaml
|
|
72
|
+
|
|
73
|
+
proving-grounds: ~/dev/my-ripple-workspace
|
|
74
|
+
downstream-projects:
|
|
75
|
+
- git+https://github.com/example/project-b.git@main
|
|
76
|
+
- ~/dev/project-c
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
ripple-effect
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
Know your blast radius before you push.
|
|
5
|
+
|
|
6
|
+
Test a Python library against the downstream projects that depend on it —
|
|
7
|
+
before you tag a release and find out the hard way.
|
|
8
|
+
|
|
9
|
+
Installation
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
.. code-block:: bash
|
|
13
|
+
|
|
14
|
+
uvx ripple-effect config.yml
|
|
15
|
+
|
|
16
|
+
Usage
|
|
17
|
+
-----
|
|
18
|
+
|
|
19
|
+
Create a YAML config describing your library and the downstream projects to test against:
|
|
20
|
+
|
|
21
|
+
.. code-block:: yaml
|
|
22
|
+
|
|
23
|
+
proving-grounds: /tmp/ripple-effect-workspace
|
|
24
|
+
downstream-projects:
|
|
25
|
+
- git+https://github.com/example/project-b.git@main
|
|
26
|
+
- git+https://github.com/example/project-c.git@main
|
|
27
|
+
|
|
28
|
+
Then run:
|
|
29
|
+
|
|
30
|
+
.. code-block:: bash
|
|
31
|
+
|
|
32
|
+
ripple-effect config.yml
|
|
33
|
+
|
|
34
|
+
ripple-effect will:
|
|
35
|
+
|
|
36
|
+
1. Clone (or update) each downstream project into ``proving-grounds``
|
|
37
|
+
2. Create a venv for each, with the downstream project + your library installed in editable mode
|
|
38
|
+
3. Run each project's test suite
|
|
39
|
+
4. Report which passed, which failed
|
|
40
|
+
|
|
41
|
+
For local iteration (e.g. testing against a local checkout of a downstream project):
|
|
42
|
+
|
|
43
|
+
.. code-block:: yaml
|
|
44
|
+
|
|
45
|
+
proving-grounds: ~/dev/my-ripple-workspace
|
|
46
|
+
downstream-projects:
|
|
47
|
+
- git+https://github.com/example/project-b.git@main
|
|
48
|
+
- ~/dev/project-c
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# ripple-effect — Spec
|
|
2
|
+
|
|
3
|
+
> Know your blast radius before you push.
|
|
4
|
+
|
|
5
|
+
Test a Python library against its downstream dependent projects — before you tag a release and find out the hard way.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Concept
|
|
10
|
+
|
|
11
|
+
Given a library **A** and a set of downstream projects **B**, **C**, …, ripple-effect:
|
|
12
|
+
|
|
13
|
+
1. Resolves and validates each project reference (library + downstream) via `uv-metadata`
|
|
14
|
+
2. Clones/updates each downstream project into a local workspace (git URL) or uses it in place (local folder)
|
|
15
|
+
3. Builds a venv for each project
|
|
16
|
+
4. Injects library **A** in editable mode into that venv
|
|
17
|
+
5. Runs the project's test suite
|
|
18
|
+
6. Reports pass/fail per project
|
|
19
|
+
|
|
20
|
+
The inject step is the point of the tool and is not configurable — it always runs
|
|
21
|
+
`uv pip install -e <library>` after the venv is prepared.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Invocation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uvx ripple-effect config.yml # build venvs and run tests as described in config.yml
|
|
29
|
+
ripple-effect config.yml # same, if installed globally
|
|
30
|
+
ripple-effect --dryrun config.yml # show what would be done, without doing it
|
|
31
|
+
ripple-effect --verbose config.yml # show more output
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Project references
|
|
37
|
+
|
|
38
|
+
Only two forms are accepted — for both `upstream` and `downstream-projects` entries:
|
|
39
|
+
|
|
40
|
+
| Form | Example | Behaviour |
|
|
41
|
+
|--------------|------------------------------------------------------------------|--------------------------------------------------------|
|
|
42
|
+
| Local folder | `.` or `~/github/runez` or `/path/to/project` | Used as-is, no git operations |
|
|
43
|
+
| Git URL | Any URL form accepted by `uv-metadata` (see below) | Cloned/updated into `proving-grounds/<name>/` |
|
|
44
|
+
|
|
45
|
+
PyPI refs (`requests`, `requests<2`), wheels, and sdists are **not** accepted — ripple-effect
|
|
46
|
+
needs a `tests/` folder to run against, which only local folders and git repos can provide.
|
|
47
|
+
|
|
48
|
+
### Validation via `uv-metadata`
|
|
49
|
+
|
|
50
|
+
Every project reference (`upstream` and each `downstream-projects` entry) is validated early via
|
|
51
|
+
[`uv-metadata`](https://github.com/zsimic/uv-metadata):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv-metadata <ref> # full metadata as JSON
|
|
55
|
+
uv-metadata <ref> --key name # just the project name
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
ripple-effect passes the reference as-is to `uv-metadata` and uses the result:
|
|
59
|
+
- If `uv-metadata` returns valid metadata — the reference is accepted, whatever form it took
|
|
60
|
+
- If `uv-metadata` returns no valid metadata — fail early with a clear error
|
|
61
|
+
|
|
62
|
+
`uv-metadata` accepts many URL forms (`https://`, `git+https://`, etc.) and normalises them
|
|
63
|
+
internally. ripple-effect doesn't need to validate or rewrite URLs — it just hands them off.
|
|
64
|
+
The `name` field from the metadata is used to name the workspace subfolder for git repos.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Config format (YAML)
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
# The upstream library under test. Accepts: local folder or git URL.
|
|
72
|
+
# Default: current working directory (.).
|
|
73
|
+
# Future: upstream-libs: [...] will allow multiple upstream libs in one run.
|
|
74
|
+
upstream: .
|
|
75
|
+
|
|
76
|
+
# Where to clone/cache downstream git repos.
|
|
77
|
+
# May use ~ and env vars. Subfolders are named after each project's name (from uv-metadata).
|
|
78
|
+
proving-grounds: /tmp/ripple-proving-grounds
|
|
79
|
+
|
|
80
|
+
# Optional global defaults, overridable per project.
|
|
81
|
+
defaults:
|
|
82
|
+
# How to build the venv. Default: auto-detected (see Three-step model below).
|
|
83
|
+
prepare: auto
|
|
84
|
+
# How to run the tests.
|
|
85
|
+
test: ".venv/bin/pytest tests/"
|
|
86
|
+
|
|
87
|
+
downstream-projects:
|
|
88
|
+
# Git URL (any form accepted by uv-metadata) — cloned/updated into proving-grounds/<name>/
|
|
89
|
+
- https://github.com/codrsquad/portable-python.git@main
|
|
90
|
+
- git+https://github.com/codrsquad/pickley.git@main
|
|
91
|
+
|
|
92
|
+
# Local folder — used as-is, no git operations
|
|
93
|
+
- ~/dev/my-other-project
|
|
94
|
+
|
|
95
|
+
# Per-project overrides (mapping form)
|
|
96
|
+
- url: https://github.com/org/project.git@main
|
|
97
|
+
prepare: "hatch env create" # project uses hatch instead of uv
|
|
98
|
+
test: ".venv/bin/pytest tests/ -x" # stop on first failure
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Project entry forms
|
|
102
|
+
|
|
103
|
+
A project entry can be a plain string (shorthand) or a mapping with explicit keys:
|
|
104
|
+
|
|
105
|
+
| Key | Description |
|
|
106
|
+
|-----------|-----------------------------------------------------------------------------|
|
|
107
|
+
| `url` | Any URL form accepted by `uv-metadata` — cloned/updated into `proving-grounds/<name>/` |
|
|
108
|
+
| `folder` | Local path — used as-is, no git operations |
|
|
109
|
+
| `prepare` | Override the global `defaults.prepare` for this project |
|
|
110
|
+
| `test` | Override the global `defaults.test` for this project |
|
|
111
|
+
|
|
112
|
+
Plain string entries are tried against `uv-metadata` first; if that succeeds and the ref
|
|
113
|
+
is not a local path, it is treated as a `url`. Local paths (`.`, `~`, `/`, or existing
|
|
114
|
+
directories) are treated as `folder` without calling out to `uv-metadata`.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Three-step execution model
|
|
119
|
+
|
|
120
|
+
For each downstream project, ripple-effect runs three steps in order:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
prepare → inject → test
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Step 1 — prepare
|
|
127
|
+
|
|
128
|
+
Builds the `.venv/` for the downstream project. Configurable via `defaults.prepare` or
|
|
129
|
+
per-project `prepare`. When set to `auto` (the default), ripple-effect detects:
|
|
130
|
+
|
|
131
|
+
| Condition | Command |
|
|
132
|
+
|----------------------------|----------------------------------------------------------------|
|
|
133
|
+
| `uv.lock` present | `uv sync` |
|
|
134
|
+
| `requirements.txt` present | `uv venv && uv pip install -r requirements.txt` |
|
|
135
|
+
| neither | error — explicit `prepare` required |
|
|
136
|
+
|
|
137
|
+
Any string is accepted as the prepare command; ripple-effect just runs it and moves on.
|
|
138
|
+
This accommodates projects using other venv managers (e.g. `hatch env create`, `poetry install`,
|
|
139
|
+
`pipenv install`) without ripple-effect needing to know about them.
|
|
140
|
+
|
|
141
|
+
### Step 2 — inject (not configurable)
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
uv pip install -e <upstream>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Injects the upstream library in editable mode into the venv built in step 1, overriding
|
|
148
|
+
whatever version was installed (pinned or otherwise).
|
|
149
|
+
|
|
150
|
+
**Special case — upstream is a local folder:**
|
|
151
|
+
Before running inject, ripple-effect checks `uv pip freeze` in the project's venv. If the
|
|
152
|
+
upstream is already installed in editable mode pointing at the right folder, inject is
|
|
153
|
+
skipped and the venv is reused as-is. If it is pinned/wrong, the venv is recreated from
|
|
154
|
+
scratch (prepare re-runs) then inject proceeds.
|
|
155
|
+
|
|
156
|
+
**Upstream identified by name:** `uv-metadata <upstream> --key name` gives the package name
|
|
157
|
+
used to find it in `uv pip freeze` output.
|
|
158
|
+
|
|
159
|
+
### Step 3 — test
|
|
160
|
+
|
|
161
|
+
Runs the project's test suite against the prepared+injected venv. Configurable via
|
|
162
|
+
`defaults.test` or per-project `test`.
|
|
163
|
+
|
|
164
|
+
Default: `.venv/bin/pytest tests/`
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Known limitation: tox
|
|
169
|
+
|
|
170
|
+
**tox is intentionally out of scope for v1.**
|
|
171
|
+
|
|
172
|
+
When tox runs, it creates its own isolated envs from scratch — the `.venv/` that
|
|
173
|
+
ripple-effect prepared and injected into is invisible to tox's envs. There is no clean
|
|
174
|
+
universal way to inject an editable library into tox environments without file surgery
|
|
175
|
+
(editing `requirements.txt`, `pyproject.toml`, or similar).
|
|
176
|
+
|
|
177
|
+
Workaround for power users: write a thin wrapper script that performs the injection
|
|
178
|
+
before calling tox, then point `test:` at that script. ripple-effect doesn't need to
|
|
179
|
+
know it's there.
|
|
180
|
+
|
|
181
|
+
A future version could add first-class tox support with explicit strategies
|
|
182
|
+
(e.g. `requirements.txt` surgery + restore, or `[tool.uv.sources]` injection + restore).
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## CI usage example (GitHub Actions)
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
- name: Run downstream integration tests
|
|
190
|
+
run: uvx ripple-effect ci-config.yml
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
With a `ci-config.yml`:
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
upstream: .
|
|
197
|
+
proving-grounds: $RUNNER_TEMP/ripple-workspace
|
|
198
|
+
downstream-projects:
|
|
199
|
+
- https://github.com/codrsquad/portable-python.git@main
|
|
200
|
+
- https://github.com/codrsquad/pickley.git@main
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Local iteration example
|
|
206
|
+
|
|
207
|
+
```yaml
|
|
208
|
+
upstream: ~/github/runez
|
|
209
|
+
proving-grounds: ~/dev/ripple-workspace
|
|
210
|
+
|
|
211
|
+
downstream-projects:
|
|
212
|
+
- https://github.com/codrsquad/portable-python.git@main
|
|
213
|
+
- ~/github/pickley # local checkout, no git pull
|
|
214
|
+
- folder: ~/github/some-hatch-project
|
|
215
|
+
prepare: "hatch env create" # project uses hatch rather than uv
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## v1 scope
|
|
221
|
+
|
|
222
|
+
- [ ] Config parsing (YAML)
|
|
223
|
+
- [ ] Project reference validation via `uv-metadata` (fail early on invalid refs)
|
|
224
|
+
- [ ] Project cloning/updating (`git clone` / `git fetch --reset`) for git URLs
|
|
225
|
+
- [ ] Auto-detection of prepare strategy (`uv.lock` vs `requirements.txt`)
|
|
226
|
+
- [ ] Editable-install check (`uv pip freeze`) before deciding to recreate venv
|
|
227
|
+
- [ ] `uv pip install -e <upstream>` injection
|
|
228
|
+
- [ ] Test runner (`.venv/bin/pytest tests/`)
|
|
229
|
+
- [ ] Per-project `prepare` / `test` overrides
|
|
230
|
+
- [ ] `--dryrun` mode
|
|
231
|
+
- [ ] Pass/fail summary report
|
|
232
|
+
|
|
233
|
+
Out of scope for v1:
|
|
234
|
+
- tox support
|
|
235
|
+
- Parallel project execution
|
|
236
|
+
- Caching venvs across runs
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "setuptools-scm"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools_scm]
|
|
6
|
+
local_scheme = "dirty-tag"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "ripple-effect"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = "Know your blast radius before you push"
|
|
12
|
+
readme = "README.rst"
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
authors = [
|
|
17
|
+
{name = "Zoran Simic", email = "zoran@simicweb.com"},
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Environment :: Console",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
|
+
"Topic :: Software Development :: Testing",
|
|
32
|
+
"Topic :: Utilities",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"click",
|
|
36
|
+
"PyYAML",
|
|
37
|
+
"runez",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
test = [
|
|
42
|
+
"pytest-cov",
|
|
43
|
+
]
|
|
44
|
+
dev = [
|
|
45
|
+
{ include-group = "test" },
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
ripple-effect = "ripple_effect:main"
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Source = "https://github.com/zsimic/ripple-effect"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
[tool.coverage.run]
|
|
56
|
+
data_file = ".tox/.coverage"
|
|
57
|
+
source = ["src"]
|
|
58
|
+
patch = ["subprocess"]
|
|
59
|
+
sigterm = true
|
|
60
|
+
|
|
61
|
+
[tool.coverage.xml]
|
|
62
|
+
output = ".tox/test-reports/coverage.xml"
|
|
63
|
+
|
|
64
|
+
[tool.coverage.html]
|
|
65
|
+
directory = ".tox/test-reports/htmlcov"
|
|
66
|
+
|
|
67
|
+
[tool.pytest]
|
|
68
|
+
cache_dir = ".tox/pytest_cache"
|
|
69
|
+
testpaths = ["tests"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
cache-dir = ".tox/.ruff_cache"
|
|
73
|
+
line-length = 140
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint]
|
|
76
|
+
extend-select = [
|
|
77
|
+
"A", # flake8-builtins
|
|
78
|
+
"B", # flake8-bugbear
|
|
79
|
+
"C4", # flake8-comprehensions
|
|
80
|
+
"C90", # mccabe
|
|
81
|
+
"DTZ", # flake8-datetimez
|
|
82
|
+
"E", # pycodestyle errors
|
|
83
|
+
"ERA", # eradicate
|
|
84
|
+
"F", # pyflakes
|
|
85
|
+
"FLY", # flynt
|
|
86
|
+
"G", # flake8-logging-format
|
|
87
|
+
"I", # isort
|
|
88
|
+
"INT", # flake8-gettext
|
|
89
|
+
"PGH", # pygrep-hooks
|
|
90
|
+
"PIE", # flake8-pie
|
|
91
|
+
"PT", # flake8-pytest
|
|
92
|
+
"PYI", # flake8-pyi
|
|
93
|
+
"Q", # flake8-quotes
|
|
94
|
+
"RSE", # flake8-raise
|
|
95
|
+
"RUF", # ruff-specific
|
|
96
|
+
"S", # flake8-bandit
|
|
97
|
+
"SIM", # flake8-simplify
|
|
98
|
+
"SLOT", # flake8-slots
|
|
99
|
+
"T10", # flake8-debugger
|
|
100
|
+
"TID", # flake8-tidy-imports
|
|
101
|
+
"TCH", # flake8-type-checking
|
|
102
|
+
"TD", # flake8-todos
|
|
103
|
+
"W", # pycodestyle warnings
|
|
104
|
+
]
|
|
105
|
+
[tool.ruff.lint.per-file-ignores]
|
|
106
|
+
"tests/**" = ["S101"]
|
|
107
|
+
|
|
108
|
+
[tool.ruff.lint.isort]
|
|
109
|
+
order-by-type = false
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint.mccabe]
|
|
112
|
+
max-complexity = 19
|
|
113
|
+
|
|
114
|
+
[tool.ruff.lint.pydocstyle]
|
|
115
|
+
convention = "numpy"
|
|
116
|
+
|
|
117
|
+
[tool.tox]
|
|
118
|
+
env_list = ["py310", "py314", "coverage", "style", "typecheck", "readme"]
|
|
119
|
+
|
|
120
|
+
[tool.tox.env_run_base]
|
|
121
|
+
use_develop = true
|
|
122
|
+
dependency_groups = ["test"]
|
|
123
|
+
commands = [
|
|
124
|
+
["pytest", "-vv", "--cov=src/ripple_effect", "tests"]
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[tool.tox.env_run_base.set_env]
|
|
128
|
+
COVERAGE_FILE = "{toxworkdir}/.coverage.{envname}"
|
|
129
|
+
PYTHONDONTWRITEBYTECODE = "1"
|
|
130
|
+
|
|
131
|
+
[tool.tox.env.coverage]
|
|
132
|
+
skip_install = true
|
|
133
|
+
deps = ["coverage"]
|
|
134
|
+
set_env = { COVERAGE_FILE = "{toxworkdir}/.coverage" }
|
|
135
|
+
commands = [
|
|
136
|
+
["coverage", "combine"],
|
|
137
|
+
["coverage", "report", "-m"],
|
|
138
|
+
["coverage", "xml"],
|
|
139
|
+
["coverage", "html"]
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[tool.tox.env.style]
|
|
143
|
+
skip_install = true
|
|
144
|
+
deps = ["ruff"]
|
|
145
|
+
commands = [
|
|
146
|
+
["ruff", "check"],
|
|
147
|
+
["ruff", "format", "--diff"]
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
[tool.tox.env.typecheck]
|
|
151
|
+
use_develop = true
|
|
152
|
+
deps = ["pyright"]
|
|
153
|
+
commands = [["pyright"]]
|
|
154
|
+
|
|
155
|
+
[tool.tox.env.readme]
|
|
156
|
+
skip_install = true
|
|
157
|
+
deps = ["uv", "twine"]
|
|
158
|
+
commands = [
|
|
159
|
+
["uv", "build", "-q", "-o", "{env_dir}/dist"],
|
|
160
|
+
["twine", "check", "{env_dir}/dist/*"]
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
[tool.tox.env.reformat]
|
|
164
|
+
skip_install = true
|
|
165
|
+
deps = ["ruff"]
|
|
166
|
+
commands = [
|
|
167
|
+
["ruff", "check", "--fix"],
|
|
168
|
+
["ruff", "format"]
|
|
169
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""CLI entry point for ripple-effect."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.argument("config", type=click.Path(exists=True, dir_okay=False))
|
|
8
|
+
@click.option("-n", "--dryrun", is_flag=True, help="Show what would be done without doing it")
|
|
9
|
+
@click.option("-v", "--verbose", is_flag=True, help="Show more output")
|
|
10
|
+
def main(config: str, dryrun: bool, verbose: bool) -> None:
|
|
11
|
+
"""Test a Python library against its downstream dependents.
|
|
12
|
+
|
|
13
|
+
CONFIG is a YAML file describing the library under test and the downstream
|
|
14
|
+
projects to run against it.
|
|
15
|
+
"""
|
|
16
|
+
click.echo("ripple-effect: not yet implemented")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ripple-effect
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Know your blast radius before you push
|
|
5
|
+
Author-email: Zoran Simic <zoran@simicweb.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/zsimic/ripple-effect
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/x-rst
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: click
|
|
25
|
+
Requires-Dist: PyYAML
|
|
26
|
+
Requires-Dist: runez
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
ripple-effect
|
|
30
|
+
=============
|
|
31
|
+
|
|
32
|
+
Know your blast radius before you push.
|
|
33
|
+
|
|
34
|
+
Test a Python library against the downstream projects that depend on it —
|
|
35
|
+
before you tag a release and find out the hard way.
|
|
36
|
+
|
|
37
|
+
Installation
|
|
38
|
+
------------
|
|
39
|
+
|
|
40
|
+
.. code-block:: bash
|
|
41
|
+
|
|
42
|
+
uvx ripple-effect config.yml
|
|
43
|
+
|
|
44
|
+
Usage
|
|
45
|
+
-----
|
|
46
|
+
|
|
47
|
+
Create a YAML config describing your library and the downstream projects to test against:
|
|
48
|
+
|
|
49
|
+
.. code-block:: yaml
|
|
50
|
+
|
|
51
|
+
proving-grounds: /tmp/ripple-effect-workspace
|
|
52
|
+
downstream-projects:
|
|
53
|
+
- git+https://github.com/example/project-b.git@main
|
|
54
|
+
- git+https://github.com/example/project-c.git@main
|
|
55
|
+
|
|
56
|
+
Then run:
|
|
57
|
+
|
|
58
|
+
.. code-block:: bash
|
|
59
|
+
|
|
60
|
+
ripple-effect config.yml
|
|
61
|
+
|
|
62
|
+
ripple-effect will:
|
|
63
|
+
|
|
64
|
+
1. Clone (or update) each downstream project into ``proving-grounds``
|
|
65
|
+
2. Create a venv for each, with the downstream project + your library installed in editable mode
|
|
66
|
+
3. Run each project's test suite
|
|
67
|
+
4. Report which passed, which failed
|
|
68
|
+
|
|
69
|
+
For local iteration (e.g. testing against a local checkout of a downstream project):
|
|
70
|
+
|
|
71
|
+
.. code-block:: yaml
|
|
72
|
+
|
|
73
|
+
proving-grounds: ~/dev/my-ripple-workspace
|
|
74
|
+
downstream-projects:
|
|
75
|
+
- git+https://github.com/example/project-b.git@main
|
|
76
|
+
- ~/dev/project-c
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
DEVELOP.md
|
|
3
|
+
LICENSE
|
|
4
|
+
README.rst
|
|
5
|
+
SPEC.md
|
|
6
|
+
pyproject.toml
|
|
7
|
+
.github/workflows/release.yml
|
|
8
|
+
.github/workflows/tests.yml
|
|
9
|
+
src/ripple_effect/__init__.py
|
|
10
|
+
src/ripple_effect/cli.py
|
|
11
|
+
src/ripple_effect.egg-info/PKG-INFO
|
|
12
|
+
src/ripple_effect.egg-info/SOURCES.txt
|
|
13
|
+
src/ripple_effect.egg-info/dependency_links.txt
|
|
14
|
+
src/ripple_effect.egg-info/entry_points.txt
|
|
15
|
+
src/ripple_effect.egg-info/requires.txt
|
|
16
|
+
src/ripple_effect.egg-info/top_level.txt
|
|
17
|
+
tests/conftest.py
|
|
18
|
+
tests/test_cli.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ripple_effect
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import runez
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_help(cli):
|
|
5
|
+
cli.run("--help")
|
|
6
|
+
assert cli.succeeded
|
|
7
|
+
assert "downstream" in cli.logged.stdout
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_missing_config(cli, temp_folder):
|
|
11
|
+
cli.run("no-such-config.yml")
|
|
12
|
+
assert cli.failed
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_sample_config(cli, temp_folder):
|
|
16
|
+
runez.write("sample.yml", "# placeholder\n", logger=None)
|
|
17
|
+
cli.run("sample.yml")
|
|
18
|
+
assert cli.succeeded
|
|
19
|
+
assert "not yet implemented" in cli.logged.stdout
|