fleetmesh-node 0.2.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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""EVERY: periodic work declared on a compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Callable, Optional, Union
|
|
8
|
+
|
|
9
|
+
from .problem import Problem, Problems, ReferenceProblem, TypeMismatchProblem
|
|
10
|
+
|
|
11
|
+
SecondsType = Union[int, float, Callable[[Any], Union[int, float]], Any]
|
|
12
|
+
ReportType = Optional[Union[str, Callable[..., Any]]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Every:
|
|
17
|
+
"""A periodic job declared on a compartment class.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
seconds: How often to run the job, in seconds. Can be an int, float,
|
|
21
|
+
a callable receiving the compartment instance, or a Parameter.
|
|
22
|
+
A callable that resolves to zero or less turns the job off
|
|
23
|
+
(see `is_off`): that is how a setting like `check_every: 0`
|
|
24
|
+
says "only on command".
|
|
25
|
+
run: The method name on the compartment to call when due.
|
|
26
|
+
name: A unique name for the job within the compartment (defaults to `run`).
|
|
27
|
+
report: Optional method name or callable to convert the method's return value
|
|
28
|
+
into a list of `[(what, ...)]` tuples for the tick log.
|
|
29
|
+
"""
|
|
30
|
+
seconds: SecondsType
|
|
31
|
+
run: str
|
|
32
|
+
name: str = ''
|
|
33
|
+
report: ReportType = None
|
|
34
|
+
# Whether `run` takes `now`, per class. inspect.signature ran for every
|
|
35
|
+
# due job on every tick (audit 2026-10-15, E6); a method's arity does not
|
|
36
|
+
# change between ticks.
|
|
37
|
+
_takes_now: dict = field(default_factory=dict, init=False, repr=False, compare=False, hash=False)
|
|
38
|
+
|
|
39
|
+
def __post_init__(self) -> None:
|
|
40
|
+
if not self.name:
|
|
41
|
+
object.__setattr__(self, 'name', self.run)
|
|
42
|
+
|
|
43
|
+
def get_seconds(self, instance: Any) -> float:
|
|
44
|
+
"""Resolve the interval in seconds against a compartment instance."""
|
|
45
|
+
sec = self.seconds
|
|
46
|
+
if callable(sec):
|
|
47
|
+
return float(sec(instance))
|
|
48
|
+
if hasattr(sec, 'value') and callable(sec.value):
|
|
49
|
+
return float(sec.value())
|
|
50
|
+
if hasattr(sec, 'default'):
|
|
51
|
+
def_val = sec.default() if callable(sec.default) else sec.default
|
|
52
|
+
return float(def_val)
|
|
53
|
+
return float(sec)
|
|
54
|
+
|
|
55
|
+
def is_off(self, instance: Any) -> bool:
|
|
56
|
+
"""Whether the interval says this job does not run on a schedule.
|
|
57
|
+
|
|
58
|
+
Zero, a negative number, or anything not finite. Updates used to spell
|
|
59
|
+
"off" as infinity, and `report()` died on `int(inf)` - which took the
|
|
60
|
+
whole health report with it, every tick (audit 2026-10-15, B1). Off is
|
|
61
|
+
now one rule, read here, and never a number anyone does arithmetic on.
|
|
62
|
+
"""
|
|
63
|
+
sec = self.get_seconds(instance)
|
|
64
|
+
return not math.isfinite(sec) or sec <= 0
|
|
65
|
+
|
|
66
|
+
def due(self, instance: Any, now: int) -> bool:
|
|
67
|
+
"""True if this job has never run or if at least `seconds` have passed since last run.
|
|
68
|
+
Never true for a job that is off, including on its first tick."""
|
|
69
|
+
if self.is_off(instance):
|
|
70
|
+
return False
|
|
71
|
+
every_last = getattr(instance, '_every_last', None)
|
|
72
|
+
if every_last is None:
|
|
73
|
+
return True
|
|
74
|
+
last = every_last.get(self.name)
|
|
75
|
+
if last is None:
|
|
76
|
+
return True
|
|
77
|
+
return (now - last) >= self.get_seconds(instance)
|
|
78
|
+
|
|
79
|
+
def execute(self, instance: Any, now: int) -> Any:
|
|
80
|
+
"""Call the target method on instance, passing `now` if accepted."""
|
|
81
|
+
method = getattr(instance, self.run, None)
|
|
82
|
+
if method is None:
|
|
83
|
+
raise AttributeError(f"{type(instance).__name__} has no method {self.run!r}")
|
|
84
|
+
cls = type(instance)
|
|
85
|
+
takes = self._takes_now.get(cls)
|
|
86
|
+
if takes is None:
|
|
87
|
+
takes = self._takes_now[cls] = len(inspect.signature(method).parameters) > 0
|
|
88
|
+
if takes:
|
|
89
|
+
return method(now)
|
|
90
|
+
return method()
|
|
91
|
+
|
|
92
|
+
def problems(self, cls: type[Any]) -> list[str]:
|
|
93
|
+
"""Lint this declaration against the target class."""
|
|
94
|
+
out = Problems()
|
|
95
|
+
if not isinstance(self.run, str) or not self.run:
|
|
96
|
+
out.append(TypeMismatchProblem('EVERY', 'non-empty str', type(self.run).__name__,
|
|
97
|
+
message=f"Every job {self.name!r}: run must be a non-empty string method name"))
|
|
98
|
+
elif not getattr(cls, 'abstract', False) and not hasattr(cls, self.run):
|
|
99
|
+
out.append(ReferenceProblem('EVERY', f"Every job {self.name!r}", self.run,
|
|
100
|
+
message=f"Every job {self.name!r}: method {self.run!r} does not exist on {cls.__name__}"))
|
|
101
|
+
if isinstance(self.report, str) and not getattr(cls, 'abstract', False) and not hasattr(cls, self.report):
|
|
102
|
+
out.append(ReferenceProblem('EVERY', f"Every job {self.name!r}", self.report,
|
|
103
|
+
message=f"Every job {self.name!r}: report method {self.report!r} does not exist on {cls.__name__}"))
|
|
104
|
+
if isinstance(self.seconds, (int, float)) and self.seconds <= 0:
|
|
105
|
+
out.append(Problem(f"Every job {self.name!r}: seconds must be positive, got {self.seconds!r}",
|
|
106
|
+
surface='EVERY', code='invalid_interval', target=self.name))
|
|
107
|
+
return out
|
|
108
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""HANDLERS: what a compartment can receive from the wire, declared where it is.
|
|
2
|
+
|
|
3
|
+
A wire compartment declares its message dispatch table as a tuple of typed
|
|
4
|
+
Handler entries on the class attribute HANDLERS. Like COMMANDS, WORKERS, and
|
|
5
|
+
CHANNELS, it is declared once, validated at class definition time by the base
|
|
6
|
+
Compartment, and dispatched uniformly by the base `handle(inbound)`.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import dataclasses
|
|
11
|
+
import inspect
|
|
12
|
+
from typing import Any, Callable, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
from .problem import (
|
|
15
|
+
InvalidIdentifierProblem,
|
|
16
|
+
Problem,
|
|
17
|
+
Problems,
|
|
18
|
+
ReferenceProblem,
|
|
19
|
+
TypeMismatchProblem,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclasses.dataclass(frozen=True)
|
|
25
|
+
class Handler:
|
|
26
|
+
"""One inbound wire protocol message handler."""
|
|
27
|
+
|
|
28
|
+
kind: str
|
|
29
|
+
run: str
|
|
30
|
+
schema: Optional[Any] = None
|
|
31
|
+
doc: str = ''
|
|
32
|
+
exact: bool = False
|
|
33
|
+
reads: tuple[str, ...] = ()
|
|
34
|
+
writes: tuple[str, ...] = ()
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
kind: str,
|
|
39
|
+
run: str | Callable[..., Any],
|
|
40
|
+
*,
|
|
41
|
+
schema: Optional[Any] = None,
|
|
42
|
+
doc: str = '',
|
|
43
|
+
exact: bool = False,
|
|
44
|
+
reads: Sequence[str] = (),
|
|
45
|
+
writes: Sequence[str] = (),
|
|
46
|
+
) -> None:
|
|
47
|
+
object.__setattr__(self, 'kind', str(kind))
|
|
48
|
+
run_name = run if isinstance(run, str) else getattr(run, '__name__', str(run))
|
|
49
|
+
object.__setattr__(self, 'run', run_name)
|
|
50
|
+
object.__setattr__(self, 'schema', schema)
|
|
51
|
+
object.__setattr__(self, 'doc', str(doc))
|
|
52
|
+
is_exact = exact or (kind.startswith(('http://', 'https://')) and not exact)
|
|
53
|
+
object.__setattr__(self, 'exact', bool(is_exact))
|
|
54
|
+
object.__setattr__(self, 'reads', tuple(reads))
|
|
55
|
+
object.__setattr__(self, 'writes', tuple(writes))
|
|
56
|
+
|
|
57
|
+
def matches(self, msg_type: str, piuris: Sequence[str] = ()) -> bool:
|
|
58
|
+
"""Check if this handler matches the given message type URI."""
|
|
59
|
+
if not msg_type:
|
|
60
|
+
return False
|
|
61
|
+
if self.kind == '*':
|
|
62
|
+
if not piuris:
|
|
63
|
+
return True
|
|
64
|
+
return any(msg_type.startswith(p + '/') for p in piuris)
|
|
65
|
+
if self.exact or self.kind.startswith(('http://', 'https://')):
|
|
66
|
+
return msg_type == self.kind
|
|
67
|
+
# Suffix / sub-path matching: https://fleetmesh.org/compute/1.0/quote
|
|
68
|
+
if msg_type.endswith('/' + self.kind):
|
|
69
|
+
if not piuris:
|
|
70
|
+
return True
|
|
71
|
+
prefix = msg_type.rsplit('/', 1)[0]
|
|
72
|
+
return any(prefix == p or prefix.startswith(p) for p in piuris)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def problems(self, cls: type) -> list[str]:
|
|
76
|
+
"""Validate this Handler against the declaring compartment class."""
|
|
77
|
+
out = Problems()
|
|
78
|
+
if not self.kind or not isinstance(self.kind, str):
|
|
79
|
+
out.append(InvalidIdentifierProblem('HANDLERS', str(self.kind), 'Handler must specify a message kind'))
|
|
80
|
+
if not self.run or not isinstance(self.run, str):
|
|
81
|
+
out.append(Problem(
|
|
82
|
+
f'Handler {self.kind} must specify a target run method',
|
|
83
|
+
surface='HANDLERS',
|
|
84
|
+
code='missing_run',
|
|
85
|
+
target=self.kind,
|
|
86
|
+
))
|
|
87
|
+
else:
|
|
88
|
+
fn = getattr(cls, self.run, None)
|
|
89
|
+
if fn is None:
|
|
90
|
+
out.append(ReferenceProblem(
|
|
91
|
+
'HANDLERS', f'{cls.__name__}.{self.run}', self.run,
|
|
92
|
+
f"handler {self.kind!r}: method {self.run!r} does not exist on {cls.__name__}"
|
|
93
|
+
))
|
|
94
|
+
elif not callable(fn):
|
|
95
|
+
out.append(TypeMismatchProblem(
|
|
96
|
+
'HANDLERS', 'callable', type(fn).__name__,
|
|
97
|
+
f"handler {self.kind!r}: attribute {self.run!r} on {cls.__name__} is not callable"
|
|
98
|
+
))
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
def execute(self, instance: Any, inbound: Any, *args: Any, **kw: Any) -> Any:
|
|
102
|
+
"""Dispatch an inbound message to this handler on the compartment instance."""
|
|
103
|
+
fn = getattr(instance, self.run)
|
|
104
|
+
sig = inspect.signature(fn)
|
|
105
|
+
num_params = len(sig.parameters)
|
|
106
|
+
msg = getattr(inbound, 'message', inbound)
|
|
107
|
+
sender = getattr(inbound, 'sender', msg.get('from', '') if isinstance(msg, dict) else '')
|
|
108
|
+
|
|
109
|
+
# Validate against schema if declared
|
|
110
|
+
if self.schema is not None and isinstance(msg, dict):
|
|
111
|
+
body = msg.get('body')
|
|
112
|
+
if callable(self.schema):
|
|
113
|
+
try:
|
|
114
|
+
self.schema(body)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return ('refused', sender, f'invalid schema: {e}')
|
|
117
|
+
|
|
118
|
+
available = (inbound, sender) + args
|
|
119
|
+
if num_params == 0:
|
|
120
|
+
return fn()
|
|
121
|
+
elif num_params == 1:
|
|
122
|
+
return fn(inbound)
|
|
123
|
+
elif num_params == 2:
|
|
124
|
+
return fn(inbound, sender)
|
|
125
|
+
elif num_params <= len(available):
|
|
126
|
+
return fn(*available[:num_params], **kw)
|
|
127
|
+
else:
|
|
128
|
+
return fn(inbound, sender)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
__all__ = ['Handler']
|