regscope 0.1.0__py3-none-any.whl
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/__init__.py +6 -0
- regscope/__main__.py +5 -0
- regscope/api/__init__.py +6 -0
- regscope/api/collectors.py +50 -0
- regscope/api/config.py +27 -0
- regscope/api/decorators.py +227 -0
- regscope/cli/__init__.py +5 -0
- regscope/cli/main.py +80 -0
- regscope/cli/trend.py +22 -0
- regscope/collectors/__init__.py +14 -0
- regscope/collectors/call_graph.py +93 -0
- regscope/collectors/http.py +76 -0
- regscope/collectors/memory.py +41 -0
- regscope/collectors/redis.py +76 -0
- regscope/collectors/sqlalchemy.py +57 -0
- regscope/core/__init__.py +1 -0
- regscope/core/comparison.py +99 -0
- regscope/core/runtime.py +49 -0
- regscope/errors.py +9 -0
- regscope/models.py +112 -0
- regscope/py.typed +0 -0
- regscope/pytest_plugin.py +74 -0
- regscope/storage/__init__.py +5 -0
- regscope/storage/json_store.py +121 -0
- regscope/trends/__init__.py +5 -0
- regscope/trends/history.py +137 -0
- regscope-0.1.0.dist-info/METADATA +214 -0
- regscope-0.1.0.dist-info/RECORD +31 -0
- regscope-0.1.0.dist-info/WHEEL +5 -0
- regscope-0.1.0.dist-info/entry_points.txt +5 -0
- regscope-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Append-only historical trend records for observed profiles."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from statistics import median
|
|
11
|
+
from typing import List, Optional, Union
|
|
12
|
+
|
|
13
|
+
from ..models import BehaviorProfile, SCHEMA_VERSION
|
|
14
|
+
from ..errors import MalformedRecordError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class TrendPoint:
|
|
19
|
+
function: str
|
|
20
|
+
recorded_at: str
|
|
21
|
+
duration_ns: int
|
|
22
|
+
call_count: int
|
|
23
|
+
exceptions: int
|
|
24
|
+
fingerprint: str
|
|
25
|
+
schema_version: int = 1
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_profile(
|
|
29
|
+
cls, profile: BehaviorProfile, recorded_at: Optional[str] = None
|
|
30
|
+
) -> "TrendPoint":
|
|
31
|
+
timestamp = recorded_at or datetime.now(timezone.utc).isoformat()
|
|
32
|
+
return cls(
|
|
33
|
+
function=profile.function,
|
|
34
|
+
recorded_at=timestamp,
|
|
35
|
+
duration_ns=profile.duration_ns,
|
|
36
|
+
call_count=profile.call_count,
|
|
37
|
+
exceptions=profile.exceptions,
|
|
38
|
+
fingerprint=profile.fingerprint(),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def to_json(self) -> str:
|
|
42
|
+
return json.dumps(self.__dict__, sort_keys=True, separators=(",", ":"))
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_json(cls, value: str) -> "TrendPoint":
|
|
46
|
+
data = json.loads(value)
|
|
47
|
+
data.setdefault("schema_version", 1)
|
|
48
|
+
if data["schema_version"] > SCHEMA_VERSION:
|
|
49
|
+
raise ValueError(f"unsupported history schema version {data['schema_version']}")
|
|
50
|
+
return cls(**data)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class TrendSummary:
|
|
55
|
+
function: str
|
|
56
|
+
samples: int
|
|
57
|
+
duration_min_ns: int
|
|
58
|
+
duration_median_ns: int
|
|
59
|
+
duration_max_ns: int
|
|
60
|
+
duration_first_ns: int
|
|
61
|
+
duration_latest_ns: int
|
|
62
|
+
duration_delta_ns: int
|
|
63
|
+
duration_change_ratio: float
|
|
64
|
+
exception_samples: int
|
|
65
|
+
exception_rate: float
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class HistoryStore:
|
|
69
|
+
"""Store append-only trend points in one JSONL file per function."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, directory: Union[str, Path], max_points: Optional[int] = None) -> None:
|
|
72
|
+
if max_points is not None and max_points < 1:
|
|
73
|
+
raise ValueError("max_points must be at least 1")
|
|
74
|
+
self.directory = Path(directory)
|
|
75
|
+
self.max_points = max_points
|
|
76
|
+
|
|
77
|
+
def path_for(self, function: str) -> Path:
|
|
78
|
+
digest = hashlib.sha256(function.encode("utf-8")).hexdigest()[:16]
|
|
79
|
+
return self.directory / f"{digest}.jsonl"
|
|
80
|
+
|
|
81
|
+
def record(self, profile: BehaviorProfile, recorded_at: Optional[str] = None) -> TrendPoint:
|
|
82
|
+
self.directory.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
point = TrendPoint.from_profile(profile, recorded_at=recorded_at)
|
|
84
|
+
points = self.load(profile.function)
|
|
85
|
+
points.append(point)
|
|
86
|
+
if self.max_points is not None:
|
|
87
|
+
points = points[-self.max_points :]
|
|
88
|
+
temporary = self.path_for(profile.function).with_suffix(".tmp")
|
|
89
|
+
temporary.write_text(
|
|
90
|
+
"".join(item.to_json() + "\n" for item in points), encoding="utf-8"
|
|
91
|
+
)
|
|
92
|
+
temporary.replace(self.path_for(profile.function))
|
|
93
|
+
return point
|
|
94
|
+
|
|
95
|
+
def load(self, function: str) -> List[TrendPoint]:
|
|
96
|
+
path = self.path_for(function)
|
|
97
|
+
if not path.exists():
|
|
98
|
+
return []
|
|
99
|
+
try:
|
|
100
|
+
return [
|
|
101
|
+
TrendPoint.from_json(line)
|
|
102
|
+
for line in path.read_text(encoding="utf-8").splitlines()
|
|
103
|
+
if line.strip()
|
|
104
|
+
]
|
|
105
|
+
except (OSError, TypeError, ValueError, KeyError) as error:
|
|
106
|
+
raise MalformedRecordError(
|
|
107
|
+
f"invalid history record at {path}: {error}"
|
|
108
|
+
) from error
|
|
109
|
+
|
|
110
|
+
def summarize(self, function: str) -> TrendSummary:
|
|
111
|
+
points = self.load(function)
|
|
112
|
+
if not points:
|
|
113
|
+
raise ValueError("no historical samples for function")
|
|
114
|
+
durations = [point.duration_ns for point in points]
|
|
115
|
+
first_duration = durations[0]
|
|
116
|
+
latest_duration = durations[-1]
|
|
117
|
+
change_ratio = (
|
|
118
|
+
0.0
|
|
119
|
+
if first_duration == 0 and latest_duration == 0
|
|
120
|
+
else float("inf")
|
|
121
|
+
if first_duration == 0
|
|
122
|
+
else (latest_duration - first_duration) / first_duration
|
|
123
|
+
)
|
|
124
|
+
exception_samples = sum(point.exceptions for point in points)
|
|
125
|
+
return TrendSummary(
|
|
126
|
+
function=function,
|
|
127
|
+
samples=len(points),
|
|
128
|
+
duration_min_ns=min(durations),
|
|
129
|
+
duration_median_ns=int(median(durations)),
|
|
130
|
+
duration_max_ns=max(durations),
|
|
131
|
+
duration_first_ns=first_duration,
|
|
132
|
+
duration_latest_ns=latest_duration,
|
|
133
|
+
duration_delta_ns=latest_duration - first_duration,
|
|
134
|
+
duration_change_ratio=change_ratio,
|
|
135
|
+
exception_samples=exception_samples,
|
|
136
|
+
exception_rate=exception_samples / len(points),
|
|
137
|
+
)
|
|
@@ -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.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
regscope/__init__.py,sha256=yTEyGGXnVqKFz3ayzr9JMXaHPKUNc6Is2ktDM3Pth78,182
|
|
2
|
+
regscope/__main__.py,sha256=PSQ4rpL0dG6f-qH4N7H-gD9igQkdHzH4yVZDcW8lfZo,80
|
|
3
|
+
regscope/errors.py,sha256=QPc5aPyH6Ol3zyMuw4uUkPmoK61Yc-p-zsXt1_7xk7k,256
|
|
4
|
+
regscope/models.py,sha256=D6auI1c2x84cAFDkVjEY-rgwgnBV-kQX5rq07GzrKKw,3817
|
|
5
|
+
regscope/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
regscope/pytest_plugin.py,sha256=PKo4HS4VCtr6fxOVu4zcffi76V7yS65E1wULqtKcSew,2828
|
|
7
|
+
regscope/api/__init__.py,sha256=J_YBGRQots7HJefLK9fluSWIQ7iTm981MKq8SVqEhqs,127
|
|
8
|
+
regscope/api/collectors.py,sha256=URtYDr7nBPwlR9yAyiOCT1WdfnfatwDvpC2D4mruiME,1874
|
|
9
|
+
regscope/api/config.py,sha256=4t08tMDqnZvUA7acfsorv3rZVc9NAK-a9_2b2ftkTR0,828
|
|
10
|
+
regscope/api/decorators.py,sha256=wi0YrDHEQajqy2qUKn4YsGYKlu_q5dxdA-bubk-c7cw,8483
|
|
11
|
+
regscope/cli/__init__.py,sha256=wB9zjByokbjGKlUiJlvjfxD4EExlt4fPUbmgcvF9Shk,87
|
|
12
|
+
regscope/cli/main.py,sha256=NnFi0MUmuHmt0GEMPfhWYFVOH8eSILVR2EwONWsK9Lk,3307
|
|
13
|
+
regscope/cli/trend.py,sha256=9RGoWP8LORwk2pm2XBR_fN4izrBPWgoO8Hq7fDcEmEQ,835
|
|
14
|
+
regscope/collectors/__init__.py,sha256=l6dtxGUTWpXLOCM_5HKf1a7AZkhrpNV2oVPMMR35d8Q,336
|
|
15
|
+
regscope/collectors/call_graph.py,sha256=DZYT9oXDeHVqnogAM8l0wlQhygO52DAU8C0sDWpKiwQ,3225
|
|
16
|
+
regscope/collectors/http.py,sha256=fHLeV3aLycmpvI2MaJ7BhtkePGKftl-jRbuTJOyPWtQ,2608
|
|
17
|
+
regscope/collectors/memory.py,sha256=AKVu8gtBl2cTDwEd5Nz_h-usZGDMHMUlr1S8--afaZs,1183
|
|
18
|
+
regscope/collectors/redis.py,sha256=SspuaeP-nUjDEi6ViWQOBGtP5837-qFqDwI_6r0zoas,2579
|
|
19
|
+
regscope/collectors/sqlalchemy.py,sha256=lKWC6SdIA5nLNamXq4PzkAhypD9ErxBRjrjfoQdVhx8,1811
|
|
20
|
+
regscope/core/__init__.py,sha256=Y85Zqm_sVCvOk-hFcNIUvpiEou12Q5PBkKyfCnTjWx8,48
|
|
21
|
+
regscope/core/comparison.py,sha256=HnCefJIdY1TQjgRMYIoRU_v6BmAOJDOMwdRoXE6QFKM,3234
|
|
22
|
+
regscope/core/runtime.py,sha256=zBLv8zM58TNpjUKySFcFG4YrbiWml--V2zm2rgWWZD8,1495
|
|
23
|
+
regscope/storage/__init__.py,sha256=xI7bMZh2vC-tDarYzXhLsG2yGShW1omDLO7a-z8geBk,127
|
|
24
|
+
regscope/storage/json_store.py,sha256=g1QoSlxyltvcC5W0ttEtxUoFPY0FT7rKX_-LsDC9LZ8,3969
|
|
25
|
+
regscope/trends/__init__.py,sha256=3POzv_DpGyB8bzFftIYb5jPKZPSmthy6hjqkWxyVY5o,174
|
|
26
|
+
regscope/trends/history.py,sha256=sRXnTYGmLTt095XQrtaSfZSrbntkl9D2oWD6Z2s260s,4797
|
|
27
|
+
regscope-0.1.0.dist-info/METADATA,sha256=kbxyBZ4fqTHj8_CMp9nu8urnfI-3TEDARLMDjULQdaU,8416
|
|
28
|
+
regscope-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
29
|
+
regscope-0.1.0.dist-info/entry_points.txt,sha256=V9KbhoDNLgfrZ_oEjPFJWK2QWH9kCqcGDjB-mcMZ0SE,93
|
|
30
|
+
regscope-0.1.0.dist-info/top_level.txt,sha256=5IbDjUFWa_h70lGCByl23GIS1C5Y1CExkqWsKtIDZxo,9
|
|
31
|
+
regscope-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
regscope
|