python-plugin 0.1.0__py3-none-any.whl
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.
- pyplugin/__init__.py +54 -0
- pyplugin/_generated/__init__.py +0 -0
- pyplugin/_generated/grpc_broker_grpc.py +40 -0
- pyplugin/_generated/grpc_broker_pb2.py +41 -0
- pyplugin/_generated/grpc_controller_grpc.py +40 -0
- pyplugin/_generated/grpc_controller_pb2.py +39 -0
- pyplugin/_generated/grpc_stdio_grpc.py +41 -0
- pyplugin/_generated/grpc_stdio_pb2.py +42 -0
- pyplugin/broker.py +225 -0
- pyplugin/client.py +399 -0
- pyplugin/controller.py +22 -0
- pyplugin/cookie.py +43 -0
- pyplugin/errors.py +39 -0
- pyplugin/handshake.py +121 -0
- pyplugin/health.py +38 -0
- pyplugin/logging_bridge.py +70 -0
- pyplugin/mtls.py +169 -0
- pyplugin/plugin.py +38 -0
- pyplugin/process.py +66 -0
- pyplugin/proto/grpc_broker.proto +21 -0
- pyplugin/proto/grpc_controller.proto +12 -0
- pyplugin/proto/grpc_stdio.proto +22 -0
- pyplugin/reattach.py +27 -0
- pyplugin/server.py +204 -0
- pyplugin/stdio.py +36 -0
- pyplugin/transport.py +103 -0
- python_plugin-0.1.0.dist-info/METADATA +254 -0
- python_plugin-0.1.0.dist-info/RECORD +30 -0
- python_plugin-0.1.0.dist-info/WHEEL +4 -0
- python_plugin-0.1.0.dist-info/licenses/LICENSE +21 -0
pyplugin/transport.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Listener helpers — unix socket on POSIX, TCP loopback elsewhere or when forced.
|
|
2
|
+
|
|
3
|
+
Mirrors go-plugin's ``serverListener_unix`` / ``serverListener_tcp``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import socket
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from .handshake import NETWORK_TCP, NETWORK_UNIX
|
|
14
|
+
|
|
15
|
+
ENV_MIN_PORT = "PLUGIN_MIN_PORT"
|
|
16
|
+
ENV_MAX_PORT = "PLUGIN_MAX_PORT"
|
|
17
|
+
ENV_UNIX_SOCKET_DIR = "PLUGIN_UNIX_SOCKET_DIR"
|
|
18
|
+
ENV_UNIX_SOCKET_GROUP = "PLUGIN_UNIX_SOCKET_GROUP"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Listener:
|
|
23
|
+
"""A bound listener ready for ``grpc.Server.add_*_port``."""
|
|
24
|
+
network: str # "unix" or "tcp"
|
|
25
|
+
address: str # "/tmp/plugin123" or "127.0.0.1:port"
|
|
26
|
+
grpc_target: str # what to pass to grpc: "unix:/tmp/sock" or "127.0.0.1:port"
|
|
27
|
+
cleanup_path: str | None = None # filesystem path to remove on close, if any
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _is_windows() -> bool:
|
|
31
|
+
return sys.platform.startswith("win")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def open_listener(*, force_tcp: bool = False) -> Listener:
|
|
35
|
+
"""Open a plugin-side listener following go-plugin's defaults.
|
|
36
|
+
|
|
37
|
+
On Windows or when ``force_tcp``, picks an unused TCP port in the range
|
|
38
|
+
given by ``PLUGIN_MIN_PORT``/``PLUGIN_MAX_PORT``. Otherwise creates a
|
|
39
|
+
unique-named unix socket file (under ``PLUGIN_UNIX_SOCKET_DIR`` if set).
|
|
40
|
+
"""
|
|
41
|
+
if force_tcp or _is_windows():
|
|
42
|
+
return _open_tcp()
|
|
43
|
+
return _open_unix()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _open_unix() -> Listener:
|
|
47
|
+
socket_dir = os.environ.get(ENV_UNIX_SOCKET_DIR) or None
|
|
48
|
+
# Mirror go-plugin's serverListener_unix: claim a unique tempfile name,
|
|
49
|
+
# remove the file, hand the (now non-existent) path to grpc which will
|
|
50
|
+
# bind the unix socket itself.
|
|
51
|
+
fd, path = tempfile.mkstemp(prefix="plugin", dir=socket_dir)
|
|
52
|
+
os.close(fd)
|
|
53
|
+
os.remove(path)
|
|
54
|
+
return Listener(
|
|
55
|
+
network=NETWORK_UNIX,
|
|
56
|
+
address=path,
|
|
57
|
+
grpc_target=f"unix:{path}",
|
|
58
|
+
cleanup_path=path,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _open_tcp() -> Listener:
|
|
63
|
+
min_port = int(os.environ.get(ENV_MIN_PORT) or 0)
|
|
64
|
+
max_port = int(os.environ.get(ENV_MAX_PORT) or 0)
|
|
65
|
+
|
|
66
|
+
if min_port == 0 and max_port == 0:
|
|
67
|
+
# Pick a free port via OS.
|
|
68
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
69
|
+
s.bind(("127.0.0.1", 0))
|
|
70
|
+
port = s.getsockname()[1]
|
|
71
|
+
s.close()
|
|
72
|
+
return Listener(
|
|
73
|
+
network=NETWORK_TCP,
|
|
74
|
+
address=f"127.0.0.1:{port}",
|
|
75
|
+
grpc_target=f"127.0.0.1:{port}",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if min_port > max_port:
|
|
79
|
+
raise OSError(f"PLUGIN_MIN_PORT={min_port} > PLUGIN_MAX_PORT={max_port}")
|
|
80
|
+
|
|
81
|
+
for port in range(min_port, max_port + 1):
|
|
82
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
83
|
+
try:
|
|
84
|
+
s.bind(("127.0.0.1", port))
|
|
85
|
+
s.close()
|
|
86
|
+
return Listener(
|
|
87
|
+
network=NETWORK_TCP,
|
|
88
|
+
address=f"127.0.0.1:{port}",
|
|
89
|
+
grpc_target=f"127.0.0.1:{port}",
|
|
90
|
+
)
|
|
91
|
+
except OSError:
|
|
92
|
+
s.close()
|
|
93
|
+
continue
|
|
94
|
+
raise OSError("couldn't bind plugin TCP listener in configured port range")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def grpc_dial_target(network: str, address: str) -> str:
|
|
98
|
+
"""Translate a (network, address) pair from a handshake into a grpc.Dial target."""
|
|
99
|
+
if network == NETWORK_UNIX:
|
|
100
|
+
return f"unix:{address}"
|
|
101
|
+
if network == NETWORK_TCP:
|
|
102
|
+
return address
|
|
103
|
+
raise ValueError(f"unknown network type: {network!r}")
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wire-compatible Python port of go-plugin (gRPC subprocess plugins with AutoMTLS)
|
|
5
|
+
Project-URL: Homepage, https://github.com/mlund01/py-plugin
|
|
6
|
+
Project-URL: Repository, https://github.com/mlund01/py-plugin
|
|
7
|
+
Project-URL: Issues, https://github.com/mlund01/py-plugin/issues
|
|
8
|
+
Author: Max Lund
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: go-plugin,grpc,ipc,mtls,plugin,rpc
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: cryptography>=41
|
|
29
|
+
Requires-Dist: grpclib[protobuf]>=0.4.7
|
|
30
|
+
Requires-Dist: protobuf>=4.25
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: grpcio-tools>=1.60; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-timeout>=2.2; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# pyplugin
|
|
39
|
+
|
|
40
|
+
A Python port of [`go-plugin`](https://github.com/hashicorp/go-plugin),
|
|
41
|
+
**byte-for-byte wire-compatible** with the original — including AutoMTLS
|
|
42
|
+
with ECDSA P-521 ephemeral certs.
|
|
43
|
+
|
|
44
|
+
A Python host can launch a Go plugin built with go-plugin, and a Go host
|
|
45
|
+
built against go-plugin can launch a Python plugin built with pyplugin.
|
|
46
|
+
**Both directions, with or without AutoMTLS.** Verified against the
|
|
47
|
+
upstream `examples/grpc/plugin-go-grpc` binary in the test suite.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install python-plugin
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The PyPI distribution name is `python-plugin`; the Python import name is
|
|
56
|
+
`pyplugin` (`from pyplugin import Client, serve, ...`).
|
|
57
|
+
|
|
58
|
+
## Why grpclib (and async)
|
|
59
|
+
|
|
60
|
+
go-plugin generates **ECDSA P-521** ephemeral certificates for AutoMTLS.
|
|
61
|
+
`grpcio` is built on BoringSSL, which deliberately omits P-521 from its
|
|
62
|
+
TLS signature algorithm list — there's no way to configure it back in,
|
|
63
|
+
and we want exact wire-compat. `grpclib` is a pure-Python gRPC library
|
|
64
|
+
on top of Python's `ssl` module (OpenSSL), which supports P-521 freely.
|
|
65
|
+
That makes interop with stock go-plugin work out of the box.
|
|
66
|
+
|
|
67
|
+
The cost: the public API is **async**. Plugin servicers are `async def`;
|
|
68
|
+
host code uses `async with Client(...) as c: await c.start()` etc.
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
A complete runnable example lives in [`examples/greeter/`](examples/greeter/) —
|
|
73
|
+
clone the repo, `pip install -e '.[dev]'`, then:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python examples/greeter/host.py "ada" # insecure
|
|
77
|
+
AUTO_MTLS=1 python examples/greeter/host.py "ada" # P-521 mTLS
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Plugin (async)
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# my_plugin.py
|
|
84
|
+
from pyplugin import HandshakeConfig, Plugin, ServeConfig, serve
|
|
85
|
+
from grpclib.client import Channel
|
|
86
|
+
|
|
87
|
+
# stubs generated by grpclib's protoc plugin (see scripts/gen_protos.py):
|
|
88
|
+
import myservice_grpc, myservice_pb2
|
|
89
|
+
|
|
90
|
+
class MyServicer(myservice_grpc.MyServiceBase):
|
|
91
|
+
async def Greet(self, stream):
|
|
92
|
+
request = await stream.recv_message()
|
|
93
|
+
await stream.send_message(myservice_pb2.GreetResponse(message=f"hello {request.name}"))
|
|
94
|
+
|
|
95
|
+
class MyPlugin(Plugin):
|
|
96
|
+
def servicers(self, broker):
|
|
97
|
+
return [MyServicer()]
|
|
98
|
+
def stub(self, broker, channel: Channel):
|
|
99
|
+
return myservice_grpc.MyServiceStub(channel)
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
serve(ServeConfig(
|
|
103
|
+
handshake_config=HandshakeConfig(
|
|
104
|
+
protocol_version=1,
|
|
105
|
+
magic_cookie_key="MYPLUGIN_COOKIE",
|
|
106
|
+
magic_cookie_value="hello",
|
|
107
|
+
),
|
|
108
|
+
plugins={"my": MyPlugin()},
|
|
109
|
+
))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Host (async)
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import asyncio, sys
|
|
116
|
+
from pyplugin import Client, ClientConfig, HandshakeConfig
|
|
117
|
+
|
|
118
|
+
async def main():
|
|
119
|
+
async with Client(ClientConfig(
|
|
120
|
+
handshake_config=HandshakeConfig(1, "MYPLUGIN_COOKIE", "hello"),
|
|
121
|
+
plugins={"my": MyPlugin()},
|
|
122
|
+
cmd=[sys.executable, "my_plugin.py"],
|
|
123
|
+
auto_mtls=True, # P-521 mTLS, fully wire-compatible with go-plugin
|
|
124
|
+
)) as client:
|
|
125
|
+
stub = client.dispense("my")
|
|
126
|
+
resp = await stub.Greet(myservice_pb2.GreetRequest(name="world"))
|
|
127
|
+
print(resp.message)
|
|
128
|
+
|
|
129
|
+
asyncio.run(main())
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## What's implemented
|
|
133
|
+
|
|
134
|
+
| Feature | Status |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| stdout handshake protocol (6/7 segments, base64.RawStdEncoding cert) | ✅ |
|
|
137
|
+
| magic cookie validation | ✅ |
|
|
138
|
+
| gRPC transport: unix sockets (POSIX) and TCP loopback | ✅ |
|
|
139
|
+
| AutoMTLS with **ECDSA P-521 / SHA-512** (matches go-plugin) | ✅ |
|
|
140
|
+
| `GRPCController.Shutdown` graceful exit | ✅ |
|
|
141
|
+
| Kill ladder: Shutdown → SIGTERM → SIGKILL | ✅ |
|
|
142
|
+
| stderr forwarding with hclog parser (JSON + pretty) | ✅ |
|
|
143
|
+
| `GRPCBroker` bidirectional sub-channels (Accept/Dial) | ✅ |
|
|
144
|
+
| `GRPCStdio` post-handshake stdout/stderr stream | ✅ |
|
|
145
|
+
| `ReattachConfig` (host re-connects to running plugin) | ✅ |
|
|
146
|
+
| `VersionedPlugins` negotiation | ✅ |
|
|
147
|
+
| gRPC reflection + health (service name `plugin`) | ✅ |
|
|
148
|
+
| `PLUGIN_MULTIPLEX_GRPC` (broker over single socket) | ❌ deferred (advertised as not supported) |
|
|
149
|
+
|
|
150
|
+
## Verified Python ↔ Go interop
|
|
151
|
+
|
|
152
|
+
The test suite includes 4 real interop tests against upstream go-plugin:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
tests/interop/test_python_host_drives_go_plugin.py
|
|
156
|
+
test_python_host_drives_go_plugin_no_mtls ✓
|
|
157
|
+
test_python_host_drives_go_plugin_with_p521_automtls ✓
|
|
158
|
+
|
|
159
|
+
tests/interop/test_go_host_drives_python_plugin.py
|
|
160
|
+
test_go_host_drives_python_plugin_no_mtls ✓
|
|
161
|
+
test_go_host_drives_python_plugin_with_p521_automtls ✓
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
These run only when the binaries are present; the README of
|
|
165
|
+
`tests/interop/` describes how to build them. Out of the box you can
|
|
166
|
+
reproduce the matrix locally:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Build go-plugin's example KV plugin (Go)
|
|
170
|
+
git clone --depth=1 https://github.com/hashicorp/go-plugin /tmp/gp
|
|
171
|
+
(cd /tmp/gp/examples/grpc && go build -o /tmp/plugin-go-grpc ./plugin-go-grpc)
|
|
172
|
+
PYPLUGIN_GO_PLUGIN_KV=/tmp/plugin-go-grpc pytest tests/interop/test_python_host_drives_go_plugin.py
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
For the Go-host-drives-Python-plugin direction, see the small Go host
|
|
176
|
+
template at the end of this README — drop it into `tests/interop/go-host/`
|
|
177
|
+
with a `replace` directive in `go.mod` pointing at the local go-plugin
|
|
178
|
+
clone, `go build`, then point `PYPLUGIN_GO_HOST_BIN` at the binary.
|
|
179
|
+
|
|
180
|
+
## Layout
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
src/pyplugin/
|
|
184
|
+
handshake.py # stdout protocol line format/parse
|
|
185
|
+
cookie.py # magic-cookie validation
|
|
186
|
+
mtls.py # ephemeral P-521 cert generation + ssl.SSLContext builders
|
|
187
|
+
transport.py # unix / tcp listener helpers
|
|
188
|
+
server.py # serve(ServeConfig) — sync entry, internal asyncio loop
|
|
189
|
+
client.py # Client / ClientConfig — async host launcher
|
|
190
|
+
process.py # cross-platform subprocess termination
|
|
191
|
+
reattach.py # ReattachConfig
|
|
192
|
+
controller.py # GRPCController.Shutdown servicer (grpclib async)
|
|
193
|
+
broker.py # GRPCBroker bidirectional multiplexer (grpclib async)
|
|
194
|
+
stdio.py # GRPCStdio post-handshake stream (grpclib async)
|
|
195
|
+
health.py # static grpc.health.v1 servicer (returns SERVING for "plugin")
|
|
196
|
+
plugin.py # Plugin ABC, PluginSet, VersionedPlugins
|
|
197
|
+
logging_bridge.py # hclog (JSON + pretty) line parser
|
|
198
|
+
errors.py # exception hierarchy
|
|
199
|
+
proto/ # vendored .proto files from go-plugin (verbatim)
|
|
200
|
+
_generated/ # checked-in grpclib stubs
|
|
201
|
+
fixtures/example_kv/ # example KV plugin used by smoke tests
|
|
202
|
+
tests/ # 40 unit + Python↔Python tests
|
|
203
|
+
tests/interop/ # 4 real-go-plugin interop tests
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
python3 -m venv .venv
|
|
210
|
+
.venv/bin/pip install -e '.[dev]'
|
|
211
|
+
.venv/bin/python scripts/gen_protos.py # regenerate stubs
|
|
212
|
+
.venv/bin/python -m pytest # run tests
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Go host template
|
|
216
|
+
|
|
217
|
+
Use this with `go.mod`'s `replace github.com/hashicorp/go-plugin => /path/to/clone`:
|
|
218
|
+
|
|
219
|
+
```go
|
|
220
|
+
package main
|
|
221
|
+
|
|
222
|
+
import (
|
|
223
|
+
"fmt"; "io"; "log"; "os"; "os/exec"
|
|
224
|
+
hclog "github.com/hashicorp/go-hclog"
|
|
225
|
+
"github.com/hashicorp/go-plugin"
|
|
226
|
+
"github.com/hashicorp/go-plugin/examples/grpc/shared"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
func main() {
|
|
230
|
+
log.SetOutput(io.Discard)
|
|
231
|
+
client := plugin.NewClient(&plugin.ClientConfig{
|
|
232
|
+
HandshakeConfig: shared.Handshake,
|
|
233
|
+
Plugins: map[string]plugin.Plugin{shared.PluginGRPC: &shared.KVGRPCPlugin{}},
|
|
234
|
+
Cmd: exec.Command(os.Args[1], os.Args[2]),
|
|
235
|
+
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
|
236
|
+
AutoMTLS: os.Getenv("AUTO_MTLS") == "1",
|
|
237
|
+
Logger: hclog.New(&hclog.LoggerOptions{Output: io.Discard, Level: hclog.Off}),
|
|
238
|
+
})
|
|
239
|
+
defer client.Kill()
|
|
240
|
+
rpc, err := client.Client(); if err != nil { panic(err) }
|
|
241
|
+
raw, err := rpc.Dispense(shared.PluginGRPC); if err != nil { panic(err) }
|
|
242
|
+
kv := raw.(shared.KV)
|
|
243
|
+
if err := kv.Put(os.Args[4], []byte(os.Args[5])); err != nil { panic(err) }
|
|
244
|
+
v, err := kv.Get(os.Args[4]); if err != nil { panic(err) }
|
|
245
|
+
fmt.Print(string(v))
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT. The vendored `.proto` files in `src/pyplugin/proto/` retain their
|
|
252
|
+
upstream MPL-2.0 headers from
|
|
253
|
+
[hashicorp/go-plugin](https://github.com/hashicorp/go-plugin); MPL-2.0 is
|
|
254
|
+
file-level and is compatible with MIT for the rest of the project.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
pyplugin/__init__.py,sha256=J1sgTlsqJCngvI2FeYD4H3uYSk79COGsQBOHhrMcfHs,1204
|
|
2
|
+
pyplugin/broker.py,sha256=z3mSzP1FyiDiNNZsC9iNFrEQQmU-_GVnPT6I5wfLgyI,7989
|
|
3
|
+
pyplugin/client.py,sha256=Fx4iq0Czqytoelj6tkUgj3mGUdRGqt8ZrVqJow_kIuI,15278
|
|
4
|
+
pyplugin/controller.py,sha256=MBkAl_9eMc694-21hUuiQTrCbkJUmNJiuZY-Dr_Db5k,757
|
|
5
|
+
pyplugin/cookie.py,sha256=kDta4YhHaK1rfiv_-iPyZWvCLjMmrn3UHlc2qwFrQFk,1407
|
|
6
|
+
pyplugin/errors.py,sha256=2tF-bITFFt-lDWs866qYd6XvhprIVsrOuwdWJRFsXow,1115
|
|
7
|
+
pyplugin/handshake.py,sha256=g2yYjxap_vLkHW1y3hQjSoTft8Ep6PkECKGNdqduIVI,3821
|
|
8
|
+
pyplugin/health.py,sha256=XqAzdMt5aMjXryV1qzxZNH9t9FEqhrJekgpLa5X0sls,1653
|
|
9
|
+
pyplugin/logging_bridge.py,sha256=Icxjw3iGAJ03IgazsVUiPIDjUY_fZR7WIzTDQAH81no,2247
|
|
10
|
+
pyplugin/mtls.py,sha256=5gYFr-iQUshY09UrGiHg0TphK9UmbBqjWh2vr35swdg,6483
|
|
11
|
+
pyplugin/plugin.py,sha256=UbPP_S0dfqD1rrqS2a0Wjo0S9ngMA6nDg-QwQHXtr0I,1199
|
|
12
|
+
pyplugin/process.py,sha256=vomevPuBmTOZrgnb4QnmmQPe2qUOrSxmMHb27Iy1bno,1764
|
|
13
|
+
pyplugin/reattach.py,sha256=ExmkGvigeG421Lo6DzcX5qM-3GK0h3Yg8XtZpX9LCHE,1049
|
|
14
|
+
pyplugin/server.py,sha256=gmgWC6_YiK4WW9U7pE5nPUvvgeQ1-Yg7iND2_vvw3Mg,7404
|
|
15
|
+
pyplugin/stdio.py,sha256=TsxBnhcHCm14oCPVpj7eHsJPcHDrq9l93fJ82SqR2Yo,1264
|
|
16
|
+
pyplugin/transport.py,sha256=j7E_O44fMwYE6G1rAh25V8ZPz_bylS5hThOBkc3C1wU,3370
|
|
17
|
+
pyplugin/_generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
pyplugin/_generated/grpc_broker_grpc.py,sha256=sj0dWUXQFyfCBVY5lXm7f2IRDb_WOeDXBOCHSEfVroo,1127
|
|
19
|
+
pyplugin/_generated/grpc_broker_pb2.py,sha256=Yc5cfRt20Hjm6vCCxNgVizshGq5_kFAu4Pj98BeLiNQ,2027
|
|
20
|
+
pyplugin/_generated/grpc_controller_grpc.py,sha256=frLWKtv-zFh3K380N_TNGDR8zq8lG5oh-z1RwL-akto,1138
|
|
21
|
+
pyplugin/_generated/grpc_controller_pb2.py,sha256=A8qRX9pcb8WcFLjYScXV46z-9KtZxbvt8uJ8EgR8AK4,1589
|
|
22
|
+
pyplugin/_generated/grpc_stdio_grpc.py,sha256=YhdaTvyPtXVMvjMgbhy78Zlz7vKF9M6rinXR08Rnqhc,1173
|
|
23
|
+
pyplugin/_generated/grpc_stdio_pb2.py,sha256=igVl4onDwxkD0XgUKR_vY8lAWD_h15qmu2K9CN5luT4,2025
|
|
24
|
+
pyplugin/proto/grpc_broker.proto,sha256=VJn8FNFAgnnaTj8Zj_Tt0-gp_83gEHpjAeBXHmjCpX0,516
|
|
25
|
+
pyplugin/proto/grpc_controller.proto,sha256=Vw2Om0jlGbfPu0uuxZb_vG1mcL87ZQAAOGC-wbdNRVQ,304
|
|
26
|
+
pyplugin/proto/grpc_stdio.proto,sha256=u2CW22euvSA7BGeLN4YbQm2cMyurY1BiMjBexneWzoA,477
|
|
27
|
+
python_plugin-0.1.0.dist-info/METADATA,sha256=43EHSne8d3JaUCfuniCNao5EJwbwrKj6QU18G6U9BSo,9597
|
|
28
|
+
python_plugin-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
29
|
+
python_plugin-0.1.0.dist-info/licenses/LICENSE,sha256=fILwPEdelkIjeEzc0o8FY3rqqQNQW_bKBYoD0gX6zmY,1065
|
|
30
|
+
python_plugin-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Max Lund
|
|
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.
|