specshift 1.1.0__tar.gz → 1.2.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.
- {specshift-1.1.0/specshift.egg-info → specshift-1.2.0}/PKG-INFO +49 -5
- {specshift-1.1.0 → specshift-1.2.0}/README.md +46 -2
- {specshift-1.1.0 → specshift-1.2.0}/pyproject.toml +4 -2
- {specshift-1.1.0 → specshift-1.2.0}/specshift/__init__.py +4 -1
- {specshift-1.1.0 → specshift-1.2.0}/specshift/cli.py +33 -2
- specshift-1.2.0/specshift/protobuf_differ.py +622 -0
- specshift-1.2.0/specshift/webapp.py +335 -0
- {specshift-1.1.0 → specshift-1.2.0/specshift.egg-info}/PKG-INFO +49 -5
- {specshift-1.1.0 → specshift-1.2.0}/specshift.egg-info/SOURCES.txt +5 -1
- specshift-1.2.0/tests/test_protobuf_differ.py +168 -0
- specshift-1.2.0/tests/test_webapp.py +62 -0
- {specshift-1.1.0 → specshift-1.2.0}/LICENSE +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/setup.cfg +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/ai_summary.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/config.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/differ.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/git_utils.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/graphql_differ.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/models.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/notifier.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/reporter.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift/spec_loader.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift.egg-info/dependency_links.txt +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift.egg-info/entry_points.txt +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift.egg-info/requires.txt +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/specshift.egg-info/top_level.txt +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/tests/test_cli.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/tests/test_differ.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/tests/test_graphql_differ.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/tests/test_reporter.py +0 -0
- {specshift-1.1.0 → specshift-1.2.0}/tests/test_spec_loader.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specshift
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger and
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI
|
|
5
5
|
Author: Lethe044
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Lethe044/specshift
|
|
8
8
|
Project-URL: Repository, https://github.com/Lethe044/specshift
|
|
9
9
|
Project-URL: Issues, https://github.com/Lethe044/specshift/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/Lethe044/specshift/blob/main/CHANGELOG.md
|
|
11
|
-
Keywords: openapi,swagger,graphql,api,breaking-changes,contract-testing,api-diff,devtools,cli,ci-cd,github-actions
|
|
11
|
+
Keywords: openapi,swagger,graphql,grpc,protobuf,api,breaking-changes,contract-testing,api-diff,devtools,cli,ci-cd,github-actions
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -96,6 +96,10 @@ requirement.
|
|
|
96
96
|
- **GraphQL support**: diff GraphQL SDL schemas with `specshift diff-graphql`,
|
|
97
97
|
covering types, fields, arguments, enum values, interfaces, unions, and
|
|
98
98
|
deprecations, no external GraphQL library required.
|
|
99
|
+
- **Protobuf/gRPC support**: diff `.proto` files with `specshift diff-proto`,
|
|
100
|
+
using protobuf's own documented wire-compatibility rules (field numbers,
|
|
101
|
+
wire type categories, zigzag encoding) rather than generic JSON-shape
|
|
102
|
+
heuristics, so severity reflects what will actually break on the wire.
|
|
99
103
|
- **Context-aware classification**: the same change is weighted
|
|
100
104
|
differently depending on whether it occurs in a request or a response
|
|
101
105
|
(or, for GraphQL, an input type versus an object type).
|
|
@@ -116,6 +120,10 @@ requirement.
|
|
|
116
120
|
- **Live monitoring**: the `specshift watch` command periodically checks
|
|
117
121
|
a remote API's specification and sends a Slack or Discord notification
|
|
118
122
|
when it changes.
|
|
123
|
+
- **Local web dashboard**: `specshift serve` opens a browser-based tool
|
|
124
|
+
for comparing specs interactively (drag in files or paste text), with
|
|
125
|
+
zero extra dependencies since it runs entirely on Python's standard
|
|
126
|
+
library and stays on localhost.
|
|
119
127
|
- **Four output formats**: a colored console table, a Markdown report
|
|
120
128
|
(ideal for PR comments), JSON (for integrating with other tools), and a
|
|
121
129
|
standalone, filterable HTML report you can open in any browser.
|
|
@@ -235,6 +243,33 @@ an http(s) URL, or raw SDL text. Supports the same `--format`, `--output`,
|
|
|
235
243
|
specshift diff-graphql old_schema.graphql new_schema.graphql
|
|
236
244
|
```
|
|
237
245
|
|
|
246
|
+
### `specshift diff-proto <old> <new>`
|
|
247
|
+
|
|
248
|
+
Compares two Protobuf/gRPC `.proto` schemas using protobuf's own
|
|
249
|
+
wire-compatibility rules (field numbers, wire type categories) rather
|
|
250
|
+
than generic JSON-shape heuristics. Supports the same `--format`,
|
|
251
|
+
`--output`, `--ai`, and `--fail-on` options as `diff`.
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
specshift diff-proto old_service.proto new_service.proto
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### `specshift serve`
|
|
258
|
+
|
|
259
|
+
Starts a local, dependency-free web dashboard for comparing specs
|
|
260
|
+
interactively. Opens a browser tab where you can drag in or paste two
|
|
261
|
+
files (OpenAPI, GraphQL, or Protobuf, auto-detected), and view the same
|
|
262
|
+
styled HTML report the CLI produces, with a short history of past
|
|
263
|
+
comparisons in the current session.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
specshift serve
|
|
267
|
+
# or: specshift serve --port 9000 --no-browser
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Everything runs in-process on your machine; nothing is uploaded anywhere,
|
|
271
|
+
and the server binds to `127.0.0.1` by default.
|
|
272
|
+
|
|
238
273
|
### `specshift check`
|
|
239
274
|
|
|
240
275
|
Designed for CI/CD. Compares the current specification file against a git
|
|
@@ -368,14 +403,21 @@ common scenarios:
|
|
|
368
403
|
The same table applies to GraphQL: `input` types behave like requests,
|
|
369
404
|
`type`/`interface` fields behave like responses.
|
|
370
405
|
|
|
406
|
+
Protobuf/gRPC follows a different model, based on the wire format rather
|
|
407
|
+
than request/response direction: adding fields or rpc methods is always
|
|
408
|
+
safe, changing a field's number is always breaking, and type changes are
|
|
409
|
+
judged by wire type category (see the `diff-proto` section above).
|
|
410
|
+
|
|
371
411
|
## Comparison with other tools
|
|
372
412
|
|
|
373
413
|
| | SpecShift | Raw JSON/YAML diff | oasdiff / openapi-diff style tools |
|
|
374
414
|
|---|---|---|---|
|
|
375
415
|
| Context-aware classification | Yes | No | Partially |
|
|
376
416
|
| GraphQL support | Yes | No | Rarely |
|
|
417
|
+
| Protobuf/gRPC support (wire-aware) | Yes | No | Rarely |
|
|
377
418
|
| Natural-language summary | Yes (optional) | No | No |
|
|
378
419
|
| Standalone HTML report | Yes | No | Rarely |
|
|
420
|
+
| Local interactive dashboard | Yes | No | Rarely |
|
|
379
421
|
| Free to use | Fully free | Free | Usually free |
|
|
380
422
|
| CI integration | Built-in (`check` + official Action) | Manual | Varies |
|
|
381
423
|
| Live URL monitoring | Built-in (`watch`) | No | Rarely |
|
|
@@ -384,11 +426,13 @@ The same table applies to GraphQL: `input` types behave like requests,
|
|
|
384
426
|
|
|
385
427
|
This project is under active development. Some planned areas:
|
|
386
428
|
|
|
387
|
-
- Support for gRPC/Protobuf contracts
|
|
388
429
|
- Path parameter pattern (regex) and discriminator-level rule refinements
|
|
389
|
-
- A hosted, shareable web viewer (beyond the offline HTML report)
|
|
390
430
|
- Deeper GraphQL support: directive-aware deprecation reasons, custom
|
|
391
431
|
scalar compatibility hints, and federation-aware schema composition
|
|
432
|
+
- Protobuf `reserved` statement awareness, so a properly reserved field
|
|
433
|
+
number isn't flagged the same way as an unreserved removal
|
|
434
|
+
- Optional shared/hosted history for the `serve` dashboard (currently
|
|
435
|
+
in-memory and per-session by design)
|
|
392
436
|
|
|
393
437
|
Feel free to open an issue if you have a feature request.
|
|
394
438
|
|
|
@@ -57,6 +57,10 @@ requirement.
|
|
|
57
57
|
- **GraphQL support**: diff GraphQL SDL schemas with `specshift diff-graphql`,
|
|
58
58
|
covering types, fields, arguments, enum values, interfaces, unions, and
|
|
59
59
|
deprecations, no external GraphQL library required.
|
|
60
|
+
- **Protobuf/gRPC support**: diff `.proto` files with `specshift diff-proto`,
|
|
61
|
+
using protobuf's own documented wire-compatibility rules (field numbers,
|
|
62
|
+
wire type categories, zigzag encoding) rather than generic JSON-shape
|
|
63
|
+
heuristics, so severity reflects what will actually break on the wire.
|
|
60
64
|
- **Context-aware classification**: the same change is weighted
|
|
61
65
|
differently depending on whether it occurs in a request or a response
|
|
62
66
|
(or, for GraphQL, an input type versus an object type).
|
|
@@ -77,6 +81,10 @@ requirement.
|
|
|
77
81
|
- **Live monitoring**: the `specshift watch` command periodically checks
|
|
78
82
|
a remote API's specification and sends a Slack or Discord notification
|
|
79
83
|
when it changes.
|
|
84
|
+
- **Local web dashboard**: `specshift serve` opens a browser-based tool
|
|
85
|
+
for comparing specs interactively (drag in files or paste text), with
|
|
86
|
+
zero extra dependencies since it runs entirely on Python's standard
|
|
87
|
+
library and stays on localhost.
|
|
80
88
|
- **Four output formats**: a colored console table, a Markdown report
|
|
81
89
|
(ideal for PR comments), JSON (for integrating with other tools), and a
|
|
82
90
|
standalone, filterable HTML report you can open in any browser.
|
|
@@ -196,6 +204,33 @@ an http(s) URL, or raw SDL text. Supports the same `--format`, `--output`,
|
|
|
196
204
|
specshift diff-graphql old_schema.graphql new_schema.graphql
|
|
197
205
|
```
|
|
198
206
|
|
|
207
|
+
### `specshift diff-proto <old> <new>`
|
|
208
|
+
|
|
209
|
+
Compares two Protobuf/gRPC `.proto` schemas using protobuf's own
|
|
210
|
+
wire-compatibility rules (field numbers, wire type categories) rather
|
|
211
|
+
than generic JSON-shape heuristics. Supports the same `--format`,
|
|
212
|
+
`--output`, `--ai`, and `--fail-on` options as `diff`.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
specshift diff-proto old_service.proto new_service.proto
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `specshift serve`
|
|
219
|
+
|
|
220
|
+
Starts a local, dependency-free web dashboard for comparing specs
|
|
221
|
+
interactively. Opens a browser tab where you can drag in or paste two
|
|
222
|
+
files (OpenAPI, GraphQL, or Protobuf, auto-detected), and view the same
|
|
223
|
+
styled HTML report the CLI produces, with a short history of past
|
|
224
|
+
comparisons in the current session.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
specshift serve
|
|
228
|
+
# or: specshift serve --port 9000 --no-browser
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Everything runs in-process on your machine; nothing is uploaded anywhere,
|
|
232
|
+
and the server binds to `127.0.0.1` by default.
|
|
233
|
+
|
|
199
234
|
### `specshift check`
|
|
200
235
|
|
|
201
236
|
Designed for CI/CD. Compares the current specification file against a git
|
|
@@ -329,14 +364,21 @@ common scenarios:
|
|
|
329
364
|
The same table applies to GraphQL: `input` types behave like requests,
|
|
330
365
|
`type`/`interface` fields behave like responses.
|
|
331
366
|
|
|
367
|
+
Protobuf/gRPC follows a different model, based on the wire format rather
|
|
368
|
+
than request/response direction: adding fields or rpc methods is always
|
|
369
|
+
safe, changing a field's number is always breaking, and type changes are
|
|
370
|
+
judged by wire type category (see the `diff-proto` section above).
|
|
371
|
+
|
|
332
372
|
## Comparison with other tools
|
|
333
373
|
|
|
334
374
|
| | SpecShift | Raw JSON/YAML diff | oasdiff / openapi-diff style tools |
|
|
335
375
|
|---|---|---|---|
|
|
336
376
|
| Context-aware classification | Yes | No | Partially |
|
|
337
377
|
| GraphQL support | Yes | No | Rarely |
|
|
378
|
+
| Protobuf/gRPC support (wire-aware) | Yes | No | Rarely |
|
|
338
379
|
| Natural-language summary | Yes (optional) | No | No |
|
|
339
380
|
| Standalone HTML report | Yes | No | Rarely |
|
|
381
|
+
| Local interactive dashboard | Yes | No | Rarely |
|
|
340
382
|
| Free to use | Fully free | Free | Usually free |
|
|
341
383
|
| CI integration | Built-in (`check` + official Action) | Manual | Varies |
|
|
342
384
|
| Live URL monitoring | Built-in (`watch`) | No | Rarely |
|
|
@@ -345,11 +387,13 @@ The same table applies to GraphQL: `input` types behave like requests,
|
|
|
345
387
|
|
|
346
388
|
This project is under active development. Some planned areas:
|
|
347
389
|
|
|
348
|
-
- Support for gRPC/Protobuf contracts
|
|
349
390
|
- Path parameter pattern (regex) and discriminator-level rule refinements
|
|
350
|
-
- A hosted, shareable web viewer (beyond the offline HTML report)
|
|
351
391
|
- Deeper GraphQL support: directive-aware deprecation reasons, custom
|
|
352
392
|
scalar compatibility hints, and federation-aware schema composition
|
|
393
|
+
- Protobuf `reserved` statement awareness, so a properly reserved field
|
|
394
|
+
number isn't flagged the same way as an unreserved removal
|
|
395
|
+
- Optional shared/hosted history for the `serve` dashboard (currently
|
|
396
|
+
in-memory and per-session by design)
|
|
353
397
|
|
|
354
398
|
Feel free to open an issue if you have a feature request.
|
|
355
399
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specshift"
|
|
7
|
-
version = "1.
|
|
8
|
-
description = "Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger and
|
|
7
|
+
version = "1.2.0"
|
|
8
|
+
description = "Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -14,6 +14,8 @@ keywords = [
|
|
|
14
14
|
"openapi",
|
|
15
15
|
"swagger",
|
|
16
16
|
"graphql",
|
|
17
|
+
"grpc",
|
|
18
|
+
"protobuf",
|
|
17
19
|
"api",
|
|
18
20
|
"breaking-changes",
|
|
19
21
|
"contract-testing",
|
|
@@ -6,15 +6,18 @@ summarizes changes in OpenAPI and Swagger contracts using AI.
|
|
|
6
6
|
from specshift.differ import diff_specs
|
|
7
7
|
from specshift.graphql_differ import diff_graphql, load_sdl as load_graphql_sdl
|
|
8
8
|
from specshift.models import Change, Severity, ChangeType, DiffResult
|
|
9
|
+
from specshift.protobuf_differ import diff_protos, load_proto
|
|
9
10
|
from specshift.spec_loader import load_spec
|
|
10
11
|
|
|
11
|
-
__version__ = "1.
|
|
12
|
+
__version__ = "1.2.0"
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"diff_specs",
|
|
15
16
|
"diff_graphql",
|
|
17
|
+
"diff_protos",
|
|
16
18
|
"load_spec",
|
|
17
19
|
"load_graphql_sdl",
|
|
20
|
+
"load_proto",
|
|
18
21
|
"Change",
|
|
19
22
|
"Severity",
|
|
20
23
|
"ChangeType",
|
|
@@ -18,6 +18,7 @@ from specshift.git_utils import GitError, load_spec_from_git
|
|
|
18
18
|
from specshift.graphql_differ import GraphQLLoadError, diff_graphql, load_sdl
|
|
19
19
|
from specshift.models import DiffResult
|
|
20
20
|
from specshift.notifier import NotifyError, notify_discord, notify_slack
|
|
21
|
+
from specshift.protobuf_differ import ProtoLoadError, diff_protos, load_proto
|
|
21
22
|
from specshift.reporter import (
|
|
22
23
|
print_console_report,
|
|
23
24
|
render_html_report,
|
|
@@ -32,7 +33,7 @@ CACHE_DIR = Path(".specshift_cache")
|
|
|
32
33
|
def build_parser() -> argparse.ArgumentParser:
|
|
33
34
|
parser = argparse.ArgumentParser(
|
|
34
35
|
prog="specshift",
|
|
35
|
-
description="Detects and classifies changes in OpenAPI/Swagger and
|
|
36
|
+
description="Detects and classifies changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts.",
|
|
36
37
|
)
|
|
37
38
|
parser.add_argument("--version", action="version", version=f"specshift {__version__}")
|
|
38
39
|
|
|
@@ -48,6 +49,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
48
49
|
graphql_parser.add_argument("new", help="New GraphQL schema: file path or URL")
|
|
49
50
|
_add_common_diff_args(graphql_parser)
|
|
50
51
|
|
|
52
|
+
proto_parser = subparsers.add_parser("diff-proto", help="Compare two Protobuf/gRPC .proto schemas")
|
|
53
|
+
proto_parser.add_argument("old", help="Old .proto schema: file path or URL")
|
|
54
|
+
proto_parser.add_argument("new", help="New .proto schema: file path or URL")
|
|
55
|
+
_add_common_diff_args(proto_parser)
|
|
56
|
+
|
|
51
57
|
check_parser = subparsers.add_parser(
|
|
52
58
|
"check", help="For CI: compares the current specification against a git reference"
|
|
53
59
|
)
|
|
@@ -71,6 +77,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
71
77
|
init_parser.add_argument("--path", default=".specshift.yml", help="Path of the file to create")
|
|
72
78
|
init_parser.add_argument("--force", action="store_true", help="Overwrite an existing file")
|
|
73
79
|
|
|
80
|
+
serve_parser = subparsers.add_parser(
|
|
81
|
+
"serve", help="Starts a local web dashboard for comparing specs interactively in the browser"
|
|
82
|
+
)
|
|
83
|
+
serve_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
|
|
84
|
+
serve_parser.add_argument("--port", type=int, default=8787, help="Port to listen on (default: 8787)")
|
|
85
|
+
serve_parser.add_argument("--no-browser", action="store_true", help="Don't automatically open a browser tab")
|
|
86
|
+
|
|
74
87
|
return parser
|
|
75
88
|
|
|
76
89
|
|
|
@@ -102,13 +115,17 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|
|
102
115
|
return _run_diff(args)
|
|
103
116
|
if args.command == "diff-graphql":
|
|
104
117
|
return _run_diff_graphql(args)
|
|
118
|
+
if args.command == "diff-proto":
|
|
119
|
+
return _run_diff_proto(args)
|
|
105
120
|
if args.command == "check":
|
|
106
121
|
return _run_check(args)
|
|
107
122
|
if args.command == "watch":
|
|
108
123
|
return _run_watch(args)
|
|
109
124
|
if args.command == "init":
|
|
110
125
|
return _run_init(args)
|
|
111
|
-
|
|
126
|
+
if args.command == "serve":
|
|
127
|
+
return _run_serve(args)
|
|
128
|
+
except (SpecLoadError, GitError, GraphQLLoadError, ProtoLoadError) as exc:
|
|
112
129
|
print(f"Error: {exc}", file=sys.stderr)
|
|
113
130
|
return 2
|
|
114
131
|
except KeyboardInterrupt:
|
|
@@ -133,6 +150,13 @@ def _run_diff_graphql(args: argparse.Namespace) -> int:
|
|
|
133
150
|
return _emit_result(result, args)
|
|
134
151
|
|
|
135
152
|
|
|
153
|
+
def _run_diff_proto(args: argparse.Namespace) -> int:
|
|
154
|
+
old_proto = load_proto(args.old)
|
|
155
|
+
new_proto = load_proto(args.new)
|
|
156
|
+
result = diff_protos(old_proto, new_proto)
|
|
157
|
+
return _emit_result(result, args)
|
|
158
|
+
|
|
159
|
+
|
|
136
160
|
def _run_check(args: argparse.Namespace) -> int:
|
|
137
161
|
config = SpecShiftConfig.load(args.config)
|
|
138
162
|
spec_path = args.spec or config.spec_path
|
|
@@ -219,6 +243,13 @@ def _run_init(args: argparse.Namespace) -> int:
|
|
|
219
243
|
return 0
|
|
220
244
|
|
|
221
245
|
|
|
246
|
+
def _run_serve(args: argparse.Namespace) -> int:
|
|
247
|
+
from specshift.webapp import run_server
|
|
248
|
+
|
|
249
|
+
run_server(host=args.host, port=args.port, open_browser=not args.no_browser)
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
|
|
222
253
|
def _run_watch(args: argparse.Namespace) -> int:
|
|
223
254
|
CACHE_DIR.mkdir(exist_ok=True)
|
|
224
255
|
cache_key = hashlib.sha256(args.url.encode("utf-8")).hexdigest()[:16]
|