regscope 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.
- regscope-0.1.0/PKG-INFO +214 -0
- regscope-0.1.0/README.md +179 -0
- regscope-0.1.0/pyproject.toml +53 -0
- regscope-0.1.0/regscope/__init__.py +6 -0
- regscope-0.1.0/regscope/__main__.py +5 -0
- regscope-0.1.0/regscope/api/__init__.py +6 -0
- regscope-0.1.0/regscope/api/collectors.py +50 -0
- regscope-0.1.0/regscope/api/config.py +27 -0
- regscope-0.1.0/regscope/api/decorators.py +227 -0
- regscope-0.1.0/regscope/cli/__init__.py +5 -0
- regscope-0.1.0/regscope/cli/main.py +80 -0
- regscope-0.1.0/regscope/cli/trend.py +22 -0
- regscope-0.1.0/regscope/collectors/__init__.py +14 -0
- regscope-0.1.0/regscope/collectors/call_graph.py +93 -0
- regscope-0.1.0/regscope/collectors/http.py +76 -0
- regscope-0.1.0/regscope/collectors/memory.py +41 -0
- regscope-0.1.0/regscope/collectors/redis.py +76 -0
- regscope-0.1.0/regscope/collectors/sqlalchemy.py +57 -0
- regscope-0.1.0/regscope/core/__init__.py +1 -0
- regscope-0.1.0/regscope/core/comparison.py +99 -0
- regscope-0.1.0/regscope/core/runtime.py +49 -0
- regscope-0.1.0/regscope/errors.py +9 -0
- regscope-0.1.0/regscope/models.py +112 -0
- regscope-0.1.0/regscope/py.typed +0 -0
- regscope-0.1.0/regscope/pytest_plugin.py +74 -0
- regscope-0.1.0/regscope/storage/__init__.py +5 -0
- regscope-0.1.0/regscope/storage/json_store.py +121 -0
- regscope-0.1.0/regscope/trends/__init__.py +5 -0
- regscope-0.1.0/regscope/trends/history.py +137 -0
- regscope-0.1.0/regscope.egg-info/PKG-INFO +214 -0
- regscope-0.1.0/regscope.egg-info/SOURCES.txt +51 -0
- regscope-0.1.0/regscope.egg-info/dependency_links.txt +1 -0
- regscope-0.1.0/regscope.egg-info/entry_points.txt +5 -0
- regscope-0.1.0/regscope.egg-info/requires.txt +18 -0
- regscope-0.1.0/regscope.egg-info/top_level.txt +1 -0
- regscope-0.1.0/setup.cfg +4 -0
- regscope-0.1.0/tests/test_async_tracking.py +52 -0
- regscope-0.1.0/tests/test_call_graph.py +127 -0
- regscope-0.1.0/tests/test_ci_baseline.py +8 -0
- regscope-0.1.0/tests/test_cli.py +65 -0
- regscope-0.1.0/tests/test_comparison.py +69 -0
- regscope-0.1.0/tests/test_decorators.py +150 -0
- regscope-0.1.0/tests/test_history.py +89 -0
- regscope-0.1.0/tests/test_http_collector.py +17 -0
- regscope-0.1.0/tests/test_memory_collector.py +25 -0
- regscope-0.1.0/tests/test_models.py +75 -0
- regscope-0.1.0/tests/test_optional_integrations.py +182 -0
- regscope-0.1.0/tests/test_package.py +5 -0
- regscope-0.1.0/tests/test_pytest_plugin.py +95 -0
- regscope-0.1.0/tests/test_redis_collector.py +17 -0
- regscope-0.1.0/tests/test_runtime.py +30 -0
- regscope-0.1.0/tests/test_sqlalchemy_collector.py +55 -0
- regscope-0.1.0/tests/test_storage.py +98 -0
regscope-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: regscope
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Observed behavioral diffing for Python functions
|
|
5
|
+
Author: RegScope contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AdeelMalik22/regscope
|
|
8
|
+
Project-URL: Repository, https://github.com/AdeelMalik22/regscope
|
|
9
|
+
Project-URL: Issues, https://github.com/AdeelMalik22/regscope/issues
|
|
10
|
+
Keywords: profiling,regression-testing,performance,observability
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Provides-Extra: db
|
|
26
|
+
Requires-Dist: SQLAlchemy>=1.4; extra == "db"
|
|
27
|
+
Provides-Extra: http
|
|
28
|
+
Requires-Dist: requests>=2.28; extra == "http"
|
|
29
|
+
Provides-Extra: redis
|
|
30
|
+
Requires-Dist: redis>=4.0; extra == "redis"
|
|
31
|
+
Provides-Extra: integration
|
|
32
|
+
Requires-Dist: SQLAlchemy>=1.4; extra == "integration"
|
|
33
|
+
Requires-Dist: requests>=2.28; extra == "integration"
|
|
34
|
+
Requires-Dist: redis>=4.0; extra == "integration"
|
|
35
|
+
|
|
36
|
+
# RegScope
|
|
37
|
+
|
|
38
|
+
RegScope is a behavioral diffing library for Python functions. It records what
|
|
39
|
+
a function did during a controlled test run so behavior can be compared across
|
|
40
|
+
code versions.
|
|
41
|
+
|
|
42
|
+
RegScope does not prove that two functions are mathematically equivalent. It
|
|
43
|
+
reports differences in behavior observed during the runs that were recorded.
|
|
44
|
+
|
|
45
|
+
## In plain language
|
|
46
|
+
|
|
47
|
+
RegScope is like a before-and-after checkup for a Python function. You place
|
|
48
|
+
`@track` above a function, run your normal tests, and RegScope quietly records
|
|
49
|
+
useful facts about that run: how long the function took, whether it failed,
|
|
50
|
+
which Python functions it called, and—when configured—how many database,
|
|
51
|
+
HTTP, or Redis operations it made.
|
|
52
|
+
|
|
53
|
+
When the function runs again after a code change, RegScope compares the new
|
|
54
|
+
checkup with earlier runs. It can warn you that a function became slower,
|
|
55
|
+
started raising errors, made more database queries, or used more memory. In
|
|
56
|
+
CI, that warning can fail the build so a performance or behavior regression is
|
|
57
|
+
noticed before the change is released.
|
|
58
|
+
|
|
59
|
+
It does not change what your function returns, and it does not decide whether
|
|
60
|
+
two implementations are mathematically identical. It compares what happened
|
|
61
|
+
during the test cases you actually ran. It also does not save function
|
|
62
|
+
arguments, return values, SQL text, URLs, request bodies, Redis keys, or Redis
|
|
63
|
+
values.
|
|
64
|
+
|
|
65
|
+
## Current status
|
|
66
|
+
|
|
67
|
+
The early v0.1 core supports:
|
|
68
|
+
|
|
69
|
+
- Synchronous and asynchronous `@track` decoration
|
|
70
|
+
- Execution duration measurement
|
|
71
|
+
- Exception counting while preserving the original exception
|
|
72
|
+
- Flat call-graph counts using `sys.setprofile()`
|
|
73
|
+
- Structured JSON behavior profiles
|
|
74
|
+
- N-run JSON baselines with atomic file replacement
|
|
75
|
+
- Configurable baseline storage through `baseline_dir`
|
|
76
|
+
- Zero required runtime dependencies
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from regscope import track
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@track
|
|
85
|
+
def calculate_total(values: list[int]) -> int:
|
|
86
|
+
return sum(values)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
total = calculate_total([1, 2, 3])
|
|
90
|
+
profile = calculate_total.last_profile
|
|
91
|
+
print(total)
|
|
92
|
+
print(profile.duration_ns)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `warmup_runs=N` to execute and report the first N calls without adding
|
|
96
|
+
them to the baseline or historical trend. This is useful when the first call
|
|
97
|
+
opens connections, imports modules, or initializes caches:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
@track(baseline_dir=".regscope", warmup_runs=1)
|
|
101
|
+
def load_dashboard() -> int:
|
|
102
|
+
return 42
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The decorator supports both `@track` and `@track(...)`. Async functions are
|
|
106
|
+
supported transparently:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from regscope import track
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@track
|
|
113
|
+
async def fetch_value() -> int:
|
|
114
|
+
return 42
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
After a call, `function.get_current_profile()` and
|
|
118
|
+
`function.get_current_comparison()` return values stored in the current
|
|
119
|
+
`contextvars` context. These accessors are task-safe for concurrent async
|
|
120
|
+
invocations. The legacy `function.last_profile` and
|
|
121
|
+
`function.last_comparison` attributes remain available as compatibility
|
|
122
|
+
snapshots, but can be overwritten by another concurrent invocation.
|
|
123
|
+
|
|
124
|
+
Each tracked function writes a bounded JSON baseline to the configured
|
|
125
|
+
directory. The default directory is `.regscope`. Profiles contain structured
|
|
126
|
+
metrics and call counts; they do not capture function arguments or sensitive
|
|
127
|
+
external data.
|
|
128
|
+
|
|
129
|
+
## CI regression checks
|
|
130
|
+
|
|
131
|
+
The repository workflow keeps trusted baselines outside Git. A push to
|
|
132
|
+
`master` runs `tests/ci_targets`, records the baseline in `.regscope`, and
|
|
133
|
+
uploads it as the `regscope-baseline-master` artifact. Pull-request jobs
|
|
134
|
+
download the latest successful artifact from `master` and compare the same
|
|
135
|
+
targets against it. A comparison that exceeds the configured threshold fails
|
|
136
|
+
the job.
|
|
137
|
+
|
|
138
|
+
Baseline artifacts are retained for 30 days. The workflow includes hidden
|
|
139
|
+
files when uploading because `.regscope` is a dot-directory. The baseline
|
|
140
|
+
publisher is restricted to trusted `master` pushes; pull requests cannot
|
|
141
|
+
replace the trusted artifact, including pull requests from forks.
|
|
142
|
+
|
|
143
|
+
To reproduce the comparison locally, first generate a trusted baseline and
|
|
144
|
+
then run the targets without the update flag:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
REGSCOPE_CI_BASELINE=1 REGSCOPE_TRUSTED_BASELINE=1 \
|
|
148
|
+
.venv/bin/python -m pytest tests/ci_targets \
|
|
149
|
+
--regscope-baseline-dir .regscope --regscope-update-baseline
|
|
150
|
+
|
|
151
|
+
REGSCOPE_CI_BASELINE=1 .venv/bin/python -m pytest tests/ci_targets \
|
|
152
|
+
--regscope-baseline-dir .regscope
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If no artifact has been published yet, the pull-request job reports that the
|
|
156
|
+
trusted baseline is unavailable and the comparison target fails rather than
|
|
157
|
+
silently treating the missing baseline as a pass.
|
|
158
|
+
|
|
159
|
+
## Limitations
|
|
160
|
+
|
|
161
|
+
- `sys.setprofile()` has one active profiler per current thread. RegScope
|
|
162
|
+
restores the profiler observed at entry, and independent threads have
|
|
163
|
+
independent collection contexts. RegScope does not arbitrate nested owners
|
|
164
|
+
in one thread or an external profiler that replaces its hook during an
|
|
165
|
+
execution. Run tracked profiling in an isolated test context when coverage,
|
|
166
|
+
a debugger, or another profiler must remain active.
|
|
167
|
+
- HTTP and Redis collectors temporarily replace process-global library hooks.
|
|
168
|
+
They restore the hook that was present when attached, are thread-safe for
|
|
169
|
+
counting calls, and leave a newer hook installed by another tool untouched.
|
|
170
|
+
Only one RegScope collector may own each library hook at a time; a
|
|
171
|
+
conflicting attachment fails clearly instead of stacking wrappers.
|
|
172
|
+
- Profiles describe observed executions, not all possible behavior.
|
|
173
|
+
- The CI artifact workflow is validated on trusted `master` runs; a real
|
|
174
|
+
pull-request event is still required to exercise GitHub's fork permissions
|
|
175
|
+
and artifact-download path end to end.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
Install development dependencies and run the tests:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
.venv/bin/python -m pip install ".[dev]"
|
|
183
|
+
.venv/bin/python -m pytest
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The implementation roadmap is in [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md).
|
|
187
|
+
Release history is in [CHANGELOG.md](CHANGELOG.md), with the release policy in
|
|
188
|
+
[VERSIONING.md](VERSIONING.md).
|
|
189
|
+
Performance and threshold guidance is in [docs/performance.md](docs/performance.md).
|
|
190
|
+
|
|
191
|
+
## Privacy and measurements
|
|
192
|
+
|
|
193
|
+
Profiles contain aggregate timing, call, exception, collector, and memory
|
|
194
|
+
metrics. RegScope does not capture function arguments, return values, SQL
|
|
195
|
+
text, bound parameters, URLs, headers, request bodies, Redis keys, or Redis
|
|
196
|
+
values. The SHA-256 fingerprint is derived from the structured profile and is
|
|
197
|
+
not a substitute for the profile itself.
|
|
198
|
+
|
|
199
|
+
The core call profiler adds measurable overhead, especially for functions with
|
|
200
|
+
large call graphs. Run the local benchmark with:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
.venv/bin/python -m benchmarks.overhead
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Benchmark results depend on the machine and Python version. Use them to tune
|
|
207
|
+
thresholds for a project rather than treating the sample output as universal.
|
|
208
|
+
|
|
209
|
+
## Schema stability
|
|
210
|
+
|
|
211
|
+
Structured profiles, baselines, and historical trend points currently use
|
|
212
|
+
schema version `1`. Older records that omit a schema field are read as version
|
|
213
|
+
1. Records from a newer unsupported schema are rejected explicitly so they
|
|
214
|
+
cannot be silently misinterpreted.
|
regscope-0.1.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# RegScope
|
|
2
|
+
|
|
3
|
+
RegScope is a behavioral diffing library for Python functions. It records what
|
|
4
|
+
a function did during a controlled test run so behavior can be compared across
|
|
5
|
+
code versions.
|
|
6
|
+
|
|
7
|
+
RegScope does not prove that two functions are mathematically equivalent. It
|
|
8
|
+
reports differences in behavior observed during the runs that were recorded.
|
|
9
|
+
|
|
10
|
+
## In plain language
|
|
11
|
+
|
|
12
|
+
RegScope is like a before-and-after checkup for a Python function. You place
|
|
13
|
+
`@track` above a function, run your normal tests, and RegScope quietly records
|
|
14
|
+
useful facts about that run: how long the function took, whether it failed,
|
|
15
|
+
which Python functions it called, and—when configured—how many database,
|
|
16
|
+
HTTP, or Redis operations it made.
|
|
17
|
+
|
|
18
|
+
When the function runs again after a code change, RegScope compares the new
|
|
19
|
+
checkup with earlier runs. It can warn you that a function became slower,
|
|
20
|
+
started raising errors, made more database queries, or used more memory. In
|
|
21
|
+
CI, that warning can fail the build so a performance or behavior regression is
|
|
22
|
+
noticed before the change is released.
|
|
23
|
+
|
|
24
|
+
It does not change what your function returns, and it does not decide whether
|
|
25
|
+
two implementations are mathematically identical. It compares what happened
|
|
26
|
+
during the test cases you actually ran. It also does not save function
|
|
27
|
+
arguments, return values, SQL text, URLs, request bodies, Redis keys, or Redis
|
|
28
|
+
values.
|
|
29
|
+
|
|
30
|
+
## Current status
|
|
31
|
+
|
|
32
|
+
The early v0.1 core supports:
|
|
33
|
+
|
|
34
|
+
- Synchronous and asynchronous `@track` decoration
|
|
35
|
+
- Execution duration measurement
|
|
36
|
+
- Exception counting while preserving the original exception
|
|
37
|
+
- Flat call-graph counts using `sys.setprofile()`
|
|
38
|
+
- Structured JSON behavior profiles
|
|
39
|
+
- N-run JSON baselines with atomic file replacement
|
|
40
|
+
- Configurable baseline storage through `baseline_dir`
|
|
41
|
+
- Zero required runtime dependencies
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from regscope import track
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@track
|
|
50
|
+
def calculate_total(values: list[int]) -> int:
|
|
51
|
+
return sum(values)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
total = calculate_total([1, 2, 3])
|
|
55
|
+
profile = calculate_total.last_profile
|
|
56
|
+
print(total)
|
|
57
|
+
print(profile.duration_ns)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `warmup_runs=N` to execute and report the first N calls without adding
|
|
61
|
+
them to the baseline or historical trend. This is useful when the first call
|
|
62
|
+
opens connections, imports modules, or initializes caches:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
@track(baseline_dir=".regscope", warmup_runs=1)
|
|
66
|
+
def load_dashboard() -> int:
|
|
67
|
+
return 42
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The decorator supports both `@track` and `@track(...)`. Async functions are
|
|
71
|
+
supported transparently:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from regscope import track
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@track
|
|
78
|
+
async def fetch_value() -> int:
|
|
79
|
+
return 42
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
After a call, `function.get_current_profile()` and
|
|
83
|
+
`function.get_current_comparison()` return values stored in the current
|
|
84
|
+
`contextvars` context. These accessors are task-safe for concurrent async
|
|
85
|
+
invocations. The legacy `function.last_profile` and
|
|
86
|
+
`function.last_comparison` attributes remain available as compatibility
|
|
87
|
+
snapshots, but can be overwritten by another concurrent invocation.
|
|
88
|
+
|
|
89
|
+
Each tracked function writes a bounded JSON baseline to the configured
|
|
90
|
+
directory. The default directory is `.regscope`. Profiles contain structured
|
|
91
|
+
metrics and call counts; they do not capture function arguments or sensitive
|
|
92
|
+
external data.
|
|
93
|
+
|
|
94
|
+
## CI regression checks
|
|
95
|
+
|
|
96
|
+
The repository workflow keeps trusted baselines outside Git. A push to
|
|
97
|
+
`master` runs `tests/ci_targets`, records the baseline in `.regscope`, and
|
|
98
|
+
uploads it as the `regscope-baseline-master` artifact. Pull-request jobs
|
|
99
|
+
download the latest successful artifact from `master` and compare the same
|
|
100
|
+
targets against it. A comparison that exceeds the configured threshold fails
|
|
101
|
+
the job.
|
|
102
|
+
|
|
103
|
+
Baseline artifacts are retained for 30 days. The workflow includes hidden
|
|
104
|
+
files when uploading because `.regscope` is a dot-directory. The baseline
|
|
105
|
+
publisher is restricted to trusted `master` pushes; pull requests cannot
|
|
106
|
+
replace the trusted artifact, including pull requests from forks.
|
|
107
|
+
|
|
108
|
+
To reproduce the comparison locally, first generate a trusted baseline and
|
|
109
|
+
then run the targets without the update flag:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
REGSCOPE_CI_BASELINE=1 REGSCOPE_TRUSTED_BASELINE=1 \
|
|
113
|
+
.venv/bin/python -m pytest tests/ci_targets \
|
|
114
|
+
--regscope-baseline-dir .regscope --regscope-update-baseline
|
|
115
|
+
|
|
116
|
+
REGSCOPE_CI_BASELINE=1 .venv/bin/python -m pytest tests/ci_targets \
|
|
117
|
+
--regscope-baseline-dir .regscope
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If no artifact has been published yet, the pull-request job reports that the
|
|
121
|
+
trusted baseline is unavailable and the comparison target fails rather than
|
|
122
|
+
silently treating the missing baseline as a pass.
|
|
123
|
+
|
|
124
|
+
## Limitations
|
|
125
|
+
|
|
126
|
+
- `sys.setprofile()` has one active profiler per current thread. RegScope
|
|
127
|
+
restores the profiler observed at entry, and independent threads have
|
|
128
|
+
independent collection contexts. RegScope does not arbitrate nested owners
|
|
129
|
+
in one thread or an external profiler that replaces its hook during an
|
|
130
|
+
execution. Run tracked profiling in an isolated test context when coverage,
|
|
131
|
+
a debugger, or another profiler must remain active.
|
|
132
|
+
- HTTP and Redis collectors temporarily replace process-global library hooks.
|
|
133
|
+
They restore the hook that was present when attached, are thread-safe for
|
|
134
|
+
counting calls, and leave a newer hook installed by another tool untouched.
|
|
135
|
+
Only one RegScope collector may own each library hook at a time; a
|
|
136
|
+
conflicting attachment fails clearly instead of stacking wrappers.
|
|
137
|
+
- Profiles describe observed executions, not all possible behavior.
|
|
138
|
+
- The CI artifact workflow is validated on trusted `master` runs; a real
|
|
139
|
+
pull-request event is still required to exercise GitHub's fork permissions
|
|
140
|
+
and artifact-download path end to end.
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
Install development dependencies and run the tests:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
.venv/bin/python -m pip install ".[dev]"
|
|
148
|
+
.venv/bin/python -m pytest
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The implementation roadmap is in [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md).
|
|
152
|
+
Release history is in [CHANGELOG.md](CHANGELOG.md), with the release policy in
|
|
153
|
+
[VERSIONING.md](VERSIONING.md).
|
|
154
|
+
Performance and threshold guidance is in [docs/performance.md](docs/performance.md).
|
|
155
|
+
|
|
156
|
+
## Privacy and measurements
|
|
157
|
+
|
|
158
|
+
Profiles contain aggregate timing, call, exception, collector, and memory
|
|
159
|
+
metrics. RegScope does not capture function arguments, return values, SQL
|
|
160
|
+
text, bound parameters, URLs, headers, request bodies, Redis keys, or Redis
|
|
161
|
+
values. The SHA-256 fingerprint is derived from the structured profile and is
|
|
162
|
+
not a substitute for the profile itself.
|
|
163
|
+
|
|
164
|
+
The core call profiler adds measurable overhead, especially for functions with
|
|
165
|
+
large call graphs. Run the local benchmark with:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
.venv/bin/python -m benchmarks.overhead
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Benchmark results depend on the machine and Python version. Use them to tune
|
|
172
|
+
thresholds for a project rather than treating the sample output as universal.
|
|
173
|
+
|
|
174
|
+
## Schema stability
|
|
175
|
+
|
|
176
|
+
Structured profiles, baselines, and historical trend points currently use
|
|
177
|
+
schema version `1`. Older records that omit a schema field are read as version
|
|
178
|
+
1. Records from a newer unsupported schema are rejected explicitly so they
|
|
179
|
+
cannot be silently misinterpreted.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "regscope"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Observed behavioral diffing for Python functions"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "RegScope contributors" }]
|
|
13
|
+
dependencies = []
|
|
14
|
+
keywords = ["profiling", "regression-testing", "performance", "observability"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Testing",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/AdeelMalik22/regscope"
|
|
29
|
+
Repository = "https://github.com/AdeelMalik22/regscope"
|
|
30
|
+
Issues = "https://github.com/AdeelMalik22/regscope/issues"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
regscope = "regscope.cli:main"
|
|
34
|
+
|
|
35
|
+
[project.entry-points.pytest11]
|
|
36
|
+
regscope = "regscope.pytest_plugin"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = ["build", "pytest"]
|
|
40
|
+
db = ["SQLAlchemy>=1.4"]
|
|
41
|
+
http = ["requests>=2.28"]
|
|
42
|
+
redis = ["redis>=4.0"]
|
|
43
|
+
integration = ["SQLAlchemy>=1.4", "requests>=2.28", "redis>=4.0"]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
include = ["regscope*"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-data]
|
|
49
|
+
regscope = ["py.typed"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
addopts = ["-ra"]
|
|
53
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Optional collector lifecycle for tracked executions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contextlib import ExitStack, contextmanager
|
|
6
|
+
from typing import Any, Dict, Iterator
|
|
7
|
+
|
|
8
|
+
from ..collectors import HTTPCollector, MemoryCollector, RedisCollector, SQLAlchemyCollector
|
|
9
|
+
from .config import TrackConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@contextmanager
|
|
13
|
+
def active_collectors(config: TrackConfig, metrics: Dict[str, int]) -> Iterator[None]:
|
|
14
|
+
"""Attach configured collectors and publish their metrics on exit."""
|
|
15
|
+
with ExitStack() as stack:
|
|
16
|
+
if config.sqlalchemy_engine is not None:
|
|
17
|
+
collector = SQLAlchemyCollector()
|
|
18
|
+
collector.attach(config.sqlalchemy_engine)
|
|
19
|
+
stack.callback(collector.detach)
|
|
20
|
+
metrics["db_queries"] = 0
|
|
21
|
+
stack.callback(lambda: metrics.update(db_queries=collector.query_count))
|
|
22
|
+
|
|
23
|
+
if config.collect_http:
|
|
24
|
+
collector = HTTPCollector()
|
|
25
|
+
collector.attach()
|
|
26
|
+
stack.callback(collector.detach)
|
|
27
|
+
metrics["http_requests"] = 0
|
|
28
|
+
stack.callback(lambda: metrics.update(http_requests=collector.request_count))
|
|
29
|
+
|
|
30
|
+
if config.collect_redis:
|
|
31
|
+
collector = RedisCollector()
|
|
32
|
+
collector.attach()
|
|
33
|
+
stack.callback(collector.detach)
|
|
34
|
+
metrics["redis_commands"] = 0
|
|
35
|
+
stack.callback(lambda: metrics.update(redis_commands=collector.command_count))
|
|
36
|
+
|
|
37
|
+
memory = None
|
|
38
|
+
if config.collect_memory:
|
|
39
|
+
memory = MemoryCollector()
|
|
40
|
+
stack.callback(
|
|
41
|
+
lambda: metrics.update(
|
|
42
|
+
memory_current_delta=memory.last_profile.current_delta,
|
|
43
|
+
memory_peak=memory.last_profile.peak,
|
|
44
|
+
)
|
|
45
|
+
if memory.last_profile is not None
|
|
46
|
+
else None
|
|
47
|
+
)
|
|
48
|
+
stack.enter_context(memory.measure())
|
|
49
|
+
|
|
50
|
+
yield
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Validated public configuration for tracked functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from os import PathLike
|
|
7
|
+
from typing import Any, Union
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class TrackConfig:
|
|
12
|
+
"""Configuration shared by a tracked function's collectors and storage."""
|
|
13
|
+
|
|
14
|
+
baseline_dir: Union[PathLike[str], str] = ".regscope"
|
|
15
|
+
history_dir: Union[PathLike[str], str] = ".regscope"
|
|
16
|
+
max_runs: int = 5
|
|
17
|
+
warmup_runs: int = 0
|
|
18
|
+
sqlalchemy_engine: Any = None
|
|
19
|
+
collect_http: bool = False
|
|
20
|
+
collect_redis: bool = False
|
|
21
|
+
collect_memory: bool = False
|
|
22
|
+
|
|
23
|
+
def __post_init__(self) -> None:
|
|
24
|
+
if self.max_runs < 1:
|
|
25
|
+
raise ValueError("max_runs must be at least 1")
|
|
26
|
+
if self.warmup_runs < 0:
|
|
27
|
+
raise ValueError("warmup_runs must not be negative")
|