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.
Files changed (29) hide show
  1. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/PKG-INFO +32 -1
  2. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/README.md +31 -0
  3. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/bus.py +4 -2
  4. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/client.py +12 -7
  5. cap_sdk_python-2.0.6/cap/pb/__init__.py +7 -0
  6. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/worker.py +20 -7
  7. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/PKG-INFO +32 -1
  8. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/pyproject.toml +1 -1
  9. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/tests/test_sdk.py +7 -5
  10. cap_sdk_python-2.0.4/cap/pb/coretex/agent/v1/__init__.py +0 -0
  11. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/__init__.py +0 -0
  12. {cap_sdk_python-2.0.4/cap/pb → cap_sdk_python-2.0.6/cap/pb/coretex}/__init__.py +0 -0
  13. {cap_sdk_python-2.0.4/cap/pb/coretex → cap_sdk_python-2.0.6/cap/pb/coretex/agent}/__init__.py +0 -0
  14. {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
  15. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/alert_pb2.py +0 -0
  16. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/alert_pb2_grpc.py +0 -0
  17. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/buspacket_pb2.py +0 -0
  18. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/buspacket_pb2_grpc.py +0 -0
  19. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/heartbeat_pb2.py +0 -0
  20. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/heartbeat_pb2_grpc.py +0 -0
  21. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/job_pb2.py +0 -0
  22. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/job_pb2_grpc.py +0 -0
  23. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/safety_pb2.py +0 -0
  24. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap/pb/coretex/agent/v1/safety_pb2_grpc.py +0 -0
  25. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/SOURCES.txt +0 -0
  26. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/dependency_links.txt +0 -0
  27. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/requires.txt +0 -0
  28. {cap_sdk_python-2.0.4 → cap_sdk_python-2.0.6}/cap_sdk_python.egg-info/top_level.txt +0 -0
  29. {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.4
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 hashlib
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(nc, job_request, trace_id: str, sender_id: str, private_key: ec.EllipticCurvePrivateKey):
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
- unsigned_data = packet.SerializeToString()
24
- digest = hashlib.sha256(unsigned_data).digest()
25
- signature = private_key.sign(digest, ec.ECDSA(hashes.SHA256()))
26
- packet.signature = signature
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())
@@ -0,0 +1,7 @@
1
+ import os
2
+ import sys
3
+
4
+ # Allow generated stubs to resolve `coretex.*` absolute imports.
5
+ _pb_root = os.path.dirname(__file__)
6
+ if _pb_root not in sys.path:
7
+ sys.path.insert(0, _pb_root)
@@ -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 = nats.connect):
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, digest, ec.ECDSA(hashes.SHA256()))
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
- digest = hashlib.sha256(unsigned_data).digest()
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.4
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,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cap-sdk-python"
7
- version = "2.0.4"
7
+ version = "2.0.6"
8
8
  description = "CAP (coretex Agent Protocol) Python SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -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 under sdk/python/cap/pb are discoverable for `coretex.agent.v1.*`.
10
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "cap", "pb")))
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
- digest = hashlib.sha256(unsigned_data).digest()
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