modaic 0.1.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.
Potentially problematic release.
This version of modaic might be problematic. Click here for more details.
- modaic/__init__.py +25 -0
- modaic/agents/rag_agent.py +33 -0
- modaic/agents/registry.py +84 -0
- modaic/auto_agent.py +228 -0
- modaic/context/__init__.py +34 -0
- modaic/context/base.py +1064 -0
- modaic/context/dtype_mapping.py +25 -0
- modaic/context/table.py +585 -0
- modaic/context/text.py +94 -0
- modaic/databases/__init__.py +35 -0
- modaic/databases/graph_database.py +269 -0
- modaic/databases/sql_database.py +355 -0
- modaic/databases/vector_database/__init__.py +12 -0
- modaic/databases/vector_database/benchmarks/baseline.py +123 -0
- modaic/databases/vector_database/benchmarks/common.py +48 -0
- modaic/databases/vector_database/benchmarks/fork.py +132 -0
- modaic/databases/vector_database/benchmarks/threaded.py +119 -0
- modaic/databases/vector_database/vector_database.py +722 -0
- modaic/databases/vector_database/vendors/milvus.py +408 -0
- modaic/databases/vector_database/vendors/mongodb.py +0 -0
- modaic/databases/vector_database/vendors/pinecone.py +0 -0
- modaic/databases/vector_database/vendors/qdrant.py +1 -0
- modaic/exceptions.py +38 -0
- modaic/hub.py +305 -0
- modaic/indexing.py +127 -0
- modaic/module_utils.py +341 -0
- modaic/observability.py +275 -0
- modaic/precompiled.py +429 -0
- modaic/query_language.py +321 -0
- modaic/storage/__init__.py +3 -0
- modaic/storage/file_store.py +239 -0
- modaic/storage/pickle_store.py +25 -0
- modaic/types.py +287 -0
- modaic/utils.py +21 -0
- modaic-0.1.0.dist-info/METADATA +281 -0
- modaic-0.1.0.dist-info/RECORD +39 -0
- modaic-0.1.0.dist-info/WHEEL +5 -0
- modaic-0.1.0.dist-info/licenses/LICENSE +31 -0
- modaic-0.1.0.dist-info/top_level.txt +1 -0
modaic/query_language.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
from types import NoneType
|
|
2
|
+
from typing import Any, Literal, Optional, Type, TypeAlias, Union
|
|
3
|
+
|
|
4
|
+
from langchain_core.structured_query import Comparator, Comparison, Operation, Operator, StructuredQuery, Visitor
|
|
5
|
+
|
|
6
|
+
ValueType: TypeAlias = Union[int, str, float, bool, NoneType, list, "Value"]
|
|
7
|
+
|
|
8
|
+
value_types = (int, str, float, bool, NoneType, list)
|
|
9
|
+
|
|
10
|
+
comparator_to_python = {
|
|
11
|
+
Comparator.EQ: "==",
|
|
12
|
+
Comparator.LT: "<",
|
|
13
|
+
Comparator.LTE: "<=",
|
|
14
|
+
Comparator.GT: ">",
|
|
15
|
+
Comparator.GTE: ">=",
|
|
16
|
+
Comparator.NE: "!=",
|
|
17
|
+
Comparator.CONTAIN: "contains",
|
|
18
|
+
Comparator.IN: "in_",
|
|
19
|
+
Comparator.NIN: "not_in",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
allowed_types = {
|
|
23
|
+
Comparator.EQ: [int, str, list, dict, bool, NoneType],
|
|
24
|
+
Comparator.LT: [int, float],
|
|
25
|
+
Comparator.LTE: [int, float],
|
|
26
|
+
Comparator.GT: [int, float],
|
|
27
|
+
Comparator.GTE: [int, float],
|
|
28
|
+
Comparator.NE: [int, str, list, dict, bool, NoneType],
|
|
29
|
+
Comparator.CONTAIN: [int, str, bool, NoneType],
|
|
30
|
+
Comparator.IN: [list, dict],
|
|
31
|
+
Comparator.NIN: [list, dict],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# def _print_return(func): # noqa: ANN001
|
|
36
|
+
# def wrapper(*args, **kwargs):
|
|
37
|
+
# result = func(*args, **kwargs)
|
|
38
|
+
# if isinstance(op := args[1], str) and op[0] == "$":
|
|
39
|
+
# if kwargs.get("recursed", False):
|
|
40
|
+
# print( # noqa: T201
|
|
41
|
+
# f"{repr(args[0])} ({mql_operator_to_python[op]}) {repr(args[2])} ->:",
|
|
42
|
+
# result,
|
|
43
|
+
# )
|
|
44
|
+
# else:
|
|
45
|
+
# print( # noqa: T201
|
|
46
|
+
# f"{repr(args[0])} {mql_operator_to_python[op]} {repr(args[2])} ->:",
|
|
47
|
+
# result,
|
|
48
|
+
# )
|
|
49
|
+
# else:
|
|
50
|
+
# if func.__name__ == "__and__":
|
|
51
|
+
# print(f"{repr(args[0])} & {repr(args[1])} ->:", result) # noqa: T201
|
|
52
|
+
# elif func.__name__ == "__rand__":
|
|
53
|
+
# print(f"{repr(args[1])} & {repr(args[0])} ->:", result) # noqa: T201
|
|
54
|
+
# return result
|
|
55
|
+
|
|
56
|
+
# return wrapper
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Condition:
|
|
60
|
+
"""
|
|
61
|
+
Modaic Query Language Property class.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
is_operation: bool = False
|
|
65
|
+
condition: Comparison | Operation = None
|
|
66
|
+
|
|
67
|
+
def __init__(self, condition: Comparison | Operation):
|
|
68
|
+
if isinstance(condition, Comparison):
|
|
69
|
+
self.is_operation = False
|
|
70
|
+
else:
|
|
71
|
+
self.is_operation = True
|
|
72
|
+
self.condition = condition
|
|
73
|
+
|
|
74
|
+
def __repr__(self):
|
|
75
|
+
return f"Condition({repr(self.condition)})"
|
|
76
|
+
|
|
77
|
+
def __str__(self):
|
|
78
|
+
return (
|
|
79
|
+
f"Prop({self.condition.attribute}) {comparator_to_python[self.condition.comparator]} {self.condition.value}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def __contains__(self, other: str):
|
|
83
|
+
raise ValueError("Modaic Filters do not support `in` use Prop.in_()/Prop.not_in() instead")
|
|
84
|
+
|
|
85
|
+
def __bool__(self):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Attempted to evaluate Modaic condition as boolean. Please make sure you wrap ALL expresions with ()"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def __and__(self, other: Union["Condition", ValueType]):
|
|
91
|
+
if not isinstance(other, Condition):
|
|
92
|
+
other = Value(other)
|
|
93
|
+
return AND(self, other)
|
|
94
|
+
|
|
95
|
+
# @_print_return
|
|
96
|
+
def __or__(self, other: Union["Condition", ValueType]):
|
|
97
|
+
if not isinstance(other, Condition):
|
|
98
|
+
other = Value(other)
|
|
99
|
+
return OR(self, other)
|
|
100
|
+
|
|
101
|
+
# @_print_return
|
|
102
|
+
def __rand__(self, other: Union[int, str, list]):
|
|
103
|
+
if not isinstance(other, Condition):
|
|
104
|
+
other = Value(other)
|
|
105
|
+
return AND(other, self)
|
|
106
|
+
|
|
107
|
+
# @_print_return
|
|
108
|
+
def __ror__(self, other: Union[int, str, list]):
|
|
109
|
+
if not isinstance(other, Condition):
|
|
110
|
+
other = Value(other)
|
|
111
|
+
return OR(other, self)
|
|
112
|
+
|
|
113
|
+
def __invert__(self):
|
|
114
|
+
# TODO: implement , use nor
|
|
115
|
+
raise NotImplementedError("Modaic queires do not support ! yet")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _enforce_types(
|
|
119
|
+
other: ValueType,
|
|
120
|
+
op: str,
|
|
121
|
+
allowed_types: list[Type],
|
|
122
|
+
):
|
|
123
|
+
other_type = _get_type(other)
|
|
124
|
+
if other_type not in allowed_types:
|
|
125
|
+
raise ValueError(f"Value must be one of {allowed_types}, got {other_type} for {op}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _get_type(value: ValueType):
|
|
129
|
+
if isinstance(value, Value):
|
|
130
|
+
return type(value.value)
|
|
131
|
+
elif isinstance(value, value_types):
|
|
132
|
+
return type(value)
|
|
133
|
+
elif isinstance(value, Condition):
|
|
134
|
+
return bool
|
|
135
|
+
elif isinstance(value, Prop):
|
|
136
|
+
raise NotImplementedError("Modaic queries do not support comparisions between properties yet.")
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError(f"Unssuported value type in Modaic condition, got {type(value)}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Prop:
|
|
142
|
+
"""
|
|
143
|
+
Modaic Query Language Property class.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
name: str,
|
|
149
|
+
):
|
|
150
|
+
super().__init__()
|
|
151
|
+
self.name = name
|
|
152
|
+
|
|
153
|
+
def __getitem__(self, key: str):
|
|
154
|
+
raise NotImplementedError("Modaic queries do not support nested properties yet")
|
|
155
|
+
|
|
156
|
+
def in_(self, other: Union["Value", list]) -> "Condition":
|
|
157
|
+
value = Value(other)
|
|
158
|
+
return self.comparison("in", Comparator.IN, value)
|
|
159
|
+
|
|
160
|
+
def not_in(self, other: Union["Value", list]) -> "Condition":
|
|
161
|
+
value = Value(other)
|
|
162
|
+
return self.comparison("not_in", Comparator.NIN, value)
|
|
163
|
+
|
|
164
|
+
def __eq__(self, other: Optional[Union[ValueType]]) -> "Condition":
|
|
165
|
+
value = Value(other)
|
|
166
|
+
return self.comparison("==", Comparator.EQ, value)
|
|
167
|
+
|
|
168
|
+
def __lt__(self, other: Union[ValueType]) -> "Condition":
|
|
169
|
+
value = Value(other)
|
|
170
|
+
return self.comparison("<", Comparator.LT, value)
|
|
171
|
+
|
|
172
|
+
def __le__(self, other: Union[ValueType]) -> "Condition":
|
|
173
|
+
value = Value(other)
|
|
174
|
+
return self.comparison("<=", Comparator.LTE, value)
|
|
175
|
+
|
|
176
|
+
def __gt__(self, other: Union[ValueType]) -> "Condition":
|
|
177
|
+
value = Value(other)
|
|
178
|
+
return self.comparison(">", Comparator.GT, value)
|
|
179
|
+
|
|
180
|
+
def __ge__(self, other: Union[ValueType]) -> "Condition":
|
|
181
|
+
value = Value(other)
|
|
182
|
+
return self.comparison(">=", Comparator.GTE, value)
|
|
183
|
+
|
|
184
|
+
def __ne__(self, other: Optional[Union[ValueType]]) -> "Condition":
|
|
185
|
+
value = Value(other)
|
|
186
|
+
return self.comparison("!=", Comparator.NE, value)
|
|
187
|
+
|
|
188
|
+
def contains(self, other: Union[ValueType]) -> "Condition":
|
|
189
|
+
value = Value(other)
|
|
190
|
+
return self.comparison("contains", Comparator.CONTAIN, value)
|
|
191
|
+
|
|
192
|
+
def exists(self) -> "Condition":
|
|
193
|
+
# TODO: implement
|
|
194
|
+
raise NotImplementedError("Prop does not support exists")
|
|
195
|
+
|
|
196
|
+
def not_exists(self) -> "Condition":
|
|
197
|
+
# TODO: implement
|
|
198
|
+
raise NotImplementedError("Prop does not support not_exists")
|
|
199
|
+
|
|
200
|
+
def comparison(self, op_expr: str, comparator: Comparator, other: "Value") -> "Condition":
|
|
201
|
+
_enforce_types(other, op_expr, allowed_types[comparator])
|
|
202
|
+
comparison = Comparison(
|
|
203
|
+
comparator=comparator,
|
|
204
|
+
attribute=self.name,
|
|
205
|
+
value=other.value,
|
|
206
|
+
)
|
|
207
|
+
return Condition(comparison)
|
|
208
|
+
|
|
209
|
+
def __len__(self):
|
|
210
|
+
# TODO: implement
|
|
211
|
+
raise NotImplementedError("Prop does not support __len__")
|
|
212
|
+
|
|
213
|
+
def all(self, other): # noqa: ANN001
|
|
214
|
+
# TODO: implement
|
|
215
|
+
raise NotImplementedError("Prop does not support all")
|
|
216
|
+
|
|
217
|
+
def any(self, other): # noqa: ANN001
|
|
218
|
+
# TODO: implement
|
|
219
|
+
raise NotImplementedError("Prop does not support any")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class Value:
|
|
223
|
+
"""
|
|
224
|
+
Modaic Query Language Value class.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def __init__(self, value: Union[int, str, list, dict, bool, "Value", None]):
|
|
228
|
+
super().__init__()
|
|
229
|
+
if not isinstance(value, ValueType):
|
|
230
|
+
raise ValueError(
|
|
231
|
+
f"Value must be one of {value_types}, got {type(value)}. Please wrap your expressions with ()"
|
|
232
|
+
)
|
|
233
|
+
if isinstance(value, Value):
|
|
234
|
+
value = value.value
|
|
235
|
+
self.value = value
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class AND(Condition):
|
|
239
|
+
"""
|
|
240
|
+
Modaic Query Language AND class.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
is_operation: bool = True
|
|
244
|
+
condition: Operation
|
|
245
|
+
|
|
246
|
+
def __init__(self, left: "Condition", right: "Condition"):
|
|
247
|
+
self.left = left
|
|
248
|
+
self.right = right
|
|
249
|
+
if isinstance(self.left, AND) and isinstance(self.right, AND):
|
|
250
|
+
arguments = self.left.condition.arguments + self.right.condition.arguments
|
|
251
|
+
elif and_other := _get_and_other(self.left, self.right):
|
|
252
|
+
arguments = and_other[0].condition.arguments + [and_other[1].condition]
|
|
253
|
+
else:
|
|
254
|
+
arguments = [self.left.condition, self.right.condition]
|
|
255
|
+
super().__init__(Operation(operator=Operator.AND, arguments=arguments))
|
|
256
|
+
|
|
257
|
+
def __repr__(self):
|
|
258
|
+
return f"AND({self.left}, {self.right})"
|
|
259
|
+
|
|
260
|
+
def __str__(self):
|
|
261
|
+
return f"({str(self.left)}) & ({str(self.right)})"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class OR(Condition):
|
|
265
|
+
"""
|
|
266
|
+
Modaic Query Language OR class.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
is_operation: bool = True
|
|
270
|
+
condition: Operation
|
|
271
|
+
|
|
272
|
+
def __init__(self, left: "Condition", right: "Condition", complete: bool = False):
|
|
273
|
+
self.left = left
|
|
274
|
+
self.right = right
|
|
275
|
+
if isinstance(self.left, OR) and isinstance(self.right, OR):
|
|
276
|
+
arguments = self.left.condition.arguments + self.right.condition.arguments
|
|
277
|
+
elif or_other := _get_or_other(self.left, self.right):
|
|
278
|
+
arguments = or_other[0].condition.arguments + [or_other[1].condition]
|
|
279
|
+
else:
|
|
280
|
+
arguments = [self.left.condition, self.right.condition]
|
|
281
|
+
super().__init__(Operation(operator=Operator.OR, arguments=arguments))
|
|
282
|
+
|
|
283
|
+
def __repr__(self):
|
|
284
|
+
return f"OR({self.left}, {self.right})"
|
|
285
|
+
|
|
286
|
+
def __str__(self):
|
|
287
|
+
return f"({str(self.left)}) | ({str(self.right)})"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _get_and_or(left: "Condition", right: "Condition"):
|
|
291
|
+
if isinstance(left, AND) and isinstance(right, OR):
|
|
292
|
+
return left, right
|
|
293
|
+
elif isinstance(right, AND) and isinstance(left, OR):
|
|
294
|
+
return right, left
|
|
295
|
+
else:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _get_and_other(left: "Condition", right: "Condition"):
|
|
300
|
+
if isinstance(left, AND) and type(right) is Condition:
|
|
301
|
+
return left, right
|
|
302
|
+
elif isinstance(right, AND) and type(left) is Condition:
|
|
303
|
+
return right, left
|
|
304
|
+
else:
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _get_or_other(left: "Condition", right: "Condition"):
|
|
309
|
+
if isinstance(left, OR) and right is Condition:
|
|
310
|
+
return left, right
|
|
311
|
+
elif isinstance(right, OR) and left is Condition:
|
|
312
|
+
return right, left
|
|
313
|
+
else:
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def parse_modaic_filter(translator: Visitor, condition: Condition) -> dict: # noqa: N802
|
|
318
|
+
if condition.is_operation:
|
|
319
|
+
return translator.visit_operation(condition.condition)
|
|
320
|
+
else:
|
|
321
|
+
return translator.visit_comparison(condition.condition)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import uuid
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import (
|
|
6
|
+
IO,
|
|
7
|
+
Iterator,
|
|
8
|
+
NamedTuple,
|
|
9
|
+
Optional,
|
|
10
|
+
Tuple,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
import immutables
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FileResult(NamedTuple):
|
|
17
|
+
"""
|
|
18
|
+
Return type for FileStore.get()
|
|
19
|
+
Args:
|
|
20
|
+
file: The file object
|
|
21
|
+
type: The type of the file without the leading dot (e.g. "csv", "txt", "xlsx", etc.). Usually the extension of the file but can be used for other purposes. (e.g. "google_doc")
|
|
22
|
+
name: The name of the file
|
|
23
|
+
metadata: A map of metadata for the file
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
file: Path | IO
|
|
27
|
+
type: str
|
|
28
|
+
name: Optional[str] = None
|
|
29
|
+
metadata: immutables.Map = immutables.Map()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FileStore(ABC):
|
|
33
|
+
"""
|
|
34
|
+
Base class for file stores. File stores are used to store files that hydrate Context objects. The files can be stored locally or remotely.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def get(self, reference: str) -> FileResult:
|
|
39
|
+
"""
|
|
40
|
+
Get a file from the file store by reference (like an id or path).
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
reference: The id or path of the file in the FileStore.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
FileNotFoundError: If the file is not found.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A Path object or a file-like object.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def contains(self, reference: str) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Checks if the file store contains a given reference.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
reference: The id or path of the file in the FileStore.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if the reference is found in the file store, False otherwise.
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def keys(self, folder: Optional[str] = None) -> Iterator[str]:
|
|
67
|
+
"""
|
|
68
|
+
Iterate over all keys in the file store.
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def values(self, folder: Optional[str] = None) -> Iterator[FileResult]:
|
|
73
|
+
"""
|
|
74
|
+
Iterate over all files in the file store.
|
|
75
|
+
"""
|
|
76
|
+
for ref in self.keys(folder):
|
|
77
|
+
yield self.get(ref)
|
|
78
|
+
|
|
79
|
+
def items(self, folder: Optional[str] = None) -> Iterator[Tuple[str, FileResult]]:
|
|
80
|
+
"""
|
|
81
|
+
Iterate over all keys and files in the file store.
|
|
82
|
+
"""
|
|
83
|
+
for ref in self.keys(folder):
|
|
84
|
+
yield ref, self.get(ref)
|
|
85
|
+
|
|
86
|
+
def __iter__(self) -> Iterator[str]:
|
|
87
|
+
"""
|
|
88
|
+
Iterate over all references in the file store.
|
|
89
|
+
"""
|
|
90
|
+
return self.keys()
|
|
91
|
+
|
|
92
|
+
def __contains__(self, reference: str) -> bool:
|
|
93
|
+
return self.contains(reference)
|
|
94
|
+
|
|
95
|
+
def __len__(self) -> int:
|
|
96
|
+
"""
|
|
97
|
+
Get the number of files in the file store.
|
|
98
|
+
"""
|
|
99
|
+
return len(list(self.keys()))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class MutableFileStore(FileStore):
|
|
103
|
+
"""
|
|
104
|
+
A FileStore that supports adding and removing files.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def add(self, file: str | Path | IO, type: Optional[str] = None) -> str:
|
|
109
|
+
"""
|
|
110
|
+
Add a file to the file store.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
file: The file to add to the file store.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The reference to the file in the FileStore.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def update(self, reference: str, file: str | Path | IO, type: Optional[str] = None) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Update a file in the file store.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
reference: The id or path of the file in the FileStore.
|
|
126
|
+
file: The file to update in the file store.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
FileNotFoundError: If the file is not found.
|
|
130
|
+
"""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def remove(self, reference: str) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Remove a file from the file store.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
FileNotFoundError: If the file is not found.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
reference: The id or path of the file in the FileStore.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class InPlaceFileStore(FileStore):
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
directory: str | Path,
|
|
150
|
+
):
|
|
151
|
+
self.directory = Path(directory)
|
|
152
|
+
if not (self.directory).exists():
|
|
153
|
+
raise FileNotFoundError(f"File store directory {self.directory} is not a valid directory")
|
|
154
|
+
|
|
155
|
+
def get(self, reference: str) -> FileResult:
|
|
156
|
+
"""
|
|
157
|
+
Get a file from the file store by path relative to self.directory.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
reference: The local path of the file relative to self.directory.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
FileNotFoundError: If the file is not found.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
A Path object.
|
|
167
|
+
|
|
168
|
+
"""
|
|
169
|
+
path = self.directory / reference
|
|
170
|
+
if not path.exists():
|
|
171
|
+
raise FileNotFoundError(f"File {reference} not found in {self.directory}")
|
|
172
|
+
return FileResult(file=path, type=path.suffix.lstrip("."), name=path.name)
|
|
173
|
+
|
|
174
|
+
def contains(self, reference: str) -> bool:
|
|
175
|
+
return (self.directory / reference).exists()
|
|
176
|
+
|
|
177
|
+
def keys(self, folder: Optional[str] = None) -> Iterator[str]:
|
|
178
|
+
"""
|
|
179
|
+
Iterate over all files in the file store.
|
|
180
|
+
"""
|
|
181
|
+
folder: Path = self.directory if folder is None else self.directory / folder
|
|
182
|
+
return (str(path.relative_to(self.directory)) for path in folder.iterdir() if path.is_file())
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class LocalFileStore(MutableFileStore):
|
|
186
|
+
def __init__(self, directory: str | Path):
|
|
187
|
+
self.directory = Path(directory)
|
|
188
|
+
(self.directory / ".modaic").mkdir(parents=True, exist_ok=True)
|
|
189
|
+
|
|
190
|
+
def get(self, reference: str) -> FileResult:
|
|
191
|
+
"""
|
|
192
|
+
Get a file from the file store by path relative to self.directory.
|
|
193
|
+
"""
|
|
194
|
+
path = self.directory / reference
|
|
195
|
+
if not path.exists():
|
|
196
|
+
raise FileNotFoundError(f"File {reference} not found in {self.directory}")
|
|
197
|
+
return FileResult(file=path, type=path.suffix.lstrip("."), name=path.name)
|
|
198
|
+
|
|
199
|
+
def contains(self, reference: str) -> bool:
|
|
200
|
+
return (self.directory / reference).exists()
|
|
201
|
+
|
|
202
|
+
def add(self, file: str | Path | IO) -> str:
|
|
203
|
+
default_filename = str(uuid.uuid4())
|
|
204
|
+
if isinstance(file, (Path, str)):
|
|
205
|
+
original_path = Path(file)
|
|
206
|
+
if is_in_dir(original_path, self.directory):
|
|
207
|
+
path = original_path
|
|
208
|
+
else:
|
|
209
|
+
path = self.directory / ".modaic" / default_filename
|
|
210
|
+
shutil.copy2(original_path, path)
|
|
211
|
+
else:
|
|
212
|
+
path = self.directory / ".modaic" / default_filename
|
|
213
|
+
with open(path, "wb") as f:
|
|
214
|
+
shutil.copyfileobj(file, f)
|
|
215
|
+
return str(path.relative_to(self.directory))
|
|
216
|
+
|
|
217
|
+
def update(self, reference: str, file: str | Path | IO) -> None:
|
|
218
|
+
if isinstance(file, (Path, str)):
|
|
219
|
+
original_path = Path(file)
|
|
220
|
+
shutil.copy2(original_path, self.directory / reference)
|
|
221
|
+
else:
|
|
222
|
+
shutil.copyfileobj(file, self.directory / reference)
|
|
223
|
+
|
|
224
|
+
def remove(self, reference: str) -> None:
|
|
225
|
+
(self.directory / reference).unlink()
|
|
226
|
+
|
|
227
|
+
def keys(self, folder: Optional[str] = None) -> Iterator[str]:
|
|
228
|
+
base: Path = self.directory if folder is None else self.directory / folder
|
|
229
|
+
return (str(path.relative_to(self.directory)) for path in base.iterdir() if path.is_file())
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def is_in_dir(path: str | Path, directory: str | Path) -> bool:
|
|
233
|
+
path = Path(path).resolve() # follows symlinks
|
|
234
|
+
directory = Path(directory).resolve()
|
|
235
|
+
try:
|
|
236
|
+
path.relative_to(directory)
|
|
237
|
+
return True
|
|
238
|
+
except ValueError:
|
|
239
|
+
return False
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pickle
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from ..context.base import Context
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ContextPickleStore:
|
|
10
|
+
def __init__(self, directory: str):
|
|
11
|
+
self.directory = directory
|
|
12
|
+
os.makedirs(self.directory, exist_ok=True)
|
|
13
|
+
|
|
14
|
+
def add(self, contexts: List[Context], **kwargs) -> List[Context]:
|
|
15
|
+
for context in contexts:
|
|
16
|
+
context_id = uuid.uuid4()
|
|
17
|
+
file_name = f"{context_id}.pkl"
|
|
18
|
+
with open(os.path.join(self.directory, file_name), "wb") as f:
|
|
19
|
+
pickle.dump(context, f)
|
|
20
|
+
context.metadata["context_id"] = context_id
|
|
21
|
+
return contexts
|
|
22
|
+
|
|
23
|
+
def get(self, source: str) -> Context:
|
|
24
|
+
with open(os.path.join(self.directory, source), "rb") as f:
|
|
25
|
+
return pickle.load(f)
|