toolfuncs 0.3.2__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.2 → toolfuncs-0.3.4}/PKG-INFO +1 -1
  2. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/pyproject.toml +1 -1
  3. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/__init__.py +1 -1
  4. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/__main__.py +27 -3
  5. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/tests/test_sdk_cli.py +56 -0
  6. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/uv.lock +1 -1
  7. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/.github/workflows/release.yml +0 -0
  8. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/.gitignore +0 -0
  9. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/.python-version +0 -0
  10. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/README.md +0 -0
  11. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/docs/contract.md +0 -0
  12. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/_errors.py +0 -0
  13. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/_import_path.py +0 -0
  14. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/_setup.py +0 -0
  15. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/_tools.py +0 -0
  16. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/agent_hooks.py +0 -0
  17. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/py.typed +0 -0
  18. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/src/toolfuncs/sdk.py +0 -0
  19. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/tests/__init__.py +0 -0
  20. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/tests/conftest.py +0 -0
  21. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/tests/test_agent_hooks.py +0 -0
  22. {toolfuncs-0.3.2 → toolfuncs-0.3.4}/tests/test_import_path.py +0 -0
  23. {toolfuncs-0.3.2 → 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.2
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.2"
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.2"
32
+ __version__ = "0.3.4"
33
33
 
34
34
  __all__ = [
35
35
  "ALLOW_FLOATING_VCS_SHEBANG",
@@ -4,13 +4,15 @@ 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
11
12
 
12
13
  from pydantic_core import to_jsonable_python
13
14
 
15
+ from . import __version__
14
16
  from ._setup import setup
15
17
  from ._tools import (
16
18
  ALLOW_FLOATING_VCS_SHEBANG,
@@ -24,6 +26,7 @@ from .sdk import _run
24
26
 
25
27
  _HELP = f"""\
26
28
  Usage:
29
+ toolfuncs --version
27
30
  toolfuncs list
28
31
  toolfuncs setup [--bin-dir DIRECTORY]
29
32
 
@@ -35,6 +38,18 @@ Floating Git revisions require the explicit shebang:
35
38
  """
36
39
 
37
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
+
38
53
  def main(argv: Sequence[str] | None = None) -> int:
39
54
  """Run a source supplied by its shebang or dispatch a management command."""
40
55
 
@@ -42,13 +57,20 @@ def main(argv: Sequence[str] | None = None) -> int:
42
57
  if not tokens or tokens[0] in {"-h", "--help", "help"}:
43
58
  print(_HELP, end="")
44
59
  return 0
60
+ if tokens[0] in {"-V", "--version"}:
61
+ if len(tokens) != 1:
62
+ raise SystemExit("toolfuncs --version accepts no arguments")
63
+ print(__version__)
64
+ return 0
45
65
 
46
66
  command = tokens.pop(0)
47
67
  if command == "__run-source":
48
68
  if not tokens:
49
69
  raise SystemExit("toolfuncs internal error: missing prepared source")
50
70
  source = Path(tokens.pop(0))
51
- result = _run(_load_prepared_source(source), tokens)
71
+ module = _load_prepared_source(source)
72
+ with _tool_program_name(source):
73
+ result = _run(module, tokens)
52
74
  return result if isinstance(result, int) else 0
53
75
  if command == "__validate-source":
54
76
  if len(tokens) != 1:
@@ -68,7 +90,9 @@ def main(argv: Sequence[str] | None = None) -> int:
68
90
  source = Path(command)
69
91
  if not source.is_file():
70
92
  raise SystemExit(f"unknown toolfuncs command or missing source: {command}")
71
- result = _run(_load_source(source), tokens)
93
+ module = _load_source(source)
94
+ with _tool_program_name(source):
95
+ result = _run(module, tokens)
72
96
  return result if isinstance(result, int) else 0
73
97
 
74
98
 
@@ -25,6 +25,15 @@ from .conftest import run_python
25
25
  _INSTALL_HOOKS = setup_impl._install_hooks
26
26
 
27
27
 
28
+ @pytest.mark.parametrize("flag", ["--version", "-V"])
29
+ def test_cli_prints_installed_version(tmp_path: Path, flag: str) -> None:
30
+ completed = run_python(["-m", "toolfuncs", flag], cwd=tmp_path)
31
+
32
+ assert completed.returncode == 0
33
+ assert completed.stdout == "0.3.4\n"
34
+ assert completed.stderr == ""
35
+
36
+
28
37
  @pytest.fixture(autouse=True)
29
38
  def _avoid_installing_live_agent_hooks(monkeypatch: pytest.MonkeyPatch) -> None:
30
39
  monkeypatch.setattr(setup_impl, "_install_hooks", lambda _hook_wrapper: None)
@@ -81,6 +90,43 @@ def test_runtime_requires_a_module_level_app(tmp_path: Path) -> None:
81
90
  assert "must define 'app'" in completed.stderr
82
91
 
83
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
+
84
130
  def test_prepared_runtime_does_not_run_an_inner_installer(tmp_path: Path) -> None:
85
131
  source = tmp_path / "prepared"
86
132
  _write_executable(
@@ -162,6 +208,14 @@ def test_direct_command_dynamic_import_and_static_listing(tmp_path: Path) -> Non
162
208
  text=True,
163
209
  check=False,
164
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
+ )
165
219
  imported = run_python(
166
220
  ["-c", "from toolfuncs import greet; print(greet.hello('Ada')['message'])"],
167
221
  cwd=tmp_path,
@@ -171,6 +225,8 @@ def test_direct_command_dynamic_import_and_static_listing(tmp_path: Path) -> Non
171
225
 
172
226
  assert direct.returncode == 0, direct.stderr
173
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"
174
230
  assert imported.returncode == 0, imported.stderr
175
231
  assert imported.stdout == "Hello, Ada.\n"
176
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.2"
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