funcd-bundle 0.3.3__tar.gz → 0.3.5__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.3
3
+ Version: 0.3.5
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.3"
4
+ version = "0.3.5"
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"
@@ -84,7 +84,10 @@ def discover(project: Path) -> dict[str, Function]:
84
84
  defaults = {project.resolve().name: DEFAULT_HANDLER}
85
85
  functions = {}
86
86
  for name, manifest in manifests.items():
87
- data = yaml.safe_load(manifest.read_text()) or {}
87
+ try:
88
+ data = yaml.safe_load(manifest.read_text()) or {}
89
+ except yaml.YAMLError as err:
90
+ raise BundleError(f"{manifest}: {err}") from err
88
91
  if not isinstance(data, dict):
89
92
  raise BundleError(f"{manifest} is not a mapping")
90
93
  main = data.get("main") or defaults[name]
@@ -347,6 +350,15 @@ def _with_main(text: str, handler: str) -> str:
347
350
  if text[start:end].endswith("\n"):
348
351
  entry += "\n"
349
352
  return text[:start] + entry + text[end:]
353
+ if root.flow_style:
354
+ # a block line after a flow mapping is a second root: the key goes inside, after its brace
355
+ brace = next(
356
+ t
357
+ for t in yaml.scan(text, Loader=yaml.SafeLoader)
358
+ if isinstance(t, yaml.FlowMappingStartToken)
359
+ )
360
+ at = brace.end_mark.index
361
+ return text[:at] + entry + (", " if root.value else "") + text[at:]
350
362
  if text and not text.endswith("\n"):
351
363
  text += "\n"
352
364
  return text + entry + "\n"
@@ -16,6 +16,7 @@ from collections.abc import Iterator
16
16
  from pathlib import Path
17
17
 
18
18
  import pytest
19
+ import yaml
19
20
 
20
21
  from funcd_bundle import BundleError, bundle, discover, host_platform
21
22
  from funcd_bundle.bundle import _check
@@ -24,6 +25,7 @@ from funcd_bundle.cli import main
24
25
  FOREIGN = {"linux/arm64": "linux/amd64", "linux/amd64": "linux/arm64"}
25
26
  ELF_MACHINE = {"linux/amd64": 0x3E, "linux/arm64": 0xB7}
26
27
  MANIFEST = "runtime: python314\nhandler: handle\nmain: src/handler.py\n"
28
+ EXAMPLES = Path(__file__).resolve().parents[2] / "examples"
27
29
 
28
30
 
29
31
  def _online() -> bool:
@@ -255,6 +257,18 @@ def test_default_handler_follows_funcdctl(tmp_path: Path) -> None:
255
257
  )
256
258
 
257
259
 
260
+ def test_issue_r54_every_example_manifest_names_a_handler_it_ships() -> None:
261
+ examples = sorted({m.parent for m in EXAMPLES.glob("*/*funcdctl.yaml")})
262
+ assert examples, f"no example manifests under {EXAMPLES}"
263
+ missing = []
264
+ for example in examples:
265
+ try:
266
+ discover(example)
267
+ except BundleError as err:
268
+ missing.append(str(err))
269
+ assert not missing, f"funcdctl dev and funcd-bundle cannot find these handlers: {missing}"
270
+
271
+
258
272
  def test_issue_154_stem_manifest_without_main_names_bundled_handler(tmp_path: Path) -> None:
259
273
  manifest = "runtime: python314\nhandler: handle\n"
260
274
  project = tmp_path / "project"
@@ -302,7 +316,49 @@ def test_issue_r29_main_key_is_rewritten_not_duplicated(tmp_path: Path, main: st
302
316
  )
303
317
 
304
318
 
319
+ @pytest.mark.parametrize(
320
+ ("manifest", "want"),
321
+ [
322
+ ("{runtime: python314, handler: handle}\n", {"runtime": "python314", "handler": "handle"}),
323
+ ("!!map &m {\n runtime: python314,\n}\n", {"runtime": "python314"}),
324
+ ("{}\n", {}),
325
+ ],
326
+ ids=["one-line", "tagged-multi-line", "empty"],
327
+ )
328
+ def test_issue_r49_flow_mapping_manifest_gets_main(
329
+ tmp_path: Path, manifest: str, want: dict[str, str]
330
+ ) -> None:
331
+ project = tmp_path / "reader"
332
+ _write(
333
+ project,
334
+ {
335
+ "pyproject.toml": '[project]\nname = "reader"\nversion = "0.1.0"\nrequires-python = ">=3.12"\n',
336
+ "funcdctl.yaml": manifest,
337
+ "handler.py": "def handle(ctx, event): ...\n",
338
+ },
339
+ )
340
+ _uv("lock", "--offline", "--python", "3.14", cwd=project)
341
+ out = bundle(project, "reader", host_platform(), tmp_path / "out", hermetic=False, check=False)
342
+
343
+ assert yaml.safe_load((out / "funcdctl.yaml").read_text()) == want | {"main": "handler.py"}, (
344
+ "the bundle's manifest is valid YAML whose main names the bundled handler"
345
+ )
346
+
347
+
305
348
  def test_missing_handler_is_named(tmp_path: Path) -> None:
306
349
  _write(tmp_path, {"funcdctl.yaml": "runtime: python314\nmain: src/nope.py\n"})
307
350
  with pytest.raises(BundleError, match="nope.py"):
308
351
  bundle(tmp_path, tmp_path.name, host_platform(), tmp_path / "out", hermetic=False, check=False)
352
+
353
+
354
+ def test_issue_r48_invalid_manifest_yaml_is_reported(
355
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
356
+ ) -> None:
357
+ _write(tmp_path, {"funcdctl.yaml": "runtime: [\n", "handler.py": ""})
358
+ monkeypatch.chdir(tmp_path)
359
+
360
+ assert main(["--no-check", "--out", str(tmp_path / "out")]) == 1, "a manifest that does not parse fails"
361
+ err = capsys.readouterr().err
362
+ assert err.startswith(f"funcd-bundle: {tmp_path / 'funcdctl.yaml'}: "), err
363
+ assert "while parsing a flow node" in err, "the parser message names the problem"
364
+ assert "Traceback" not in err
File without changes
File without changes
File without changes