vitruvian-tool-kit 0.10.0__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.
- vitruvian_tool_kit-0.10.0/.gitignore +2 -0
- vitruvian_tool_kit-0.10.0/LICENSE +21 -0
- vitruvian_tool_kit-0.10.0/PKG-INFO +68 -0
- vitruvian_tool_kit-0.10.0/README.md +53 -0
- vitruvian_tool_kit-0.10.0/pyproject.toml +53 -0
- vitruvian_tool_kit-0.10.0/tests/test_generated.py +114 -0
- vitruvian_tool_kit-0.10.0/tests/test_tool.py +509 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/__init__.py +60 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/generated/__init__.py +24 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/generated/manifest.py +237 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/generated/messages.py +645 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/generated/methods.py +210 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/tool.py +334 -0
- vitruvian_tool_kit-0.10.0/vitruvian_tool_kit/wire.py +237 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VitruvianSoftware
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vitruvian-tool-kit
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: Write a tool for the Vitruvian app in Python: declare its commands, handle them, call the app.
|
|
5
|
+
Project-URL: Repository, https://github.com/VitruvianSoftware/vitruvian-sdk
|
|
6
|
+
Project-URL: Documentation, https://github.com/VitruvianSoftware/vitruvian-sdk/blob/main/docs/writing-a-tool.md
|
|
7
|
+
Author: VitruvianSoftware
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: json-rpc,macos,tool,vitruvian
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# vitruvian-tool-kit
|
|
17
|
+
|
|
18
|
+
Write a tool for the [Vitruvian](https://vitruviansoftware.dev) Mac app in
|
|
19
|
+
Python. A tool is a folder with a `vitruvian-tool.json` and a program; the app
|
|
20
|
+
starts the program and talks to it over its standard input and output. This
|
|
21
|
+
kit is that conversation: you say what each command does, and call the app
|
|
22
|
+
from inside a command as an ordinary function call.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from vitruvian_tool_kit import Tool
|
|
26
|
+
|
|
27
|
+
tool = Tool(version="0.1.0")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@tool.command("hello")
|
|
31
|
+
def hello(app):
|
|
32
|
+
app.notify.hud(icon="hand.wave", message="Hello")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
tool.run()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **One call per message.** `app.notify.hud(...)`, `app.clipboard.read_text()`,
|
|
39
|
+
`app.storage.get(key=...)`: each is one request of the protocol, named as
|
|
40
|
+
the protocol names it. The app may refuse any of them; the call then raises
|
|
41
|
+
`Refusal`. Raise `CommandRefused("why")` to refuse a command yourself.
|
|
42
|
+
- **Synchronous, on one thread.** A call to the app waits for its answer, ten
|
|
43
|
+
seconds at most. A handler must not be `async`.
|
|
44
|
+
- **Never `print`.** Standard output is the link to the app. Write to
|
|
45
|
+
standard error, or call `app.log(message=...)`.
|
|
46
|
+
|
|
47
|
+
## Shipping it
|
|
48
|
+
|
|
49
|
+
The app starts a tool with Apple's `python3` (`/usr/bin/python3`, Python
|
|
50
|
+
3.9), which has none of your packages. So the kit goes inside the tool's
|
|
51
|
+
folder, and the program finds it there:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
python3 -m pip install --target lib vitruvian-tool-kit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import os, sys
|
|
59
|
+
|
|
60
|
+
HERE = os.path.dirname(os.path.realpath(__file__))
|
|
61
|
+
sys.path.insert(0, os.path.join(HERE, "..", "lib"))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`vitruvian-tool init --template python-kit` writes a tool set up this way.
|
|
65
|
+
The kit needs nothing but the standard library and Python 3.9 or later.
|
|
66
|
+
|
|
67
|
+
The whole guide, the protocol and the other kits are in
|
|
68
|
+
[the SDK](https://github.com/VitruvianSoftware/vitruvian-sdk). MIT licence.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# vitruvian-tool-kit
|
|
2
|
+
|
|
3
|
+
Write a tool for the [Vitruvian](https://vitruviansoftware.dev) Mac app in
|
|
4
|
+
Python. A tool is a folder with a `vitruvian-tool.json` and a program; the app
|
|
5
|
+
starts the program and talks to it over its standard input and output. This
|
|
6
|
+
kit is that conversation: you say what each command does, and call the app
|
|
7
|
+
from inside a command as an ordinary function call.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from vitruvian_tool_kit import Tool
|
|
11
|
+
|
|
12
|
+
tool = Tool(version="0.1.0")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@tool.command("hello")
|
|
16
|
+
def hello(app):
|
|
17
|
+
app.notify.hud(icon="hand.wave", message="Hello")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
tool.run()
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **One call per message.** `app.notify.hud(...)`, `app.clipboard.read_text()`,
|
|
24
|
+
`app.storage.get(key=...)`: each is one request of the protocol, named as
|
|
25
|
+
the protocol names it. The app may refuse any of them; the call then raises
|
|
26
|
+
`Refusal`. Raise `CommandRefused("why")` to refuse a command yourself.
|
|
27
|
+
- **Synchronous, on one thread.** A call to the app waits for its answer, ten
|
|
28
|
+
seconds at most. A handler must not be `async`.
|
|
29
|
+
- **Never `print`.** Standard output is the link to the app. Write to
|
|
30
|
+
standard error, or call `app.log(message=...)`.
|
|
31
|
+
|
|
32
|
+
## Shipping it
|
|
33
|
+
|
|
34
|
+
The app starts a tool with Apple's `python3` (`/usr/bin/python3`, Python
|
|
35
|
+
3.9), which has none of your packages. So the kit goes inside the tool's
|
|
36
|
+
folder, and the program finds it there:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
python3 -m pip install --target lib vitruvian-tool-kit
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import os, sys
|
|
44
|
+
|
|
45
|
+
HERE = os.path.dirname(os.path.realpath(__file__))
|
|
46
|
+
sys.path.insert(0, os.path.join(HERE, "..", "lib"))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`vitruvian-tool init --template python-kit` writes a tool set up this way.
|
|
50
|
+
The kit needs nothing but the standard library and Python 3.9 or later.
|
|
51
|
+
|
|
52
|
+
The whole guide, the protocol and the other kits are in
|
|
53
|
+
[the SDK](https://github.com/VitruvianSoftware/vitruvian-sdk). MIT licence.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright (c) 2026 VitruvianSoftware
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling>=1.27"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[project]
|
|
26
|
+
name = "vitruvian-tool-kit"
|
|
27
|
+
# The SDK's version: every kit is published at it on every release
|
|
28
|
+
# (release-please, component vitruvian-sdk).
|
|
29
|
+
version = "0.10.0"
|
|
30
|
+
description = "Write a tool for the Vitruvian app in Python: declare its commands, handle them, call the app."
|
|
31
|
+
readme = "README.md"
|
|
32
|
+
license = "MIT"
|
|
33
|
+
license-files = ["LICENSE"]
|
|
34
|
+
# 3.9: the python3 the Command Line Tools put on every Mac, which is the one
|
|
35
|
+
# the app finds when it starts a tool. The standard library only.
|
|
36
|
+
requires-python = ">=3.9"
|
|
37
|
+
authors = [{ name = "VitruvianSoftware" }]
|
|
38
|
+
keywords = ["vitruvian", "macos", "tool", "json-rpc"]
|
|
39
|
+
classifiers = [
|
|
40
|
+
"Programming Language :: Python :: 3",
|
|
41
|
+
"Operating System :: MacOS",
|
|
42
|
+
]
|
|
43
|
+
dependencies = []
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Repository = "https://github.com/VitruvianSoftware/vitruvian-sdk"
|
|
47
|
+
Documentation = "https://github.com/VitruvianSoftware/vitruvian-sdk/blob/main/docs/writing-a-tool.md"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["vitruvian_tool_kit"]
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.sdist]
|
|
53
|
+
include = ["vitruvian_tool_kit", "tests", "README.md", "LICENSE"]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Copyright (c) 2026 VitruvianSoftware
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
"""The generated types read every example the protocol calls valid, and give
|
|
21
|
+
back what they read.
|
|
22
|
+
|
|
23
|
+
protocol/examples/ is what the schemas and the Swift types are both held to
|
|
24
|
+
(protocol_examples_test.py, ProtocolExamplesTests.swift). Reading the same
|
|
25
|
+
files here holds the generated Python to them too: a field the generator
|
|
26
|
+
dropped or misnamed fails here, by the example's own words.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
import unittest
|
|
33
|
+
|
|
34
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
sys.path.insert(0, os.path.dirname(HERE))
|
|
36
|
+
PROTOCOL = os.path.join(HERE, "..", "..", "..", "protocol")
|
|
37
|
+
|
|
38
|
+
from vitruvian_tool_kit import METHODS, ShapeError, ToolManifest # noqa: E402
|
|
39
|
+
from vitruvian_tool_kit.generated import messages # noqa: E402
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load(name):
|
|
43
|
+
with open(os.path.join(PROTOCOL, name), encoding="utf-8") as handle:
|
|
44
|
+
return json.load(handle)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def type_named(name):
|
|
48
|
+
title = {"empty": "EmptyObject", "error": "RPCErrorObject"}.get(
|
|
49
|
+
name, name[:1].upper() + name[1:]
|
|
50
|
+
)
|
|
51
|
+
return getattr(messages, title)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GeneratedTests(unittest.TestCase):
|
|
55
|
+
def test_every_valid_message_reads_and_writes_back(self):
|
|
56
|
+
"""What is written back is what was read, less a field the schema does
|
|
57
|
+
not name: the protocol says such a field is ignored."""
|
|
58
|
+
table = {m.method: m for m in METHODS}
|
|
59
|
+
named = {
|
|
60
|
+
name: set(node.get("properties", {}))
|
|
61
|
+
for name, node in load("messages.schema.json")["$defs"].items()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def known(name, value):
|
|
65
|
+
return {key: item for key, item in value.items() if key in named[name]}
|
|
66
|
+
|
|
67
|
+
read = 0
|
|
68
|
+
for example in load("examples/messages.json")["examples"]:
|
|
69
|
+
if not example["valid"]:
|
|
70
|
+
continue
|
|
71
|
+
message = example["message"]
|
|
72
|
+
row = table.get(example.get("answers", message.get("method")))
|
|
73
|
+
if row is None:
|
|
74
|
+
continue
|
|
75
|
+
with self.subTest(example["about"]):
|
|
76
|
+
if "method" in message and row.params:
|
|
77
|
+
value = type_named(row.params).from_json(message.get("params"))
|
|
78
|
+
self.assertEqual(
|
|
79
|
+
value.to_json(), known(row.params, message["params"])
|
|
80
|
+
)
|
|
81
|
+
read += 1
|
|
82
|
+
elif "result" in message:
|
|
83
|
+
value = type_named(row.result).from_json(message["result"])
|
|
84
|
+
self.assertEqual(
|
|
85
|
+
value.to_json(), known(row.result, message["result"])
|
|
86
|
+
)
|
|
87
|
+
read += 1
|
|
88
|
+
self.assertGreater(read, 20)
|
|
89
|
+
|
|
90
|
+
def test_a_wrong_field_is_named(self):
|
|
91
|
+
with self.assertRaises(ShapeError) as raised:
|
|
92
|
+
messages.NotifyHUDParams.from_json({"icon": "x", "message": 3})
|
|
93
|
+
self.assertEqual(raised.exception.where, "notifyHUDParams.message")
|
|
94
|
+
|
|
95
|
+
def test_every_valid_manifest_reads(self):
|
|
96
|
+
examples = load("examples/manifests.json")
|
|
97
|
+
read = 0
|
|
98
|
+
for example in examples["examples"]:
|
|
99
|
+
if not example["valid"]:
|
|
100
|
+
continue
|
|
101
|
+
manifest = dict(examples["base"])
|
|
102
|
+
for key, value in example["change"].items():
|
|
103
|
+
if value is None:
|
|
104
|
+
manifest.pop(key, None)
|
|
105
|
+
else:
|
|
106
|
+
manifest[key] = value
|
|
107
|
+
with self.subTest(example["about"]):
|
|
108
|
+
ToolManifest.from_json(manifest)
|
|
109
|
+
read += 1
|
|
110
|
+
self.assertGreater(read, 5)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
unittest.main()
|