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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: funcd-bundle
3
- Version: 0.3.0
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.0"
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
- _copy_in(cid, out, "/bundle")
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 caches and virtualenvs."""
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
- hermetic = args.hermetic or settings(project).hermetic
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