cap-sdk-python 2.0.4__tar.gz → 2.0.6__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.
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/PKG-INFO +32 -1
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/README.md +31 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/bus.py +4 -2
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/client.py +12 -7
- cap_sdk_python-2.0.6/cap/pb/__init__.py +7 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/worker.py +20 -7
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/PKG-INFO +32 -1
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/pyproject.toml +1 -1
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/tests/test_sdk.py +7 -5
- cap_sdk_python-2.0.4/cap/pb/coretex/agent/v1/__init__.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/__init__.py +0 -0
- {cap_sdk_python-2.0.4/cap/pb → cap_sdk_python-2.0.6/cap/pb/coretex}/__init__.py +0 -0
- {cap_sdk_python-2.0.4/cap/pb/coretex → cap_sdk_python-2.0.6/cap/pb/coretex/agent}/__init__.py +0 -0
- {cap_sdk_python-2.0.4/cap/pb/coretex/agent → cap_sdk_python-2.0.6/cap/pb/coretex/agent/v1}/__init__.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/alert_pb2.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/alert_pb2_grpc.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/buspacket_pb2.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/buspacket_pb2_grpc.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/heartbeat_pb2.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/heartbeat_pb2_grpc.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/job_pb2.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/job_pb2_grpc.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/safety_pb2.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/safety_pb2_grpc.py +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/SOURCES.txt +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/dependency_links.txt +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/requires.txt +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/top_level.txt +0 -0
- {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cap-sdk-python
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: CAP (coretex Agent Protocol) Python SDK
|
|
5
5
|
Project-URL: Homepage, https://github.com/coretexos/cap
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -46,6 +46,28 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
|
|
|
46
46
|
asyncio.run(worker.run_worker("nats://127.0.0.1:4222", "job.echo", handle))
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
4. Submit a job (client):
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
53
|
+
from cap import client
|
|
54
|
+
from cap.pb.coretex.agent.v1 import job_pb2
|
|
55
|
+
import nats
|
|
56
|
+
|
|
57
|
+
async def main():
|
|
58
|
+
nc = await nats.connect("nats://127.0.0.1:4222")
|
|
59
|
+
priv = ec.generate_private_key(ec.SECP256R1())
|
|
60
|
+
req = job_pb2.JobRequest(
|
|
61
|
+
job_id="job-echo-1",
|
|
62
|
+
topic="job.echo",
|
|
63
|
+
context_ptr="redis://ctx/job-echo-1",
|
|
64
|
+
)
|
|
65
|
+
await client.submit_job(nc, req, "trace-1", "client-py", priv)
|
|
66
|
+
await nc.drain()
|
|
67
|
+
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
```
|
|
70
|
+
|
|
49
71
|
## Files
|
|
50
72
|
- `cap/bus.py` — NATS connector.
|
|
51
73
|
- `cap/worker.py` — worker skeleton with handler hook.
|
|
@@ -55,5 +77,14 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
|
|
|
55
77
|
## Defaults
|
|
56
78
|
- Subjects: `sys.job.submit`, `sys.job.result`, `sys.heartbeat`.
|
|
57
79
|
- Protocol version: `1`.
|
|
80
|
+
- Signing: `submit_job` and `run_worker` sign envelopes when given an `ec.EllipticCurvePrivateKey`. Generate a keypair with `cryptography`:
|
|
81
|
+
```python
|
|
82
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
83
|
+
priv = ec.generate_private_key(ec.SECP256R1())
|
|
84
|
+
pub = priv.public_key()
|
|
85
|
+
```
|
|
86
|
+
- Set `public_keys` on `run_worker` to verify incoming packets.
|
|
87
|
+
- Omit `public_keys` to accept unsigned packets.
|
|
88
|
+
- Pass `private_key=None` to `submit_job` if you want to send unsigned envelopes.
|
|
58
89
|
|
|
59
90
|
Swap out `cap.bus` if you need a different transport.
|
|
@@ -35,6 +35,28 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
|
|
|
35
35
|
asyncio.run(worker.run_worker("nats://127.0.0.1:4222", "job.echo", handle))
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
4. Submit a job (client):
|
|
39
|
+
```python
|
|
40
|
+
import asyncio
|
|
41
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
42
|
+
from cap import client
|
|
43
|
+
from cap.pb.coretex.agent.v1 import job_pb2
|
|
44
|
+
import nats
|
|
45
|
+
|
|
46
|
+
async def main():
|
|
47
|
+
nc = await nats.connect("nats://127.0.0.1:4222")
|
|
48
|
+
priv = ec.generate_private_key(ec.SECP256R1())
|
|
49
|
+
req = job_pb2.JobRequest(
|
|
50
|
+
job_id="job-echo-1",
|
|
51
|
+
topic="job.echo",
|
|
52
|
+
context_ptr="redis://ctx/job-echo-1",
|
|
53
|
+
)
|
|
54
|
+
await client.submit_job(nc, req, "trace-1", "client-py", priv)
|
|
55
|
+
await nc.drain()
|
|
56
|
+
|
|
57
|
+
asyncio.run(main())
|
|
58
|
+
```
|
|
59
|
+
|
|
38
60
|
## Files
|
|
39
61
|
- `cap/bus.py` — NATS connector.
|
|
40
62
|
- `cap/worker.py` — worker skeleton with handler hook.
|
|
@@ -44,5 +66,14 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
|
|
|
44
66
|
## Defaults
|
|
45
67
|
- Subjects: `sys.job.submit`, `sys.job.result`, `sys.heartbeat`.
|
|
46
68
|
- Protocol version: `1`.
|
|
69
|
+
- Signing: `submit_job` and `run_worker` sign envelopes when given an `ec.EllipticCurvePrivateKey`. Generate a keypair with `cryptography`:
|
|
70
|
+
```python
|
|
71
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
72
|
+
priv = ec.generate_private_key(ec.SECP256R1())
|
|
73
|
+
pub = priv.public_key()
|
|
74
|
+
```
|
|
75
|
+
- Set `public_keys` on `run_worker` to verify incoming packets.
|
|
76
|
+
- Omit `public_keys` to accept unsigned packets.
|
|
77
|
+
- Pass `private_key=None` to `submit_job` if you want to send unsigned envelopes.
|
|
47
78
|
|
|
48
79
|
Swap out `cap.bus` if you need a different transport.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
import nats
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
class NATSConfig:
|
|
8
6
|
def __init__(
|
|
@@ -21,6 +19,10 @@ class NATSConfig:
|
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
async def connect_nats(cfg: NATSConfig):
|
|
22
|
+
try:
|
|
23
|
+
import nats # type: ignore
|
|
24
|
+
except ImportError as exc:
|
|
25
|
+
raise RuntimeError("nats-py is required to connect to NATS") from exc
|
|
24
26
|
opts = {"servers": cfg.url, "name": cfg.name}
|
|
25
27
|
if cfg.token:
|
|
26
28
|
opts["token"] = cfg.token
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import nats
|
|
2
1
|
from google.protobuf import timestamp_pb2
|
|
3
2
|
from cap.pb.coretex.agent.v1 import buspacket_pb2
|
|
4
3
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
5
4
|
from cryptography.hazmat.primitives import hashes
|
|
6
|
-
import
|
|
5
|
+
from typing import Optional
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
DEFAULT_PROTOCOL_VERSION = 1
|
|
10
9
|
SUBJECT_SUBMIT = "sys.job.submit"
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
async def submit_job(
|
|
12
|
+
async def submit_job(
|
|
13
|
+
nc,
|
|
14
|
+
job_request,
|
|
15
|
+
trace_id: str,
|
|
16
|
+
sender_id: str,
|
|
17
|
+
private_key: Optional[ec.EllipticCurvePrivateKey] = None,
|
|
18
|
+
):
|
|
14
19
|
ts = timestamp_pb2.Timestamp()
|
|
15
20
|
ts.GetCurrentTime()
|
|
16
21
|
packet = buspacket_pb2.BusPacket()
|
|
@@ -20,9 +25,9 @@ async def submit_job(nc, job_request, trace_id: str, sender_id: str, private_key
|
|
|
20
25
|
packet.protocol_version = DEFAULT_PROTOCOL_VERSION
|
|
21
26
|
packet.job_request.CopyFrom(job_request)
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
if private_key:
|
|
29
|
+
unsigned_data = packet.SerializeToString()
|
|
30
|
+
signature = private_key.sign(unsigned_data, ec.ECDSA(hashes.SHA256()))
|
|
31
|
+
packet.signature = signature
|
|
27
32
|
|
|
28
33
|
await nc.publish(SUBJECT_SUBMIT, packet.SerializeToString())
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Callable, Awaitable, Dict
|
|
3
3
|
|
|
4
|
-
import nats
|
|
5
4
|
from google.protobuf import timestamp_pb2
|
|
6
5
|
from cap.pb.coretex.agent.v1 import buspacket_pb2, job_pb2
|
|
7
6
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
8
7
|
from cryptography.hazmat.primitives import hashes
|
|
9
|
-
import hashlib
|
|
10
8
|
|
|
11
9
|
DEFAULT_PROTOCOL_VERSION = 1
|
|
12
10
|
SUBJECT_RESULT = "sys.job.result"
|
|
@@ -16,9 +14,16 @@ async def run_worker(nats_url: str, subject: str, handler: Callable[[job_pb2.Job
|
|
|
16
14
|
public_keys: Dict[str, ec.EllipticCurvePublicKey] = None,
|
|
17
15
|
private_key: ec.EllipticCurvePrivateKey = None,
|
|
18
16
|
sender_id: str = "cap-worker",
|
|
19
|
-
connect_fn: Callable =
|
|
17
|
+
connect_fn: Callable = None):
|
|
20
18
|
|
|
21
19
|
# Allow injection for tests; defaults to nats.connect.
|
|
20
|
+
if connect_fn is None:
|
|
21
|
+
try:
|
|
22
|
+
import nats # type: ignore
|
|
23
|
+
except ImportError as exc:
|
|
24
|
+
raise RuntimeError("nats-py is required to connect to NATS") from exc
|
|
25
|
+
connect_fn = nats.connect
|
|
26
|
+
|
|
22
27
|
nc = await connect_fn(servers=nats_url, name=sender_id)
|
|
23
28
|
|
|
24
29
|
async def on_msg(msg):
|
|
@@ -34,9 +39,8 @@ async def run_worker(nats_url: str, subject: str, handler: Callable[[job_pb2.Job
|
|
|
34
39
|
signature = packet.signature
|
|
35
40
|
packet.ClearField("signature")
|
|
36
41
|
unsigned_data = packet.SerializeToString()
|
|
37
|
-
digest = hashlib.sha256(unsigned_data).digest()
|
|
38
42
|
try:
|
|
39
|
-
public_key.verify(signature,
|
|
43
|
+
public_key.verify(signature, unsigned_data, ec.ECDSA(hashes.SHA256()))
|
|
40
44
|
except Exception:
|
|
41
45
|
print(f"worker: invalid signature from sender: {packet.sender_id}")
|
|
42
46
|
return
|
|
@@ -46,12 +50,22 @@ async def run_worker(nats_url: str, subject: str, handler: Callable[[job_pb2.Job
|
|
|
46
50
|
return
|
|
47
51
|
try:
|
|
48
52
|
res = await handler(req)
|
|
53
|
+
if res is None:
|
|
54
|
+
res = job_pb2.JobResult(
|
|
55
|
+
job_id=req.job_id,
|
|
56
|
+
status=job_pb2.JOB_STATUS_FAILED,
|
|
57
|
+
error_message="handler returned null",
|
|
58
|
+
)
|
|
49
59
|
except Exception as exc: # noqa: BLE001
|
|
50
60
|
res = job_pb2.JobResult(
|
|
51
61
|
job_id=req.job_id,
|
|
52
62
|
status=job_pb2.JOB_STATUS_FAILED,
|
|
53
63
|
error_message=str(exc),
|
|
54
64
|
)
|
|
65
|
+
if not res.job_id:
|
|
66
|
+
res.job_id = req.job_id
|
|
67
|
+
if not res.worker_id:
|
|
68
|
+
res.worker_id = sender_id
|
|
55
69
|
ts = timestamp_pb2.Timestamp()
|
|
56
70
|
ts.GetCurrentTime()
|
|
57
71
|
out = buspacket_pb2.BusPacket()
|
|
@@ -63,8 +77,7 @@ async def run_worker(nats_url: str, subject: str, handler: Callable[[job_pb2.Job
|
|
|
63
77
|
|
|
64
78
|
if private_key:
|
|
65
79
|
unsigned_data = out.SerializeToString()
|
|
66
|
-
|
|
67
|
-
signature = private_key.sign(digest, ec.ECDSA(hashes.SHA256()))
|
|
80
|
+
signature = private_key.sign(unsigned_data, ec.ECDSA(hashes.SHA256()))
|
|
68
81
|
out.signature = signature
|
|
69
82
|
|
|
70
83
|
await nc.publish(SUBJECT_RESULT, out.SerializeToString())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cap-sdk-python
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: CAP (coretex Agent Protocol) Python SDK
|
|
5
5
|
Project-URL: Homepage, https://github.com/coretexos/cap
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -46,6 +46,28 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
|
|
|
46
46
|
asyncio.run(worker.run_worker("nats://127.0.0.1:4222", "job.echo", handle))
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
4. Submit a job (client):
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
53
|
+
from cap import client
|
|
54
|
+
from cap.pb.coretex.agent.v1 import job_pb2
|
|
55
|
+
import nats
|
|
56
|
+
|
|
57
|
+
async def main():
|
|
58
|
+
nc = await nats.connect("nats://127.0.0.1:4222")
|
|
59
|
+
priv = ec.generate_private_key(ec.SECP256R1())
|
|
60
|
+
req = job_pb2.JobRequest(
|
|
61
|
+
job_id="job-echo-1",
|
|
62
|
+
topic="job.echo",
|
|
63
|
+
context_ptr="redis://ctx/job-echo-1",
|
|
64
|
+
)
|
|
65
|
+
await client.submit_job(nc, req, "trace-1", "client-py", priv)
|
|
66
|
+
await nc.drain()
|
|
67
|
+
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
```
|
|
70
|
+
|
|
49
71
|
## Files
|
|
50
72
|
- `cap/bus.py` — NATS connector.
|
|
51
73
|
- `cap/worker.py` — worker skeleton with handler hook.
|
|
@@ -55,5 +77,14 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
|
|
|
55
77
|
## Defaults
|
|
56
78
|
- Subjects: `sys.job.submit`, `sys.job.result`, `sys.heartbeat`.
|
|
57
79
|
- Protocol version: `1`.
|
|
80
|
+
- Signing: `submit_job` and `run_worker` sign envelopes when given an `ec.EllipticCurvePrivateKey`. Generate a keypair with `cryptography`:
|
|
81
|
+
```python
|
|
82
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
83
|
+
priv = ec.generate_private_key(ec.SECP256R1())
|
|
84
|
+
pub = priv.public_key()
|
|
85
|
+
```
|
|
86
|
+
- Set `public_keys` on `run_worker` to verify incoming packets.
|
|
87
|
+
- Omit `public_keys` to accept unsigned packets.
|
|
88
|
+
- Pass `private_key=None` to `submit_job` if you want to send unsigned envelopes.
|
|
58
89
|
|
|
59
90
|
Swap out `cap.bus` if you need a different transport.
|
|
@@ -4,10 +4,14 @@ import os
|
|
|
4
4
|
import unittest
|
|
5
5
|
from typing import Callable, Awaitable, Dict
|
|
6
6
|
|
|
7
|
+
_repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
|
8
|
+
_sdk_root = os.path.join(_repo_root, "sdk", "python")
|
|
9
|
+
|
|
7
10
|
# Avoid loading duplicate generated stubs from both /python and sdk/python/cap/pb.
|
|
8
11
|
sys.path = [p for p in sys.path if not p.rstrip("/").endswith("python")]
|
|
9
|
-
# Ensure generated modules
|
|
10
|
-
sys.path.
|
|
12
|
+
# Ensure the SDK package and generated modules are discoverable from repo root.
|
|
13
|
+
sys.path.insert(0, _sdk_root)
|
|
14
|
+
sys.path.append(os.path.join(_sdk_root, "cap", "pb"))
|
|
11
15
|
|
|
12
16
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
13
17
|
from cryptography.hazmat.primitives import hashes
|
|
@@ -85,14 +89,12 @@ class TestSDK(unittest.TestCase):
|
|
|
85
89
|
|
|
86
90
|
# Verify the signature of the result
|
|
87
91
|
from cap.pb.coretex.agent.v1 import buspacket_pb2
|
|
88
|
-
import hashlib
|
|
89
92
|
result_packet = buspacket_pb2.BusPacket()
|
|
90
93
|
result_packet.ParseFromString(data)
|
|
91
94
|
signature = result_packet.signature
|
|
92
95
|
result_packet.ClearField("signature")
|
|
93
96
|
unsigned_data = result_packet.SerializeToString()
|
|
94
|
-
|
|
95
|
-
worker_key.public_key().verify(signature, digest, ec.ECDSA(hashes.SHA256()))
|
|
97
|
+
worker_key.public_key().verify(signature, unsigned_data, ec.ECDSA(hashes.SHA256()))
|
|
96
98
|
|
|
97
99
|
worker_task.cancel()
|
|
98
100
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cap_sdk_python-2.0.4/cap/pb/coretex → cap_sdk_python-2.0.6/cap/pb/coretex/agent}/__init__.py
RENAMED
|
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
|