huitzo-sdk 0.0.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.
- huitzo_sdk/__init__.py +85 -0
- huitzo_sdk/command.py +180 -0
- huitzo_sdk/context.py +122 -0
- huitzo_sdk/errors.py +241 -0
- huitzo_sdk/integrations/__init__.py +26 -0
- huitzo_sdk/integrations/email.py +109 -0
- huitzo_sdk/integrations/files.py +188 -0
- huitzo_sdk/integrations/http.py +173 -0
- huitzo_sdk/integrations/llm.py +157 -0
- huitzo_sdk/integrations/telegram.py +117 -0
- huitzo_sdk/storage/__init__.py +21 -0
- huitzo_sdk/storage/memory.py +181 -0
- huitzo_sdk/storage/namespace.py +64 -0
- huitzo_sdk/storage/protocol.py +173 -0
- huitzo_sdk/types.py +47 -0
- huitzo_sdk-0.0.0.dist-info/METADATA +12 -0
- huitzo_sdk-0.0.0.dist-info/RECORD +18 -0
- huitzo_sdk-0.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: integrations.telegram
|
|
3
|
+
Description: Telegram integration client for sending messages, documents, and photos.
|
|
4
|
+
|
|
5
|
+
Implements:
|
|
6
|
+
- docs/sdk/integrations.md#telegram-integration
|
|
7
|
+
|
|
8
|
+
See Also:
|
|
9
|
+
- docs/sdk/error-handling.md (for IntegrationError)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Protocol, runtime_checkable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@runtime_checkable
|
|
19
|
+
class TelegramBackend(Protocol):
|
|
20
|
+
"""Backend protocol for Telegram bot API."""
|
|
21
|
+
|
|
22
|
+
async def send(
|
|
23
|
+
self,
|
|
24
|
+
*,
|
|
25
|
+
chat_id: str,
|
|
26
|
+
message: str,
|
|
27
|
+
parse_mode: str | None,
|
|
28
|
+
) -> None: ...
|
|
29
|
+
|
|
30
|
+
async def send_document(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
chat_id: str,
|
|
34
|
+
document: bytes,
|
|
35
|
+
filename: str,
|
|
36
|
+
caption: str | None,
|
|
37
|
+
) -> None: ...
|
|
38
|
+
|
|
39
|
+
async def send_photo(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
chat_id: str,
|
|
43
|
+
photo: bytes,
|
|
44
|
+
caption: str | None,
|
|
45
|
+
) -> None: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class TelegramClient:
|
|
50
|
+
"""Client for sending Telegram messages and media.
|
|
51
|
+
|
|
52
|
+
This is the SDK-side interface. The backend injects a real implementation
|
|
53
|
+
at runtime that calls the Telegram Bot API.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
_backend: TelegramBackend
|
|
57
|
+
|
|
58
|
+
async def send(
|
|
59
|
+
self,
|
|
60
|
+
*,
|
|
61
|
+
chat_id: str,
|
|
62
|
+
message: str,
|
|
63
|
+
parse_mode: str | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Send a text message.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
chat_id: Target chat ID.
|
|
69
|
+
message: Message text.
|
|
70
|
+
parse_mode: "Markdown" or "HTML".
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
IntegrationError: If sending fails.
|
|
74
|
+
"""
|
|
75
|
+
await self._backend.send(chat_id=chat_id, message=message, parse_mode=parse_mode)
|
|
76
|
+
|
|
77
|
+
async def send_document(
|
|
78
|
+
self,
|
|
79
|
+
*,
|
|
80
|
+
chat_id: str,
|
|
81
|
+
document: bytes,
|
|
82
|
+
filename: str,
|
|
83
|
+
caption: str | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Send a document.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
chat_id: Target chat ID.
|
|
89
|
+
document: File bytes.
|
|
90
|
+
filename: Filename for the document.
|
|
91
|
+
caption: Optional caption.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
IntegrationError: If sending fails.
|
|
95
|
+
"""
|
|
96
|
+
await self._backend.send_document(
|
|
97
|
+
chat_id=chat_id, document=document, filename=filename, caption=caption
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
async def send_photo(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
chat_id: str,
|
|
104
|
+
photo: bytes,
|
|
105
|
+
caption: str | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Send a photo.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
chat_id: Target chat ID.
|
|
111
|
+
photo: Image bytes.
|
|
112
|
+
caption: Optional caption.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
IntegrationError: If sending fails.
|
|
116
|
+
"""
|
|
117
|
+
await self._backend.send_photo(chat_id=chat_id, photo=photo, caption=caption)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: storage
|
|
3
|
+
Description: Storage interface, backends, and key scoping for the Huitzo SDK.
|
|
4
|
+
|
|
5
|
+
Implements:
|
|
6
|
+
- docs/sdk/storage.md
|
|
7
|
+
- docs/sdk/storage-backends.md
|
|
8
|
+
|
|
9
|
+
See Also:
|
|
10
|
+
- docs/sdk/context.md#ctx-storage (for Context integration)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from huitzo_sdk.storage.memory import InMemoryBackend
|
|
14
|
+
from huitzo_sdk.storage.namespace import StorageNamespace
|
|
15
|
+
from huitzo_sdk.storage.protocol import StorageBackend
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"StorageBackend",
|
|
19
|
+
"InMemoryBackend",
|
|
20
|
+
"StorageNamespace",
|
|
21
|
+
]
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: storage.memory
|
|
3
|
+
Description: In-memory storage backend for testing and development.
|
|
4
|
+
|
|
5
|
+
Implements:
|
|
6
|
+
- docs/sdk/storage-backends.md#storage-backend-protocol
|
|
7
|
+
- docs/sdk/storage.md#core-methods
|
|
8
|
+
- docs/sdk/storage.md#ttl-time-to-live
|
|
9
|
+
- docs/sdk/storage.md#batch-operations
|
|
10
|
+
- docs/sdk/storage.md#transactions
|
|
11
|
+
- docs/sdk/storage.md#query-by-metadata
|
|
12
|
+
|
|
13
|
+
See Also:
|
|
14
|
+
- docs/sdk/storage.md (for high-level storage API)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import builtins
|
|
20
|
+
import copy
|
|
21
|
+
import time
|
|
22
|
+
from contextlib import asynccontextmanager
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from typing import Any, AsyncIterator
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class _Entry:
|
|
29
|
+
"""Internal storage entry."""
|
|
30
|
+
|
|
31
|
+
value: Any
|
|
32
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
33
|
+
expires_at: float | None = None
|
|
34
|
+
|
|
35
|
+
def is_expired(self) -> bool:
|
|
36
|
+
return self.expires_at is not None and time.monotonic() >= self.expires_at
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InMemoryBackend:
|
|
40
|
+
"""In-memory implementation of StorageBackend for testing.
|
|
41
|
+
|
|
42
|
+
Supports all protocol methods including TTL, batch operations,
|
|
43
|
+
metadata queries, and transactions.
|
|
44
|
+
|
|
45
|
+
Note: This backend has the following limitations:
|
|
46
|
+
- Expired entries are cleaned up lazily (only when accessed), which may
|
|
47
|
+
cause memory usage to grow with many TTL-based entries that are never
|
|
48
|
+
accessed again.
|
|
49
|
+
- Transaction implementation is not thread-safe for concurrent operations.
|
|
50
|
+
The _in_transaction flag check and setting are not atomic.
|
|
51
|
+
- Transactions create a deep copy of all data, which has O(n) time and
|
|
52
|
+
space overhead where n is the total number of keys in storage.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self._data: dict[str, _Entry] = {}
|
|
57
|
+
self._in_transaction: bool = False
|
|
58
|
+
self._snapshot: dict[str, _Entry] | None = None
|
|
59
|
+
|
|
60
|
+
def _clean_expired(self, key: str) -> None:
|
|
61
|
+
entry = self._data.get(key)
|
|
62
|
+
if entry is not None and entry.is_expired():
|
|
63
|
+
del self._data[key]
|
|
64
|
+
|
|
65
|
+
def _set_expiration_for_testing(self, key: str, expires_at: float) -> None:
|
|
66
|
+
"""Set expiration time for a key (for testing purposes only).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
key: The key to modify
|
|
70
|
+
expires_at: Expiration timestamp (from time.monotonic())
|
|
71
|
+
"""
|
|
72
|
+
if key in self._data:
|
|
73
|
+
self._data[key].expires_at = expires_at
|
|
74
|
+
|
|
75
|
+
def _get_entry(self, key: str) -> _Entry | None:
|
|
76
|
+
self._clean_expired(key)
|
|
77
|
+
return self._data.get(key)
|
|
78
|
+
|
|
79
|
+
async def save(
|
|
80
|
+
self,
|
|
81
|
+
key: str,
|
|
82
|
+
value: Any,
|
|
83
|
+
*,
|
|
84
|
+
ttl: int | None = None,
|
|
85
|
+
metadata: dict[str, Any] | None = None,
|
|
86
|
+
) -> str:
|
|
87
|
+
expires_at = (time.monotonic() + ttl) if ttl is not None else None
|
|
88
|
+
self._data[key] = _Entry(
|
|
89
|
+
value=value,
|
|
90
|
+
metadata=metadata or {},
|
|
91
|
+
expires_at=expires_at,
|
|
92
|
+
)
|
|
93
|
+
return key
|
|
94
|
+
|
|
95
|
+
async def get(self, key: str, *, default: Any = None) -> Any:
|
|
96
|
+
entry = self._get_entry(key)
|
|
97
|
+
if entry is None:
|
|
98
|
+
return default
|
|
99
|
+
return entry.value
|
|
100
|
+
|
|
101
|
+
async def delete(self, key: str) -> bool:
|
|
102
|
+
self._clean_expired(key)
|
|
103
|
+
if key in self._data:
|
|
104
|
+
del self._data[key]
|
|
105
|
+
return True
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
async def exists(self, key: str) -> bool:
|
|
109
|
+
return self._get_entry(key) is not None
|
|
110
|
+
|
|
111
|
+
async def list(
|
|
112
|
+
self, prefix: str = "", *, limit: int = 100, offset: int = 0
|
|
113
|
+
) -> builtins.list[str]:
|
|
114
|
+
# Clean expired keys lazily
|
|
115
|
+
keys = [k for k in self._data if k.startswith(prefix) and not self._data[k].is_expired()]
|
|
116
|
+
keys.sort()
|
|
117
|
+
return keys[offset : offset + limit]
|
|
118
|
+
|
|
119
|
+
async def save_many(self, items: dict[str, Any], *, ttl: int | None = None) -> None:
|
|
120
|
+
for key, value in items.items():
|
|
121
|
+
await self.save(key, value, ttl=ttl)
|
|
122
|
+
|
|
123
|
+
async def get_many(self, keys: builtins.list[str]) -> dict[str, Any]:
|
|
124
|
+
return {key: await self.get(key) for key in keys}
|
|
125
|
+
|
|
126
|
+
async def delete_many(self, keys: builtins.list[str]) -> int:
|
|
127
|
+
count = 0
|
|
128
|
+
for key in keys:
|
|
129
|
+
if await self.delete(key):
|
|
130
|
+
count += 1
|
|
131
|
+
return count
|
|
132
|
+
|
|
133
|
+
async def query(
|
|
134
|
+
self,
|
|
135
|
+
prefix: str = "",
|
|
136
|
+
*,
|
|
137
|
+
metadata: dict[str, Any] | None = None,
|
|
138
|
+
limit: int = 100,
|
|
139
|
+
) -> builtins.list[dict[str, Any]]:
|
|
140
|
+
results: builtins.list[dict[str, Any]] = []
|
|
141
|
+
for key, entry in sorted(self._data.items()):
|
|
142
|
+
if entry.is_expired():
|
|
143
|
+
continue
|
|
144
|
+
if not key.startswith(prefix):
|
|
145
|
+
continue
|
|
146
|
+
if metadata:
|
|
147
|
+
if not all(entry.metadata.get(k) == v for k, v in metadata.items()):
|
|
148
|
+
continue
|
|
149
|
+
results.append(
|
|
150
|
+
{"key": key, "value": entry.value, "metadata": copy.deepcopy(entry.metadata)}
|
|
151
|
+
)
|
|
152
|
+
if len(results) >= limit:
|
|
153
|
+
break
|
|
154
|
+
return results
|
|
155
|
+
|
|
156
|
+
@asynccontextmanager
|
|
157
|
+
async def transaction(self) -> AsyncIterator[None]:
|
|
158
|
+
"""Atomic transaction with snapshot-based rollback."""
|
|
159
|
+
if self._in_transaction:
|
|
160
|
+
# Nested transaction: just yield (no new snapshot)
|
|
161
|
+
yield
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
# Take snapshot
|
|
165
|
+
self._snapshot = {
|
|
166
|
+
k: _Entry(copy.deepcopy(v.value), copy.deepcopy(v.metadata), v.expires_at)
|
|
167
|
+
for k, v in self._data.items()
|
|
168
|
+
}
|
|
169
|
+
self._in_transaction = True
|
|
170
|
+
try:
|
|
171
|
+
yield
|
|
172
|
+
# Commit: discard snapshot
|
|
173
|
+
self._snapshot = None
|
|
174
|
+
except BaseException:
|
|
175
|
+
# Rollback: restore snapshot
|
|
176
|
+
if self._snapshot is not None:
|
|
177
|
+
self._data = self._snapshot
|
|
178
|
+
self._snapshot = None
|
|
179
|
+
raise
|
|
180
|
+
finally:
|
|
181
|
+
self._in_transaction = False
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: storage.namespace
|
|
3
|
+
Description: Key scoping for tenant-isolated storage.
|
|
4
|
+
|
|
5
|
+
Implements:
|
|
6
|
+
- docs/sdk/storage.md#automatic-scoping
|
|
7
|
+
- docs/sdk/storage.md#scope-levels
|
|
8
|
+
|
|
9
|
+
See Also:
|
|
10
|
+
- docs/sdk/storage-backends.md (for backend protocol)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from uuid import UUID
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class StorageNamespace:
|
|
21
|
+
"""Builds scoped storage keys for tenant isolation.
|
|
22
|
+
|
|
23
|
+
Keys are scoped as:
|
|
24
|
+
user scope: tenant:{tenant_id}:user:{user_id}:pack:{pack_id}:{key}
|
|
25
|
+
tenant scope: tenant:{tenant_id}:pack:{pack_id}:{key}
|
|
26
|
+
pack scope: pack:{pack_id}:{key}
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
tenant_id: UUID
|
|
30
|
+
user_id: UUID
|
|
31
|
+
pack_id: str
|
|
32
|
+
|
|
33
|
+
def scope_key(self, key: str, scope: str = "user") -> str:
|
|
34
|
+
"""Build a fully-scoped storage key.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
key: The user-provided key.
|
|
38
|
+
scope: Scope level - "user" (default), "tenant", or "pack".
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The scoped key string.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If scope is invalid.
|
|
45
|
+
"""
|
|
46
|
+
if scope == "user":
|
|
47
|
+
return f"tenant:{self.tenant_id}:user:{self.user_id}:pack:{self.pack_id}:{key}"
|
|
48
|
+
if scope == "tenant":
|
|
49
|
+
return f"tenant:{self.tenant_id}:pack:{self.pack_id}:{key}"
|
|
50
|
+
if scope == "pack":
|
|
51
|
+
return f"pack:{self.pack_id}:{key}"
|
|
52
|
+
raise ValueError(f"Invalid scope: {scope!r}. Must be 'user', 'tenant', or 'pack'.")
|
|
53
|
+
|
|
54
|
+
def scope_prefix(self, prefix: str, scope: str = "user") -> str:
|
|
55
|
+
"""Build a scoped prefix for listing keys.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
prefix: The user-provided prefix.
|
|
59
|
+
scope: Scope level.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The scoped prefix string.
|
|
63
|
+
"""
|
|
64
|
+
return self.scope_key(prefix, scope)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: storage.protocol
|
|
3
|
+
Description: StorageBackend Protocol defining the abstract storage interface.
|
|
4
|
+
|
|
5
|
+
Implements:
|
|
6
|
+
- docs/sdk/storage-backends.md#storage-backend-protocol
|
|
7
|
+
- docs/sdk/storage.md#core-methods
|
|
8
|
+
|
|
9
|
+
See Also:
|
|
10
|
+
- docs/sdk/storage.md (for high-level storage API)
|
|
11
|
+
- docs/sdk/context.md#ctx-storage (for Context integration)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import builtins
|
|
17
|
+
from contextlib import AbstractAsyncContextManager
|
|
18
|
+
from typing import Any, Protocol, runtime_checkable
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@runtime_checkable
|
|
22
|
+
class StorageBackend(Protocol):
|
|
23
|
+
"""Abstract storage interface enabling multiple backends.
|
|
24
|
+
|
|
25
|
+
All storage backends must implement this protocol. Keys are pre-scoped
|
|
26
|
+
by StorageNamespace before reaching the backend.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
async def save(
|
|
30
|
+
self,
|
|
31
|
+
key: str,
|
|
32
|
+
value: Any,
|
|
33
|
+
*,
|
|
34
|
+
ttl: int | None = None,
|
|
35
|
+
metadata: dict[str, Any] | None = None,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""Save a value to storage.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
key: Storage key (max 256 characters, pre-scoped).
|
|
41
|
+
value: JSON-serializable value.
|
|
42
|
+
ttl: Time-to-live in seconds.
|
|
43
|
+
metadata: Optional metadata for querying.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The storage key.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
StorageError: If save operation fails.
|
|
50
|
+
"""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
async def get(self, key: str, *, default: Any = None) -> Any:
|
|
54
|
+
"""Retrieve a value from storage.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
key: Storage key.
|
|
58
|
+
default: Value returned if key not found.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The stored value, or default if not found.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
StorageError: If retrieval fails.
|
|
65
|
+
"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
async def delete(self, key: str) -> bool:
|
|
69
|
+
"""Delete a value from storage.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
key: Storage key.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
True if deleted, False if not found.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
StorageError: If deletion fails.
|
|
79
|
+
"""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
async def exists(self, key: str) -> bool:
|
|
83
|
+
"""Check if a key exists in storage.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
key: Storage key.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
True if key exists and is not expired.
|
|
90
|
+
"""
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
async def list(
|
|
94
|
+
self, prefix: str = "", *, limit: int = 100, offset: int = 0
|
|
95
|
+
) -> builtins.list[str]:
|
|
96
|
+
"""List keys matching a prefix.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
prefix: Key prefix to match.
|
|
100
|
+
limit: Maximum number of keys to return.
|
|
101
|
+
offset: Pagination offset.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of matching keys.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
StorageError: If listing fails.
|
|
108
|
+
"""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
async def save_many(self, items: dict[str, Any], *, ttl: int | None = None) -> None:
|
|
112
|
+
"""Save multiple key-value pairs.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
items: Dictionary of key-value pairs.
|
|
116
|
+
ttl: Optional TTL applied to all items.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
StorageError: If any save fails.
|
|
120
|
+
"""
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
async def get_many(self, keys: builtins.list[str]) -> dict[str, Any]:
|
|
124
|
+
"""Retrieve multiple values.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
keys: List of keys to retrieve.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dictionary mapping keys to values (None for missing keys).
|
|
131
|
+
"""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
async def delete_many(self, keys: builtins.list[str]) -> int:
|
|
135
|
+
"""Delete multiple keys.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
keys: List of keys to delete.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Number of keys actually deleted.
|
|
142
|
+
"""
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
async def query(
|
|
146
|
+
self,
|
|
147
|
+
prefix: str = "",
|
|
148
|
+
*,
|
|
149
|
+
metadata: dict[str, Any] | None = None,
|
|
150
|
+
limit: int = 100,
|
|
151
|
+
) -> builtins.list[dict[str, Any]]:
|
|
152
|
+
"""Query storage by prefix and metadata filters.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
prefix: Key prefix to match.
|
|
156
|
+
metadata: Metadata key-value filters (exact match).
|
|
157
|
+
limit: Maximum number of results.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of records with key, value, and metadata.
|
|
161
|
+
"""
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
def transaction(self) -> AbstractAsyncContextManager[None]:
|
|
165
|
+
"""Begin an atomic transaction.
|
|
166
|
+
|
|
167
|
+
Usage:
|
|
168
|
+
async with backend.transaction():
|
|
169
|
+
await backend.save("k1", v1)
|
|
170
|
+
await backend.save("k2", v2)
|
|
171
|
+
# Both commit or both rollback.
|
|
172
|
+
"""
|
|
173
|
+
...
|
huitzo_sdk/types.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: types
|
|
3
|
+
Description: Core type definitions for the Huitzo SDK.
|
|
4
|
+
|
|
5
|
+
Implements:
|
|
6
|
+
- docs/sdk/commands.md#decorator-parameters
|
|
7
|
+
- docs/sdk/context.md#deployment-mode
|
|
8
|
+
|
|
9
|
+
See Also:
|
|
10
|
+
- docs/sdk/overview.md (for SDK design philosophy)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Any, Union
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DeploymentMode(Enum):
|
|
23
|
+
"""Deployment mode for the Huitzo platform."""
|
|
24
|
+
|
|
25
|
+
CLOUD = "cloud"
|
|
26
|
+
SELF_HOSTED = "self_hosted"
|
|
27
|
+
EDGE = "edge"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class CommandMetadata:
|
|
32
|
+
"""Metadata stored on decorated command functions."""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
namespace: str
|
|
36
|
+
version: str = "1.0.0"
|
|
37
|
+
timeout: int = 60
|
|
38
|
+
retries: int = 3
|
|
39
|
+
retry_backoff: float = 1.0
|
|
40
|
+
retry_max_wait: int = 60
|
|
41
|
+
queue: str = "default"
|
|
42
|
+
output_format: str = "auto"
|
|
43
|
+
description: str | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Result can be a dict, Pydantic model, str, int, or None
|
|
47
|
+
Result = Union[dict[str, Any], BaseModel, str, int, None]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: huitzo-sdk
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Huitzo SDK for building Intelligence Packs
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: pydantic>=2.12
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: mypy>=1.19; extra == 'dev'
|
|
9
|
+
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: pytest-cov>=7.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=9.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: ruff>=0.14; extra == 'dev'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
huitzo_sdk/__init__.py,sha256=3FeobaJAjqVkTA2r3mp0eeHGL8wZHea4zO-ZJ_7HvTo,1776
|
|
2
|
+
huitzo_sdk/command.py,sha256=ANsJ6pY4ADI6CNi2ZKtEdtjY8NqEO2n6d98hwtTk5HU,5512
|
|
3
|
+
huitzo_sdk/context.py,sha256=YXJfe2m79IlDE7_sGacPM6G-JJP6_T9XByDWKcuS_vE,4044
|
|
4
|
+
huitzo_sdk/errors.py,sha256=ZVXVH6a-mqiVvq8Ma5Uc1_7fcoAE7xdvXXQbmkqy758,5816
|
|
5
|
+
huitzo_sdk/types.py,sha256=2ZbkH_MKz2LAz8Dwnexv1pjTU-4uyWzh0R4p-xCPGLo,1036
|
|
6
|
+
huitzo_sdk/integrations/__init__.py,sha256=cx2U6Z0vOwkkEeli1IyGxE70NaY1b1n7tYjcRxHdR1w,685
|
|
7
|
+
huitzo_sdk/integrations/email.py,sha256=9hDSt5lgbh_w4P3sFTHfmaL15x0lCCeYVTVzSRP5G9o,2631
|
|
8
|
+
huitzo_sdk/integrations/files.py,sha256=c92RdjxGPUDpD6x1Mepi8sHfcugiziQTl_Pmy98eKYY,4961
|
|
9
|
+
huitzo_sdk/integrations/http.py,sha256=cBwOmTW9SyT9ltmIQB-ihemrHVy0OH4opbamcFD8rqU,4574
|
|
10
|
+
huitzo_sdk/integrations/llm.py,sha256=9EHBTX6piIFs2PNRQJDU2ruUpYZLc5Iop0pozdMlUW8,4178
|
|
11
|
+
huitzo_sdk/integrations/telegram.py,sha256=v6yjzuYADYLHBP0UokWqo95wvcbR61bSni1IPX9SuLQ,2716
|
|
12
|
+
huitzo_sdk/storage/__init__.py,sha256=D3M5-3JrGey3GDV-qenTqQKQtwutoiYArrMJWzkUsvQ,503
|
|
13
|
+
huitzo_sdk/storage/memory.py,sha256=dCPng8zqOHU5ucXK-zhgC2ctFEGfQWwKMxXwb8e8Giw,5855
|
|
14
|
+
huitzo_sdk/storage/namespace.py,sha256=24dSCIj7n4tVvFippqJSq0J7rMHpYvnzk6rye3xWLaQ,1803
|
|
15
|
+
huitzo_sdk/storage/protocol.py,sha256=xNPfdPsiOKG7ku0uGsUoJaN07GvO1_TlS9j-lmRwvAI,4363
|
|
16
|
+
huitzo_sdk-0.0.0.dist-info/METADATA,sha256=6DuYxD3GsdgzBBssIJHNK00IxEynnWCx1iEAnh1gDSQ,405
|
|
17
|
+
huitzo_sdk-0.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
18
|
+
huitzo_sdk-0.0.0.dist-info/RECORD,,
|