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.
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/PKG-INFO +1 -1
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/pyproject.toml +1 -1
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/__init__.py +1 -1
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/__main__.py +20 -3
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_sdk_cli.py +48 -1
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/uv.lock +1 -1
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/.github/workflows/release.yml +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/.gitignore +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/.python-version +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/README.md +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/docs/contract.md +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_errors.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_import_path.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_setup.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/_tools.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/agent_hooks.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/py.typed +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/src/toolfuncs/sdk.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/__init__.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/conftest.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_agent_hooks.py +0 -0
- {toolfuncs-0.3.3 → toolfuncs-0.3.4}/tests/test_import_path.py +0 -0
- {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
|
+
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|