funcd-bundle 0.3.0__tar.gz → 0.3.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.
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/PKG-INFO +1 -1
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/pyproject.toml +1 -1
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/src/funcd_bundle/bundle.py +7 -8
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/src/funcd_bundle/cli.py +6 -1
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/tests/test_bundle.py +25 -1
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/.gitignore +0 -0
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/README.md +0 -0
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/src/funcd_bundle/__init__.py +0 -0
- {funcd_bundle-0.3.0 → funcd_bundle-0.3.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: funcd-bundle
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Bundle a Python funcd function and its locked dependencies for the funcd runtime
|
|
5
5
|
Project-URL: Homepage, https://github.com/pyvvo/funcd-python
|
|
6
6
|
Project-URL: Repository, https://github.com/pyvvo/funcd-python
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "funcd-bundle"
|
|
3
3
|
# Not bumped per release: the vX.Y.Z git tag is the version, stamped into the build at publish time.
|
|
4
|
-
version = "0.3.
|
|
4
|
+
version = "0.3.1"
|
|
5
5
|
description = "Bundle a Python funcd function and its locked dependencies for the funcd runtime"
|
|
6
6
|
authors = [{ name = "The funcd Authors" }]
|
|
7
7
|
readme = "README.md"
|
|
@@ -264,8 +264,8 @@ def _install_in_container(
|
|
|
264
264
|
env = ["-e", "FUNCD_BUNDLE_DIR=/out", "-e", "PYTHONPATH=/out", "-w", "/src"]
|
|
265
265
|
cid = _create(platform, env, ["sh", "-c", "; ".join(steps)])
|
|
266
266
|
try:
|
|
267
|
-
_copy_in(cid, inputs, "/in")
|
|
268
|
-
_copy_in(cid, project, "/src")
|
|
267
|
+
_copy_in(cid, inputs, "/in", NOT_COPIED)
|
|
268
|
+
_copy_in(cid, project, "/src", NOT_COPIED)
|
|
269
269
|
_start(cid, "hermetic install")
|
|
270
270
|
_copy_out(cid, "/out", out)
|
|
271
271
|
finally:
|
|
@@ -277,7 +277,8 @@ def _check(out: Path, platform: str, modules: list[str]) -> None:
|
|
|
277
277
|
code = "; ".join(f"import {m}" for m in modules)
|
|
278
278
|
cid = _create(platform, ["-e", "PYTHONPATH=/bundle"], ["python", "-c", code])
|
|
279
279
|
try:
|
|
280
|
-
|
|
280
|
+
# a vendored package may hold a dist or node_modules directory of its own: check the tree as built
|
|
281
|
+
_copy_in(cid, out, "/bundle", frozenset({"__pycache__"}))
|
|
281
282
|
_start(cid, f"import check ({', '.join(modules)}) on {platform}")
|
|
282
283
|
finally:
|
|
283
284
|
_run(["docker", "rm", "-f", cid])
|
|
@@ -294,13 +295,11 @@ def _start(cid: str, what: str) -> None:
|
|
|
294
295
|
raise BundleError(f"{what} failed:\n{detail}")
|
|
295
296
|
|
|
296
297
|
|
|
297
|
-
def _copy_in(cid: str, src: Path, dest: str) -> None:
|
|
298
|
-
"""Stream src into the container at dest as a tar (no bind mount), leaving out
|
|
298
|
+
def _copy_in(cid: str, src: Path, dest: str, skip: frozenset[str]) -> None:
|
|
299
|
+
"""Stream src into the container at dest as a tar (no bind mount), leaving out the names in skip."""
|
|
299
300
|
buf = io.BytesIO()
|
|
300
301
|
with tarfile.open(fileobj=buf, mode="w") as tar:
|
|
301
|
-
tar.add(
|
|
302
|
-
src, arcname=dest.lstrip("/"), filter=lambda ti: None if Path(ti.name).name in NOT_COPIED else ti
|
|
303
|
-
)
|
|
302
|
+
tar.add(src, arcname=dest.lstrip("/"), filter=lambda ti: None if Path(ti.name).name in skip else ti)
|
|
304
303
|
_run(["docker", "cp", "-", f"{cid}:/"], stdin=buf.getvalue())
|
|
305
304
|
|
|
306
305
|
|
|
@@ -33,7 +33,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
33
33
|
unknown = [n for n in args.names if n not in functions]
|
|
34
34
|
if unknown:
|
|
35
35
|
parser.error(f"unknown function(s) {', '.join(unknown)}; found {', '.join(functions)}")
|
|
36
|
-
|
|
36
|
+
conf = settings(project)
|
|
37
|
+
hermetic = args.hermetic or conf.hermetic
|
|
38
|
+
if conf.post_install and not hermetic:
|
|
39
|
+
parser.error(
|
|
40
|
+
"[tool.funcd-bundle] post-install needs a hermetic build (hermetic = true or --hermetic)"
|
|
41
|
+
)
|
|
37
42
|
platforms = args.platform or [host_platform()]
|
|
38
43
|
for name in args.names or list(functions):
|
|
39
44
|
for platform in platforms:
|
|
@@ -7,6 +7,7 @@ Each skips with the reason when its requirement is absent. CI has both.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
+
import re
|
|
10
11
|
import shutil
|
|
11
12
|
import socket
|
|
12
13
|
import subprocess
|
|
@@ -18,6 +19,7 @@ import pytest
|
|
|
18
19
|
|
|
19
20
|
from funcd_bundle import BundleError, bundle, discover, host_platform
|
|
20
21
|
from funcd_bundle.bundle import _check
|
|
22
|
+
from funcd_bundle.cli import main
|
|
21
23
|
|
|
22
24
|
FOREIGN = {"linux/arm64": "linux/amd64", "linux/amd64": "linux/arm64"}
|
|
23
25
|
ELF_MACHINE = {"linux/amd64": 0x3E, "linux/arm64": 0xB7}
|
|
@@ -176,8 +178,12 @@ def test_scenario_bundle_includes_workspace_package(workspace: Path, tmp_path: P
|
|
|
176
178
|
def test_scenario_bundle_check_catches_mismatch(workspace: Path, tmp_path: Path) -> None:
|
|
177
179
|
foreign = FOREIGN[host_platform()]
|
|
178
180
|
out = bundle(workspace, "reader", foreign, tmp_path / "reader", hermetic=False, check=False)
|
|
179
|
-
with pytest.raises(BundleError, match="import check"):
|
|
181
|
+
with pytest.raises(BundleError, match="import check") as err:
|
|
180
182
|
_check(out, host_platform(), ["orjson"])
|
|
183
|
+
detail = str(err.value).split("\n", 1)[1]
|
|
184
|
+
assert re.search("ModuleNotFoundError|ImportError", detail), (
|
|
185
|
+
f"the import error itself is reported: {detail}"
|
|
186
|
+
)
|
|
181
187
|
|
|
182
188
|
|
|
183
189
|
# scenario: bundle-hermetic-post-install
|
|
@@ -218,6 +224,24 @@ def test_post_install_needs_hermetic(tmp_path: Path) -> None:
|
|
|
218
224
|
bundle(tmp_path, tmp_path.name, host_platform(), tmp_path / "out", hermetic=False, check=False)
|
|
219
225
|
|
|
220
226
|
|
|
227
|
+
def test_cli_post_install_needs_hermetic(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
228
|
+
_write(
|
|
229
|
+
tmp_path,
|
|
230
|
+
{
|
|
231
|
+
"pyproject.toml": (
|
|
232
|
+
'[project]\nname = "x"\nversion = "0"\n\n[tool.funcd-bundle]\npost-install = ["true"]\n'
|
|
233
|
+
),
|
|
234
|
+
"funcdctl.yaml": MANIFEST,
|
|
235
|
+
"src/handler.py": "",
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
monkeypatch.chdir(tmp_path)
|
|
239
|
+
with pytest.raises(SystemExit) as exit_:
|
|
240
|
+
main(["--out", str(tmp_path / "out")])
|
|
241
|
+
assert exit_.value.code == 2, "a post-install without hermetic is a usage error"
|
|
242
|
+
assert not (tmp_path / "out").exists(), "nothing is bundled"
|
|
243
|
+
|
|
244
|
+
|
|
221
245
|
def test_default_handler_follows_funcdctl(tmp_path: Path) -> None:
|
|
222
246
|
_write(
|
|
223
247
|
tmp_path, {"counter.funcdctl.yaml": "runtime: python314\n", "counter.py": "def handle(c, e): ...\n"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|