toolfuncs 0.3.3__tar.gz → 0.3.4__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.
Files changed (23) hide show
  1. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/PKG-INFO +1 -1
  2. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/pyproject.toml +1 -1
  3. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/__init__.py +1 -1
  4. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/__main__.py +20 -3
  5. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_sdk_cli.py +48 -1
  6. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/uv.lock +1 -1
  7. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/.github/workflows/release.yml +0 -0
  8. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/.gitignore +0 -0
  9. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/.python-version +0 -0
  10. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/README.md +0 -0
  11. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/docs/contract.md +0 -0
  12. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_errors.py +0 -0
  13. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_import_path.py +0 -0
  14. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_setup.py +0 -0
  15. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_tools.py +0 -0
  16. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/agent_hooks.py +0 -0
  17. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/py.typed +0 -0
  18. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/sdk.py +0 -0
  19. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/__init__.py +0 -0
  20. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/conftest.py +0 -0
  21. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_agent_hooks.py +0 -0
  22. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_import_path.py +0 -0
  23. {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: toolfuncs
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: Executable typed Python function tools discovered through PATH
5
5
  Project-URL: Repository, https://github.com/nimashoghi/toolfuncs
6
6
  Project-URL: Issues, https://github.com/nimashoghi/toolfuncs/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "toolfuncs"
3
- version = "0.3.3"
3
+ version = "0.3.4"
4
4
  description = "Executable typed Python function tools discovered through PATH"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -29,7 +29,7 @@ from .sdk import (
29
29
  validators,
30
30
  )
31
31
 
32
- __version__ = "0.3.3"
32
+ __version__ = "0.3.4"
33
33
 
34
34
  __all__ = [
35
35
  "ALLOW_FLOATING_VCS_SHEBANG",
@@ -4,7 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import sys
7
- from collections.abc import Sequence
7
+ from collections.abc import Iterator, Sequence
8
+ from contextlib import contextmanager
8
9
  from dataclasses import asdict
9
10
  from pathlib import Path
10
11
  from typing import Any
@@ -37,6 +38,18 @@ Floating Git revisions require the explicit shebang:
37
38
  """
38
39
 
39
40
 
41
+ @contextmanager
42
+ def _tool_program_name(source: Path) -> Iterator[None]:
43
+ """Expose the source executable as the invoked app's program name."""
44
+
45
+ previous = sys.argv[0]
46
+ sys.argv[0] = source.name
47
+ try:
48
+ yield
49
+ finally:
50
+ sys.argv[0] = previous
51
+
52
+
40
53
  def main(argv: Sequence[str] | None = None) -> int:
41
54
  """Run a source supplied by its shebang or dispatch a management command."""
42
55
 
@@ -55,7 +68,9 @@ def main(argv: Sequence[str] | None = None) -> int:
55
68
  if not tokens:
56
69
  raise SystemExit("toolfuncs internal error: missing prepared source")
57
70
  source = Path(tokens.pop(0))
58
- result = _run(_load_prepared_source(source), tokens)
71
+ module = _load_prepared_source(source)
72
+ with _tool_program_name(source):
73
+ result = _run(module, tokens)
59
74
  return result if isinstance(result, int) else 0
60
75
  if command == "__validate-source":
61
76
  if len(tokens) != 1:
@@ -75,7 +90,9 @@ def main(argv: Sequence[str] | None = None) -> int:
75
90
  source = Path(command)
76
91
  if not source.is_file():
77
92
  raise SystemExit(f"unknown toolfuncs command or missing source: {command}")
78
- result = _run(_load_source(source), tokens)
93
+ module = _load_source(source)
94
+ with _tool_program_name(source):
95
+ result = _run(module, tokens)
79
96
  return result if isinstance(result, int) else 0
80
97
 
81
98
 
@@ -30,7 +30,7 @@ def test_cli_prints_installed_version(tmp_path: Path, flag: str) -> None:
30
30
  completed = run_python(["-m", "toolfuncs", flag], cwd=tmp_path)
31
31
 
32
32
  assert completed.returncode == 0
33
- assert completed.stdout == "0.3.3\n"
33
+ assert completed.stdout == "0.3.4\n"
34
34
  assert completed.stderr == ""
35
35
 
36
36
 
@@ -90,6 +90,43 @@ def test_runtime_requires_a_module_level_app(tmp_path: Path) -> None:
90
90
  assert "must define 'app'" in completed.stderr
91
91
 
92
92
 
93
+ @pytest.mark.parametrize("prepared", [False, True])
94
+ @pytest.mark.parametrize(
95
+ ("app_definition", "expected_name"),
96
+ [
97
+ ("app = toolfuncs.App()", "named_tool"),
98
+ ("app = toolfuncs.App(name='explicit')", "explicit"),
99
+ ],
100
+ )
101
+ def test_runtime_uses_source_name_unless_app_name_is_explicit(
102
+ tmp_path: Path,
103
+ prepared: bool,
104
+ app_definition: str,
105
+ expected_name: str,
106
+ ) -> None:
107
+ source = tmp_path / "named_tool"
108
+ _write_executable(
109
+ source,
110
+ _script(
111
+ "Named tool",
112
+ "import toolfuncs\n\n"
113
+ f"{app_definition}\n\n"
114
+ "@app.command\n"
115
+ "def ping() -> str:\n"
116
+ " return 'pong'\n",
117
+ ),
118
+ )
119
+ runtime_prefix = ["__run-source"] if prepared else []
120
+
121
+ completed = run_python(
122
+ ["-m", "toolfuncs", *runtime_prefix, str(source), "--help"],
123
+ cwd=tmp_path,
124
+ )
125
+
126
+ assert completed.returncode == 0, completed.stderr
127
+ assert completed.stdout.splitlines()[0] == f"Usage: {expected_name} COMMAND"
128
+
129
+
93
130
  def test_prepared_runtime_does_not_run_an_inner_installer(tmp_path: Path) -> None:
94
131
  source = tmp_path / "prepared"
95
132
  _write_executable(
@@ -171,6 +208,14 @@ def test_direct_command_dynamic_import_and_static_listing(tmp_path: Path) -> Non
171
208
  text=True,
172
209
  check=False,
173
210
  )
211
+ helped = subprocess.run(
212
+ [str(source), "--help"],
213
+ cwd=tmp_path,
214
+ env={**os.environ, **environment},
215
+ capture_output=True,
216
+ text=True,
217
+ check=False,
218
+ )
174
219
  imported = run_python(
175
220
  ["-c", "from toolfuncs import greet; print(greet.hello('Ada')['message'])"],
176
221
  cwd=tmp_path,
@@ -180,6 +225,8 @@ def test_direct_command_dynamic_import_and_static_listing(tmp_path: Path) -> Non
180
225
 
181
226
  assert direct.returncode == 0, direct.stderr
182
227
  assert json.loads(direct.stdout) == {"message": "Hello, Ada!"}
228
+ assert helped.returncode == 0, helped.stderr
229
+ assert helped.stdout.splitlines()[0] == "Usage: greet COMMAND"
183
230
  assert imported.returncode == 0, imported.stderr
184
231
  assert imported.stdout == "Hello, Ada.\n"
185
232
  record = next(item for item in json.loads(listed.stdout) if item["name"] == "greet")
@@ -601,7 +601,7 @@ wheels = [
601
601
 
602
602
  [[package]]
603
603
  name = "toolfuncs"
604
- version = "0.3.3"
604
+ version = "0.3.4"
605
605
  source = { editable = "." }
606
606
  dependencies = [
607
607
  { name = "cyclopts" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes