aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a2__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.
- aethergraph/__init__.py +4 -10
- aethergraph/__main__.py +293 -0
- aethergraph/api/v1/__init__.py +0 -0
- aethergraph/api/v1/agents.py +46 -0
- aethergraph/api/v1/apps.py +70 -0
- aethergraph/api/v1/artifacts.py +415 -0
- aethergraph/api/v1/channels.py +89 -0
- aethergraph/api/v1/deps.py +168 -0
- aethergraph/api/v1/graphs.py +259 -0
- aethergraph/api/v1/identity.py +25 -0
- aethergraph/api/v1/memory.py +353 -0
- aethergraph/api/v1/misc.py +47 -0
- aethergraph/api/v1/pagination.py +29 -0
- aethergraph/api/v1/runs.py +568 -0
- aethergraph/api/v1/schemas.py +535 -0
- aethergraph/api/v1/session.py +323 -0
- aethergraph/api/v1/stats.py +201 -0
- aethergraph/api/v1/viz.py +152 -0
- aethergraph/config/config.py +22 -0
- aethergraph/config/loader.py +3 -2
- aethergraph/config/storage.py +209 -0
- aethergraph/contracts/__init__.py +0 -0
- aethergraph/contracts/services/__init__.py +0 -0
- aethergraph/contracts/services/artifacts.py +27 -14
- aethergraph/contracts/services/memory.py +45 -17
- aethergraph/contracts/services/metering.py +129 -0
- aethergraph/contracts/services/runs.py +50 -0
- aethergraph/contracts/services/sessions.py +87 -0
- aethergraph/contracts/services/state_stores.py +3 -0
- aethergraph/contracts/services/viz.py +44 -0
- aethergraph/contracts/storage/artifact_index.py +88 -0
- aethergraph/contracts/storage/artifact_store.py +99 -0
- aethergraph/contracts/storage/async_kv.py +34 -0
- aethergraph/contracts/storage/blob_store.py +50 -0
- aethergraph/contracts/storage/doc_store.py +35 -0
- aethergraph/contracts/storage/event_log.py +31 -0
- aethergraph/contracts/storage/vector_index.py +48 -0
- aethergraph/core/__init__.py +0 -0
- aethergraph/core/execution/forward_scheduler.py +13 -2
- aethergraph/core/execution/global_scheduler.py +21 -15
- aethergraph/core/execution/step_forward.py +10 -1
- aethergraph/core/graph/__init__.py +0 -0
- aethergraph/core/graph/graph_builder.py +8 -4
- aethergraph/core/graph/graph_fn.py +156 -15
- aethergraph/core/graph/graph_spec.py +8 -0
- aethergraph/core/graph/graphify.py +146 -27
- aethergraph/core/graph/node_spec.py +0 -2
- aethergraph/core/graph/node_state.py +3 -0
- aethergraph/core/graph/task_graph.py +39 -1
- aethergraph/core/runtime/__init__.py +0 -0
- aethergraph/core/runtime/ad_hoc_context.py +64 -4
- aethergraph/core/runtime/base_service.py +28 -4
- aethergraph/core/runtime/execution_context.py +13 -15
- aethergraph/core/runtime/graph_runner.py +222 -37
- aethergraph/core/runtime/node_context.py +510 -6
- aethergraph/core/runtime/node_services.py +12 -5
- aethergraph/core/runtime/recovery.py +15 -1
- aethergraph/core/runtime/run_manager.py +783 -0
- aethergraph/core/runtime/run_manager_local.py +204 -0
- aethergraph/core/runtime/run_registration.py +2 -2
- aethergraph/core/runtime/run_types.py +89 -0
- aethergraph/core/runtime/runtime_env.py +136 -7
- aethergraph/core/runtime/runtime_metering.py +71 -0
- aethergraph/core/runtime/runtime_registry.py +36 -13
- aethergraph/core/runtime/runtime_services.py +194 -6
- aethergraph/core/tools/builtins/toolset.py +1 -1
- aethergraph/core/tools/toolkit.py +5 -0
- aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
- aethergraph/plugins/agents/default_chat_agent.py +171 -0
- aethergraph/plugins/agents/shared.py +81 -0
- aethergraph/plugins/channel/adapters/webui.py +112 -112
- aethergraph/plugins/channel/routes/webui_routes.py +367 -102
- aethergraph/plugins/channel/utils/slack_utils.py +115 -59
- aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
- aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
- aethergraph/runtime/__init__.py +15 -0
- aethergraph/server/app_factory.py +190 -34
- aethergraph/server/clients/channel_client.py +202 -0
- aethergraph/server/http/channel_http_routes.py +116 -0
- aethergraph/server/http/channel_ws_routers.py +45 -0
- aethergraph/server/loading.py +117 -0
- aethergraph/server/server.py +131 -0
- aethergraph/server/server_state.py +240 -0
- aethergraph/server/start.py +227 -66
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
- aethergraph/server/ui_static/index.html +15 -0
- aethergraph/server/ui_static/logo.png +0 -0
- aethergraph/services/artifacts/__init__.py +0 -0
- aethergraph/services/artifacts/facade.py +1239 -132
- aethergraph/services/auth/{dev.py → authn.py} +0 -8
- aethergraph/services/auth/authz.py +100 -0
- aethergraph/services/channel/__init__.py +0 -0
- aethergraph/services/channel/channel_bus.py +19 -1
- aethergraph/services/channel/factory.py +13 -1
- aethergraph/services/channel/ingress.py +311 -0
- aethergraph/services/channel/queue_adapter.py +75 -0
- aethergraph/services/channel/session.py +502 -19
- aethergraph/services/container/default_container.py +122 -43
- aethergraph/services/continuations/continuation.py +6 -0
- aethergraph/services/continuations/stores/fs_store.py +19 -0
- aethergraph/services/eventhub/event_hub.py +76 -0
- aethergraph/services/kv/__init__.py +0 -0
- aethergraph/services/kv/ephemeral.py +244 -0
- aethergraph/services/llm/__init__.py +0 -0
- aethergraph/services/llm/generic_client copy.py +691 -0
- aethergraph/services/llm/generic_client.py +1288 -187
- aethergraph/services/llm/providers.py +3 -1
- aethergraph/services/llm/types.py +47 -0
- aethergraph/services/llm/utils.py +284 -0
- aethergraph/services/logger/std.py +3 -0
- aethergraph/services/mcp/__init__.py +9 -0
- aethergraph/services/mcp/http_client.py +38 -0
- aethergraph/services/mcp/service.py +225 -1
- aethergraph/services/mcp/stdio_client.py +41 -6
- aethergraph/services/mcp/ws_client.py +44 -2
- aethergraph/services/memory/__init__.py +0 -0
- aethergraph/services/memory/distillers/llm_long_term.py +234 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
- aethergraph/services/memory/distillers/long_term.py +225 -0
- aethergraph/services/memory/facade/__init__.py +3 -0
- aethergraph/services/memory/facade/chat.py +440 -0
- aethergraph/services/memory/facade/core.py +447 -0
- aethergraph/services/memory/facade/distillation.py +424 -0
- aethergraph/services/memory/facade/rag.py +410 -0
- aethergraph/services/memory/facade/results.py +315 -0
- aethergraph/services/memory/facade/retrieval.py +139 -0
- aethergraph/services/memory/facade/types.py +77 -0
- aethergraph/services/memory/facade/utils.py +43 -0
- aethergraph/services/memory/facade_dep.py +1539 -0
- aethergraph/services/memory/factory.py +9 -3
- aethergraph/services/memory/utils.py +10 -0
- aethergraph/services/metering/eventlog_metering.py +470 -0
- aethergraph/services/metering/noop.py +25 -4
- aethergraph/services/rag/__init__.py +0 -0
- aethergraph/services/rag/facade.py +279 -23
- aethergraph/services/rag/index_factory.py +2 -2
- aethergraph/services/rag/node_rag.py +317 -0
- aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
- aethergraph/services/registry/__init__.py +0 -0
- aethergraph/services/registry/agent_app_meta.py +419 -0
- aethergraph/services/registry/registry_key.py +1 -1
- aethergraph/services/registry/unified_registry.py +74 -6
- aethergraph/services/scope/scope.py +159 -0
- aethergraph/services/scope/scope_factory.py +164 -0
- aethergraph/services/state_stores/serialize.py +5 -0
- aethergraph/services/state_stores/utils.py +2 -1
- aethergraph/services/viz/__init__.py +0 -0
- aethergraph/services/viz/facade.py +413 -0
- aethergraph/services/viz/viz_service.py +69 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
- aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
- aethergraph/storage/artifacts/cas_store.py +422 -0
- aethergraph/storage/artifacts/fs_cas.py +18 -0
- aethergraph/storage/artifacts/s3_cas.py +14 -0
- aethergraph/storage/artifacts/utils.py +124 -0
- aethergraph/storage/blob/fs_blob.py +86 -0
- aethergraph/storage/blob/s3_blob.py +115 -0
- aethergraph/storage/continuation_store/fs_cont.py +283 -0
- aethergraph/storage/continuation_store/inmem_cont.py +146 -0
- aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
- aethergraph/storage/docstore/fs_doc.py +63 -0
- aethergraph/storage/docstore/sqlite_doc.py +31 -0
- aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
- aethergraph/storage/eventlog/fs_event.py +136 -0
- aethergraph/storage/eventlog/sqlite_event.py +47 -0
- aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
- aethergraph/storage/factory.py +432 -0
- aethergraph/storage/fs_utils.py +28 -0
- aethergraph/storage/graph_state_store/state_store.py +64 -0
- aethergraph/storage/kv/inmem_kv.py +103 -0
- aethergraph/storage/kv/layered_kv.py +52 -0
- aethergraph/storage/kv/sqlite_kv.py +39 -0
- aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
- aethergraph/storage/memory/event_persist.py +68 -0
- aethergraph/storage/memory/fs_persist.py +118 -0
- aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
- aethergraph/{services → storage}/memory/indices.py +31 -7
- aethergraph/storage/metering/meter_event.py +55 -0
- aethergraph/storage/runs/doc_store.py +280 -0
- aethergraph/storage/runs/inmen_store.py +82 -0
- aethergraph/storage/runs/sqlite_run_store.py +403 -0
- aethergraph/storage/sessions/doc_store.py +183 -0
- aethergraph/storage/sessions/inmem_store.py +110 -0
- aethergraph/storage/sessions/sqlite_session_store.py +399 -0
- aethergraph/storage/vector_index/chroma_index.py +138 -0
- aethergraph/storage/vector_index/faiss_index.py +179 -0
- aethergraph/storage/vector_index/sqlite_index.py +187 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a2.dist-info/RECORD +356 -0
- aethergraph-0.1.0a2.dist-info/entry_points.txt +3 -0
- aethergraph/services/artifacts/factory.py +0 -35
- aethergraph/services/artifacts/fs_store.py +0 -656
- aethergraph/services/artifacts/jsonl_index.py +0 -123
- aethergraph/services/artifacts/sqlite_index.py +0 -209
- aethergraph/services/memory/distillers/episode.py +0 -116
- aethergraph/services/memory/distillers/rolling.py +0 -74
- aethergraph/services/memory/facade.py +0 -633
- aethergraph/services/memory/persist_fs.py +0 -40
- aethergraph/services/rag/index/base.py +0 -27
- aethergraph/services/rag/index/faiss_index.py +0 -121
- aethergraph/services/rag/index/sqlite_index.py +0 -134
- aethergraph-0.1.0a1.dist-info/RECORD +0 -182
- aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/top_level.txt +0 -0
|
@@ -24,6 +24,31 @@ class EphemeralKV:
|
|
|
24
24
|
return f"{self._prefix}{k}" if self._prefix else k
|
|
25
25
|
|
|
26
26
|
async def get(self, key: str, default: Any = None) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
Retrieve the value associated with a key from the ephemeral key-value store.
|
|
29
|
+
|
|
30
|
+
This method checks for key existence and expiration, automatically removing
|
|
31
|
+
expired entries. If the key does not exist or is expired, the provided
|
|
32
|
+
`default` value is returned.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
Basic usage to fetch a value:
|
|
36
|
+
```python
|
|
37
|
+
value = await context.kv().get("session_token")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Providing a default if the key is missing or expired:
|
|
41
|
+
```python
|
|
42
|
+
user_id = await context.kv().get("user_id", default=None)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
key: The string key to look up.
|
|
47
|
+
default: The value to return if the key is not found or has expired.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The stored value if present and not expired; otherwise, the `default`.
|
|
51
|
+
"""
|
|
27
52
|
k = self._k(key)
|
|
28
53
|
with self._lock:
|
|
29
54
|
e = self._data.get(k)
|
|
@@ -35,11 +60,60 @@ class EphemeralKV:
|
|
|
35
60
|
return e.value
|
|
36
61
|
|
|
37
62
|
async def set(self, key: str, value: Any, *, ttl_s: int | None = None) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Store a value in the ephemeral key-value store with optional expiration.
|
|
65
|
+
|
|
66
|
+
This method inserts or updates the value for a given key, optionally setting
|
|
67
|
+
a time-to-live (TTL) in seconds. If a TTL is provided, the entry will expire
|
|
68
|
+
and be automatically removed after the specified duration.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
Basic usage to store a value:
|
|
72
|
+
```python
|
|
73
|
+
await context.kv().set("session_token", "abc123")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Storing a value with a 10-minute expiration:
|
|
77
|
+
```python
|
|
78
|
+
await context.kv().set("user_id", 42, ttl_s=600)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
key: The string key under which to store the value.
|
|
83
|
+
value: The value to store (any serializable object).
|
|
84
|
+
ttl_s: Optional expiration time in seconds. If None, the value does not expire.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
None
|
|
88
|
+
"""
|
|
38
89
|
k = self._k(key)
|
|
39
90
|
with self._lock:
|
|
40
91
|
self._data[k] = KVEntry(value=value, expire_at=(time.time() + ttl_s) if ttl_s else None)
|
|
41
92
|
|
|
42
93
|
async def delete(self, key: str) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Remove a key and its associated value from the ephemeral key-value store.
|
|
96
|
+
|
|
97
|
+
This method deletes the specified key from the store if it exists. If the key
|
|
98
|
+
does not exist, the operation is a no-op and does not raise an error.
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
Basic usage to delete a key:
|
|
102
|
+
```python
|
|
103
|
+
await context.kv().delete("session_token")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Deleting a user-specific cache entry:
|
|
107
|
+
```python
|
|
108
|
+
await context.kv().delete(f"user_cache:{user_id}")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
key: The string key to remove from the store.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
None
|
|
116
|
+
"""
|
|
43
117
|
k = self._k(key)
|
|
44
118
|
with self._lock:
|
|
45
119
|
self._data.pop(k, None)
|
|
@@ -47,6 +121,52 @@ class EphemeralKV:
|
|
|
47
121
|
async def list_append_unique(
|
|
48
122
|
self, key: str, items: list[dict], *, id_key: str = "id", ttl_s: int | None = None
|
|
49
123
|
) -> list[dict]:
|
|
124
|
+
"""
|
|
125
|
+
Append unique dictionary items to a list stored in the ephemeral key-value store.
|
|
126
|
+
|
|
127
|
+
This method ensures that only items with unique `id_key` values are added to the list
|
|
128
|
+
associated with the given key. If the key does not exist, a new list is created.
|
|
129
|
+
Optionally, a time-to-live (TTL) can be set for the entry.
|
|
130
|
+
|
|
131
|
+
Examples:
|
|
132
|
+
Basic usage to append unique items:
|
|
133
|
+
```python
|
|
134
|
+
await context.kv().list_append_unique("recent_users", [{"id": 1, "name": "Alice"}])
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Appending multiple items with a custom ID key and expiration:
|
|
138
|
+
```python
|
|
139
|
+
await context.kv().list_append_unique(
|
|
140
|
+
"tasks",
|
|
141
|
+
[{"task_id": 42, "desc": "Review PR"}],
|
|
142
|
+
id_key="task_id",
|
|
143
|
+
ttl_s=3600
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
key: The string key under which the list is stored.
|
|
149
|
+
items: A list of dictionaries to append. Only items with unique `id_key` values
|
|
150
|
+
(not already present in the list) will be added.
|
|
151
|
+
id_key: The dictionary key used to determine uniqueness (default: `"id"`).
|
|
152
|
+
ttl_s: Optional expiration time in seconds for the updated list. If None, the list does not expire.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
list[dict]: The updated list of dictionaries after appending unique items.
|
|
156
|
+
|
|
157
|
+
Notes:
|
|
158
|
+
- This method is used for lists of dictionaries where each dictionary has a unique identifier. For example,
|
|
159
|
+
it can be used to maintain a list of recent user actions, ensuring no duplicates based on user ID.
|
|
160
|
+
- Example of the stored list structure:
|
|
161
|
+
```python
|
|
162
|
+
[
|
|
163
|
+
{"id": 1, "name": "Alice"},
|
|
164
|
+
{"id": 2, "name": "Bob"},
|
|
165
|
+
...
|
|
166
|
+
]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
"""
|
|
50
170
|
k = self._k(key)
|
|
51
171
|
with self._lock:
|
|
52
172
|
cur = list(self._data.get(k, KVEntry([])).value or [])
|
|
@@ -56,6 +176,30 @@ class EphemeralKV:
|
|
|
56
176
|
return cur
|
|
57
177
|
|
|
58
178
|
async def list_pop_all(self, key: str) -> list:
|
|
179
|
+
"""
|
|
180
|
+
Atomically remove and return all items from a list stored in the ephemeral key-value store.
|
|
181
|
+
|
|
182
|
+
This method retrieves the entire list associated with the given key and removes the key from the store.
|
|
183
|
+
If the key does not exist or does not contain a list, an empty list is returned. This operation is atomic
|
|
184
|
+
and ensures no items are left behind after the call.
|
|
185
|
+
|
|
186
|
+
Examples:
|
|
187
|
+
Basic usage to pop all items from a list:
|
|
188
|
+
```python
|
|
189
|
+
items = await context.kv().list_pop_all("recent_events")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Handling the case where the key may not exist:
|
|
193
|
+
```python
|
|
194
|
+
logs = await context.kv().list_pop_all("logs") # returns [] if "logs" is missing
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
key: The string key under which the list is stored.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
list: The list of items that were stored under the key, or an empty list if the key was not found.
|
|
202
|
+
"""
|
|
59
203
|
k = self._k(key)
|
|
60
204
|
with self._lock:
|
|
61
205
|
e = self._data.pop(k, None)
|
|
@@ -63,13 +207,89 @@ class EphemeralKV:
|
|
|
63
207
|
|
|
64
208
|
# Optional helpers
|
|
65
209
|
async def mget(self, keys: list[str]) -> list[Any]:
|
|
210
|
+
"""
|
|
211
|
+
Retrieve multiple values from the ephemeral key-value store in a single call.
|
|
212
|
+
|
|
213
|
+
This method fetches the values associated with each key in the provided list,
|
|
214
|
+
preserving the order of the input. If a key does not exist or is expired, `None`
|
|
215
|
+
is returned in its place.
|
|
216
|
+
|
|
217
|
+
Examples:
|
|
218
|
+
Basic usage to fetch several values:
|
|
219
|
+
```python
|
|
220
|
+
values = await context.kv().mget(["user_id", "session_token", "profile"])
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Handling missing or expired keys:
|
|
224
|
+
```python
|
|
225
|
+
results = await context.kv().mget(["foo", "bar"])
|
|
226
|
+
# results might be [None, "bar_value"] if "foo" is missing or expired
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
keys: A list of string keys to retrieve from the store.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
list[Any]: A list of values corresponding to the input keys. If a key is not found
|
|
234
|
+
or has expired, its position in the list will be `None`.
|
|
235
|
+
"""
|
|
66
236
|
return [await self.get(k) for k in keys]
|
|
67
237
|
|
|
68
238
|
async def mset(self, kv: dict[str, Any], *, ttl_s: int | None = None) -> None:
|
|
239
|
+
"""
|
|
240
|
+
Set multiple key-value pairs in the ephemeral key-value store.
|
|
241
|
+
|
|
242
|
+
This asynchronous method iterates over the provided dictionary and sets each key-value pair
|
|
243
|
+
in the store, optionally applying a time-to-live (TTL) to each entry. If a TTL is specified,
|
|
244
|
+
each key will expire after the given number of seconds.
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
Basic usage to set multiple values:
|
|
248
|
+
```python
|
|
249
|
+
await context.kv().mset({"foo": 1, "bar": "baz"})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Setting multiple values with a TTL of 60 seconds:
|
|
253
|
+
```python
|
|
254
|
+
await context.kv().mset({"session": "abc", "count": 42}, ttl_s=60)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
kv: A dictionary mapping string keys to values to be stored.
|
|
259
|
+
ttl_s: Optional; the time-to-live for each key in seconds. If None, keys do not expire.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
None
|
|
263
|
+
"""
|
|
69
264
|
for k, v in kv.items():
|
|
70
265
|
await self.set(k, v, ttl_s=ttl_s)
|
|
71
266
|
|
|
72
267
|
async def expire(self, key: str, ttl_s: int) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Set or update the expiration time (TTL) for a key in the ephemeral key-value store.
|
|
270
|
+
|
|
271
|
+
This method updates the expiration timestamp for an existing key, causing it to expire
|
|
272
|
+
and be automatically removed after the specified number of seconds. If the key does not
|
|
273
|
+
exist, this operation is a no-op.
|
|
274
|
+
|
|
275
|
+
Examples:
|
|
276
|
+
Basic usage to set a 5-minute expiration:
|
|
277
|
+
```python
|
|
278
|
+
await context.kv().expire("session_token", ttl_s=300)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Updating the TTL for a cached user profile:
|
|
282
|
+
```python
|
|
283
|
+
await context.kv().expire("user_profile:42", ttl_s=60)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
key: The string key whose expiration time should be set or updated.
|
|
288
|
+
ttl_s: The time-to-live in seconds from now. After this duration, the key will expire.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
None
|
|
292
|
+
"""
|
|
73
293
|
k = self._k(key)
|
|
74
294
|
with self._lock:
|
|
75
295
|
e = self._data.get(k)
|
|
@@ -77,6 +297,30 @@ class EphemeralKV:
|
|
|
77
297
|
e.expire_at = time.time() + ttl_s
|
|
78
298
|
|
|
79
299
|
async def purge_expired(self, limit: int = 1000) -> int:
|
|
300
|
+
"""
|
|
301
|
+
Remove expired key-value entries from the ephemeral store.
|
|
302
|
+
|
|
303
|
+
This method scans the internal data store for entries whose expiration
|
|
304
|
+
timestamp has passed and removes them, up to the specified limit. It is
|
|
305
|
+
intended to be called periodically to keep the store clean and efficient.
|
|
306
|
+
|
|
307
|
+
Examples:
|
|
308
|
+
Purge up to 1000 expired entries:
|
|
309
|
+
```python
|
|
310
|
+
removed = await context.kv().purge_expired()
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Purge a custom number of expired entries:
|
|
314
|
+
```python
|
|
315
|
+
removed = await context.kv().purge_expired(limit=500)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
limit: The maximum number of expired entries to remove in a single call.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
int: The number of expired entries that were removed from the store.
|
|
323
|
+
"""
|
|
80
324
|
n = 0
|
|
81
325
|
now = time.time()
|
|
82
326
|
with self._lock:
|
|
File without changes
|