digitalkin 0.2.7__py3-none-any.whl → 0.2.9__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.
- digitalkin/__version__.py +1 -1
- digitalkin/services/storage/default_storage.py +127 -125
- digitalkin/services/storage/grpc_storage.py +124 -83
- digitalkin/services/storage/storage_strategy.py +156 -104
- {digitalkin-0.2.7.dist-info → digitalkin-0.2.9.dist-info}/METADATA +7 -7
- {digitalkin-0.2.7.dist-info → digitalkin-0.2.9.dist-info}/RECORD +10 -10
- {digitalkin-0.2.7.dist-info → digitalkin-0.2.9.dist-info}/WHEEL +1 -1
- modules/storage_module.py +9 -4
- {digitalkin-0.2.7.dist-info → digitalkin-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.2.7.dist-info → digitalkin-0.2.9.dist-info}/top_level.txt +0 -0
digitalkin/__version__.py
CHANGED
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
import logging
|
|
6
6
|
import tempfile
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
from pydantic import BaseModel
|
|
10
11
|
|
|
@@ -14,115 +15,91 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class DefaultStorage(StorageStrategy):
|
|
17
|
-
"""
|
|
18
|
+
"""Persist records in a local JSON file for quick local development.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
File format: a JSON object of
|
|
21
|
+
{ "<collection>:<record_id>": { ... StorageRecord fields ... },
|
|
21
22
|
"""
|
|
22
23
|
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _json_default(o: Any) -> str: # noqa: ANN401
|
|
26
|
+
"""JSON serializer for non-standard types (datetime → ISO).
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
o: The object to serialize
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: The serialized object
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
TypeError: If the object is not serializable
|
|
36
|
+
"""
|
|
37
|
+
if isinstance(o, datetime.datetime):
|
|
38
|
+
return o.isoformat()
|
|
39
|
+
msg = f"Type {o.__class__.__name__} not serializable"
|
|
40
|
+
raise TypeError(msg)
|
|
41
|
+
|
|
23
42
|
def _load_from_file(self) -> dict[str, StorageRecord]:
|
|
24
43
|
"""Load storage data from the file.
|
|
25
44
|
|
|
26
45
|
Returns:
|
|
27
46
|
A dictionary containing the loaded storage records
|
|
28
47
|
"""
|
|
29
|
-
|
|
30
|
-
if not file_path.exists():
|
|
48
|
+
if not self.storage_file.exists():
|
|
31
49
|
return {}
|
|
32
50
|
|
|
33
51
|
try:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for key,
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if not model_class:
|
|
43
|
-
logger.warning("No model found for record %s", name)
|
|
52
|
+
raw = json.loads(self.storage_file.read_text(encoding="utf-8"))
|
|
53
|
+
out: dict[str, StorageRecord] = {}
|
|
54
|
+
|
|
55
|
+
for key, rd in raw.items():
|
|
56
|
+
# rd is a dict with the StorageRecord fields
|
|
57
|
+
model_cls = self.config.get(rd["collection"])
|
|
58
|
+
if not model_cls:
|
|
59
|
+
logger.warning("No model for collection '%s'", rd["collection"])
|
|
44
60
|
continue
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
except Exception:
|
|
51
|
-
logger.exception("Failed to validate data for record %s", name)
|
|
52
|
-
continue
|
|
53
|
-
|
|
54
|
-
# Create a StorageRecord object
|
|
55
|
-
record = StorageRecord(
|
|
56
|
-
mission_id=record_dict.get("mission_id", ""),
|
|
57
|
-
name=name,
|
|
61
|
+
data_model = model_cls.model_validate(rd["data"])
|
|
62
|
+
rec = StorageRecord(
|
|
63
|
+
mission_id=rd["mission_id"],
|
|
64
|
+
collection=rd["collection"],
|
|
65
|
+
record_id=rd["record_id"],
|
|
58
66
|
data=data_model,
|
|
59
|
-
data_type=DataType[
|
|
67
|
+
data_type=DataType[rd["data_type"]],
|
|
68
|
+
creation_date=datetime.datetime.fromisoformat(rd["creation_date"])
|
|
69
|
+
if rd.get("creation_date")
|
|
70
|
+
else None,
|
|
71
|
+
update_date=datetime.datetime.fromisoformat(rd["update_date"]) if rd.get("update_date") else None,
|
|
60
72
|
)
|
|
61
|
-
|
|
62
|
-
# Set dates if they exist
|
|
63
|
-
if "creation_date" in record_dict:
|
|
64
|
-
record.creation_date = datetime.datetime.fromisoformat(record_dict["creation_date"])
|
|
65
|
-
if "update_date" in record_dict:
|
|
66
|
-
record.update_date = datetime.datetime.fromisoformat(record_dict["update_date"])
|
|
67
|
-
|
|
68
|
-
records[key] = record
|
|
69
|
-
except json.JSONDecodeError:
|
|
70
|
-
logger.exception("Error decoding JSON from file")
|
|
71
|
-
return {}
|
|
72
|
-
except FileNotFoundError:
|
|
73
|
-
logger.info("Storage file not found, starting with empty storage")
|
|
74
|
-
return {}
|
|
73
|
+
out[key] = rec
|
|
75
74
|
except Exception:
|
|
76
|
-
logger.exception("
|
|
75
|
+
logger.exception("Failed to load default storage file")
|
|
77
76
|
return {}
|
|
78
|
-
return
|
|
77
|
+
return out
|
|
79
78
|
|
|
80
79
|
def _save_to_file(self) -> None:
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# usage of NamedTemporaryFile for atomic writes
|
|
106
|
-
with tempfile.NamedTemporaryFile(
|
|
107
|
-
mode="w", encoding="utf-8", dir=str(file_path.parent), delete=False, suffix=".tmp"
|
|
108
|
-
) as temp_file:
|
|
109
|
-
json.dump(serializable_data, temp_file, indent=2)
|
|
110
|
-
temp_path = temp_file.name
|
|
111
|
-
|
|
112
|
-
# Creation of a backup if the file already exists
|
|
113
|
-
if file_path.exists():
|
|
114
|
-
backup_path = f"{self.storage_file_path}.bak"
|
|
115
|
-
file_path.replace(backup_path)
|
|
116
|
-
|
|
117
|
-
# Remplacement du fichier (opération atomique) avec pathlib
|
|
118
|
-
Path(temp_path).replace(str(file_path))
|
|
119
|
-
|
|
120
|
-
except PermissionError:
|
|
121
|
-
logger.exception("Permission denied when saving to file")
|
|
122
|
-
except OSError:
|
|
123
|
-
logger.exception("OS error when saving to file")
|
|
124
|
-
except Exception:
|
|
125
|
-
logger.exception("Unexpected error saving storage")
|
|
80
|
+
"""Atomically write `self.storage` back to disk as JSON."""
|
|
81
|
+
self.storage_file.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
with tempfile.NamedTemporaryFile(
|
|
83
|
+
mode="w", encoding="utf-8", delete=False, dir=str(self.storage_file.parent), suffix=".tmp"
|
|
84
|
+
) as temp:
|
|
85
|
+
try:
|
|
86
|
+
# Convert storage to a serializable format
|
|
87
|
+
serial: dict[str, dict] = {}
|
|
88
|
+
for key, record in self.storage.items():
|
|
89
|
+
serial[key] = {
|
|
90
|
+
"mission_id": record.mission_id,
|
|
91
|
+
"collection": record.collection,
|
|
92
|
+
"record_id": record.record_id,
|
|
93
|
+
"data_type": record.data_type.name,
|
|
94
|
+
"data": record.data.model_dump(),
|
|
95
|
+
"creation_date": record.creation_date.isoformat() if record.creation_date else None,
|
|
96
|
+
"update_date": record.update_date.isoformat() if record.update_date else None,
|
|
97
|
+
}
|
|
98
|
+
json.dump(serial, temp, indent=2, default=self._json_default)
|
|
99
|
+
temp.flush()
|
|
100
|
+
Path(temp.name).replace(self.storage_file)
|
|
101
|
+
except Exception:
|
|
102
|
+
logger.exception("Unexpected error saving storage")
|
|
126
103
|
|
|
127
104
|
def _store(self, record: StorageRecord) -> StorageRecord:
|
|
128
105
|
"""Store a new record in the database and persist to file.
|
|
@@ -136,73 +113,97 @@ class DefaultStorage(StorageStrategy):
|
|
|
136
113
|
Raises:
|
|
137
114
|
ValueError: If the record already exists
|
|
138
115
|
"""
|
|
139
|
-
|
|
140
|
-
if
|
|
141
|
-
msg = f"
|
|
116
|
+
key = f"{record.collection}:{record.record_id}"
|
|
117
|
+
if key in self.storage:
|
|
118
|
+
msg = f"Document {key!r} already exists"
|
|
142
119
|
raise ValueError(msg)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Persist to file
|
|
120
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
121
|
+
record.creation_date = now
|
|
122
|
+
record.update_date = now
|
|
123
|
+
self.storage[key] = record
|
|
148
124
|
self._save_to_file()
|
|
125
|
+
logger.debug("Created %s", key)
|
|
126
|
+
return record
|
|
149
127
|
|
|
150
|
-
|
|
151
|
-
return self.storage[name]
|
|
152
|
-
|
|
153
|
-
def _read(self, name: str) -> StorageRecord | None:
|
|
128
|
+
def _read(self, collection: str, record_id: str) -> StorageRecord | None:
|
|
154
129
|
"""Get records from the database.
|
|
155
130
|
|
|
156
131
|
Args:
|
|
157
|
-
|
|
132
|
+
collection: The unique name to retrieve data for
|
|
133
|
+
record_id: The unique ID of the record
|
|
158
134
|
|
|
159
135
|
Returns:
|
|
160
136
|
StorageRecord: The corresponding record
|
|
161
137
|
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
logger.info("GET key = %s: DOESN'T EXIST", name)
|
|
165
|
-
return None
|
|
166
|
-
return self.storage[name]
|
|
138
|
+
key = f"{collection}:{record_id}"
|
|
139
|
+
return self.storage.get(key)
|
|
167
140
|
|
|
168
|
-
def
|
|
141
|
+
def _update(self, collection: str, record_id: str, data: BaseModel) -> StorageRecord | None:
|
|
169
142
|
"""Update records in the database and persist to file.
|
|
170
143
|
|
|
171
144
|
Args:
|
|
172
|
-
|
|
145
|
+
collection: The unique name to retrieve data for
|
|
146
|
+
record_id: The unique ID of the record
|
|
173
147
|
data: The data to modify
|
|
174
148
|
|
|
175
149
|
Returns:
|
|
176
150
|
StorageRecord: The modified record
|
|
177
151
|
"""
|
|
178
|
-
|
|
179
|
-
|
|
152
|
+
key = f"{collection}:{record_id}"
|
|
153
|
+
rec = self.storage.get(key)
|
|
154
|
+
if not rec:
|
|
180
155
|
return None
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Persist to file
|
|
156
|
+
rec.data = data
|
|
157
|
+
rec.update_date = datetime.datetime.now(datetime.timezone.utc)
|
|
185
158
|
self._save_to_file()
|
|
159
|
+
logger.debug("Modified %s", key)
|
|
160
|
+
return rec
|
|
186
161
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def _remove(self, name: str) -> bool:
|
|
162
|
+
def _remove(self, collection: str, record_id: str) -> bool:
|
|
190
163
|
"""Delete records from the database and update file.
|
|
191
164
|
|
|
192
165
|
Args:
|
|
193
|
-
|
|
166
|
+
collection: The unique name to retrieve data for
|
|
167
|
+
record_id: The unique ID of the record
|
|
194
168
|
|
|
195
169
|
Returns:
|
|
196
170
|
bool: True if the record was removed, False otherwise
|
|
197
171
|
"""
|
|
198
|
-
|
|
199
|
-
|
|
172
|
+
key = f"{collection}:{record_id}"
|
|
173
|
+
if key not in self.storage:
|
|
200
174
|
return False
|
|
201
|
-
del self.storage[
|
|
202
|
-
|
|
203
|
-
# Persist to file
|
|
175
|
+
del self.storage[key]
|
|
204
176
|
self._save_to_file()
|
|
177
|
+
logger.debug("Removed %s", key)
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def _list(self, collection: str) -> list[StorageRecord]:
|
|
181
|
+
"""Implements StorageStrategy._list.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
collection: The unique name to retrieve data for
|
|
205
185
|
|
|
186
|
+
Returns:
|
|
187
|
+
A list of storage records
|
|
188
|
+
"""
|
|
189
|
+
prefix = f"{collection}:"
|
|
190
|
+
return [r for k, r in self.storage.items() if k.startswith(prefix)]
|
|
191
|
+
|
|
192
|
+
def _remove_collection(self, collection: str) -> bool:
|
|
193
|
+
"""Implements StorageStrategy._remove_collection.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
collection: The unique name to retrieve data for
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
bool: True if the collection was removed, False otherwise
|
|
200
|
+
"""
|
|
201
|
+
prefix = f"{collection}:"
|
|
202
|
+
to_delete = [k for k in self.storage if k.startswith(prefix)]
|
|
203
|
+
for k in to_delete:
|
|
204
|
+
del self.storage[k]
|
|
205
|
+
self._save_to_file()
|
|
206
|
+
logger.debug("Removed collection %s (%d docs)", collection, len(to_delete))
|
|
206
207
|
return True
|
|
207
208
|
|
|
208
209
|
def __init__(
|
|
@@ -215,4 +216,5 @@ class DefaultStorage(StorageStrategy):
|
|
|
215
216
|
"""Initialize the storage."""
|
|
216
217
|
super().__init__(mission_id=mission_id, config=config)
|
|
217
218
|
self.storage_file_path = f"{self.mission_id}_{storage_file_path}.json"
|
|
219
|
+
self.storage_file = Path(self.storage_file_path)
|
|
218
220
|
self.storage = self._load_from_file()
|
|
@@ -17,19 +17,36 @@ logger = logging.getLogger(__name__)
|
|
|
17
17
|
class GrpcStorage(StorageStrategy, GrpcClientWrapper):
|
|
18
18
|
"""This class implements the default storage strategy."""
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
mission_id: str,
|
|
23
|
-
config: dict[str, type[BaseModel]],
|
|
24
|
-
client_config: ClientConfig,
|
|
25
|
-
**kwargs, # noqa: ANN003, ARG002
|
|
26
|
-
) -> None:
|
|
27
|
-
"""Initialize the storage."""
|
|
28
|
-
super().__init__(mission_id=mission_id, config=config)
|
|
20
|
+
def _build_record_from_proto(self, proto: data_pb2.StorageRecord) -> StorageRecord:
|
|
21
|
+
"""Convert a protobuf StorageRecord message into our Pydantic model.
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
Args:
|
|
24
|
+
proto: gRPC StorageRecord
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A fully validated StorageRecord.
|
|
28
|
+
"""
|
|
29
|
+
raw = json_format.MessageToDict(
|
|
30
|
+
proto,
|
|
31
|
+
preserving_proto_field_name=True,
|
|
32
|
+
always_print_fields_with_no_presence=True,
|
|
33
|
+
)
|
|
34
|
+
mission = raw["mission_id"]
|
|
35
|
+
coll = raw["collection"]
|
|
36
|
+
rid = raw["record_id"]
|
|
37
|
+
dtype = DataType[raw["data_type"]]
|
|
38
|
+
payload = raw.get("data", {})
|
|
39
|
+
|
|
40
|
+
validated = self._validate_data(rid, payload)
|
|
41
|
+
return StorageRecord(
|
|
42
|
+
mission_id=mission,
|
|
43
|
+
collection=coll,
|
|
44
|
+
record_id=rid,
|
|
45
|
+
data=validated,
|
|
46
|
+
data_type=dtype,
|
|
47
|
+
creation_date=raw.get("creation_date"),
|
|
48
|
+
update_date=raw.get("update_date"),
|
|
49
|
+
)
|
|
33
50
|
|
|
34
51
|
def _store(self, record: StorageRecord) -> StorageRecord:
|
|
35
52
|
"""Create a new record in the database.
|
|
@@ -44,113 +61,137 @@ class GrpcStorage(StorageStrategy, GrpcClientWrapper):
|
|
|
44
61
|
StorageServiceError: If there is an error while storing the record
|
|
45
62
|
"""
|
|
46
63
|
try:
|
|
47
|
-
# Create a Struct for the data
|
|
48
64
|
data_struct = Struct()
|
|
49
65
|
data_struct.update(record.data.model_dump())
|
|
50
|
-
|
|
51
|
-
request = data_pb2.StoreRecordRequest(
|
|
66
|
+
req = data_pb2.StoreRecordRequest(
|
|
52
67
|
data=data_struct,
|
|
53
68
|
mission_id=record.mission_id,
|
|
54
|
-
|
|
69
|
+
collection=record.collection,
|
|
70
|
+
record_id=record.record_id,
|
|
55
71
|
data_type=record.data_type.name,
|
|
56
72
|
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
logger.exception(
|
|
61
|
-
raise StorageServiceError(
|
|
73
|
+
resp = self.exec_grpc_query("StoreRecord", req)
|
|
74
|
+
return self._build_record_from_proto(resp.stored_data)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.exception("gRPC StoreRecord failed for %s:%s", record.collection, record.record_id)
|
|
77
|
+
raise StorageServiceError(str(e)) from e
|
|
62
78
|
|
|
63
|
-
def _read(self,
|
|
64
|
-
"""
|
|
79
|
+
def _read(self, collection: str, record_id: str) -> StorageRecord | None:
|
|
80
|
+
"""Fetch a single document by collection + record_id.
|
|
65
81
|
|
|
66
82
|
Returns:
|
|
67
|
-
|
|
83
|
+
StorageData: The record
|
|
68
84
|
"""
|
|
69
85
|
try:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
preserving_proto_field_name=True,
|
|
75
|
-
always_print_fields_with_no_presence=True,
|
|
76
|
-
)
|
|
77
|
-
return StorageRecord(
|
|
78
|
-
mission_id=response_dict["mission_id"],
|
|
79
|
-
name=response_dict["name"],
|
|
80
|
-
data_type=response_dict["data_type"],
|
|
81
|
-
data=self._validate_data(name, response_dict["data"]),
|
|
86
|
+
req = data_pb2.ReadRecordRequest(
|
|
87
|
+
mission_id=self.mission_id,
|
|
88
|
+
collection=collection,
|
|
89
|
+
record_id=record_id,
|
|
82
90
|
)
|
|
91
|
+
resp = self.exec_grpc_query("ReadRecord", req)
|
|
92
|
+
return self._build_record_from_proto(resp.stored_data)
|
|
83
93
|
except Exception:
|
|
84
|
-
|
|
85
|
-
logger.exception(msg)
|
|
94
|
+
logger.exception("gRPC ReadRecord failed for %s:%s", collection, record_id)
|
|
86
95
|
return None
|
|
87
96
|
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
97
|
+
def _update(self, collection: str, record_id: str, data: BaseModel) -> StorageRecord | None:
|
|
98
|
+
"""Overwrite a document via gRPC.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
collection: The unique name for the record type
|
|
102
|
+
record_id: The unique ID for the record
|
|
103
|
+
data: The validated data model
|
|
90
104
|
|
|
91
105
|
Returns:
|
|
92
|
-
|
|
106
|
+
StorageRecord: The updated record
|
|
93
107
|
"""
|
|
94
108
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
struct = Struct()
|
|
110
|
+
struct.update(data.model_dump())
|
|
111
|
+
req = data_pb2.UpdateRecordRequest(
|
|
112
|
+
data=struct,
|
|
113
|
+
mission_id=self.mission_id,
|
|
114
|
+
collection=collection,
|
|
115
|
+
record_id=record_id,
|
|
116
|
+
)
|
|
117
|
+
resp = self.exec_grpc_query("ModifyRecord", req)
|
|
118
|
+
return self._build_record_from_proto(resp.stored_data)
|
|
102
119
|
except Exception:
|
|
103
|
-
|
|
104
|
-
logger.exception(msg)
|
|
120
|
+
logger.exception("gRPC ModifyRecord failed for %s:%s", collection, record_id)
|
|
105
121
|
return None
|
|
106
122
|
|
|
107
|
-
def _remove(self,
|
|
108
|
-
"""Delete
|
|
123
|
+
def _remove(self, collection: str, record_id: str) -> bool:
|
|
124
|
+
"""Delete a document via gRPC.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
collection: The unique name for the record type
|
|
128
|
+
record_id: The unique ID for the record
|
|
109
129
|
|
|
110
130
|
Returns:
|
|
111
|
-
|
|
131
|
+
bool: True if the record was deleted, False otherwise
|
|
112
132
|
"""
|
|
113
133
|
try:
|
|
114
|
-
|
|
134
|
+
req = data_pb2.RemoveRecordRequest(
|
|
115
135
|
mission_id=self.mission_id,
|
|
116
|
-
|
|
136
|
+
collection=collection,
|
|
137
|
+
record_id=record_id,
|
|
117
138
|
)
|
|
118
|
-
self.exec_grpc_query("RemoveRecord",
|
|
139
|
+
self.exec_grpc_query("RemoveRecord", req)
|
|
119
140
|
except Exception:
|
|
120
|
-
|
|
121
|
-
logger.exception(msg)
|
|
141
|
+
logger.exception("gRPC RemoveRecord failed for %s:%s", collection, record_id)
|
|
122
142
|
return False
|
|
123
143
|
return True
|
|
124
144
|
|
|
125
|
-
def
|
|
126
|
-
"""
|
|
145
|
+
def _list(self, collection: str) -> list[StorageRecord]:
|
|
146
|
+
"""List all documents in a collection via gRPC.
|
|
127
147
|
|
|
128
148
|
Args:
|
|
129
|
-
|
|
130
|
-
default_name: Le nom par défaut à utiliser si le nom n'est pas présent dans les données.
|
|
149
|
+
collection: The unique name for the record type
|
|
131
150
|
|
|
132
151
|
Returns:
|
|
133
|
-
StorageRecord:
|
|
152
|
+
list[StorageRecord]: A list of storage records
|
|
134
153
|
"""
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
154
|
+
try:
|
|
155
|
+
req = data_pb2.ListRecordsRequest(
|
|
156
|
+
mission_id=self.mission_id,
|
|
157
|
+
collection=collection,
|
|
158
|
+
)
|
|
159
|
+
resp = self.exec_grpc_query("ListRecords", req)
|
|
160
|
+
return [self._build_record_from_proto(r) for r in resp.records]
|
|
161
|
+
except Exception:
|
|
162
|
+
logger.exception("gRPC ListRecords failed for %s", collection)
|
|
163
|
+
return []
|
|
145
164
|
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
def _remove_collection(self, collection: str) -> bool:
|
|
166
|
+
"""Delete an entire collection via gRPC.
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
168
|
+
Args:
|
|
169
|
+
collection: The unique name for the record type
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
bool: True if the collection was deleted, False otherwise
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
req = data_pb2.RemoveCollectionRequest(
|
|
176
|
+
mission_id=self.mission_id,
|
|
177
|
+
collection=collection,
|
|
178
|
+
)
|
|
179
|
+
self.exec_grpc_query("RemoveCollection", req)
|
|
180
|
+
except Exception:
|
|
181
|
+
logger.exception("gRPC RemoveCollection failed for %s", collection)
|
|
182
|
+
return False
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
def __init__(
|
|
186
|
+
self,
|
|
187
|
+
mission_id: str,
|
|
188
|
+
config: dict[str, type[BaseModel]],
|
|
189
|
+
client_config: ClientConfig,
|
|
190
|
+
**kwargs, # noqa: ANN003, ARG002
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Initialize the storage."""
|
|
193
|
+
super().__init__(mission_id=mission_id, config=config)
|
|
194
|
+
|
|
195
|
+
channel = self._init_channel(client_config)
|
|
196
|
+
self.stub = storage_service_pb2_grpc.StorageServiceStub(channel)
|
|
197
|
+
logger.debug("Channel client 'storage' initialized succesfully")
|
|
@@ -4,6 +4,7 @@ import datetime
|
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from typing import Any, Literal, TypeGuard
|
|
7
|
+
from uuid import uuid4
|
|
7
8
|
|
|
8
9
|
from pydantic import BaseModel, Field
|
|
9
10
|
|
|
@@ -24,35 +25,69 @@ class DataType(Enum):
|
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class StorageRecord(BaseModel):
|
|
27
|
-
"""
|
|
28
|
+
"""A single record stored in a collection, with metadata."""
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
data: BaseModel = Field(description="The data stored in the record")
|
|
30
|
+
mission_id: str = Field(..., description="ID of the mission (bucket) this doc belongs to")
|
|
31
|
+
collection: str = Field(..., description="Logical collection name")
|
|
32
|
+
record_id: str = Field(..., description="Unique ID of this record in its collection")
|
|
33
|
+
data_type: DataType = Field(default=DataType.OUTPUT, description="Category of the data of this record")
|
|
34
|
+
data: BaseModel = Field(..., description="The typed payload of this record")
|
|
35
|
+
creation_date: datetime.datetime | None = Field(default=None, description="When this record was first created")
|
|
36
|
+
update_date: datetime.datetime | None = Field(default=None, description="When this record was last modified")
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class StorageStrategy(BaseStrategy, ABC):
|
|
40
|
-
"""
|
|
40
|
+
"""Define CRUD + list/remove-collection against a collection/record store."""
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"""
|
|
42
|
+
def _validate_data(self, record_id: str, data: dict[str, Any]) -> BaseModel:
|
|
43
|
+
"""Validate data against the model schema for the given key.
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
Args:
|
|
46
|
+
record_id: The unique ID for the record
|
|
47
|
+
data: The data to validate
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A validated model instance
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If the key has no associated model or validation fails
|
|
54
|
+
"""
|
|
55
|
+
model_cls = self.config.get(record_id)
|
|
56
|
+
if not model_cls:
|
|
57
|
+
msg = f"No schema registered for collection '{record_id}'"
|
|
58
|
+
raise ValueError(msg)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
return model_cls.model_validate(data)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
msg = f"Validation failed for '{record_id}': {e!s}"
|
|
64
|
+
raise ValueError(msg) from e
|
|
65
|
+
|
|
66
|
+
def _create_storage_record(
|
|
67
|
+
self,
|
|
68
|
+
collection: str,
|
|
69
|
+
record_id: str,
|
|
70
|
+
validated_data: BaseModel,
|
|
71
|
+
data_type: DataType,
|
|
72
|
+
) -> StorageRecord:
|
|
73
|
+
"""Create a storage record with metadata.
|
|
48
74
|
|
|
49
75
|
Args:
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
collection: The unique name for the record type
|
|
77
|
+
record_id: The unique ID for the record
|
|
78
|
+
validated_data: The validated data model
|
|
79
|
+
data_type: The type of data
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A complete storage record with metadata
|
|
52
83
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
84
|
+
return StorageRecord(
|
|
85
|
+
mission_id=self.mission_id,
|
|
86
|
+
collection=collection,
|
|
87
|
+
record_id=record_id,
|
|
88
|
+
data=validated_data,
|
|
89
|
+
data_type=data_type,
|
|
90
|
+
)
|
|
56
91
|
|
|
57
92
|
@staticmethod
|
|
58
93
|
def _is_valid_data_type_name(value: str) -> TypeGuard[str]:
|
|
@@ -69,145 +104,162 @@ class StorageStrategy(BaseStrategy, ABC):
|
|
|
69
104
|
The ID of the created record
|
|
70
105
|
"""
|
|
71
106
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
data: dict[str, Any],
|
|
76
|
-
data_type: Literal["OUTPUT", "VIEW", "LOGS", "OTHER"] = "OUTPUT",
|
|
77
|
-
) -> StorageRecord:
|
|
78
|
-
"""Store a new record in the storage.
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def _read(self, collection: str, record_id: str) -> StorageRecord | None:
|
|
109
|
+
"""Get records from storage by key.
|
|
79
110
|
|
|
80
111
|
Args:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
data_type: The type of data being stored (default: OUTPUT)
|
|
112
|
+
collection: The unique name to retrieve data for
|
|
113
|
+
record_id: The unique ID of the record
|
|
84
114
|
|
|
85
115
|
Returns:
|
|
86
|
-
|
|
116
|
+
A storage record with validated data
|
|
117
|
+
"""
|
|
87
118
|
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def _update(self, collection: str, record_id: str, data: BaseModel) -> StorageRecord | None:
|
|
121
|
+
"""Overwrite an existing record's payload.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
collection: The unique name for the record type
|
|
125
|
+
record_id: The unique ID of the record
|
|
126
|
+
data: The new data to store
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
StorageRecord: The modified record
|
|
90
130
|
"""
|
|
91
|
-
if not self._is_valid_data_type_name(data_type):
|
|
92
|
-
msg = f"Invalid data type '{data_type}'. Must be one of {list(DataType.__members__.keys())}"
|
|
93
|
-
raise ValueError(msg)
|
|
94
|
-
data_type_enum = DataType[data_type]
|
|
95
|
-
validated_data = self._validate_data(name, {**data, "mission_id": self.mission_id})
|
|
96
|
-
record = self._create_storage_record(name, validated_data, data_type_enum)
|
|
97
|
-
return self._store(record)
|
|
98
131
|
|
|
99
132
|
@abstractmethod
|
|
100
|
-
def
|
|
101
|
-
"""
|
|
133
|
+
def _remove(self, collection: str, record_id: str) -> bool:
|
|
134
|
+
"""Delete a record from the storage.
|
|
102
135
|
|
|
103
136
|
Args:
|
|
104
|
-
|
|
137
|
+
collection: The unique name for the record type
|
|
138
|
+
record_id: The unique ID of the record
|
|
105
139
|
|
|
106
140
|
Returns:
|
|
107
|
-
|
|
141
|
+
True if the deletion was successful, False otherwise
|
|
108
142
|
"""
|
|
109
143
|
|
|
110
|
-
|
|
111
|
-
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def _list(self, collection: str) -> list[StorageRecord]:
|
|
146
|
+
"""List all records in a collection.
|
|
112
147
|
|
|
113
148
|
Args:
|
|
114
|
-
|
|
149
|
+
collection: The unique name for the record type
|
|
115
150
|
|
|
116
151
|
Returns:
|
|
117
|
-
A
|
|
152
|
+
A list of storage records
|
|
118
153
|
"""
|
|
119
|
-
return self._read(name)
|
|
120
154
|
|
|
121
155
|
@abstractmethod
|
|
122
|
-
def
|
|
123
|
-
"""
|
|
156
|
+
def _remove_collection(self, collection: str) -> bool:
|
|
157
|
+
"""Delete all records in a collection.
|
|
124
158
|
|
|
125
159
|
Args:
|
|
126
|
-
|
|
127
|
-
data: The new data to store
|
|
160
|
+
collection: The unique name for the record type
|
|
128
161
|
|
|
129
162
|
Returns:
|
|
130
|
-
|
|
163
|
+
True if the deletion was successful, False otherwise
|
|
131
164
|
"""
|
|
132
165
|
|
|
133
|
-
def
|
|
134
|
-
"""
|
|
166
|
+
def __init__(self, mission_id: str, config: dict[str, type[BaseModel]]) -> None:
|
|
167
|
+
"""Initialize the storage strategy.
|
|
135
168
|
|
|
136
169
|
Args:
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
mission_id: The ID of the mission this strategy is associated with
|
|
171
|
+
config: A dictionary mapping names to Pydantic model classes
|
|
172
|
+
"""
|
|
173
|
+
super().__init__(mission_id)
|
|
174
|
+
# Schema configuration mapping keys to model classes
|
|
175
|
+
self.config: dict[str, type[BaseModel]] = config
|
|
176
|
+
|
|
177
|
+
def store(
|
|
178
|
+
self,
|
|
179
|
+
collection: str,
|
|
180
|
+
record_id: str | None,
|
|
181
|
+
data: dict[str, Any],
|
|
182
|
+
data_type: Literal["OUTPUT", "VIEW", "LOGS", "OTHER"] = "OUTPUT",
|
|
183
|
+
) -> StorageRecord:
|
|
184
|
+
"""Store a new record in the storage.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
collection: The unique name for the record type
|
|
188
|
+
record_id: The unique ID for the record (optional)
|
|
189
|
+
data: The data to store
|
|
190
|
+
data_type: The type of data being stored (default: OUTPUT)
|
|
139
191
|
|
|
140
192
|
Returns:
|
|
141
|
-
|
|
193
|
+
The ID of the created record
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
ValueError: If the data type is invalid or if validation fails
|
|
142
197
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
198
|
+
if not self._is_valid_data_type_name(data_type):
|
|
199
|
+
msg = f"Invalid data type '{data_type}'. Must be one of {list(DataType.__members__.keys())}"
|
|
200
|
+
raise ValueError(msg)
|
|
201
|
+
record_id = record_id or uuid4().hex
|
|
202
|
+
data_type_enum = DataType[data_type]
|
|
203
|
+
validated_data = self._validate_data(record_id, {**data, "mission_id": self.mission_id})
|
|
204
|
+
record = self._create_storage_record(collection, record_id, validated_data, data_type_enum)
|
|
205
|
+
return self._store(record)
|
|
145
206
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"""Delete a record from the storage.
|
|
207
|
+
def read(self, collection: str, record_id: str) -> StorageRecord | None:
|
|
208
|
+
"""Get records from storage by key.
|
|
149
209
|
|
|
150
210
|
Args:
|
|
151
|
-
|
|
211
|
+
collection: The unique name to retrieve data for
|
|
212
|
+
record_id: The unique ID of the record
|
|
152
213
|
|
|
153
214
|
Returns:
|
|
154
|
-
|
|
215
|
+
A storage record with validated data
|
|
216
|
+
"""
|
|
217
|
+
return self._read(collection, record_id)
|
|
218
|
+
|
|
219
|
+
def update(self, collection: str, record_id: str, data: dict[str, Any]) -> StorageRecord | None:
|
|
220
|
+
"""Validate & overwrite an existing record.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
collection: The unique name for the record type
|
|
224
|
+
record_id: The unique ID of the record
|
|
225
|
+
data: The new data to store
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
StorageRecord: The modified record
|
|
155
229
|
"""
|
|
230
|
+
validated_data = self._validate_data(record_id, data)
|
|
231
|
+
return self._update(collection, record_id, validated_data)
|
|
156
232
|
|
|
157
|
-
def remove(self,
|
|
233
|
+
def remove(self, collection: str, record_id: str) -> bool:
|
|
158
234
|
"""Delete a record from the storage.
|
|
159
235
|
|
|
160
236
|
Args:
|
|
161
|
-
|
|
237
|
+
collection: The unique name for the record type
|
|
238
|
+
record_id: The unique ID of the record
|
|
162
239
|
|
|
163
240
|
Returns:
|
|
164
241
|
True if the deletion was successful, False otherwise
|
|
165
242
|
"""
|
|
166
|
-
return self._remove(
|
|
243
|
+
return self._remove(collection, record_id)
|
|
167
244
|
|
|
168
|
-
def
|
|
169
|
-
"""
|
|
245
|
+
def list(self, collection: str) -> list[StorageRecord]:
|
|
246
|
+
"""Get all records within a collection.
|
|
170
247
|
|
|
171
248
|
Args:
|
|
172
|
-
|
|
173
|
-
data: The data to validate
|
|
249
|
+
collection: The unique name for the record type
|
|
174
250
|
|
|
175
251
|
Returns:
|
|
176
|
-
A
|
|
177
|
-
|
|
178
|
-
Raises:
|
|
179
|
-
ValueError: If the key has no associated model or validation fails
|
|
252
|
+
A list of storage records
|
|
180
253
|
"""
|
|
181
|
-
|
|
182
|
-
if not model_cls:
|
|
183
|
-
msg = f"No model schema defined for name: {name}"
|
|
184
|
-
raise ValueError(msg)
|
|
254
|
+
return self._list(collection)
|
|
185
255
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
except Exception as e:
|
|
189
|
-
msg = f"Data validation failed for key '{name}': {e!s}"
|
|
190
|
-
raise ValueError(msg) from e
|
|
191
|
-
|
|
192
|
-
def _create_storage_record(
|
|
193
|
-
self,
|
|
194
|
-
name: str,
|
|
195
|
-
validated_data: BaseModel,
|
|
196
|
-
data_type: DataType,
|
|
197
|
-
) -> StorageRecord:
|
|
198
|
-
"""Create a storage record with metadata.
|
|
256
|
+
def remove_collection(self, collection: str) -> bool:
|
|
257
|
+
"""Wipe a record clean.
|
|
199
258
|
|
|
200
259
|
Args:
|
|
201
|
-
|
|
202
|
-
validated_data: The validated data model
|
|
203
|
-
data_type: The type of data
|
|
260
|
+
collection: The unique name for the record type
|
|
204
261
|
|
|
205
262
|
Returns:
|
|
206
|
-
|
|
263
|
+
True if the deletion was successful, False otherwise
|
|
207
264
|
"""
|
|
208
|
-
return
|
|
209
|
-
mission_id=self.mission_id,
|
|
210
|
-
name=name,
|
|
211
|
-
data=validated_data,
|
|
212
|
-
data_type=data_type,
|
|
213
|
-
)
|
|
265
|
+
return self._remove_collection(collection)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: digitalkin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: SDK to build kin used in DigitalKin
|
|
5
5
|
Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
|
|
6
6
|
License: Attribution-NonCommercial-ShareAlike 4.0 International
|
|
@@ -452,20 +452,20 @@ Classifier: License :: Other/Proprietary License
|
|
|
452
452
|
Requires-Python: >=3.10
|
|
453
453
|
Description-Content-Type: text/markdown
|
|
454
454
|
License-File: LICENSE
|
|
455
|
-
Requires-Dist: digitalkin-proto>=0.1.
|
|
455
|
+
Requires-Dist: digitalkin-proto>=0.1.8
|
|
456
456
|
Requires-Dist: grpcio-health-checking>=1.71.0
|
|
457
457
|
Requires-Dist: grpcio-reflection>=1.71.0
|
|
458
458
|
Requires-Dist: grpcio-status>=1.71.0
|
|
459
|
-
Requires-Dist: openai>=1.
|
|
460
|
-
Requires-Dist: pydantic>=2.11.
|
|
459
|
+
Requires-Dist: openai>=1.76.2
|
|
460
|
+
Requires-Dist: pydantic>=2.11.4
|
|
461
461
|
Provides-Extra: dev
|
|
462
462
|
Requires-Dist: pytest>=8.3.4; extra == "dev"
|
|
463
463
|
Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
|
|
464
464
|
Requires-Dist: pytest-cov>=6.1.0; extra == "dev"
|
|
465
|
-
Requires-Dist: typos>=1.31.
|
|
466
|
-
Requires-Dist: ruff>=0.11.
|
|
465
|
+
Requires-Dist: typos>=1.31.2; extra == "dev"
|
|
466
|
+
Requires-Dist: ruff>=0.11.7; extra == "dev"
|
|
467
467
|
Requires-Dist: mypy>=1.15.0; extra == "dev"
|
|
468
|
-
Requires-Dist: pyright>=1.1.
|
|
468
|
+
Requires-Dist: pyright>=1.1.400; extra == "dev"
|
|
469
469
|
Requires-Dist: pre-commit>=4.2.0; extra == "dev"
|
|
470
470
|
Requires-Dist: bump2version>=1.0.1; extra == "dev"
|
|
471
471
|
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
@@ -7,7 +7,7 @@ base_server/mock/__init__.py,sha256=YZFT-F1l_TpvJYuIPX-7kTeE1CfOjhx9YmNRXVoi-jQ,
|
|
|
7
7
|
base_server/mock/mock_pb2.py,sha256=sETakcS3PAAm4E-hTCV1jIVaQTPEAIoVVHupB8Z_k7Y,1843
|
|
8
8
|
base_server/mock/mock_pb2_grpc.py,sha256=BbOT70H6q3laKgkHfOx1QdfmCS_HxCY4wCOX84YAdG4,3180
|
|
9
9
|
digitalkin/__init__.py,sha256=7LLBAba0th-3SGqcpqFO-lopWdUkVLKzLZiMtB-mW3M,162
|
|
10
|
-
digitalkin/__version__.py,sha256=
|
|
10
|
+
digitalkin/__version__.py,sha256=IkrpHiIjAqkWkr05KjzZfvz6pjpVY9ZG9eZexXjyz6A,190
|
|
11
11
|
digitalkin/logger.py,sha256=9cDgyJV2QXXT8F--xRODFlZyDgjuTTXNdpCU3GdqCsk,382
|
|
12
12
|
digitalkin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
digitalkin/grpc_servers/__init__.py,sha256=0cJBlwipSmFdXkyH3T0i6OJ1WpAtNsZgYX7JaSnkbtg,804
|
|
@@ -62,18 +62,18 @@ digitalkin/services/snapshot/__init__.py,sha256=Uzlnzo0CYlSpVsdiI37hW7xQk8hu3YA1
|
|
|
62
62
|
digitalkin/services/snapshot/default_snapshot.py,sha256=Mb8QwWRsHh9I_tN0ln_ZiFa1QCZxOVWmuVLemQOTWpc,1058
|
|
63
63
|
digitalkin/services/snapshot/snapshot_strategy.py,sha256=B1TU3V_k9A-OdqBkdyc41-ihnrW5Btcwd1KyQdHT46A,898
|
|
64
64
|
digitalkin/services/storage/__init__.py,sha256=T-ocYLLphudkQgzvG47jBOm5GQsRFRIGA88y7Ur4akg,341
|
|
65
|
-
digitalkin/services/storage/default_storage.py,sha256=
|
|
66
|
-
digitalkin/services/storage/grpc_storage.py,sha256=
|
|
67
|
-
digitalkin/services/storage/storage_strategy.py,sha256=
|
|
65
|
+
digitalkin/services/storage/default_storage.py,sha256=qzLPrND92NR9hB7Ok6BF3Yxot14Efa_CHIvVte6kLsU,7817
|
|
66
|
+
digitalkin/services/storage/grpc_storage.py,sha256=rx77CkBoC3lJoRxXRybErM4WsZfEE29FMFl5R3ishpc,6936
|
|
67
|
+
digitalkin/services/storage/storage_strategy.py,sha256=PPRFWmZTy2HEaWMGwiGzcx5NIhiGWaX3h0_NOb3a4Oo,8621
|
|
68
68
|
digitalkin/utils/__init__.py,sha256=sJnY-ZUgsjMfojAjONC1VN14mhgIDnzyOlGkw21rRnM,28
|
|
69
69
|
digitalkin/utils/arg_parser.py,sha256=3YyI6oZhhrlTmPTrzlwpQzbCNWDFAT3pggcLxNtJoc0,4388
|
|
70
70
|
digitalkin/utils/llm_ready_schema.py,sha256=JjMug_lrQllqFoanaC091VgOqwAd-_YzcpqFlS7p778,2375
|
|
71
|
-
digitalkin-0.2.
|
|
71
|
+
digitalkin-0.2.9.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
|
|
72
72
|
modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
73
|
modules/minimal_llm_module.py,sha256=W-E3OrRbAsRJ6hvSeTU8pzmacdJC_PbcWfDapRv5A1A,5617
|
|
74
|
-
modules/storage_module.py,sha256=
|
|
74
|
+
modules/storage_module.py,sha256=528tfWyRw3q5h0lUlP9Or8E7m3AnnyXezXgwisXd8BI,6399
|
|
75
75
|
modules/text_transform_module.py,sha256=1KaA7abwxltKKtbmiW1rkkIK3BTYFPegUq54px0LOQs,7277
|
|
76
|
-
digitalkin-0.2.
|
|
77
|
-
digitalkin-0.2.
|
|
78
|
-
digitalkin-0.2.
|
|
79
|
-
digitalkin-0.2.
|
|
76
|
+
digitalkin-0.2.9.dist-info/METADATA,sha256=jGm1DjaKk10LQqFlZjvh_r9h-vnz4wfDR8rkRrTcd6M,29125
|
|
77
|
+
digitalkin-0.2.9.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
|
78
|
+
digitalkin-0.2.9.dist-info/top_level.txt,sha256=5_5e35inSM5YfWNZE21p5wGBojiVtQQML_WzbEk4BRU,31
|
|
79
|
+
digitalkin-0.2.9.dist-info/RECORD,,
|
modules/storage_module.py
CHANGED
|
@@ -119,7 +119,12 @@ class ExampleModule(ArchetypeModule[ExampleInput, ExampleOutput, ExampleSetup, E
|
|
|
119
119
|
)
|
|
120
120
|
|
|
121
121
|
# Store the output data in storage
|
|
122
|
-
storage_id = self.storage.store(
|
|
122
|
+
storage_id = self.storage.store(
|
|
123
|
+
collection="example",
|
|
124
|
+
record_id=f"example_outputs",
|
|
125
|
+
data=output_data.model_dump(),
|
|
126
|
+
data_type="OUTPUT"
|
|
127
|
+
)
|
|
123
128
|
|
|
124
129
|
logger.info("Stored output data with ID: %s", storage_id)
|
|
125
130
|
|
|
@@ -159,7 +164,7 @@ async def test_module() -> None:
|
|
|
159
164
|
|
|
160
165
|
# Check the storage
|
|
161
166
|
if module.status == ModuleStatus.STOPPED:
|
|
162
|
-
result: StorageRecord = module.storage.read("example_outputs")
|
|
167
|
+
result: StorageRecord = module.storage.read("example", "example_outputs")
|
|
163
168
|
if result:
|
|
164
169
|
pass
|
|
165
170
|
|
|
@@ -170,10 +175,10 @@ def test_storage_directly() -> None:
|
|
|
170
175
|
storage = ServicesConfig().storage(mission_id="test-mission", config={"test_table": ExampleStorage})
|
|
171
176
|
|
|
172
177
|
# Create a test record
|
|
173
|
-
storage.store("test_table", {"test_key": "test_value"}, "OUTPUT")
|
|
178
|
+
storage.store("example", "test_table", {"test_key": "test_value"}, "OUTPUT")
|
|
174
179
|
|
|
175
180
|
# Retrieve the record
|
|
176
|
-
retrieved = storage.read("test_table")
|
|
181
|
+
retrieved = storage.read("example", "test_table")
|
|
177
182
|
|
|
178
183
|
if retrieved:
|
|
179
184
|
pass
|
|
File without changes
|
|
File without changes
|