piphi-runtime-kit-python 0.6.0__py3-none-any.whl → 0.7.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.
@@ -1,3 +1,17 @@
1
+ from .automations import (
2
+ AutomationActionDefinition,
3
+ AutomationActionRequest,
4
+ AutomationActionResult,
5
+ AutomationClient,
6
+ AutomationContractReport,
7
+ AutomationEventDefinition,
8
+ AutomationIdempotencyStore,
9
+ AutomationRegistry,
10
+ InMemoryAutomationIdempotencyStore,
11
+ assert_behaviors_contract,
12
+ audit_behaviors_contract,
13
+ build_mock_automation_event,
14
+ )
1
15
  from .runtime.auth import (
2
16
  RUNTIME_CONTAINER_ID_HEADER_NAME,
3
17
  RUNTIME_INTERNAL_TOKEN_HEADER_NAME,
@@ -139,6 +153,15 @@ from .schemas import (
139
153
  )
140
154
 
141
155
  __all__ = [
156
+ "AutomationActionDefinition",
157
+ "AutomationActionRequest",
158
+ "AutomationActionResult",
159
+ "AutomationClient",
160
+ "AutomationContractReport",
161
+ "AutomationEventDefinition",
162
+ "AutomationIdempotencyStore",
163
+ "AutomationRegistry",
164
+ "InMemoryAutomationIdempotencyStore",
142
165
  "BackgroundTaskLimitError",
143
166
  "ConfigSyncCoordinator",
144
167
  "CoreAuthError",
@@ -193,6 +216,7 @@ __all__ = [
193
216
  "IntegrationEventIngestResponse",
194
217
  "IntegrationEventListResponse",
195
218
  "build_runtime_config_snapshot_from_core_rows",
219
+ "build_mock_automation_event",
196
220
  "build_discovery_response",
197
221
  "classify_core_delivery_error",
198
222
  "build_core_event_payload",
@@ -241,6 +265,8 @@ __all__ = [
241
265
  "resolve_device_id",
242
266
  "validate_typed_config",
243
267
  "validate_typed_configs",
268
+ "assert_behaviors_contract",
269
+ "audit_behaviors_contract",
244
270
  "DEFAULT_SOURCE_TOPIC_PREFIX",
245
271
  "build_source_errors_topic",
246
272
  "build_source_model_packets_topic",
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING, Any, Mapping
6
6
 
7
+ from ..automations import AutomationActionResult, AutomationRegistry
7
8
  from ..runtime.auth import RuntimeAuthHeaders
8
9
  from ..runtime.context import RuntimeContext
9
10
 
@@ -11,6 +12,31 @@ if TYPE_CHECKING:
11
12
  from fastapi import Request
12
13
 
13
14
 
15
+ async def dispatch_automation_action_from_fastapi(
16
+ registry: AutomationRegistry,
17
+ request: "Request",
18
+ payload: object | Mapping[str, Any],
19
+ ) -> AutomationActionResult:
20
+ """Dispatch a Core command while preserving its stable idempotency key.
21
+
22
+ The key is accepted from the canonical header first and then from the JSON
23
+ payload for compatibility with runtimes that proxy requests internally.
24
+ """
25
+ payload_data = (
26
+ dict(payload)
27
+ if isinstance(payload, Mapping)
28
+ else payload.model_dump(mode="python")
29
+ if hasattr(payload, "model_dump")
30
+ else dict(vars(payload))
31
+ )
32
+ header_key = str(request.headers.get("X-PiPhi-Idempotency-Key") or "").strip()
33
+ payload_key = str(payload_data.get("idempotency_key") or "").strip()
34
+ return await registry.dispatch(
35
+ payload_data,
36
+ idempotency_key=header_key or payload_key or None,
37
+ )
38
+
39
+
14
40
  def get_payload_container_id(
15
41
  payload: object | Mapping[str, Any] | None,
16
42
  *,
@@ -0,0 +1,416 @@
1
+ """Typed automation helpers for PiPhi integration runtimes.
2
+
3
+ ``behaviors.json`` remains the declarative catalog consumed by Core and the
4
+ builders. This module implements the runtime half of that contract: emitting
5
+ declared events, dispatching declared actions, preserving idempotency keys, and
6
+ checking that runtime handlers match the published behavior schema.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from collections.abc import Awaitable, Callable, Mapping
13
+ from dataclasses import dataclass
14
+ from typing import Any, Literal, Protocol, TypeAlias
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field
17
+
18
+ from .runtime.auth import RuntimeAuthContext
19
+ from .runtime.events import EventClient, build_core_event_payload
20
+ from .runtime.identity import RuntimeDeviceRef
21
+ from .schemas import CoreEventPayload, EventSeverity
22
+
23
+ class AutomationActionRequest(BaseModel):
24
+ """Normalized action request received from PiPhi Core."""
25
+
26
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
27
+
28
+ command: str = Field(min_length=1)
29
+ args: dict[str, Any] = Field(default_factory=dict)
30
+ idempotency_key: str | None = None
31
+ command_id: str | None = None
32
+ correlation_id: str | None = None
33
+ integration_id: str | None = None
34
+ config_id: str | None = None
35
+ container_id: str | None = None
36
+ device_id: str | None = None
37
+ entity_id: str | None = None
38
+ capability_id: str | None = None
39
+ run_id: str | None = None
40
+ node_id: str | None = None
41
+
42
+
43
+ class AutomationActionResult(BaseModel):
44
+ """Structured result returned by an automation action handler."""
45
+
46
+ model_config = ConfigDict(extra="forbid")
47
+
48
+ status: Literal["success", "failed"]
49
+ result: dict[str, Any] = Field(default_factory=dict)
50
+ error: str | None = None
51
+ retryable: bool = False
52
+ metadata: dict[str, Any] = Field(default_factory=dict)
53
+ replayed: bool = False
54
+
55
+ @property
56
+ def ok(self) -> bool:
57
+ return self.status == "success"
58
+
59
+ @classmethod
60
+ def success(
61
+ cls,
62
+ result: Mapping[str, Any] | None = None,
63
+ **values: Any,
64
+ ) -> "AutomationActionResult":
65
+ payload = dict(result or {})
66
+ payload.update(values)
67
+ return cls(status="success", result=payload)
68
+
69
+ @classmethod
70
+ def failure(
71
+ cls,
72
+ error: str,
73
+ *,
74
+ retryable: bool = False,
75
+ metadata: Mapping[str, Any] | None = None,
76
+ ) -> "AutomationActionResult":
77
+ return cls(
78
+ status="failed",
79
+ error=str(error),
80
+ retryable=retryable,
81
+ metadata=dict(metadata or {}),
82
+ )
83
+
84
+
85
+ AutomationHandlerValue: TypeAlias = (
86
+ AutomationActionResult | Mapping[str, Any] | None
87
+ )
88
+ AutomationActionHandler: TypeAlias = Callable[
89
+ [AutomationActionRequest],
90
+ AutomationHandlerValue | Awaitable[AutomationHandlerValue],
91
+ ]
92
+
93
+
94
+ class AutomationIdempotencyStore(Protocol):
95
+ """Persistence contract for action results keyed by Core idempotency key."""
96
+
97
+ async def get(self, key: str) -> AutomationActionResult | None: ...
98
+
99
+ async def put(self, key: str, result: AutomationActionResult) -> None: ...
100
+
101
+
102
+ class InMemoryAutomationIdempotencyStore:
103
+ """Process-local idempotency store for development and tests.
104
+
105
+ Production runtimes should provide a durable implementation when an action
106
+ changes external state. Core supplies a stable key, but the runtime owns the
107
+ device-side effect and therefore must persist the final outcome.
108
+ """
109
+
110
+ def __init__(self) -> None:
111
+ self._results: dict[str, AutomationActionResult] = {}
112
+
113
+ async def get(self, key: str) -> AutomationActionResult | None:
114
+ result = self._results.get(key)
115
+ return result.model_copy(deep=True) if result else None
116
+
117
+ async def put(self, key: str, result: AutomationActionResult) -> None:
118
+ self._results[key] = result.model_copy(deep=True)
119
+
120
+
121
+ @dataclass(frozen=True, slots=True)
122
+ class AutomationActionDefinition:
123
+ command: str
124
+ label: str
125
+ parameter_schema: Mapping[str, Any]
126
+ result_schema: Mapping[str, Any]
127
+
128
+
129
+ @dataclass(frozen=True, slots=True)
130
+ class AutomationEventDefinition:
131
+ event_type: str
132
+ label: str
133
+ data_schema: Mapping[str, Any]
134
+
135
+
136
+ @dataclass(frozen=True, slots=True)
137
+ class AutomationContractReport:
138
+ missing_action_handlers: tuple[str, ...]
139
+ missing_event_publishers: tuple[str, ...]
140
+ undeclared_action_handlers: tuple[str, ...]
141
+ undeclared_event_publishers: tuple[str, ...]
142
+
143
+ @property
144
+ def ok(self) -> bool:
145
+ return not any(
146
+ (
147
+ self.missing_action_handlers,
148
+ self.missing_event_publishers,
149
+ self.undeclared_action_handlers,
150
+ self.undeclared_event_publishers,
151
+ )
152
+ )
153
+
154
+ def messages(self) -> tuple[str, ...]:
155
+ groups = (
156
+ ("Missing action handlers", self.missing_action_handlers),
157
+ ("Missing event publishers", self.missing_event_publishers),
158
+ ("Handlers absent from behaviors.json", self.undeclared_action_handlers),
159
+ ("Events absent from behaviors.json", self.undeclared_event_publishers),
160
+ )
161
+ return tuple(f"{label}: {', '.join(values)}" for label, values in groups if values)
162
+
163
+
164
+ class AutomationRegistry:
165
+ """Registry for typed runtime action handlers and event definitions."""
166
+
167
+ def __init__(
168
+ self,
169
+ *,
170
+ idempotency_store: AutomationIdempotencyStore | None = None,
171
+ ) -> None:
172
+ self.idempotency_store = idempotency_store
173
+ self._actions: dict[str, tuple[AutomationActionDefinition, AutomationActionHandler]] = {}
174
+ self._events: dict[str, AutomationEventDefinition] = {}
175
+ self._key_locks: dict[str, asyncio.Lock] = {}
176
+
177
+ @property
178
+ def action_definitions(self) -> tuple[AutomationActionDefinition, ...]:
179
+ return tuple(item[0] for item in self._actions.values())
180
+
181
+ @property
182
+ def event_definitions(self) -> tuple[AutomationEventDefinition, ...]:
183
+ return tuple(self._events.values())
184
+
185
+ def action(
186
+ self,
187
+ command: str,
188
+ *,
189
+ label: str | None = None,
190
+ parameter_schema: Mapping[str, Any] | None = None,
191
+ result_schema: Mapping[str, Any] | None = None,
192
+ ) -> Callable[[AutomationActionHandler], AutomationActionHandler]:
193
+ """Register an action handler while preserving its builder schemas."""
194
+ normalized = str(command or "").strip()
195
+ if not normalized:
196
+ raise ValueError("Automation action command cannot be empty")
197
+
198
+ def decorator(handler: AutomationActionHandler) -> AutomationActionHandler:
199
+ if normalized in self._actions:
200
+ raise ValueError(f"Automation action '{normalized}' is already registered")
201
+ definition = AutomationActionDefinition(
202
+ command=normalized,
203
+ label=str(label or normalized).strip(),
204
+ parameter_schema=dict(parameter_schema or {}),
205
+ result_schema=dict(result_schema or {}),
206
+ )
207
+ self._actions[normalized] = (definition, handler)
208
+ return handler
209
+
210
+ return decorator
211
+
212
+ def event(
213
+ self,
214
+ event_type: str,
215
+ *,
216
+ label: str | None = None,
217
+ data_schema: Mapping[str, Any] | None = None,
218
+ ) -> AutomationEventDefinition:
219
+ """Declare an event the integration may publish."""
220
+ normalized = str(event_type or "").strip()
221
+ if not normalized:
222
+ raise ValueError("Automation event type cannot be empty")
223
+ if normalized in self._events:
224
+ raise ValueError(f"Automation event '{normalized}' is already registered")
225
+ definition = AutomationEventDefinition(
226
+ event_type=normalized,
227
+ label=str(label or normalized).strip(),
228
+ data_schema=dict(data_schema or {}),
229
+ )
230
+ self._events[normalized] = definition
231
+ return definition
232
+
233
+ async def dispatch(
234
+ self,
235
+ request: AutomationActionRequest | Mapping[str, Any],
236
+ *,
237
+ idempotency_key: str | None = None,
238
+ ) -> AutomationActionResult:
239
+ """Validate and dispatch an action, replaying durable terminal outcomes."""
240
+ action_request = (
241
+ request
242
+ if isinstance(request, AutomationActionRequest)
243
+ else AutomationActionRequest.model_validate(request)
244
+ )
245
+ if idempotency_key:
246
+ action_request = action_request.model_copy(
247
+ update={"idempotency_key": str(idempotency_key).strip() or None}
248
+ )
249
+ registered = self._actions.get(action_request.command)
250
+ if registered is None:
251
+ return AutomationActionResult.failure(
252
+ f"Unknown automation action '{action_request.command}'",
253
+ retryable=False,
254
+ )
255
+
256
+ scoped_key = (
257
+ f"{action_request.command}:{action_request.idempotency_key}"
258
+ if action_request.idempotency_key
259
+ else ""
260
+ )
261
+ if scoped_key and self.idempotency_store is not None:
262
+ lock = self._key_locks.setdefault(scoped_key, asyncio.Lock())
263
+ async with lock:
264
+ cached = await self.idempotency_store.get(scoped_key)
265
+ if cached is not None:
266
+ return cached.model_copy(update={"replayed": True})
267
+ result = await self._invoke(registered[1], action_request)
268
+ if result.ok or not result.retryable:
269
+ await self.idempotency_store.put(scoped_key, result)
270
+ return result
271
+ return await self._invoke(registered[1], action_request)
272
+
273
+ async def _invoke(
274
+ self,
275
+ handler: AutomationActionHandler,
276
+ request: AutomationActionRequest,
277
+ ) -> AutomationActionResult:
278
+ try:
279
+ candidate = handler(request)
280
+ value = await candidate if isinstance(candidate, Awaitable) else candidate
281
+ if isinstance(value, AutomationActionResult):
282
+ return value
283
+ if value is None:
284
+ return AutomationActionResult.success()
285
+ if isinstance(value, Mapping):
286
+ return AutomationActionResult.success(value)
287
+ return AutomationActionResult.failure(
288
+ "Automation handler returned an unsupported result type"
289
+ )
290
+ except Exception as exc:
291
+ return AutomationActionResult.failure(str(exc), retryable=False)
292
+
293
+ def contract_snapshot(self) -> dict[str, Any]:
294
+ """Return stable JSON data for conformance snapshots and tooling."""
295
+ return {
296
+ "actions": [
297
+ {
298
+ "command": item.command,
299
+ "label": item.label,
300
+ "parameter_schema": dict(item.parameter_schema),
301
+ "result_schema": dict(item.result_schema),
302
+ }
303
+ for item in self.action_definitions
304
+ ],
305
+ "events": [
306
+ {
307
+ "event_type": item.event_type,
308
+ "label": item.label,
309
+ "data_schema": dict(item.data_schema),
310
+ }
311
+ for item in self.event_definitions
312
+ ],
313
+ }
314
+
315
+
316
+ @dataclass(slots=True)
317
+ class AutomationClient:
318
+ """High-level automation event publisher backed by the existing EventClient."""
319
+
320
+ event_client: EventClient
321
+ auth_context: RuntimeAuthContext
322
+ registry: AutomationRegistry | None = None
323
+
324
+ async def emit(
325
+ self,
326
+ event_type: str,
327
+ *,
328
+ device: RuntimeDeviceRef | Mapping[str, Any],
329
+ data: Mapping[str, Any] | None = None,
330
+ source: str | None = None,
331
+ severity: EventSeverity | str = EventSeverity.info,
332
+ event_id: str | None = None,
333
+ ) -> bool:
334
+ if self.registry is not None and event_type not in {
335
+ item.event_type for item in self.registry.event_definitions
336
+ }:
337
+ raise ValueError(f"Automation event '{event_type}' is not registered")
338
+ return await self.event_client.send_device_event(
339
+ device=device,
340
+ event_type=event_type,
341
+ auth_context=self.auth_context,
342
+ payload=data,
343
+ source=source,
344
+ severity=severity,
345
+ event_id=event_id,
346
+ )
347
+
348
+
349
+ def build_mock_automation_event(
350
+ event_type: str,
351
+ *,
352
+ data: Mapping[str, Any] | None = None,
353
+ integration_id: str = "mock-integration",
354
+ config_id: str = "mock-config",
355
+ container_id: str = "mock-container",
356
+ device_id: str = "mock-device",
357
+ event_id: str | None = None,
358
+ ) -> CoreEventPayload:
359
+ """Create a canonical, non-dispatched event fixture for automated tests."""
360
+ return build_core_event_payload(
361
+ event_type=event_type,
362
+ integration_id=integration_id,
363
+ config_id=config_id,
364
+ container_id=container_id,
365
+ device_id=device_id,
366
+ payload=data,
367
+ source="automation-sdk-mock",
368
+ event_id=event_id,
369
+ )
370
+
371
+
372
+ def audit_behaviors_contract(
373
+ behaviors: Mapping[str, Any],
374
+ registry: AutomationRegistry,
375
+ ) -> AutomationContractReport:
376
+ """Compare runtime registrations with commands/events in ``behaviors.json``."""
377
+ declared_actions: set[str] = set()
378
+ declared_events: set[str] = set()
379
+ devices = behaviors.get("devices", [])
380
+ if not isinstance(devices, list):
381
+ devices = []
382
+ for device in devices:
383
+ if not isinstance(device, Mapping):
384
+ continue
385
+ actions = device.get("actions", [])
386
+ triggers = device.get("triggers", [])
387
+ for action in actions if isinstance(actions, list) else []:
388
+ if not isinstance(action, Mapping):
389
+ continue
390
+ runtime = action.get("runtime")
391
+ if isinstance(runtime, Mapping) and runtime.get("command"):
392
+ declared_actions.add(str(runtime["command"]).strip())
393
+ for trigger in triggers if isinstance(triggers, list) else []:
394
+ if not isinstance(trigger, Mapping):
395
+ continue
396
+ runtime = trigger.get("runtime")
397
+ if isinstance(runtime, Mapping) and runtime.get("event"):
398
+ declared_events.add(str(runtime["event"]).strip())
399
+
400
+ registered_actions = {item.command for item in registry.action_definitions}
401
+ registered_events = {item.event_type for item in registry.event_definitions}
402
+ return AutomationContractReport(
403
+ missing_action_handlers=tuple(sorted(declared_actions - registered_actions)),
404
+ missing_event_publishers=tuple(sorted(declared_events - registered_events)),
405
+ undeclared_action_handlers=tuple(sorted(registered_actions - declared_actions)),
406
+ undeclared_event_publishers=tuple(sorted(registered_events - declared_events)),
407
+ )
408
+
409
+
410
+ def assert_behaviors_contract(
411
+ behaviors: Mapping[str, Any],
412
+ registry: AutomationRegistry,
413
+ ) -> None:
414
+ report = audit_behaviors_contract(behaviors, registry)
415
+ if not report.ok:
416
+ raise ValueError("; ".join(report.messages()))
@@ -1,12 +1,14 @@
1
1
  """Convenience re-exports for the optional FastAPI adapter layer."""
2
2
 
3
3
  from .adapters.fastapi import (
4
+ dispatch_automation_action_from_fastapi,
4
5
  get_payload_container_id,
5
6
  sync_runtime_auth_from_fastapi_payload,
6
7
  sync_runtime_auth_from_fastapi_request,
7
8
  )
8
9
 
9
10
  __all__ = [
11
+ "dispatch_automation_action_from_fastapi",
10
12
  "get_payload_container_id",
11
13
  "sync_runtime_auth_from_fastapi_payload",
12
14
  "sync_runtime_auth_from_fastapi_request",
@@ -114,11 +114,21 @@ class IntegrationDiscoveryResponse(BaseModel):
114
114
  class IntegrationCommandRequest(BaseModel):
115
115
  """Standard command payload forwarded from PiPhi Core to a runtime."""
116
116
 
117
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
118
+
117
119
  command: str
118
120
  capability_id: str | None = None
119
121
  device_id: str | None = None
120
122
  entity_id: str | None = None
121
123
  args: dict[str, Any] = Field(default_factory=dict)
124
+ idempotency_key: str | None = None
125
+ command_id: str | None = None
126
+ correlation_id: str | None = None
127
+ integration_id: str | None = None
128
+ config_id: str | None = None
129
+ container_id: str | None = None
130
+ run_id: str | None = None
131
+ node_id: str | None = None
122
132
 
123
133
 
124
134
  class RuntimeEntityDashboardResponse(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piphi-runtime-kit-python
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: PiPhi Network runtime integration helpers
5
5
  Keywords: piphi,runtime,integration,iot
6
6
  Author-Email: KelvinSan <support@piphi.network>
@@ -34,7 +34,7 @@ plumbing without hiding the HTTP contract behind a large framework. The goal
34
34
  is to help developers move faster while still understanding what their runtime
35
35
  is doing.
36
36
 
37
- Version `0.6.0` is the current documented baseline.
37
+ Version `0.7.0` is the current documented baseline.
38
38
 
39
39
  > New to PiPhi? Start with [First 15 Minutes](#first-15-minutes), then read [The Golden Path](#the-golden-path), then compare your code to the example app.
40
40
 
@@ -95,6 +95,8 @@ The SDK is meant to own the shared PiPhi runtime plumbing:
95
95
  - discovery normalization and response helpers
96
96
  - lifecycle bootstrap helpers
97
97
  - runtime health and diagnostics helpers
98
+ - typed automation actions and event publishers
99
+ - automation idempotency propagation and contract auditing
98
100
  - in-memory runtime registry for active entries, state, and recent events
99
101
  - clearer PiPhi-specific delivery errors
100
102
 
@@ -167,6 +169,49 @@ The SDK remains intentionally thin: it validates and sends the data, while
167
169
  PiPhi Core owns state-change derivation, persistence policy, and automation
168
170
  semantics.
169
171
 
172
+ ## Automation SDK
173
+
174
+ `behaviors.json` and the Automation SDK solve two different halves of the same
175
+ integration contract:
176
+
177
+ - `behaviors.json` is the declarative catalog. It tells Core and the builders
178
+ which triggers and actions exist, how they are labeled, and which fields the
179
+ user can configure.
180
+ - the Automation SDK is executable runtime code. It publishes declared events,
181
+ receives action commands, validates their context, invokes the correct
182
+ handler, and preserves Core's idempotency key.
183
+
184
+ Declare the user-facing card in `behaviors.json`, then implement its runtime
185
+ command with `AutomationRegistry`:
186
+
187
+ ```python
188
+ from piphi_runtime_kit_python import (
189
+ AutomationActionRequest,
190
+ AutomationRegistry,
191
+ InMemoryAutomationIdempotencyStore,
192
+ )
193
+
194
+ automations = AutomationRegistry(
195
+ # Development/tests only. Use a durable store for production side effects.
196
+ idempotency_store=InMemoryAutomationIdempotencyStore(),
197
+ )
198
+
199
+ @automations.action("set_fan_speed", label="Set fan speed")
200
+ async def set_fan_speed(request: AutomationActionRequest) -> dict[str, int]:
201
+ speed = int(request.args["speed"])
202
+ await vendor_client.set_speed(request.device_id, speed)
203
+ return {"speed": speed}
204
+ ```
205
+
206
+ Core supplies a stable idempotency key for each action. Integrations that
207
+ change a device or external service must persist terminal results in a durable
208
+ `AutomationIdempotencyStore`; the included in-memory store is deliberately for
209
+ tests and local development only.
210
+
211
+ For the complete FastAPI wiring, event publishing, contract audit, and mock
212
+ event testing patterns, read [Building automation-capable integrations](docs/automations.md)
213
+ and run the [automation runtime example](examples/automation_runtime/app.py).
214
+
170
215
  ## Install
171
216
 
172
217
  The package currently supports Python `>=3.11`.
@@ -1,11 +1,12 @@
1
- piphi_runtime_kit_python-0.6.0.dist-info/METADATA,sha256=pIgtdp6nS2WlVavrZ8OAAqSkEtrpMWhR-EsUqv3ArUY,34631
2
- piphi_runtime_kit_python-0.6.0.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90
3
- piphi_runtime_kit_python-0.6.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- piphi_runtime_kit_python-0.6.0.dist-info/licenses/LICENSE,sha256=pS6szQI4X7qAin1ODdoDp-XwZvsFy6ZZ28-JAwGHgzo,1070
5
- piphi_runtime_kit_python/__init__.py,sha256=GpYxQ9cCRjLa8tZ6fxAfn407M0-R-Xbt5oHk4WqnBQ8,7247
1
+ piphi_runtime_kit_python-0.7.0.dist-info/METADATA,sha256=BwOlIggxC3uZuVxEtfw6eTmbZW29Df_OfBXoG3xyIyg,36458
2
+ piphi_runtime_kit_python-0.7.0.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90
3
+ piphi_runtime_kit_python-0.7.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ piphi_runtime_kit_python-0.7.0.dist-info/licenses/LICENSE,sha256=pS6szQI4X7qAin1ODdoDp-XwZvsFy6ZZ28-JAwGHgzo,1070
5
+ piphi_runtime_kit_python/__init__.py,sha256=9IEH3g4lIeGeJCnCmQqJbfjBuq_EmsFfmK61UwelDDA,8024
6
6
  piphi_runtime_kit_python/adapters/__init__.py,sha256=3jBhPZSBN3Qo7MKHlxAol2jPU3cK23BXHR2oTlircsw,361
7
- piphi_runtime_kit_python/adapters/fastapi.py,sha256=hpTWnWKYWsNuCzXm-NGdqO8IwARrcQkp6cTuwOyE6O4,2098
8
- piphi_runtime_kit_python/fastapi.py,sha256=OZ-ds2-g32kB4YkF90D5bM4vVVjpOWPtrNC-WdhDNyo,361
7
+ piphi_runtime_kit_python/adapters/fastapi.py,sha256=6sAEqJLKM_p4CYt3WB5VLsylXlyhe4v3GxBQawO4rps,3078
8
+ piphi_runtime_kit_python/automations.py,sha256=aQN19M3wAH9AH2qw5JOoEmPrTtAX55u8QiKmMno5odI,15053
9
+ piphi_runtime_kit_python/fastapi.py,sha256=xvHU19-CVDPKjmXW6nW72mpdbJEOLZzT8Twal7lCCkc,453
9
10
  piphi_runtime_kit_python/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
10
11
  piphi_runtime_kit_python/runtime/__init__.py,sha256=cVJj6eniXw9oM_H795vVhVyI1c_dYrcZ-eYQgbT4LE4,4081
11
12
  piphi_runtime_kit_python/runtime/auth.py,sha256=qKKkAK4RB-WFPyjQAoXp3t8sQuLDEPb-DR5TTe_OSnA,4702
@@ -25,5 +26,5 @@ piphi_runtime_kit_python/runtime/starter.py,sha256=D_mCuwiQl6xLLKQHjf963tKB2EUdm
25
26
  piphi_runtime_kit_python/runtime/state.py,sha256=OIwVxAzX2ErsZ2XdrWlIP5r3GHHub5y9PDtoqNDlwkQ,952
26
27
  piphi_runtime_kit_python/runtime/tasks.py,sha256=E9WoE75NQiJp3buJYexysl3NuP8HJHJ3KfaGVKLdiw0,3238
27
28
  piphi_runtime_kit_python/runtime/telemetry.py,sha256=vV8f1SY4zD-YjVNZWGl3UdCAEYYxFzaPiSNJ7iFBDNI,6729
28
- piphi_runtime_kit_python/schemas.py,sha256=p1GsExPvhIbXWb8GET0n6WhXa91ujEhASDwwII07n-I,10110
29
- piphi_runtime_kit_python-0.6.0.dist-info/RECORD,,
29
+ piphi_runtime_kit_python/schemas.py,sha256=Dq6PVJxA2rOIt9rEKopHlmRfZujpnVfZIWYzW1uc3iw,10458
30
+ piphi_runtime_kit_python-0.7.0.dist-info/RECORD,,