modularizer-plat 0.1.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.
- modularizer_plat-0.1.0/.gitignore +89 -0
- modularizer_plat-0.1.0/PKG-INFO +588 -0
- modularizer_plat-0.1.0/README.md +572 -0
- modularizer_plat-0.1.0/plat/__init__.py +217 -0
- modularizer_plat-0.1.0/plat/__main__.py +5 -0
- modularizer_plat-0.1.0/plat/auth.py +139 -0
- modularizer_plat-0.1.0/plat/call_sessions.py +139 -0
- modularizer_plat-0.1.0/plat/cli.py +408 -0
- modularizer_plat-0.1.0/plat/cli_main.py +475 -0
- modularizer_plat-0.1.0/plat/client.py +180 -0
- modularizer_plat-0.1.0/plat/date_coerce.py +318 -0
- modularizer_plat-0.1.0/plat/decorators.py +90 -0
- modularizer_plat-0.1.0/plat/errors.py +94 -0
- modularizer_plat-0.1.0/plat/file_queue.py +29 -0
- modularizer_plat-0.1.0/plat/file_queue_protocol_plugin.py +203 -0
- modularizer_plat-0.1.0/plat/file_transport_plugin.py +87 -0
- modularizer_plat-0.1.0/plat/http_transport_plugin.py +167 -0
- modularizer_plat-0.1.0/plat/literalenum_support.py +170 -0
- modularizer_plat-0.1.0/plat/logging.py +39 -0
- modularizer_plat-0.1.0/plat/metadata.py +26 -0
- modularizer_plat-0.1.0/plat/openapi_client.py +1016 -0
- modularizer_plat-0.1.0/plat/openapi_codegen.py +432 -0
- modularizer_plat-0.1.0/plat/plugins.py +523 -0
- modularizer_plat-0.1.0/plat/protocol_plugin.py +93 -0
- modularizer_plat-0.1.0/plat/py.typed +0 -0
- modularizer_plat-0.1.0/plat/response_serialize.py +76 -0
- modularizer_plat-0.1.0/plat/routing.py +45 -0
- modularizer_plat-0.1.0/plat/rpc.py +4 -0
- modularizer_plat-0.1.0/plat/rpc_protocol_plugin.py +169 -0
- modularizer_plat-0.1.0/plat/rpc_transport_plugin.py +68 -0
- modularizer_plat-0.1.0/plat/server.py +1444 -0
- modularizer_plat-0.1.0/plat/server_types.py +117 -0
- modularizer_plat-0.1.0/plat/tools.py +150 -0
- modularizer_plat-0.1.0/plat/transport_plugin.py +71 -0
- modularizer_plat-0.1.0/plat/transports.py +32 -0
- modularizer_plat-0.1.0/plat/type_registry.py +23 -0
- modularizer_plat-0.1.0/pyproject.toml +29 -0
- modularizer_plat-0.1.0/samples/README.md +109 -0
- modularizer_plat-0.1.0/samples/__init__.py +1 -0
- modularizer_plat-0.1.0/samples/basic/__init__.py +1 -0
- modularizer_plat-0.1.0/samples/basic/models.py +44 -0
- modularizer_plat-0.1.0/samples/basic/orders.api.py +25 -0
- modularizer_plat-0.1.0/samples/basic/products.api.py +52 -0
- modularizer_plat-0.1.0/samples/basic/server.py +23 -0
- modularizer_plat-0.1.0/samples/long_running/__init__.py +1 -0
- modularizer_plat-0.1.0/samples/long_running/client.py +27 -0
- modularizer_plat-0.1.0/samples/long_running/imports.api.py +47 -0
- modularizer_plat-0.1.0/samples/long_running/models.py +14 -0
- modularizer_plat-0.1.0/samples/long_running/server.py +23 -0
- modularizer_plat-0.1.0/tests/test_cli_helpers.py +68 -0
- modularizer_plat-0.1.0/tests/test_date_coerce.py +103 -0
- modularizer_plat-0.1.0/tests/test_literalenum_support.py +47 -0
- modularizer_plat-0.1.0/tests/test_openapi_client.py +290 -0
- modularizer_plat-0.1.0/tests/test_response_serialize.py +66 -0
- modularizer_plat-0.1.0/tests/test_samples.py +145 -0
- modularizer_plat-0.1.0/tests/test_server.py +449 -0
- modularizer_plat-0.1.0/tests/test_type_registry.py +49 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
############################
|
|
2
|
+
# Node
|
|
3
|
+
############################
|
|
4
|
+
|
|
5
|
+
node_modules/
|
|
6
|
+
npm-debug.log*
|
|
7
|
+
yarn-debug.log*
|
|
8
|
+
yarn-error.log*
|
|
9
|
+
pnpm-debug.log*
|
|
10
|
+
|
|
11
|
+
############################
|
|
12
|
+
# Build output
|
|
13
|
+
############################
|
|
14
|
+
|
|
15
|
+
dist/
|
|
16
|
+
build/
|
|
17
|
+
coverage/
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
############################
|
|
22
|
+
# TypeScript
|
|
23
|
+
############################
|
|
24
|
+
|
|
25
|
+
*.tsbuildinfo
|
|
26
|
+
|
|
27
|
+
############################
|
|
28
|
+
# Environment variables
|
|
29
|
+
############################
|
|
30
|
+
|
|
31
|
+
.env
|
|
32
|
+
typpescript/.env.sample
|
|
33
|
+
!.env.example
|
|
34
|
+
|
|
35
|
+
############################
|
|
36
|
+
# Logs
|
|
37
|
+
############################
|
|
38
|
+
|
|
39
|
+
logs/
|
|
40
|
+
*.log
|
|
41
|
+
|
|
42
|
+
############################
|
|
43
|
+
# Editors
|
|
44
|
+
############################
|
|
45
|
+
|
|
46
|
+
.vscode/
|
|
47
|
+
.idea/
|
|
48
|
+
*.swp
|
|
49
|
+
*.swo
|
|
50
|
+
*.swn
|
|
51
|
+
*~
|
|
52
|
+
|
|
53
|
+
############################
|
|
54
|
+
# OS files
|
|
55
|
+
############################
|
|
56
|
+
|
|
57
|
+
.DS_Store
|
|
58
|
+
Thumbs.db
|
|
59
|
+
|
|
60
|
+
############################
|
|
61
|
+
# Temporary files
|
|
62
|
+
############################
|
|
63
|
+
|
|
64
|
+
tmp/
|
|
65
|
+
temp/
|
|
66
|
+
|
|
67
|
+
############################
|
|
68
|
+
# Misc
|
|
69
|
+
############################
|
|
70
|
+
|
|
71
|
+
*.pid
|
|
72
|
+
*.seed
|
|
73
|
+
*.lock
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
###
|
|
78
|
+
**/*u-*
|
|
79
|
+
*-u.*
|
|
80
|
+
**/*untracked-*
|
|
81
|
+
*-untracked.*
|
|
82
|
+
**/*u_*
|
|
83
|
+
*_u.*
|
|
84
|
+
**/*untracked_*
|
|
85
|
+
*_untracked.*
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
*venv*
|
|
89
|
+
*.egg-info
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modularizer-plat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Protocol and Language Agnostic Tooling Yielding Universal Semantics
|
|
5
|
+
Project-URL: Repository, https://github.com/modularizer/plat
|
|
6
|
+
License-Expression: Unlicense
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: fastapi>=0.115
|
|
9
|
+
Requires-Dist: httpx>=0.25
|
|
10
|
+
Requires-Dist: pydantic>=2.8
|
|
11
|
+
Requires-Dist: pyjwt>=2.8
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: uvicorn>=0.30
|
|
14
|
+
Requires-Dist: websockets>=12.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# 🦫 plat (aka "platypus")
|
|
18
|
+
> **"call it like you're there"**
|
|
19
|
+
|
|
20
|
+
plat is a **Protocol and Language Agnostic Tooling Yielding Proxy-like Universal Semantics**. In short:
|
|
21
|
+
- just write your methods...
|
|
22
|
+
- ...then call them
|
|
23
|
+
|
|
24
|
+
#### It doesn't matter what you are designing
|
|
25
|
+
- Standard REST API
|
|
26
|
+
- MCP Server
|
|
27
|
+
- A CLI tool
|
|
28
|
+
- Database CRUD
|
|
29
|
+
- AI Tool Calls
|
|
30
|
+
- Inter Process Communication
|
|
31
|
+
- Worker Queues
|
|
32
|
+
- Client-to-client Chat Room
|
|
33
|
+
|
|
34
|
+
> At the end of the day, all there is is **handlers and callers**.
|
|
35
|
+
|
|
36
|
+
#### Yes there is an API layer there, but it isn't really your concern.
|
|
37
|
+
- auth
|
|
38
|
+
- exceptions
|
|
39
|
+
- headers
|
|
40
|
+
- serialization/deserialization/type coercion
|
|
41
|
+
- param validation
|
|
42
|
+
- route building
|
|
43
|
+
- response building
|
|
44
|
+
- rate limiting
|
|
45
|
+
- caching
|
|
46
|
+
- client-side retry logic
|
|
47
|
+
- client-side param validation
|
|
48
|
+
|
|
49
|
+
> All of that can be **handled easily behind the scenes** with easily standardizable **middleware plugins**.
|
|
50
|
+
|
|
51
|
+
#### In fact, the transport method itself doesn't even matter:
|
|
52
|
+
- HTTP
|
|
53
|
+
- WS
|
|
54
|
+
- File Queues
|
|
55
|
+
- DB triggers
|
|
56
|
+
- Zapier Integrations
|
|
57
|
+
- External APIs
|
|
58
|
+
- USPS mail delivery
|
|
59
|
+
|
|
60
|
+
> It's all important, but your function handler **does not need to know** about it, and **neither does your call site**.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
#### Okay, seriously ... what are we even talking about here?
|
|
64
|
+
Fair... try this
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
# Quickstart
|
|
68
|
+
|
|
69
|
+
## 1. Install
|
|
70
|
+
```bash
|
|
71
|
+
pip install modularizer-plat
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 2. Make a server
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from plat import Controller, POST, RouteContext, create_server
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@Controller()
|
|
83
|
+
class OrdersApi:
|
|
84
|
+
@POST()
|
|
85
|
+
def createOrder(
|
|
86
|
+
self,
|
|
87
|
+
input: dict[str, str | int],
|
|
88
|
+
ctx: RouteContext,
|
|
89
|
+
) -> dict[str, str]:
|
|
90
|
+
return {"orderId": "ord_123", "status": "pending"}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
server = create_server({"port": 3000}, OrdersApi)
|
|
94
|
+
server.listen()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 3. Serve it with the CLI
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
plat serve
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 4. See the docs
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
open http://localhost:3000/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 5. Make a client
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
plat gen client http://localhost:3000/ --dst client.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from client import create_client
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
client = create_client("http://localhost:3000")
|
|
120
|
+
order = client.createOrder(itemId="sku_123", qty=2)
|
|
121
|
+
print(order)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 6. Make a CLI
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
plat gen cli http://localhost:3000/ --dst cli.py
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python cli.py createOrder --itemId=sku_123 --qty=2
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 7. Let your AI loose
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from plat import create_openapi_client
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
client = create_openapi_client(
|
|
141
|
+
"http://localhost:3000/openapi.json",
|
|
142
|
+
"http://localhost:3000",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
tools = client.tools
|
|
146
|
+
# hand `tools` to your AI provider
|
|
147
|
+
# then call back into `client.createOrder(...)` when it selects a tool
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Use it
|
|
151
|
+
|
|
152
|
+
- Client call: `client.createOrder(itemId="...", qty=2)`
|
|
153
|
+
- Generated CLI call: `plat createOrder --itemId=... --qty=2`
|
|
154
|
+
- AI tool call: an LLM can see `createOrder` as a tool with a name, input shape, and result shape
|
|
155
|
+
- Documentation: generated `openapi.json`, plus docs/tool metadata derived from the same method
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
## 🎯 Why plat exists
|
|
161
|
+
|
|
162
|
+
Most API frameworks make you think about:
|
|
163
|
+
|
|
164
|
+
- routes
|
|
165
|
+
- methods
|
|
166
|
+
- headers
|
|
167
|
+
- authentication
|
|
168
|
+
- serialization/deserialization/coercion
|
|
169
|
+
- sending responses
|
|
170
|
+
- REST hierarchies
|
|
171
|
+
- request shapes
|
|
172
|
+
- wire protocols
|
|
173
|
+
- client generation drift
|
|
174
|
+
- transport details
|
|
175
|
+
|
|
176
|
+
plat tries to make most of that disappear.
|
|
177
|
+
|
|
178
|
+
What you should be thinking about is:
|
|
179
|
+
|
|
180
|
+
- what methods exist
|
|
181
|
+
- what input each method accepts
|
|
182
|
+
- what result each method returns
|
|
183
|
+
|
|
184
|
+
That’s the part plat treats as sacred.
|
|
185
|
+
|
|
186
|
+
> It feels like you are adding a new class, and behind the scenes an API is born
|
|
187
|
+
|
|
188
|
+
One of the biggest reasons plat exists is to make it easy to use **any AI provider**:
|
|
189
|
+
|
|
190
|
+
- on the client side
|
|
191
|
+
- on the server side
|
|
192
|
+
- as the initiator of your tasks
|
|
193
|
+
- or as the doer of your tasks
|
|
194
|
+
- or both
|
|
195
|
+
|
|
196
|
+
Everything below is in service of that same promise: define useful methods once, then let clients, CLIs, docs, and AI tools all see the same surface.
|
|
197
|
+
|
|
198
|
+
### Diagram
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
┌───────────────────────────────────────────────────────┐
|
|
202
|
+
│ Tool Definitions │
|
|
203
|
+
│ (controllers + decorated methods) │
|
|
204
|
+
│ │
|
|
205
|
+
│ TypeScript (plain types) Python (type hints) │
|
|
206
|
+
│ class Orders { @Controller() │
|
|
207
|
+
│ @Post() class Orders: │
|
|
208
|
+
│ create(input, ctx) {} @POST() │
|
|
209
|
+
│ @Get() def create(self): ... │
|
|
210
|
+
│ list(input, ctx) {} @GET() │
|
|
211
|
+
│ } def list(self): ... │
|
|
212
|
+
└──────────────────────────┬────────────────────────────┘
|
|
213
|
+
│
|
|
214
|
+
┌──────────┴──────────┐
|
|
215
|
+
│ Operation Registry │
|
|
216
|
+
│ │
|
|
217
|
+
│ operationId ─────► bound handler
|
|
218
|
+
│ method+path ─────► bound handler
|
|
219
|
+
└──────────┬──────────┘
|
|
220
|
+
│
|
|
221
|
+
server protocol plugins (how tool calls arrive)
|
|
222
|
+
│
|
|
223
|
+
┌───────┬────────┬─────┴───┬────────┬───────┬───────┐
|
|
224
|
+
│ │ │ │ │ │ │
|
|
225
|
+
┌───┴──┐┌───┴──┐┌────┴───┐┌────┴───┐┌───┴──┐┌───┴──┐┌───┴──┐
|
|
226
|
+
│ HTTP ││ WS ││ File ││ WebRTC ││ DB ││BullMQ││ MQTT │
|
|
227
|
+
│ REST ││ RPC ││ Queue ││ Data ││ Poll ││ Redis││Pub/ │
|
|
228
|
+
│ ││ ││ ││ Chan ││ Rows ││Queue ││ Sub │
|
|
229
|
+
└──────┘└──────┘└────────┘└────────┘└──────┘└──────┘└──────┘
|
|
230
|
+
...
|
|
231
|
+
literally anything that can carry a JSON envelope
|
|
232
|
+
...
|
|
233
|
+
┌──────┐┌──────┐┌────────┐┌────────┐┌──────┐┌──────┐┌──────┐
|
|
234
|
+
│ HTTP ││ WS ││ File ││ WebRTC ││ POST ││ eBay ││ FB │
|
|
235
|
+
│ fetch││ RPC ││ IO ││ Peer ││to ext││ list ││ Msg │
|
|
236
|
+
│ ││ ││ ││ Conn ││ API ││ poll ││ poll │
|
|
237
|
+
└───┬──┘└───┬──┘└────┬───┘└────┬───┘└───┬──┘└───┬──┘└───┬──┘
|
|
238
|
+
│ │ │ │ │ │ │
|
|
239
|
+
└───────┴────────┴────┬────┴────────┴───────┴───────┘
|
|
240
|
+
│
|
|
241
|
+
client transport plugins (how tool calls are sent)
|
|
242
|
+
│
|
|
243
|
+
┌──────────┴──────────┐
|
|
244
|
+
│ OpenAPI Client │
|
|
245
|
+
│ (typed proxy) │
|
|
246
|
+
└──────────┬──────────┘
|
|
247
|
+
│
|
|
248
|
+
┌─────────┬────────┼────────┬───────────┐
|
|
249
|
+
│ │ │ │ │
|
|
250
|
+
┌────┴───┐┌────┴───┐┌───┴───┐┌───┴────┐┌─────┴─────┐
|
|
251
|
+
│ TS ││ Python ││ CLI ││ curl ││ LLM Agent │
|
|
252
|
+
│ ││ ││ ││ bash ││ │
|
|
253
|
+
│ node ││ sync ││ plat do ││ write ││ Claude │
|
|
254
|
+
│ bun ││ async ││plat poll││ JSON ││ ChatGPT │
|
|
255
|
+
│ browser││ promise││ ││to inbox││ Gemini │
|
|
256
|
+
└────────┘└────────┘└───────┘└────────┘└───────────┘
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The transport protocol, serialization, deserialization, queueing, and delivery mechanics are intentionally pushed out of your way.
|
|
260
|
+
|
|
261
|
+
That is especially powerful for AI-heavy systems, because you can keep swapping providers and execution patterns while preserving the same tool-shaped surface.
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
## 🎭 What the user experience should feel like
|
|
265
|
+
|
|
266
|
+
It should feel like this:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
order = client.createOrder(itemId="sku_123", qty=2)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Not like this:
|
|
273
|
+
|
|
274
|
+
- choosing between totally different client libraries
|
|
275
|
+
- hand-authoring RPC envelopes
|
|
276
|
+
- thinking about HTTP vs WS every time you call a method
|
|
277
|
+
- manually syncing method names, routes, SDK methods, and OpenAPI operation IDs
|
|
278
|
+
- re-implementing error handling, retries, and auth every time you make a request
|
|
279
|
+
- refactoring if you change languages or protocols
|
|
280
|
+
- endless boilerplate
|
|
281
|
+
|
|
282
|
+
It's like an SDK except you don't have to write it. It just comes for free with every `openapi.json`.
|
|
283
|
+
|
|
284
|
+
## 🦫 Flat by design
|
|
285
|
+
|
|
286
|
+
plat is intentionally opinionated about the API shape.
|
|
287
|
+
|
|
288
|
+
<details open>
|
|
289
|
+
<summary><strong>The rules</strong></summary>
|
|
290
|
+
|
|
291
|
+
- Method names are globally unique
|
|
292
|
+
- Method names are the canonical route names
|
|
293
|
+
- Input comes in as one object
|
|
294
|
+
- Return values matter as first-class API types
|
|
295
|
+
- Controllers organize code and docs, not URL hierarchies
|
|
296
|
+
- The API surface stays flat and easy to call
|
|
297
|
+
|
|
298
|
+
</details>
|
|
299
|
+
|
|
300
|
+
### Example
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from plat import Controller, GET, POST, RouteContext
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@Controller()
|
|
307
|
+
class OrdersApi:
|
|
308
|
+
@GET()
|
|
309
|
+
def getOrder(self, input: dict[str, str], ctx: RouteContext) -> dict[str, str]:
|
|
310
|
+
return {"id": input["id"], "status": "pending"}
|
|
311
|
+
|
|
312
|
+
@POST()
|
|
313
|
+
def createOrder(self, input: dict[str, str | int], ctx: RouteContext) -> dict[str, str]:
|
|
314
|
+
return {"id": "ord_123", "status": "pending"}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Canonical routes:
|
|
318
|
+
|
|
319
|
+
- `GET /getOrder`
|
|
320
|
+
- `POST /createOrder`
|
|
321
|
+
|
|
322
|
+
Canonical client calls:
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
client.getOrder(id="ord_123")
|
|
326
|
+
client.createOrder(itemId="sku_123", qty=2)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
That flatness matters because it makes the generated and dynamic clients obvious:
|
|
330
|
+
|
|
331
|
+
- easy for humans to remember
|
|
332
|
+
- easy for CLIs to expose
|
|
333
|
+
- easy for AI agents to understand
|
|
334
|
+
- easy for generated clients to mirror exactly
|
|
335
|
+
- easy to hand to any AI provider as tool definitions
|
|
336
|
+
|
|
337
|
+
## ⏳ Long-running calls without changing the mental model
|
|
338
|
+
|
|
339
|
+
Sometimes a method is fast:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
client.createOrder(itemId="sku_123", qty=2)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Sometimes a method is slow, and you want visibility:
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
import logging
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
logger = logging.getLogger("plat.example")
|
|
352
|
+
|
|
353
|
+
client.importCatalog(
|
|
354
|
+
source="s3://bucket/catalog.csv",
|
|
355
|
+
_rpc_events=lambda event: logger.info("%s %s", event.event, event.data),
|
|
356
|
+
)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Or you want deferred execution:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
handle = client.importCatalog(
|
|
363
|
+
source="s3://bucket/catalog.csv",
|
|
364
|
+
_execution="deferred",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
result = handle.wait()
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The important part is that it is still the same method.
|
|
371
|
+
|
|
372
|
+
As a bonus, in the right mode you can get:
|
|
373
|
+
|
|
374
|
+
- progress updates
|
|
375
|
+
- logs
|
|
376
|
+
- chunks/messages
|
|
377
|
+
- cancellation
|
|
378
|
+
|
|
379
|
+
That is what most users actually care about. The carrier and plugin details are for transport authors.
|
|
380
|
+
|
|
381
|
+
## 🐍 Python support
|
|
382
|
+
|
|
383
|
+
plat supports Python servers and clients too.
|
|
384
|
+
|
|
385
|
+
You can:
|
|
386
|
+
|
|
387
|
+
- write Python controllers with plat decorators
|
|
388
|
+
- generate OpenAPI from `*.api.py`
|
|
389
|
+
- generate Python clients from OpenAPI
|
|
390
|
+
- use sync, async, and promise-style Python clients
|
|
391
|
+
|
|
392
|
+
<details>
|
|
393
|
+
<summary><strong>Python highlights</strong></summary>
|
|
394
|
+
|
|
395
|
+
- Sync clients
|
|
396
|
+
- Async clients
|
|
397
|
+
- Promise-style clients
|
|
398
|
+
- Deferred call handles
|
|
399
|
+
- Automatic input coercion
|
|
400
|
+
- Automatic output serialization
|
|
401
|
+
- First-class HTTP exception types
|
|
402
|
+
|
|
403
|
+
</details>
|
|
404
|
+
|
|
405
|
+
## 🔌 One client, many transports
|
|
406
|
+
|
|
407
|
+
The same method call should stay usable even when transport changes.
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
http_client = create_client("http://localhost:3000")
|
|
411
|
+
rpc_client = create_client("ws://localhost:3000")
|
|
412
|
+
file_client = create_client("file:///tmp/plat-queue")
|
|
413
|
+
|
|
414
|
+
http_client.createOrder(itemId="sku_123", qty=2)
|
|
415
|
+
rpc_client.createOrder(itemId="sku_123", qty=2)
|
|
416
|
+
file_client.createOrder(itemId="sku_123", qty=2)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Same tool call. Different carrier.
|
|
420
|
+
|
|
421
|
+
### Diagram
|
|
422
|
+
|
|
423
|
+
```txt
|
|
424
|
+
createOrder({ itemId, qty })
|
|
425
|
+
│
|
|
426
|
+
┌───────┼────────┐
|
|
427
|
+
│ │ │
|
|
428
|
+
▼ ▼ ▼
|
|
429
|
+
HTTP WS File
|
|
430
|
+
│ │ │
|
|
431
|
+
└───────┼────────┘
|
|
432
|
+
▼
|
|
433
|
+
same type-aware method call
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## 🤖 AI tool calling
|
|
437
|
+
|
|
438
|
+
plat is a natural fit for LLM tools because the API shape is already tool-shaped.
|
|
439
|
+
|
|
440
|
+
Every operation has:
|
|
441
|
+
|
|
442
|
+
- a stable name
|
|
443
|
+
- one input object
|
|
444
|
+
- one result
|
|
445
|
+
- generated schema
|
|
446
|
+
|
|
447
|
+
That means you can use AI providers in whichever role you want:
|
|
448
|
+
|
|
449
|
+
- as the caller deciding what tools to use
|
|
450
|
+
- as the worker fulfilling part of a task
|
|
451
|
+
- as interchangeable providers inside the same larger workflow
|
|
452
|
+
- on the client side or the server side
|
|
453
|
+
|
|
454
|
+
That makes the same API useful to:
|
|
455
|
+
|
|
456
|
+
- normal app code
|
|
457
|
+
- a CLI
|
|
458
|
+
- generated SDKs
|
|
459
|
+
- an LLM agent
|
|
460
|
+
|
|
461
|
+
## 🧰 Dynamic clients and generated clients
|
|
462
|
+
|
|
463
|
+
plat supports both styles.
|
|
464
|
+
|
|
465
|
+
<details>
|
|
466
|
+
<summary><strong>Dynamic clients</strong></summary>
|
|
467
|
+
|
|
468
|
+
The OpenAPI client can work directly from an OpenAPI document and a runtime proxy.
|
|
469
|
+
|
|
470
|
+
Best when you want:
|
|
471
|
+
|
|
472
|
+
- low ceremony
|
|
473
|
+
- transport flexibility
|
|
474
|
+
- no generated wrapper code
|
|
475
|
+
|
|
476
|
+
</details>
|
|
477
|
+
|
|
478
|
+
<details>
|
|
479
|
+
<summary><strong>Generated clients</strong></summary>
|
|
480
|
+
|
|
481
|
+
plat can also generate clients that materialize types and methods.
|
|
482
|
+
|
|
483
|
+
Especially useful in Python, where explicit generated models and wrappers help more than in TypeScript.
|
|
484
|
+
|
|
485
|
+
</details>
|
|
486
|
+
|
|
487
|
+
## 🖥️ CLI
|
|
488
|
+
|
|
489
|
+
plat includes a spec-first CLI.
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
plat gen openapi
|
|
493
|
+
plat gen client
|
|
494
|
+
plat gen cli
|
|
495
|
+
plat run openapi.json
|
|
496
|
+
plat serve
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The CLI is available from both Node and Python packaging surfaces, with capability moving toward parity.
|
|
500
|
+
|
|
501
|
+
## 🧩 Plugin architecture
|
|
502
|
+
|
|
503
|
+
The plugin architecture matters, but mostly as an implementation and extension story.
|
|
504
|
+
|
|
505
|
+
For normal plat users, the important thing is:
|
|
506
|
+
|
|
507
|
+
- methods stay flat
|
|
508
|
+
- typing stays strong
|
|
509
|
+
- clients feel direct
|
|
510
|
+
- transport details stay hidden
|
|
511
|
+
- provider complexity stays hidden too
|
|
512
|
+
|
|
513
|
+
For plugin developers, plat provides the escape hatch.
|
|
514
|
+
|
|
515
|
+
### Client-side transport plugins
|
|
516
|
+
|
|
517
|
+
Transport plugins follow a generic lifecycle:
|
|
518
|
+
|
|
519
|
+
- connect
|
|
520
|
+
- send request
|
|
521
|
+
- receive updates
|
|
522
|
+
- receive result
|
|
523
|
+
- disconnect
|
|
524
|
+
|
|
525
|
+
### Server-side protocol plugins
|
|
526
|
+
|
|
527
|
+
Protocol plugins are how tool calls arrive and how updates/results leave.
|
|
528
|
+
|
|
529
|
+
The goal is for the core method/typing/invocation story to be independent from:
|
|
530
|
+
|
|
531
|
+
- HTTP
|
|
532
|
+
- WebSockets
|
|
533
|
+
- Node
|
|
534
|
+
- any specific host process
|
|
535
|
+
|
|
536
|
+
<details>
|
|
537
|
+
<summary><strong>Why this matters</strong></summary>
|
|
538
|
+
|
|
539
|
+
That is what enables ideas like:
|
|
540
|
+
|
|
541
|
+
- a browser-side server
|
|
542
|
+
- a mobile-hosted server
|
|
543
|
+
- a worker-hosted server
|
|
544
|
+
- IndexedDB-backed local APIs
|
|
545
|
+
- WebRTC-based peer-to-peer tools
|
|
546
|
+
- custom carriers like DB polling or Redis streams
|
|
547
|
+
|
|
548
|
+
Most users should not have to think about any of this unless they are building a transport.
|
|
549
|
+
|
|
550
|
+
</details>
|
|
551
|
+
|
|
552
|
+
## 🌍 What makes plat different
|
|
553
|
+
|
|
554
|
+
Most systems force you to choose:
|
|
555
|
+
|
|
556
|
+
- REST or RPC
|
|
557
|
+
- server or client
|
|
558
|
+
- app integration or AI tool integration
|
|
559
|
+
- HTTP or "something custom"
|
|
560
|
+
|
|
561
|
+
plat is trying to collapse those choices into one model:
|
|
562
|
+
|
|
563
|
+
1. Define useful tools
|
|
564
|
+
2. Expose them everywhere
|
|
565
|
+
3. Change carriers without changing the API itself
|
|
566
|
+
|
|
567
|
+
That makes plat especially interesting for:
|
|
568
|
+
|
|
569
|
+
- internal tools
|
|
570
|
+
- AI agents
|
|
571
|
+
- automation systems
|
|
572
|
+
- offline-first systems
|
|
573
|
+
- browser-hosted local APIs
|
|
574
|
+
- weird protocol experiments
|
|
575
|
+
|
|
576
|
+
## 🛣️ Direction
|
|
577
|
+
|
|
578
|
+
plat is actively moving toward:
|
|
579
|
+
|
|
580
|
+
- deeper transport neutrality
|
|
581
|
+
- stronger portable server core extraction
|
|
582
|
+
- easier custom protocol plugins
|
|
583
|
+
- stronger generated clients and CLIs
|
|
584
|
+
- better cross-language symmetry
|
|
585
|
+
|
|
586
|
+
The north star is simple:
|
|
587
|
+
|
|
588
|
+
> **Define tools once. Call them from anywhere. Carry them over anything.**
|