lambda-watcher 0.2.0__tar.gz → 0.3.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.
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/PKG-INFO +1 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/pyproject.toml +1 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/__init__.py +1 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/cli.py +30 -6
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/compare.py +42 -5
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/render_html.py +433 -97
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/gitmirror.py +5 -3
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/notify.py +2 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/reindex.py +8 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/service.py +11 -2
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/store.py +53 -5
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/utils.py +46 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher.egg-info/PKG-INFO +1 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher.egg-info/SOURCES.txt +1 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_cli.py +47 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_diff.py +47 -2
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_ingest.py +30 -0
- lambda_watcher-0.3.0/tests/test_no_window.py +87 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_render_html.py +129 -1
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/LICENSE +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/README.md +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/setup.cfg +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/__main__.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/__init__.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/deps.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/envvars.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/handler.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/inventory.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/runtime.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/secrets.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/analysis/services.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/config.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/db.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/__init__.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/build.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/highlight.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/icons.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/intraline.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/diffing/render_text.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/extract.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/identify.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/ingest.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/templates.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher/watcher.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher.egg-info/dependency_links.txt +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher.egg-info/entry_points.txt +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher.egg-info/requires.txt +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/src/lambda_watcher.egg-info/top_level.txt +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_analysis.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_docs.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_extract.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_identify.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_service.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_utils.py +0 -0
- {lambda_watcher-0.2.0 → lambda_watcher-0.3.0}/tests/test_watcher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lambda-watcher
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Watch your Downloads folder for AWS Lambda deployment zips, archive them as versions, analyse them, and diff any two versions.
|
|
5
5
|
Author: Lambda Watcher contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lambda-watcher"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Watch your Downloads folder for AWS Lambda deployment zips, archive them as versions, analyse them, and diff any two versions."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -11,7 +11,7 @@ import sys
|
|
|
11
11
|
import webbrowser
|
|
12
12
|
import zipfile
|
|
13
13
|
from datetime import datetime, timezone
|
|
14
|
-
from pathlib import Path
|
|
14
|
+
from pathlib import Path, PurePosixPath
|
|
15
15
|
from typing import NoReturn, Optional
|
|
16
16
|
|
|
17
17
|
import typer
|
|
@@ -36,7 +36,7 @@ from .service import (
|
|
|
36
36
|
install_service,
|
|
37
37
|
stop_service,
|
|
38
38
|
)
|
|
39
|
-
from .store import Store
|
|
39
|
+
from .store import Store, posix_stored_dir
|
|
40
40
|
from .utils import format_ts, human_size, relative_ts, rmtree, setup_logging, slugify
|
|
41
41
|
|
|
42
42
|
#: Index connections opened by the running command; see `_close_open_dbs`.
|
|
@@ -187,6 +187,19 @@ def _version_or_fail(db: Database, function_id: int, seq: int):
|
|
|
187
187
|
return row
|
|
188
188
|
|
|
189
189
|
|
|
190
|
+
def _stored_dirname(stored_dir: str) -> str:
|
|
191
|
+
"""The last segment of a version's stored path: ``0007-a1b2c3d4``.
|
|
192
|
+
|
|
193
|
+
Goes through :func:`~lambda_watcher.store.posix_stored_dir` first, so it
|
|
194
|
+
reads the same answer out of a value an older release wrote on Windows —
|
|
195
|
+
where ``Path(...).name`` on Linux returns the whole backslash-joined string
|
|
196
|
+
instead of the directory name, and the caller then builds a path to nothing.
|
|
197
|
+
"""
|
|
198
|
+
# back-compat: `posix_stored_dir` is the whole point of this helper existing
|
|
199
|
+
# rather than callers reaching for `Path(...).name` themselves.
|
|
200
|
+
return PurePosixPath(posix_stored_dir(stored_dir)).name
|
|
201
|
+
|
|
202
|
+
|
|
190
203
|
def _build_diff(db: Database, store: Store, cfg: Config, function_row, a_seq: int, b_seq: int,
|
|
191
204
|
include_vendor: bool | None, compute_diffs: bool = True):
|
|
192
205
|
"""Resolve two versions and build the diff between them.
|
|
@@ -194,7 +207,10 @@ def _build_diff(db: Database, store: Store, cfg: Config, function_row, a_seq: in
|
|
|
194
207
|
Warns, rather than fails, when a version's extracted code is missing from
|
|
195
208
|
disk: the index still knows what changed at the file level, so the summary
|
|
196
209
|
and dependency layers are worth printing even though the line diffs come out
|
|
197
|
-
empty. Deleting an archive directory by hand is
|
|
210
|
+
empty. Deleting an archive directory by hand is one cause; an index pointing
|
|
211
|
+
somewhere the directory no longer is, after the archive was moved or copied
|
|
212
|
+
from another machine, is the other — which is why the warning names
|
|
213
|
+
``lw reindex``, the command that rebuilds the index from what is on disk.
|
|
198
214
|
"""
|
|
199
215
|
a = _version_or_fail(db, function_row["id"], a_seq)
|
|
200
216
|
b = _version_or_fail(db, function_row["id"], b_seq)
|
|
@@ -203,7 +219,8 @@ def _build_diff(db: Database, store: Store, cfg: Config, function_row, a_seq: in
|
|
|
203
219
|
if not path.exists():
|
|
204
220
|
err_console.print(
|
|
205
221
|
f"[yellow]warning:[/yellow] code for v{seq:04d} is missing at {path}; "
|
|
206
|
-
"line diffs for it will be empty"
|
|
222
|
+
"line diffs for it will be empty. If the archive was moved or copied "
|
|
223
|
+
"here, `lw reindex` re-points the index at what is on disk."
|
|
207
224
|
)
|
|
208
225
|
return diff_from_index(db, store, cfg.diff, function_row["name"], a, b,
|
|
209
226
|
include_vendor=include_vendor, compute_diffs=compute_diffs)
|
|
@@ -1128,8 +1145,15 @@ def rename(
|
|
|
1128
1145
|
if new_dir.exists():
|
|
1129
1146
|
_fail(f"{new_dir} already exists on disk; move it aside first")
|
|
1130
1147
|
old_dir.rename(new_dir)
|
|
1148
|
+
# back-compat: re-point the index by rebuilding each path from where the
|
|
1149
|
+
# directory now is, rather than by patching the stored string. A
|
|
1150
|
+
# `functions/<old slug>/` replacement silently matched nothing in an
|
|
1151
|
+
# archive an older release wrote on Windows, where the separator is a
|
|
1152
|
+
# backslash — and a version left pointing at the pre-rename path reads
|
|
1153
|
+
# as missing from then on, which is a diff with no lines in it.
|
|
1154
|
+
new_versions = store.versions_dir(new_slug)
|
|
1131
1155
|
for version in db.list_versions(int(row["id"])):
|
|
1132
|
-
updated = version["dir"]
|
|
1156
|
+
updated = store.relative(new_versions / _stored_dirname(version["dir"]))
|
|
1133
1157
|
db.conn.execute("UPDATE versions SET dir = ? WHERE id = ?", (updated, version["id"]))
|
|
1134
1158
|
if old_slug != new_slug:
|
|
1135
1159
|
# The mirror lives outside the function directory, so it does not move
|
|
@@ -1209,7 +1233,7 @@ def merge(
|
|
|
1209
1233
|
stored = store.resolve_version_dir(version["dir"])
|
|
1210
1234
|
if stored.exists():
|
|
1211
1235
|
continue
|
|
1212
|
-
candidate = dst_versions /
|
|
1236
|
+
candidate = dst_versions / _stored_dirname(version["dir"])
|
|
1213
1237
|
if candidate.exists():
|
|
1214
1238
|
db.conn.execute(
|
|
1215
1239
|
"UPDATE versions SET dir = ? WHERE id = ?",
|
|
@@ -38,7 +38,15 @@ class FileRecord:
|
|
|
38
38
|
|
|
39
39
|
@classmethod
|
|
40
40
|
def from_row(cls, row: Any) -> FileRecord:
|
|
41
|
-
"""Build a record from an index row, filling in defaults for null columns.
|
|
41
|
+
"""Build a record from an index row, filling in defaults for null columns.
|
|
42
|
+
|
|
43
|
+
The defaults are not decoration: a row reindexed from a manifest written
|
|
44
|
+
before ``lang`` and ``mode`` were recorded has NULL in those columns, and
|
|
45
|
+
the diff would rather call the file ``text`` at 644 than fail on it.
|
|
46
|
+
"""
|
|
47
|
+
# back-compat: `or "text"` / `or 0o644` cover columns an older manifest
|
|
48
|
+
# cannot fill. Drop them and reindexing an old archive builds rows that
|
|
49
|
+
# crash the diff instead of one that is merely vaguer than it could be.
|
|
42
50
|
return cls(
|
|
43
51
|
path=row["path"], size=int(row["size"]), sha256=row["sha256"],
|
|
44
52
|
is_text=bool(row["is_text"]), is_vendor=bool(row["is_vendor"]),
|
|
@@ -56,8 +64,9 @@ class FileChange:
|
|
|
56
64
|
|
|
57
65
|
``diff_lines`` may be empty even for a real modification —
|
|
58
66
|
``skipped_reason`` then says why (binary, too large, ignored by config,
|
|
59
|
-
whitespace only), so the renderer can explain the
|
|
60
|
-
nothing changed. Two of those absences are not
|
|
67
|
+
whitespace only, missing from the archive), so the renderer can explain the
|
|
68
|
+
absence rather than imply nothing changed. Two of those absences are not
|
|
69
|
+
refusals but better answers:
|
|
61
70
|
a whitespace-only change is fully described by its label, and a file with no
|
|
62
71
|
usable lines is described by ``word_edits`` instead. See :func:`_fill_diff`.
|
|
63
72
|
"""
|
|
@@ -87,6 +96,12 @@ class FileChange:
|
|
|
87
96
|
#: The changed runs of a ``long_lines`` file, with enough text either side to
|
|
88
97
|
#: place them. Empty for every ordinary file.
|
|
89
98
|
word_edits: list[WordEdit] = field(default_factory=list)
|
|
99
|
+
#: True when the index lists the file but the archived version directory does
|
|
100
|
+
#: not hold it, so there was nothing to diff. Unlike every other reason a
|
|
101
|
+
#: hunk is absent, this one is not about the file at all — the archive is
|
|
102
|
+
#: incomplete, or the index is pointing at the wrong place — so the
|
|
103
|
+
#: renderers say what to do about it rather than only what happened.
|
|
104
|
+
missing: bool = False
|
|
90
105
|
|
|
91
106
|
@property
|
|
92
107
|
def is_vendor(self) -> bool:
|
|
@@ -146,6 +161,7 @@ class FileChange:
|
|
|
146
161
|
"is_vendor": self.is_vendor,
|
|
147
162
|
"whitespace_only": self.whitespace_only,
|
|
148
163
|
"long_lines": self.long_lines,
|
|
164
|
+
"missing": self.missing,
|
|
149
165
|
"word_edits": [e.as_dict() for e in self.word_edits],
|
|
150
166
|
}
|
|
151
167
|
|
|
@@ -764,6 +780,21 @@ def _whitespace_key(text: str) -> tuple[str, ...]:
|
|
|
764
780
|
return tuple(" ".join(line.split()) for line in text.splitlines() if line.strip())
|
|
765
781
|
|
|
766
782
|
|
|
783
|
+
def _absent(root: Path, record: FileRecord | None) -> bool:
|
|
784
|
+
"""True when the index lists this file but the archive no longer holds it.
|
|
785
|
+
|
|
786
|
+
:func:`~lambda_watcher.utils.read_text` answers None both for bytes that are
|
|
787
|
+
not text and for a file it could not open at all, and the second is by far
|
|
788
|
+
the likelier of the two here: the index is derived from disk, so a file it
|
|
789
|
+
lists was readable when it was written. A version directory that has been
|
|
790
|
+
deleted, moved, or restored under a path the index does not know about is
|
|
791
|
+
what actually produces it — see :meth:`lambda_watcher.store.Store.relative`.
|
|
792
|
+
Telling the reader "not decodable as text" for that sends them looking for an
|
|
793
|
+
encoding problem in a file that is simply not there.
|
|
794
|
+
"""
|
|
795
|
+
return record is not None and not (root / record.path).is_file()
|
|
796
|
+
|
|
797
|
+
|
|
767
798
|
def _fill_diff(change: FileChange, old_root: Path, new_root: Path, cfg: DiffConfig) -> None:
|
|
768
799
|
"""Work out what changed inside one file and write it onto the change.
|
|
769
800
|
|
|
@@ -792,7 +823,10 @@ def _fill_diff(change: FileChange, old_root: Path, new_root: Path, cfg: DiffConf
|
|
|
792
823
|
Everything else, from :func:`difflib.unified_diff`.
|
|
793
824
|
|
|
794
825
|
Before any of that, a file can be refused outright — binary, over
|
|
795
|
-
``max_diff_file_kb``,
|
|
826
|
+
``max_diff_file_kb``, not decodable, or missing from the archive — and
|
|
827
|
+
``skipped_reason`` says which. The last of those is not a property of the
|
|
828
|
+
file's bytes but of the archive around it, so it is told apart by
|
|
829
|
+
:func:`_absent` rather than lumped in with the encodings.
|
|
796
830
|
Line counts stay at zero on every route but the last: the counts describe a
|
|
797
831
|
hunk, and where there is no hunk ``+3/-3`` is the noise the route exists to
|
|
798
832
|
remove.
|
|
@@ -813,7 +847,10 @@ def _fill_diff(change: FileChange, old_root: Path, new_root: Path, cfg: DiffConf
|
|
|
813
847
|
old_text = read_text(old_root / old.path) if old else ""
|
|
814
848
|
new_text = read_text(new_root / new.path) if new else ""
|
|
815
849
|
if old_text is None or new_text is None:
|
|
816
|
-
change.
|
|
850
|
+
change.missing = _absent(old_root, old) or _absent(new_root, new)
|
|
851
|
+
change.skipped_reason = (
|
|
852
|
+
"missing from the archive" if change.missing else "not decodable as text"
|
|
853
|
+
)
|
|
817
854
|
return
|
|
818
855
|
|
|
819
856
|
if old_text and new_text and old_text != new_text:
|