erioon 0.1.4__py3-none-any.whl → 0.1.6__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.
- erioon/auth.py +34 -0
- erioon/client.py +118 -0
- erioon/collection.py +371 -0
- erioon/create.py +130 -0
- erioon/database.py +82 -0
- erioon/delete.py +342 -0
- erioon/functions.py +422 -0
- erioon/ping.py +53 -0
- erioon/read.py +301 -0
- erioon/transaction.py +58 -0
- erioon/update.py +322 -0
- {erioon-0.1.4.dist-info → erioon-0.1.6.dist-info}/METADATA +1 -1
- erioon-0.1.6.dist-info/RECORD +16 -0
- erioon-0.1.6.dist-info/top_level.txt +1 -0
- erioon-0.1.4.dist-info/RECORD +0 -5
- erioon-0.1.4.dist-info/top_level.txt +0 -1
- {erioon-0.1.4.dist-info → erioon-0.1.6.dist-info}/LICENSE +0 -0
- {erioon-0.1.4.dist-info → erioon-0.1.6.dist-info}/WHEEL +0 -0
erioon/auth.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright 2025-present Erioon, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# Visit www.erioon.com/dev-docs for more information about the python SDK
|
15
|
+
|
16
|
+
from erioon.client import ErioonClient
|
17
|
+
|
18
|
+
def Auth(credential_string):
|
19
|
+
"""
|
20
|
+
Authenticates a user using a colon-separated email:password string.
|
21
|
+
|
22
|
+
Parameters:
|
23
|
+
- credential_string (str): A string in the format "email:password"
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
- ErioonClient instance: An instance representing the authenticated user.
|
27
|
+
If authentication fails, the instance will contain the error message.
|
28
|
+
|
29
|
+
Example usage:
|
30
|
+
>>> from erioon.auth import Auth
|
31
|
+
>>> client = Auth("<API_KEY>:<EMAIL>:<PASSWORD>")
|
32
|
+
>>> print(client) # prints user_id if successful or error message if not
|
33
|
+
"""
|
34
|
+
return ErioonClient(credential_string)
|
erioon/client.py
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# Copyright 2025-present Erioon, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# Visit www.erioon.com/dev-docs for more information about the python SDK
|
15
|
+
|
16
|
+
import requests
|
17
|
+
from erioon.database import Database
|
18
|
+
|
19
|
+
class ErioonClient:
|
20
|
+
def __init__(self, credential_string, base_url="https://sdk.erioon.com"):
|
21
|
+
self.credential_string = credential_string
|
22
|
+
self.base_url = base_url
|
23
|
+
self.user_id = None
|
24
|
+
self.login_metadata = None
|
25
|
+
|
26
|
+
parts = credential_string.split(":")
|
27
|
+
|
28
|
+
if len(parts) == 1 and credential_string.startswith("erioon"):
|
29
|
+
self.api = credential_string
|
30
|
+
self.email = None
|
31
|
+
self.password = None
|
32
|
+
else:
|
33
|
+
if len(parts) == 2:
|
34
|
+
self.email, self.password = parts
|
35
|
+
self.api = None
|
36
|
+
else:
|
37
|
+
raise ValueError("Invalid credential format. Use 'erioon-xxxxx' or 'email:password'")
|
38
|
+
|
39
|
+
try:
|
40
|
+
self.login_metadata = self._login()
|
41
|
+
self._update_metadata_fields()
|
42
|
+
except Exception as e:
|
43
|
+
print(f"[ErioonClient] Initialization error: {e}")
|
44
|
+
|
45
|
+
|
46
|
+
def _login(self):
|
47
|
+
url = f"{self.base_url}/login"
|
48
|
+
payload = {"api_key": self.api, "email": self.email, "password": self.password}
|
49
|
+
headers = {"Content-Type": "application/json"}
|
50
|
+
|
51
|
+
response = requests.post(url, json=payload, headers=headers)
|
52
|
+
if response.status_code == 200:
|
53
|
+
data = response.json()
|
54
|
+
self.login_metadata = data
|
55
|
+
return data
|
56
|
+
else:
|
57
|
+
try:
|
58
|
+
msg = response.json().get("error", "Login failed")
|
59
|
+
except Exception:
|
60
|
+
msg = response.text
|
61
|
+
print(f"[ErioonClient] Login failed: {msg}")
|
62
|
+
raise RuntimeError(msg)
|
63
|
+
|
64
|
+
def _update_metadata_fields(self):
|
65
|
+
if self.login_metadata:
|
66
|
+
self.user_id = self.login_metadata.get("_id")
|
67
|
+
self.cluster = self.login_metadata.get("cluster")
|
68
|
+
self.database = self.login_metadata.get("database")
|
69
|
+
self.sas_tokens = self.login_metadata.get("sas_tokens", {})
|
70
|
+
|
71
|
+
def __getitem__(self, db_id):
|
72
|
+
if not self.user_id:
|
73
|
+
raise ValueError("Client not authenticated. Cannot access database.")
|
74
|
+
|
75
|
+
return self._get_database_info(db_id)
|
76
|
+
|
77
|
+
def _get_database_info(self, db_id):
|
78
|
+
payload = {"user_id": self.user_id, "db_id": db_id}
|
79
|
+
headers = {"Content-Type": "application/json"}
|
80
|
+
response = requests.post(f"{self.base_url}/db_info", json=payload, headers=headers)
|
81
|
+
|
82
|
+
if response.status_code == 200:
|
83
|
+
db_info = response.json()
|
84
|
+
sas_info = self.sas_tokens.get(db_id)
|
85
|
+
if not sas_info:
|
86
|
+
raise Exception(f"No SAS token info for database id {db_id}")
|
87
|
+
|
88
|
+
container_url = sas_info.get("container_url")
|
89
|
+
sas_token = sas_info.get("sas_token")
|
90
|
+
|
91
|
+
if not container_url or not sas_token:
|
92
|
+
raise Exception("Missing SAS URL components")
|
93
|
+
|
94
|
+
if not sas_token.startswith("?"):
|
95
|
+
sas_token = "?" + sas_token
|
96
|
+
|
97
|
+
sas_url = container_url.split("?")[0] + sas_token
|
98
|
+
|
99
|
+
return Database(
|
100
|
+
user_id=self.user_id,
|
101
|
+
metadata=db_info,
|
102
|
+
database=self.database,
|
103
|
+
cluster=self.cluster,
|
104
|
+
sas_url=sas_url
|
105
|
+
)
|
106
|
+
else:
|
107
|
+
try:
|
108
|
+
error_json = response.json()
|
109
|
+
error_msg = error_json.get("error", response.text)
|
110
|
+
except Exception:
|
111
|
+
error_msg = response.text
|
112
|
+
raise Exception(f"Failed to fetch database info: {error_msg}")
|
113
|
+
|
114
|
+
def __str__(self):
|
115
|
+
return self.user_id if self.user_id else "[ErioonClient] Unauthenticated"
|
116
|
+
|
117
|
+
def __repr__(self):
|
118
|
+
return f"<ErioonClient user_id={self.user_id}>" if self.user_id else "<ErioonClient unauthenticated>"
|
erioon/collection.py
ADDED
@@ -0,0 +1,371 @@
|
|
1
|
+
# Copyright 2025-present Erioon, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# Visit www.erioon.com/dev-docs for more information about the python SDK
|
15
|
+
|
16
|
+
import json
|
17
|
+
from urllib.parse import urlparse
|
18
|
+
from erioon.read import handle_get_all, handle_find_one, handle_find_many, handle_count_records
|
19
|
+
from erioon.create import handle_insert_one, handle_insert_many
|
20
|
+
from erioon.delete import handle_delete_one, handle_delete_many
|
21
|
+
from erioon.update import handle_update_one, handle_update_many, handle_replace_one
|
22
|
+
from erioon.ping import handle_connection_ping
|
23
|
+
|
24
|
+
class Collection:
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
user_id,
|
28
|
+
db_id,
|
29
|
+
coll_id,
|
30
|
+
metadata,
|
31
|
+
database,
|
32
|
+
cluster,
|
33
|
+
sas_url,
|
34
|
+
):
|
35
|
+
"""
|
36
|
+
Initialize a Collection object that wraps Erioon collection access.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
user_id (str): The authenticated user's ID.
|
40
|
+
db_id (str): The database ID.
|
41
|
+
coll_id (str): The collection ID.
|
42
|
+
metadata (dict): Metadata info about this collection (e.g., schema, indexing, etc.).
|
43
|
+
database (str): Name or ID of the database.
|
44
|
+
cluster (str): Cluster name or ID hosting the database.
|
45
|
+
sas_url (str): Full SAS URL used to access the storage container.
|
46
|
+
"""
|
47
|
+
self.user_id = user_id
|
48
|
+
self.db_id = db_id
|
49
|
+
self.coll_id = coll_id
|
50
|
+
self.metadata = metadata
|
51
|
+
self.database = database
|
52
|
+
self.cluster = cluster
|
53
|
+
|
54
|
+
parsed_url = urlparse(sas_url.rstrip("/"))
|
55
|
+
container_name = parsed_url.path.lstrip("/").split("/")[0]
|
56
|
+
account_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
57
|
+
sas_token = parsed_url.query
|
58
|
+
self.container_url = f"{account_url}/{container_name}?{sas_token}"
|
59
|
+
|
60
|
+
# PRINT ERIOON
|
61
|
+
def _print_loading(self):
|
62
|
+
"""Prints a loading message (likely for UX in CLI or SDK usage)."""
|
63
|
+
print("Erioon is loading...")
|
64
|
+
|
65
|
+
# CHECK READ / WRITE LICENCE
|
66
|
+
def _is_read_only(self):
|
67
|
+
"""Check if the current database is marked as read-only."""
|
68
|
+
return self.database == "read"
|
69
|
+
|
70
|
+
# RESPONSE FOR ONLY WRITE
|
71
|
+
def _read_only_response(self):
|
72
|
+
"""Standardized error response for blocked write operations."""
|
73
|
+
return "This user is not allowed to perform write operations.", 403
|
74
|
+
|
75
|
+
# GET ALL RECORDS OF A COLLECTION
|
76
|
+
def get_all(self, limit=1000000):
|
77
|
+
"""
|
78
|
+
Fetch all records from the collection (up to a limit).
|
79
|
+
"""
|
80
|
+
self._print_loading()
|
81
|
+
result, status_code = handle_get_all(
|
82
|
+
user_id=self.user_id,
|
83
|
+
db_id=self.db_id,
|
84
|
+
coll_id=self.coll_id,
|
85
|
+
limit=limit,
|
86
|
+
container_url=self.container_url,
|
87
|
+
)
|
88
|
+
return result
|
89
|
+
|
90
|
+
# FINDS A SPECIFIC RECORD OF A COLLECTION
|
91
|
+
def find_one(self, filters: dict | None = None):
|
92
|
+
"""
|
93
|
+
Fetch a single record that matches specific key-value filters.
|
94
|
+
"""
|
95
|
+
if self._is_read_only():
|
96
|
+
return self._read_only_response()
|
97
|
+
|
98
|
+
if filters is None:
|
99
|
+
filters = {}
|
100
|
+
|
101
|
+
search_criteria = [{k: v} for k, v in filters.items()]
|
102
|
+
|
103
|
+
result, status_code = handle_find_one(
|
104
|
+
user_id=self.user_id,
|
105
|
+
db_id=self.db_id,
|
106
|
+
coll_id=self.coll_id,
|
107
|
+
search_criteria=search_criteria,
|
108
|
+
container_url=self.container_url,
|
109
|
+
)
|
110
|
+
return result
|
111
|
+
|
112
|
+
# FINDS MULTIPLE RECORDS OF A COLLECTION
|
113
|
+
def find_many(self, filters: dict | None = None, limit: int = 1000):
|
114
|
+
"""
|
115
|
+
Fetch multiple records that match specific key-value filters.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
filters (dict): Filters to match records.
|
119
|
+
limit (int): Maximum number of records to return (default: 1000).
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
dict: Result from `handle_find_many()`
|
123
|
+
"""
|
124
|
+
if self._is_read_only():
|
125
|
+
return self._read_only_response()
|
126
|
+
|
127
|
+
self._print_loading()
|
128
|
+
|
129
|
+
if filters is None:
|
130
|
+
filters = {}
|
131
|
+
|
132
|
+
if limit > 500_000:
|
133
|
+
raise ValueError("Limit of 500,000 exceeded")
|
134
|
+
|
135
|
+
search_criteria = [{k: v} for k, v in filters.items()]
|
136
|
+
|
137
|
+
result, status_code = handle_find_many(
|
138
|
+
user_id=self.user_id,
|
139
|
+
db_id=self.db_id,
|
140
|
+
coll_id=self.coll_id,
|
141
|
+
search_criteria=search_criteria,
|
142
|
+
limit=limit,
|
143
|
+
container_url=self.container_url,
|
144
|
+
)
|
145
|
+
return result
|
146
|
+
|
147
|
+
# INSERT A SINGLE RECORD IN A COLLECTION
|
148
|
+
def insert_one(self, record):
|
149
|
+
"""
|
150
|
+
Insert a single record into the collection.
|
151
|
+
"""
|
152
|
+
if self._is_read_only():
|
153
|
+
return self._read_only_response()
|
154
|
+
response, status = handle_insert_one(
|
155
|
+
user_id_cont=self.user_id,
|
156
|
+
database=self.db_id,
|
157
|
+
collection=self.coll_id,
|
158
|
+
record=record,
|
159
|
+
container_url=self.container_url,
|
160
|
+
)
|
161
|
+
if status == 200:
|
162
|
+
print("Insertion was successful.")
|
163
|
+
else:
|
164
|
+
print(f"Error inserting record: {response}")
|
165
|
+
return response, status
|
166
|
+
|
167
|
+
# INSERT MULTIPLE RECORDS INTO A COLLECTION
|
168
|
+
def insert_many(self, data):
|
169
|
+
"""
|
170
|
+
Insert multiple records into the collection.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
data (list): List of record dicts.
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
tuple: (response message, HTTP status code)
|
177
|
+
"""
|
178
|
+
if self._is_read_only():
|
179
|
+
return self._read_only_response()
|
180
|
+
self._print_loading()
|
181
|
+
response, status = handle_insert_many(
|
182
|
+
user_id_cont=self.user_id,
|
183
|
+
database=self.db_id,
|
184
|
+
collection=self.coll_id,
|
185
|
+
data=data,
|
186
|
+
container_url=self.container_url,
|
187
|
+
)
|
188
|
+
if status == 200:
|
189
|
+
print("Insertion of multiple records was successful.")
|
190
|
+
else:
|
191
|
+
print(f"Error inserting records: {response}")
|
192
|
+
return response, status
|
193
|
+
|
194
|
+
# DELETE A SINGLE RECORD BASED ON _ID OR KEY
|
195
|
+
def delete_one(self, record_to_delete):
|
196
|
+
"""
|
197
|
+
Delete a single record based on its _id or nested key.
|
198
|
+
"""
|
199
|
+
if self._is_read_only():
|
200
|
+
return self._read_only_response()
|
201
|
+
response, status = handle_delete_one(
|
202
|
+
user_id=self.user_id,
|
203
|
+
db_id=self.db_id,
|
204
|
+
coll_id=self.coll_id,
|
205
|
+
data_to_delete=record_to_delete,
|
206
|
+
container_url=self.container_url,
|
207
|
+
)
|
208
|
+
if status == 200:
|
209
|
+
print("Deletion was successful.")
|
210
|
+
else:
|
211
|
+
print(f"Error deleting record: {response}")
|
212
|
+
return response, status
|
213
|
+
|
214
|
+
# DELETE MANY RECORDS IN BATCHES
|
215
|
+
def delete_many(self, records_to_delete_list, batch_size=10):
|
216
|
+
"""
|
217
|
+
Delete multiple records in batches.
|
218
|
+
"""
|
219
|
+
if self._is_read_only():
|
220
|
+
return self._read_only_response()
|
221
|
+
self._print_loading()
|
222
|
+
response, status = handle_delete_many(
|
223
|
+
user_id=self.user_id,
|
224
|
+
db_id=self.db_id,
|
225
|
+
coll_id=self.coll_id,
|
226
|
+
data_to_delete_list=records_to_delete_list,
|
227
|
+
batch_size=batch_size,
|
228
|
+
container_url=self.container_url,
|
229
|
+
)
|
230
|
+
if status == 200:
|
231
|
+
print("Batch deletion was successful.")
|
232
|
+
else:
|
233
|
+
print(f"Error deleting records: {response}")
|
234
|
+
return response, status
|
235
|
+
|
236
|
+
# UPDATE A RECORD
|
237
|
+
def update_one(self, filter_query: dict, update_query: dict):
|
238
|
+
"""
|
239
|
+
Update a record in-place by filtering and applying update logic.
|
240
|
+
"""
|
241
|
+
if self._is_read_only():
|
242
|
+
return self._read_only_response()
|
243
|
+
response, status = handle_update_one(
|
244
|
+
user_id=self.user_id,
|
245
|
+
db_id=self.db_id,
|
246
|
+
coll_id=self.coll_id,
|
247
|
+
filter_query=filter_query,
|
248
|
+
update_query=update_query,
|
249
|
+
container_url=self.container_url,
|
250
|
+
)
|
251
|
+
if status == 200:
|
252
|
+
print("Update was successful.")
|
253
|
+
else:
|
254
|
+
print(f"Error updating record: {response}")
|
255
|
+
return response, status
|
256
|
+
|
257
|
+
# UPDATE MULTIPLE RECORDS
|
258
|
+
def update_many(self, update_tasks: list):
|
259
|
+
"""
|
260
|
+
Update multiple records in-place by applying a list of filter + update operations.
|
261
|
+
|
262
|
+
Each item in `update_tasks` should be a dict:
|
263
|
+
{
|
264
|
+
"filter": { ... },
|
265
|
+
"update": {
|
266
|
+
"$set": {...}, "$push": {...}, "$remove": [...]
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
(dict, int): Summary response and HTTP status code.
|
272
|
+
"""
|
273
|
+
if self._is_read_only():
|
274
|
+
return self._read_only_response()
|
275
|
+
self._print_loading()
|
276
|
+
|
277
|
+
response, status = handle_update_many(
|
278
|
+
user_id=self.user_id,
|
279
|
+
db_id=self.db_id,
|
280
|
+
coll_id=self.coll_id,
|
281
|
+
update_tasks=update_tasks,
|
282
|
+
container_url=self.container_url,
|
283
|
+
)
|
284
|
+
|
285
|
+
if status == 200:
|
286
|
+
print(f"Successfully updated {response.get('success')}")
|
287
|
+
else:
|
288
|
+
print(f"Error updating records: {response}")
|
289
|
+
|
290
|
+
return response, status
|
291
|
+
|
292
|
+
# REPLACE A SINGLE RECORDS BASED ON THE FILTER QUERY
|
293
|
+
def replace_one(self, filter_query: dict, replacement: dict):
|
294
|
+
"""
|
295
|
+
Replaces a single record matching `filter_query` with the full `replacement` document.
|
296
|
+
|
297
|
+
- If `_id` is **not** in the replacement, preserves the original `_id`.
|
298
|
+
- If `_id` **is** in the replacement, uses the new `_id`.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
filter_query (dict): Must contain a single key-value pair.
|
302
|
+
replacement (dict): New record to replace the old one.
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
(dict, int): Response message and HTTP status code.
|
306
|
+
"""
|
307
|
+
if self._is_read_only():
|
308
|
+
return self._read_only_response()
|
309
|
+
|
310
|
+
response, status = handle_replace_one(
|
311
|
+
user_id=self.user_id,
|
312
|
+
db_id=self.db_id,
|
313
|
+
coll_id=self.coll_id,
|
314
|
+
filter_query=filter_query,
|
315
|
+
replacement=replacement,
|
316
|
+
container_url=self.container_url,
|
317
|
+
)
|
318
|
+
|
319
|
+
if status == 200:
|
320
|
+
print("Replacement was successful.")
|
321
|
+
else:
|
322
|
+
print(f"Error replacing record: {response}")
|
323
|
+
|
324
|
+
return response, status
|
325
|
+
|
326
|
+
# PING AND CHECK CONNECTION
|
327
|
+
def ping(self):
|
328
|
+
"""
|
329
|
+
Health check / ping to verify collection accessibility.
|
330
|
+
"""
|
331
|
+
self._print_loading()
|
332
|
+
response, status = handle_connection_ping(
|
333
|
+
user_id=self.user_id,
|
334
|
+
db_id=self.db_id,
|
335
|
+
coll_id=self.coll_id,
|
336
|
+
container_url=self.container_url,
|
337
|
+
)
|
338
|
+
if status == 200:
|
339
|
+
print("Connection ping successful.")
|
340
|
+
else:
|
341
|
+
print(f"Ping failed: {response}")
|
342
|
+
return response, status
|
343
|
+
|
344
|
+
# COUNT ALL THE RECORDS
|
345
|
+
def count_records(self) -> int:
|
346
|
+
"""
|
347
|
+
Count the total number of documents in the collection (across all shards).
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
int: Total document count.
|
351
|
+
"""
|
352
|
+
if self._is_read_only():
|
353
|
+
return 0
|
354
|
+
self._print_loading()
|
355
|
+
|
356
|
+
count, status = handle_count_records(
|
357
|
+
user_id=self.user_id,
|
358
|
+
db_id=self.db_id,
|
359
|
+
coll_id=self.coll_id,
|
360
|
+
container_url=self.container_url,
|
361
|
+
)
|
362
|
+
return count
|
363
|
+
|
364
|
+
|
365
|
+
def __str__(self):
|
366
|
+
"""Pretty print the collection metadata."""
|
367
|
+
return json.dumps(self.metadata, indent=4)
|
368
|
+
|
369
|
+
def __repr__(self):
|
370
|
+
"""Simplified representation for debugging or introspection."""
|
371
|
+
return f"<Collection coll_id={self.coll_id}>"
|
erioon/create.py
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# Copyright 2025-present Erioon, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
# Visit www.erioon.com/dev-docs for more information about the python SDK
|
15
|
+
|
16
|
+
import uuid
|
17
|
+
from erioon.functions import (
|
18
|
+
create_msgpack_file,
|
19
|
+
update_index_file_insert,
|
20
|
+
calculate_shard_number,
|
21
|
+
async_log,
|
22
|
+
is_duplicate_id
|
23
|
+
)
|
24
|
+
|
25
|
+
# INSERT ONE RECORD
|
26
|
+
def handle_insert_one(user_id_cont, database, collection, record, container_url):
|
27
|
+
"""
|
28
|
+
Insert a single record into the collection.
|
29
|
+
|
30
|
+
- If no '_id' provided, generate a new UUID.
|
31
|
+
- If provided '_id' is duplicate, generate a new one and update the record.
|
32
|
+
- Create or append the record in a shard file.
|
33
|
+
- Update index.json to map the record to the appropriate shard.
|
34
|
+
- Log success or errors asynchronously.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
user_id_cont: User identifier.
|
38
|
+
database: Database name.
|
39
|
+
collection: Collection name.
|
40
|
+
record: Dict representing the record to insert.
|
41
|
+
container_url: Blob Storage container SAS URL.
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
Tuple (response dict, status code) indicating success or failure.
|
45
|
+
"""
|
46
|
+
try:
|
47
|
+
if "_id" not in record or not record["_id"]:
|
48
|
+
record["_id"] = str(uuid.uuid4())
|
49
|
+
|
50
|
+
rec_id = record["_id"]
|
51
|
+
|
52
|
+
if is_duplicate_id(user_id_cont, database, collection, rec_id, container_url):
|
53
|
+
new_id = str(uuid.uuid4())
|
54
|
+
record["_id"] = new_id
|
55
|
+
rec_id = new_id
|
56
|
+
msg = f"Record inserted successfully in {collection} with a new _id {rec_id} because the provided _id was already present."
|
57
|
+
else:
|
58
|
+
msg = f"Record inserted successfully in {collection} with _id {rec_id}"
|
59
|
+
|
60
|
+
async_log(user_id_cont, database, collection, "POST", "SUCCESS", msg, 1, container_url)
|
61
|
+
|
62
|
+
create_msgpack_file(user_id_cont, database, collection, record, container_url)
|
63
|
+
|
64
|
+
shard_number = calculate_shard_number(user_id_cont, database, collection, container_url)
|
65
|
+
update_index_file_insert(user_id_cont, database, collection, rec_id, shard_number, container_url)
|
66
|
+
|
67
|
+
return {"status": "OK", "message": msg, "record": record}, 200
|
68
|
+
|
69
|
+
except Exception as e:
|
70
|
+
error_msg = f"An error occurred during insert in {collection}: {str(e)}"
|
71
|
+
async_log(user_id_cont, database, collection,"POST", "ERROR", error_msg, 1, container_url)
|
72
|
+
return {"status": "KO", "message": "Failed to insert record.", "error": str(e)}, 500
|
73
|
+
|
74
|
+
# INSERT MANY RECORDS
|
75
|
+
def handle_insert_many(user_id_cont, database, collection, data, container_url):
|
76
|
+
"""
|
77
|
+
Insert multiple records in bulk.
|
78
|
+
|
79
|
+
- `data` is a list of dicts, each representing a record.
|
80
|
+
- For each record:
|
81
|
+
- Ensure it has a unique _id (generate new UUID if missing or duplicate).
|
82
|
+
- Write the record to the appropriate shard.
|
83
|
+
- Update index.json with _id to shard mapping.
|
84
|
+
- Log the batch insert operation with details.
|
85
|
+
- Return aggregate success or failure response.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
user_id_cont: User identifier.
|
89
|
+
database: Database name.
|
90
|
+
collection: Collection name.
|
91
|
+
data: List of record dicts.
|
92
|
+
container_url: Blob Storage container SAS URL.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Tuple (response dict, status code) with summary of insert results.
|
96
|
+
"""
|
97
|
+
insert_results = []
|
98
|
+
count = len(data)
|
99
|
+
|
100
|
+
try:
|
101
|
+
for record in data:
|
102
|
+
if "_id" not in record or not record["_id"]:
|
103
|
+
record["_id"] = str(uuid.uuid4())
|
104
|
+
|
105
|
+
rec_id = record["_id"]
|
106
|
+
|
107
|
+
if is_duplicate_id(user_id_cont, database, collection, rec_id, container_url):
|
108
|
+
new_id = str(uuid.uuid4())
|
109
|
+
record["_id"] = new_id
|
110
|
+
rec_id = new_id
|
111
|
+
msg = f"Inserted with new _id {rec_id} (original _id was already present)."
|
112
|
+
else:
|
113
|
+
msg = f"Inserted with _id {rec_id}."
|
114
|
+
|
115
|
+
create_msgpack_file(user_id_cont, database, collection, record, container_url)
|
116
|
+
|
117
|
+
shard_number = calculate_shard_number(user_id_cont, database, collection, container_url)
|
118
|
+
update_index_file_insert(
|
119
|
+
user_id_cont, database, collection, rec_id, shard_number, container_url
|
120
|
+
)
|
121
|
+
|
122
|
+
insert_results.append({"_id": rec_id, "message": msg})
|
123
|
+
|
124
|
+
async_log(user_id_cont, database, collection, "POST", "SUCCESS", insert_results, count, container_url)
|
125
|
+
return {"success": "Records inserted successfully", "details": insert_results}, 200
|
126
|
+
|
127
|
+
except Exception as e:
|
128
|
+
general_error_msg = f"Unexpected error during bulk insert: {str(e)}"
|
129
|
+
async_log(user_id_cont, database, collection, "POST", "ERROR", general_error_msg, 1, container_url)
|
130
|
+
return {"status": "KO", "message": general_error_msg}, 500
|