sapiopycommons 2025.8.14a703__py3-none-any.whl → 2026.1.22a847__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.
- sapiopycommons/ai/agent_service_base.py +2051 -0
- sapiopycommons/ai/converter_service_base.py +163 -0
- sapiopycommons/ai/external_credentials.py +131 -0
- sapiopycommons/ai/protoapi/agent/agent_pb2.py +87 -0
- sapiopycommons/ai/protoapi/agent/agent_pb2.pyi +282 -0
- sapiopycommons/ai/protoapi/agent/agent_pb2_grpc.py +154 -0
- sapiopycommons/ai/protoapi/agent/entry_pb2.py +49 -0
- sapiopycommons/ai/protoapi/agent/entry_pb2.pyi +40 -0
- sapiopycommons/ai/protoapi/agent/entry_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/agent/item/item_container_pb2.py +61 -0
- sapiopycommons/ai/protoapi/agent/item/item_container_pb2.pyi +181 -0
- sapiopycommons/ai/protoapi/agent/item/item_container_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.py +41 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.pyi +36 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +51 -0
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +59 -0
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +123 -0
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +599 -0
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2.py +59 -0
- sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2.pyi +68 -0
- sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2_grpc.py +149 -0
- sapiopycommons/ai/protoapi/pipeline/script/script_pb2.py +69 -0
- sapiopycommons/ai/protoapi/pipeline/script/script_pb2.pyi +109 -0
- sapiopycommons/ai/protoapi/pipeline/script/script_pb2_grpc.py +153 -0
- sapiopycommons/ai/protoapi/pipeline/step_output_pb2.py +49 -0
- sapiopycommons/ai/protoapi/pipeline/step_output_pb2.pyi +56 -0
- sapiopycommons/ai/protoapi/pipeline/step_output_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/pipeline/step_pb2.py +43 -0
- sapiopycommons/ai/protoapi/pipeline/step_pb2.pyi +44 -0
- sapiopycommons/ai/protoapi/pipeline/step_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +39 -0
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +33 -0
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +24 -0
- sapiopycommons/ai/protobuf_utils.py +583 -0
- sapiopycommons/ai/request_validation.py +561 -0
- sapiopycommons/ai/server.py +152 -0
- sapiopycommons/ai/test_client.py +534 -0
- sapiopycommons/callbacks/callback_util.py +26 -7
- sapiopycommons/eln/experiment_handler.py +12 -5
- sapiopycommons/files/file_util.py +128 -1
- sapiopycommons/files/temp_files.py +82 -0
- sapiopycommons/general/aliases.py +4 -1
- sapiopycommons/general/macros.py +172 -0
- sapiopycommons/general/time_util.py +199 -4
- sapiopycommons/recordmodel/record_handler.py +47 -12
- sapiopycommons/rules/eln_rule_handler.py +3 -0
- sapiopycommons/rules/on_save_rule_handler.py +3 -0
- sapiopycommons/webhook/webservice_handlers.py +1 -1
- {sapiopycommons-2025.8.14a703.dist-info → sapiopycommons-2026.1.22a847.dist-info}/METADATA +2 -2
- sapiopycommons-2026.1.22a847.dist-info/RECORD +113 -0
- sapiopycommons/ai/tool_of_tools.py +0 -917
- sapiopycommons-2025.8.14a703.dist-info/RECORD +0 -72
- {sapiopycommons-2025.8.14a703.dist-info → sapiopycommons-2026.1.22a847.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.8.14a703.dist-info → sapiopycommons-2026.1.22a847.dist-info}/licenses/LICENSE +0 -0
|
@@ -133,9 +133,10 @@ class TimeUtil:
|
|
|
133
133
|
return datetime.fromtimestamp(millis / 1000, tz).strftime(time_format)
|
|
134
134
|
|
|
135
135
|
@staticmethod
|
|
136
|
-
def format_to_millis(time_point: str, time_format: str, timezone: str | int = None) -> int:
|
|
136
|
+
def format_to_millis(time_point: str, time_format: str, timezone: str | int = None) -> int | None:
|
|
137
137
|
"""
|
|
138
|
-
Convert the input time from the provided format to milliseconds.
|
|
138
|
+
Convert the input time from the provided format to milliseconds. If None is passed to the time_point parameter,
|
|
139
|
+
None will be returned.
|
|
139
140
|
|
|
140
141
|
:param time_point: The time in some date/time format to convert from.
|
|
141
142
|
:param time_format: The format that the time_point is in. Documentation for how the time formatting works
|
|
@@ -144,6 +145,9 @@ class TimeUtil:
|
|
|
144
145
|
timezone variable set by the TimeUtil. A list of valid timezones can be found at
|
|
145
146
|
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. May also accept a UTC offset in seconds.
|
|
146
147
|
"""
|
|
148
|
+
if time_point is None:
|
|
149
|
+
return None
|
|
150
|
+
|
|
147
151
|
tz = TimeUtil.__to_tz(timezone)
|
|
148
152
|
return int(datetime.strptime(time_point, time_format).replace(tzinfo=tz).timestamp() * 1000)
|
|
149
153
|
|
|
@@ -166,11 +170,12 @@ class TimeUtil:
|
|
|
166
170
|
return TimeUtil.shift_millis(millis, to_timezone, from_timezone)
|
|
167
171
|
|
|
168
172
|
@staticmethod
|
|
169
|
-
def shift_millis(millis: int, to_timezone: str = "UTC", from_timezone: str | None = None) -> int:
|
|
173
|
+
def shift_millis(millis: int, to_timezone: str = "UTC", from_timezone: str | None = None) -> int | None:
|
|
170
174
|
"""
|
|
171
175
|
Take a number of milliseconds for a time in from_timezone and output the epoch timestamp that would display that
|
|
172
176
|
same time in to_timezone. A use case for this is when dealing with static date fields to convert a provided
|
|
173
177
|
timestamp to the value necessary to display that timestamp in the same way when viewed in the static date field.
|
|
178
|
+
If None is passed to the millis parameter, None will be returned.
|
|
174
179
|
|
|
175
180
|
:param millis: The time in milliseconds to convert from.
|
|
176
181
|
:param to_timezone: The timezone to shift to. If not provided, uses UTC.
|
|
@@ -180,17 +185,21 @@ class TimeUtil:
|
|
|
180
185
|
:return: The epoch timestamp that would display as the same time in to_timezone as the given time in
|
|
181
186
|
from_timezone.
|
|
182
187
|
"""
|
|
188
|
+
if millis is None:
|
|
189
|
+
return None
|
|
190
|
+
|
|
183
191
|
to_offset: int = TimeUtil.__get_timezone_offset(to_timezone) * 1000
|
|
184
192
|
from_offset: int = TimeUtil.__get_timezone_offset(from_timezone) * 1000
|
|
185
193
|
return millis + from_offset - to_offset
|
|
186
194
|
|
|
187
195
|
@staticmethod
|
|
188
196
|
def shift_format(time_point: str, time_format: str, to_timezone: str = "UTC", from_timezone: str | None = None) \
|
|
189
|
-
-> int:
|
|
197
|
+
-> int | None:
|
|
190
198
|
"""
|
|
191
199
|
Take a timestamp for a time in from_timezone and output the epoch timestamp that would display that same time
|
|
192
200
|
in to_timezone. A use case for this is when dealing with static date fields to convert a provided timestamp to
|
|
193
201
|
the value necessary to display that timestamp in the same way when viewed in the static date field.
|
|
202
|
+
If None is passed to the time_point parameter, None will be returned.
|
|
194
203
|
|
|
195
204
|
:param time_point: The time in some date/time format to convert from.
|
|
196
205
|
:param time_format: The format that the time_point is in. Documentation for how the time formatting works
|
|
@@ -202,6 +211,9 @@ class TimeUtil:
|
|
|
202
211
|
:return: The epoch timestamp that would display as the same time in to_timezone as the given time in
|
|
203
212
|
from_timezone.
|
|
204
213
|
"""
|
|
214
|
+
if time_point is None:
|
|
215
|
+
return None
|
|
216
|
+
|
|
205
217
|
millis: int = TimeUtil.format_to_millis(time_point, time_format, from_timezone)
|
|
206
218
|
return TimeUtil.shift_millis(millis, to_timezone, from_timezone)
|
|
207
219
|
|
|
@@ -215,9 +227,192 @@ class TimeUtil:
|
|
|
215
227
|
:param time_format: The format that the time_point should be in. Documentation for how the time formatting works
|
|
216
228
|
can be found at https://docs.python.org/3.10/library/datetime.html#strftime-and-strptime-behavior
|
|
217
229
|
"""
|
|
230
|
+
if time_point is None:
|
|
231
|
+
return False
|
|
218
232
|
try:
|
|
219
233
|
# If this function successfully runs, then the time_point matches the time_format.
|
|
220
234
|
TimeUtil.format_to_millis(time_point, time_format, "UTC")
|
|
221
235
|
return True
|
|
222
236
|
except Exception:
|
|
223
237
|
return False
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
MILLISECONDS_IN_A_SECOND: int = 1000
|
|
241
|
+
MILLISECONDS_IN_A_MINUTE: int = 60 * MILLISECONDS_IN_A_SECOND
|
|
242
|
+
MILLISECONDS_IN_AN_HOUR: int = 60 * MILLISECONDS_IN_A_MINUTE
|
|
243
|
+
MILLISECONDS_IN_A_DAY: int = 24 * MILLISECONDS_IN_AN_HOUR
|
|
244
|
+
|
|
245
|
+
class ElapsedTime:
|
|
246
|
+
_start: int
|
|
247
|
+
_end: int
|
|
248
|
+
|
|
249
|
+
_total_days: float
|
|
250
|
+
_total_hours: float
|
|
251
|
+
_total_minutes: float
|
|
252
|
+
_total_seconds: float
|
|
253
|
+
_total_milliseconds: int
|
|
254
|
+
|
|
255
|
+
_days: int
|
|
256
|
+
_hours: int
|
|
257
|
+
_minutes: int
|
|
258
|
+
_seconds: int
|
|
259
|
+
_milliseconds: int
|
|
260
|
+
|
|
261
|
+
def __init__(self, start: int | float, end: int | float | None = None):
|
|
262
|
+
"""
|
|
263
|
+
:param start: The start timestamp in milliseconds (int) or seconds (float).
|
|
264
|
+
:param end: The end timestamp in milliseconds (int) or seconds (float). If None, uses the current epoch time in
|
|
265
|
+
milliseconds.
|
|
266
|
+
"""
|
|
267
|
+
if isinstance(start, float):
|
|
268
|
+
start = int(start * 1000)
|
|
269
|
+
|
|
270
|
+
if end is None:
|
|
271
|
+
end = TimeUtil.now_in_millis()
|
|
272
|
+
elif isinstance(end, float):
|
|
273
|
+
end = int(end * 1000)
|
|
274
|
+
|
|
275
|
+
self._start = start
|
|
276
|
+
self._end = end
|
|
277
|
+
|
|
278
|
+
self._total_milliseconds = end - start
|
|
279
|
+
self._total_seconds = self._total_milliseconds / MILLISECONDS_IN_A_SECOND
|
|
280
|
+
self._total_minutes = self._total_milliseconds / MILLISECONDS_IN_A_MINUTE
|
|
281
|
+
self._total_hours = self._total_milliseconds / MILLISECONDS_IN_AN_HOUR
|
|
282
|
+
self._total_days = self._total_milliseconds / MILLISECONDS_IN_A_DAY
|
|
283
|
+
|
|
284
|
+
elapsed: int = end - start
|
|
285
|
+
self._days: int = elapsed // MILLISECONDS_IN_A_DAY
|
|
286
|
+
elapsed -= self._days * MILLISECONDS_IN_A_DAY
|
|
287
|
+
self._hours: int = elapsed // MILLISECONDS_IN_AN_HOUR
|
|
288
|
+
elapsed -= self._hours * MILLISECONDS_IN_AN_HOUR
|
|
289
|
+
self._minutes: int = elapsed // MILLISECONDS_IN_A_MINUTE
|
|
290
|
+
elapsed -= self._minutes * MILLISECONDS_IN_A_MINUTE
|
|
291
|
+
self._seconds: int = elapsed // MILLISECONDS_IN_A_SECOND
|
|
292
|
+
elapsed -= self._seconds * MILLISECONDS_IN_A_SECOND
|
|
293
|
+
self._milliseconds = elapsed
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def as_eta(total: int, progress: int, start: int | float, now: int | float | None = None) -> ElapsedTime:
|
|
297
|
+
"""
|
|
298
|
+
Calculate the estimated time remaining for a task based on how much time has passed so far and how many items
|
|
299
|
+
have been completed. The estimated time remaining is calculated by determining the average time per item
|
|
300
|
+
completed so far and multiplying that by the number of items remaining.
|
|
301
|
+
|
|
302
|
+
:param total: The total number of items that need to be completed for the task.
|
|
303
|
+
:param progress: The number of items that have been completed so far.
|
|
304
|
+
:param start: The start time of a task in milliseconds (int) or seconds (float).
|
|
305
|
+
:param now: The amount of time that has passed so far while performing the task in milliseconds (int)
|
|
306
|
+
or seconds (float). If None, uses the current epoch time in milliseconds.
|
|
307
|
+
:return: An ElapsedTime object representing the estimated time remaining.
|
|
308
|
+
"""
|
|
309
|
+
if now is None:
|
|
310
|
+
now = TimeUtil.now_in_millis()
|
|
311
|
+
is_int: bool = isinstance(start, int) and isinstance(now, int)
|
|
312
|
+
# How much time has elapsed so far.
|
|
313
|
+
elapsed: int | float = now - start
|
|
314
|
+
# The average time it has taken to complete each record so far.
|
|
315
|
+
per_record: int | float = (elapsed // progress) if is_int else (elapsed / progress)
|
|
316
|
+
# The estimated time remaining based on the average time per record.
|
|
317
|
+
remaining: int | float = (total - progress) * per_record
|
|
318
|
+
return ElapsedTime(now, now + remaining)
|
|
319
|
+
|
|
320
|
+
def __str__(self) -> str:
|
|
321
|
+
time_str: str = f"{self._hours:02d}:{self._minutes:02d}:{self._seconds:02d}.{self._milliseconds:03d}"
|
|
322
|
+
if self._days:
|
|
323
|
+
return f"{self._days}d {time_str}"
|
|
324
|
+
return time_str
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def start(self) -> int:
|
|
328
|
+
"""
|
|
329
|
+
The start timestamp in milliseconds.
|
|
330
|
+
"""
|
|
331
|
+
return self._start
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def end(self) -> int:
|
|
335
|
+
"""
|
|
336
|
+
The end timestamp in milliseconds.
|
|
337
|
+
"""
|
|
338
|
+
return self._end
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def total_days(self) -> float:
|
|
342
|
+
"""
|
|
343
|
+
The total number of days in the elapsed time. For example, an elapsed time of 1.5 days would
|
|
344
|
+
return 1.5.
|
|
345
|
+
"""
|
|
346
|
+
return self._total_days
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def total_hours(self) -> float:
|
|
350
|
+
"""
|
|
351
|
+
The total number of hours in the elapsed time. For example, an elapsed time of 1.5 days would
|
|
352
|
+
return 36.0.
|
|
353
|
+
"""
|
|
354
|
+
return self._total_hours
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def total_minutes(self) -> float:
|
|
358
|
+
"""
|
|
359
|
+
The total number of minutes in the elapsed time. For example, an elapsed time of 1.5 days would
|
|
360
|
+
return 2,160.0.
|
|
361
|
+
"""
|
|
362
|
+
return self._total_minutes
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def total_seconds(self) -> float:
|
|
366
|
+
"""
|
|
367
|
+
The total number of seconds in the elapsed time. For example, an elapsed time of 1.5 days would
|
|
368
|
+
return 129,600.0.
|
|
369
|
+
"""
|
|
370
|
+
return self._total_seconds
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def total_milliseconds(self) -> int:
|
|
374
|
+
"""
|
|
375
|
+
The total number of milliseconds in the elapsed time. For example, an elapsed time of 1.5 days would
|
|
376
|
+
return 129,600,000.
|
|
377
|
+
"""
|
|
378
|
+
return self._total_milliseconds
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def days(self) -> int:
|
|
382
|
+
"""
|
|
383
|
+
The number of full days in the elapsed time. For example, an elapsed time of 1.5 days would
|
|
384
|
+
return 1.
|
|
385
|
+
"""
|
|
386
|
+
return self._days
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def hours(self) -> int:
|
|
390
|
+
"""
|
|
391
|
+
The number of full hours in the elapsed time, not counting full days. For example, an elapsed time of 1.5 days
|
|
392
|
+
would return 12.
|
|
393
|
+
"""
|
|
394
|
+
return self._hours
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def minutes(self) -> int:
|
|
398
|
+
"""
|
|
399
|
+
The number of full minutes in the elapsed time, not counting full hours. For example, an elapsed time of
|
|
400
|
+
1.5 hours would return 30.
|
|
401
|
+
"""
|
|
402
|
+
return self._minutes
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def seconds(self) -> int:
|
|
406
|
+
"""
|
|
407
|
+
The number of full seconds in the elapsed time, not counting full minutes. For example, an elapsed time of 1
|
|
408
|
+
minute and 45 seconds would return 45.
|
|
409
|
+
"""
|
|
410
|
+
return self._seconds
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def milliseconds(self) -> int:
|
|
414
|
+
"""
|
|
415
|
+
The number of milliseconds in the elapsed time, not counting full seconds. For example, an elapsed time of
|
|
416
|
+
1.25 seconds would return 250.
|
|
417
|
+
"""
|
|
418
|
+
return self._milliseconds
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import io
|
|
4
4
|
import warnings
|
|
5
5
|
from collections.abc import Iterable
|
|
6
|
-
from typing import Collection, TypeVar
|
|
6
|
+
from typing import Collection, TypeVar
|
|
7
7
|
from weakref import WeakValueDictionary
|
|
8
8
|
|
|
9
9
|
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap, FieldIdentifier, AliasUtil, \
|
|
@@ -31,13 +31,6 @@ from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManage
|
|
|
31
31
|
from sapiopylib.rest.utils.recordmodel.properties import Parents, Parent, Children, Child, ForwardSideLink, \
|
|
32
32
|
ReverseSideLink
|
|
33
33
|
|
|
34
|
-
# Aliases for longer name.
|
|
35
|
-
_PropertyGetter: TypeAlias = AbstractRecordModelPropertyGetter
|
|
36
|
-
_PropertyAdder: TypeAlias = AbstractRecordModelPropertyAdder
|
|
37
|
-
_PropertyRemover: TypeAlias = AbstractRecordModelPropertyRemover
|
|
38
|
-
_PropertySetter: TypeAlias = AbstractRecordModelPropertySetter
|
|
39
|
-
_PropertyType: TypeAlias = RecordModelPropertyType
|
|
40
|
-
|
|
41
34
|
# CR-47717: Use TypeVars in the type hints of certain functions to prevent PyCharm from erroneously flagging certain
|
|
42
35
|
# return type hints as incorrect.
|
|
43
36
|
IsRecordModel = TypeVar('IsRecordModel', bound=RecordModel)
|
|
@@ -100,6 +93,10 @@ class RecordHandler:
|
|
|
100
93
|
PyRecordModel instead of a WrappedRecordModel.
|
|
101
94
|
:return: The record model for the input.
|
|
102
95
|
"""
|
|
96
|
+
# PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType]. The type hints
|
|
97
|
+
# say this shouldn't be done anyway, but using this as a safeguard against user error.
|
|
98
|
+
if isinstance(wrapper_type, str):
|
|
99
|
+
wrapper_type = None
|
|
103
100
|
if wrapper_type is not None:
|
|
104
101
|
self.__verify_data_type(record, wrapper_type)
|
|
105
102
|
if isinstance(record, PyRecordModel):
|
|
@@ -642,6 +639,40 @@ class RecordHandler:
|
|
|
642
639
|
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as stream:
|
|
643
640
|
self.dr_man.set_record_image(record, stream)
|
|
644
641
|
|
|
642
|
+
def get_file_blob_data(self, record: SapioRecord, field_name: FieldIdentifier) -> bytes:
|
|
643
|
+
"""
|
|
644
|
+
Retrieve file blob data for a given record from one of its file blob fields.
|
|
645
|
+
|
|
646
|
+
:param record: The record model to retrieve from.
|
|
647
|
+
:param field_name: The name of the file blob field to retrieve the data from.
|
|
648
|
+
:return: The file bytes of the given record's file blob data for the input field.
|
|
649
|
+
"""
|
|
650
|
+
record: DataRecord = AliasUtil.to_data_record(record)
|
|
651
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
652
|
+
with io.BytesIO() as data_sink:
|
|
653
|
+
def consume_data(chunk: bytes):
|
|
654
|
+
data_sink.write(chunk)
|
|
655
|
+
|
|
656
|
+
self.dr_man.get_file_blob_data(record, field_name, consume_data)
|
|
657
|
+
data_sink.flush()
|
|
658
|
+
data_sink.seek(0)
|
|
659
|
+
file_bytes = data_sink.read()
|
|
660
|
+
return file_bytes
|
|
661
|
+
|
|
662
|
+
def set_file_blob_data(self, record: SapioRecord, field_name: FieldIdentifier, file_name: str, file_data: str | bytes) -> None:
|
|
663
|
+
"""
|
|
664
|
+
Set the file blob data for a given record on one of its file blob fields.
|
|
665
|
+
|
|
666
|
+
:param record: The record model to set the file blob data of.
|
|
667
|
+
:param field_name: The name of the file blob field to set the data for.
|
|
668
|
+
:param file_name: The name of the file being stored in the file blob field.
|
|
669
|
+
:param file_data: The file data of the blob to set on the record.
|
|
670
|
+
"""
|
|
671
|
+
record: DataRecord = AliasUtil.to_data_record(record)
|
|
672
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
673
|
+
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as stream:
|
|
674
|
+
self.dr_man.set_file_blob_data(record, field_name, file_name, stream)
|
|
675
|
+
|
|
645
676
|
@staticmethod
|
|
646
677
|
def sum_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
647
678
|
"""
|
|
@@ -783,7 +814,8 @@ class RecordHandler:
|
|
|
783
814
|
return [{field_name: value} for value in values]
|
|
784
815
|
|
|
785
816
|
@staticmethod
|
|
786
|
-
def get_from_all(records: Iterable[RecordModel],
|
|
817
|
+
def get_from_all(records: Iterable[RecordModel],
|
|
818
|
+
getter: AbstractRecordModelPropertyGetter[RecordModelPropertyType]) \
|
|
787
819
|
-> list[RecordModelPropertyType]:
|
|
788
820
|
"""
|
|
789
821
|
Use a getter property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -798,7 +830,8 @@ class RecordHandler:
|
|
|
798
830
|
return [x.get(getter) for x in records]
|
|
799
831
|
|
|
800
832
|
@staticmethod
|
|
801
|
-
def set_on_all(records: Iterable[RecordModel],
|
|
833
|
+
def set_on_all(records: Iterable[RecordModel],
|
|
834
|
+
setter: AbstractRecordModelPropertySetter[RecordModelPropertyType]) \
|
|
802
835
|
-> list[RecordModelPropertyType]:
|
|
803
836
|
"""
|
|
804
837
|
Use a setter property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -813,7 +846,8 @@ class RecordHandler:
|
|
|
813
846
|
return [x.set(setter) for x in records]
|
|
814
847
|
|
|
815
848
|
@staticmethod
|
|
816
|
-
def add_to_all(records: Iterable[RecordModel],
|
|
849
|
+
def add_to_all(records: Iterable[RecordModel],
|
|
850
|
+
adder: AbstractRecordModelPropertyAdder[RecordModelPropertyType]) \
|
|
817
851
|
-> list[RecordModelPropertyType]:
|
|
818
852
|
"""
|
|
819
853
|
Use an adder property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -827,7 +861,8 @@ class RecordHandler:
|
|
|
827
861
|
return [x.add(adder) for x in records]
|
|
828
862
|
|
|
829
863
|
@staticmethod
|
|
830
|
-
def remove_from_all(records: Iterable[RecordModel],
|
|
864
|
+
def remove_from_all(records: Iterable[RecordModel],
|
|
865
|
+
remover: AbstractRecordModelPropertyRemover[RecordModelPropertyType]) \
|
|
831
866
|
-> list[RecordModelPropertyType]:
|
|
832
867
|
"""
|
|
833
868
|
Use a remover property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -184,4 +184,7 @@ class ElnRuleHandler:
|
|
|
184
184
|
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
185
185
|
"""
|
|
186
186
|
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
187
|
+
# PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType].
|
|
188
|
+
if isinstance(wrapper_type, str):
|
|
189
|
+
wrapper_type = None
|
|
187
190
|
return self._rec_handler.wrap_models(self.get_records(dt, entry), wrapper_type)
|
|
@@ -180,4 +180,7 @@ class OnSaveRuleHandler:
|
|
|
180
180
|
instead of a model wrapper, then the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
181
181
|
"""
|
|
182
182
|
dt: str = AliasUtil.to_data_type_name(wrapper_type)
|
|
183
|
+
# PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType].
|
|
184
|
+
if isinstance(wrapper_type, str):
|
|
185
|
+
wrapper_type = None
|
|
183
186
|
return self._rec_handler.wrap_models(self.get_records(dt, record_id), wrapper_type)
|
|
@@ -140,7 +140,7 @@ class AbstractWebserviceHandler(AbstractWebhookHandler):
|
|
|
140
140
|
# Get the login credentials from the headers.
|
|
141
141
|
auth: str = headers.get("Authorization")
|
|
142
142
|
if auth and auth.startswith("Basic "):
|
|
143
|
-
credentials: list[str] = b64decode(auth.split("Basic ")[1]).decode().split(":")
|
|
143
|
+
credentials: list[str] = b64decode(auth.split("Basic ")[1]).decode().split(":", 1)
|
|
144
144
|
user = self.basic_auth(url, credentials[0], credentials[1])
|
|
145
145
|
elif auth and auth.startswith("Bearer "):
|
|
146
146
|
user = self.bearer_token_auth(url, auth.split("Bearer ")[1])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2026.1.22a847
|
|
4
4
|
Summary: Official Sapio Python API Utilities Package
|
|
5
5
|
Project-URL: Homepage, https://github.com/sapiosciences
|
|
6
6
|
Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
|
|
@@ -17,7 +17,7 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Requires-Dist: databind>=4.5
|
|
20
|
-
Requires-Dist: sapiopylib>=2025.7.
|
|
20
|
+
Requires-Dist: sapiopylib>=2025.10.7.295
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
sapiopycommons/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
sapiopycommons/ai/agent_service_base.py,sha256=EtImPyKZuRtHn2w0XjiqytHJPzDWOty6mY-2qciRvXk,108986
|
|
4
|
+
sapiopycommons/ai/converter_service_base.py,sha256=NjMCrpyoEh2TYrP9zN3SY0XtOir3IGWGPjU_snr6Usw,6360
|
|
5
|
+
sapiopycommons/ai/external_credentials.py,sha256=DKg3EMGrm6XSt09WbHWy3UHUphuakItiFeToluGSxlM,4427
|
|
6
|
+
sapiopycommons/ai/protobuf_utils.py,sha256=PeXpjfurg8EvvgM_jNLp0F1zAlV7cad8x0zewqi5Bhw,28235
|
|
7
|
+
sapiopycommons/ai/request_validation.py,sha256=fW3I_G6PUrJhpalQEfI1T5nAqcNUYYOVf_oJnlROrNM,30430
|
|
8
|
+
sapiopycommons/ai/server.py,sha256=VgHNvY2751qDZ8t7JTS67uxSnzOcpKdKc2Dick1p22g,6393
|
|
9
|
+
sapiopycommons/ai/test_client.py,sha256=hExCLASmqm59nTnhph9OIQpTSbde14cH4Sayik3Enpk,26350
|
|
10
|
+
sapiopycommons/ai/protoapi/agent/agent_pb2.py,sha256=dKKB52qOf7lCZWt9kAgqFu8bdhemMwGid8u0aC_Rb2s,10489
|
|
11
|
+
sapiopycommons/ai/protoapi/agent/agent_pb2.pyi,sha256=EkN98-gIGCbj4OuPOZMjZWDi-hhAgtThR0hR6Mk-FoM,20222
|
|
12
|
+
sapiopycommons/ai/protoapi/agent/agent_pb2_grpc.py,sha256=q4sac85YnJG6vjqNXKdNji3FaLSu0lEBnjKMKRBJm90,6851
|
|
13
|
+
sapiopycommons/ai/protoapi/agent/entry_pb2.py,sha256=EDFp_UNaWw_RnwrrEfACFRXUEUy18OaxPDB8ljWPKBk,3082
|
|
14
|
+
sapiopycommons/ai/protoapi/agent/entry_pb2.pyi,sha256=DMb3vd-O1cqo7Q_2ehx6ubFEa1SR_ho_qBrEvaIQTbk,2745
|
|
15
|
+
sapiopycommons/ai/protoapi/agent/entry_pb2_grpc.py,sha256=hiZtUu9Ij-zQoKeMGKt15-cRlxThdrfe8z5qxoKYKfk,918
|
|
16
|
+
sapiopycommons/ai/protoapi/agent/item/item_container_pb2.py,sha256=oBHslKp2eni0wicsuk5BfnWPLp6g5FzwR0CGBqfyqDQ,4830
|
|
17
|
+
sapiopycommons/ai/protoapi/agent/item/item_container_pb2.pyi,sha256=6JQ_i6DPs0lSTTSl8qImNsBg6aEMNjbyUUh6ARwz6YI,11626
|
|
18
|
+
sapiopycommons/ai/protoapi/agent/item/item_container_pb2_grpc.py,sha256=Uj5rcKQuXFU_dn1vnJEEblkK_kdh30BXQeBY0JgAkuI,932
|
|
19
|
+
sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.py,sha256=vJRtIbxRm-CM9WTfFlcn4V5oDCQeK1hRoVS9WwdYbjU,2481
|
|
20
|
+
sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.pyi,sha256=3_vdkYM56c31cCVo6VvXDV9F4ENN7Mq20PAPHOHvHZw,1685
|
|
21
|
+
sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2_grpc.py,sha256=mZLcay2VLNCuabBp8k2Sw3d6FcHXWFzze31Q3O0l7es,947
|
|
22
|
+
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py,sha256=Oral3k23576HIKu6mWIcZOuk87cvbpmRmiZJcU6kG1o,3344
|
|
23
|
+
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi,sha256=tJXn_hO7Xii5ZQPCGvAYkrqiqnqNQxMEdKC6Wm_rmJE,2812
|
|
24
|
+
sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py,sha256=scBvKFAGDWSAPJ4cnJCpvBNbROMjYGWF8y6gugOjudI,930
|
|
25
|
+
sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py,sha256=CE4cM7UlT6fbTw0JafKiAH2jdEnmcuAzKYoF-3oYGcw,20864
|
|
26
|
+
sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi,sha256=h24h8dh6d4dsw0Bc9Oyfffdxv73aljfgt72ZJIXwxBw,34055
|
|
27
|
+
sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py,sha256=hcd7T-_SbDWGG22-GigTihxCAPQ_hryCcX_hqFFF-KE,939
|
|
28
|
+
sapiopycommons/ai/protoapi/pipeline/step_output_pb2.py,sha256=nntHXqMbbgqhm9Ic17FMaIoR7CGIrzbzxknIAkTtr9w,3293
|
|
29
|
+
sapiopycommons/ai/protoapi/pipeline/step_output_pb2.pyi,sha256=Md2SaMnflALL_TnoAkRWrAsAYj0hMg-Yi76F0NfFnQg,3016
|
|
30
|
+
sapiopycommons/ai/protoapi/pipeline/step_output_pb2_grpc.py,sha256=7fWSkfpljHFr1RqFMuyIgQ87rlRBGluWLs02Bg_pJnc,927
|
|
31
|
+
sapiopycommons/ai/protoapi/pipeline/step_pb2.py,sha256=c5g5h4LIX1FKjVzoNZKku-jclnHhTCa64LBDLYd-BKA,2457
|
|
32
|
+
sapiopycommons/ai/protoapi/pipeline/step_pb2.pyi,sha256=tGFKu574nSLRURNhbxFcbajY66G-wfKIJm5D_dy58uk,2089
|
|
33
|
+
sapiopycommons/ai/protoapi/pipeline/step_pb2_grpc.py,sha256=mSdi68T_lXIxsaD23hIYaNk4TzxvNRAfD31xSLHX8lY,920
|
|
34
|
+
sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2.py,sha256=4FfhtxyGMUyDUZO_GXQzmjsJU47z775XAprgTMjLHYk,4590
|
|
35
|
+
sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2.pyi,sha256=pe-cq7ITHC0egMs8Zz5dp2IPtC_QG_Hlt0FwTtqIFZs,4641
|
|
36
|
+
sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2_grpc.py,sha256=lhmz45-cy9mJnRVZAD0T7s4pKmGQfw_Hg98DbVq9qHE,6507
|
|
37
|
+
sapiopycommons/ai/protoapi/pipeline/script/script_pb2.py,sha256=Rkhmbfm4jFqGU5v03iIWT8SBJ-9uLBYGIdDAkPMlh5A,6510
|
|
38
|
+
sapiopycommons/ai/protoapi/pipeline/script/script_pb2.pyi,sha256=7zgxMpp8KBzDTABrrX5roKhfoPda7v93lMLzIYxR4F8,6886
|
|
39
|
+
sapiopycommons/ai/protoapi/pipeline/script/script_pb2_grpc.py,sha256=o-uJ2sQ4HBrIJhPo0tU8qBvL8m3_xVqLePut4-iMa5A,6923
|
|
40
|
+
sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py,sha256=HY0c7dvR6MpzDC43PTR6C3COYV2F_EnH1GuU2VXBOCs,2094
|
|
41
|
+
sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi,sha256=IyvYgtz-APrZjPvv5wISxSnPmpVK0moLa_nYaEyuFn0,1648
|
|
42
|
+
sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py,sha256=Gk5SC9Dpx6JRJsKY5gONctgrLcIPTUwtH8oUw6jaN3w,930
|
|
43
|
+
sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
sapiopycommons/callbacks/callback_util.py,sha256=qzSxMOnMT1fdPhKiL5tD4MYLgLKYVkYoSs5um9wZRZ0,154890
|
|
45
|
+
sapiopycommons/callbacks/field_builder.py,sha256=rnIP-RJafk3mZlAx1eJ8a0eSW9Ps_L6_WadCmusnENw,38772
|
|
46
|
+
sapiopycommons/chem/IndigoMolecules.py,sha256=7ucCaRMLu1zfH2uPIvXwRTSdpNcS03O1P9p_O-5B4xQ,5110
|
|
47
|
+
sapiopycommons/chem/Molecules.py,sha256=mVqPn32MPMjF0iZas-5MFkS-upIdoW5OB72KKZmJRJA,12523
|
|
48
|
+
sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
sapiopycommons/customreport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
+
sapiopycommons/customreport/auto_pagers.py,sha256=89p-tik0MhsOplYje6LbAW4WClldpAmb8YXFDoXhIlY,17144
|
|
51
|
+
sapiopycommons/customreport/column_builder.py,sha256=0RO53e9rKPZ07C--KcepN6_tpRw_FxF3O9vdG0ilKG8,3014
|
|
52
|
+
sapiopycommons/customreport/custom_report_builder.py,sha256=BlTxZ4t1sfZA2Ciur1EfYvkZxHxJ7ADwYNAe2zwiN0c,7176
|
|
53
|
+
sapiopycommons/customreport/term_builder.py,sha256=1_PGjxNUy5YWim8WJ_HJfiTq6i0D3gLPDxLySlFt30o,18573
|
|
54
|
+
sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
|
+
sapiopycommons/datatype/attachment_util.py,sha256=N-nhsJ0oxa_Ft6Y6VWeNFYLzfuQqsjhHA6_-yIt2wVw,3596
|
|
56
|
+
sapiopycommons/datatype/data_fields.py,sha256=pczUlEcE0TeHEDU0Gkvu7voacSLPXCB7l9UbI1Tb6V0,5656
|
|
57
|
+
sapiopycommons/datatype/pseudo_data_types.py,sha256=lAJDnFuStrUP0mK5AuYlFvLerwjEB-ABd6Z4qlCrwJA,40637
|
|
58
|
+
sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
+
sapiopycommons/eln/experiment_cache.py,sha256=Zv4IcsAl95ftO2ul3DRc_Hyno0AfC3OvFV7RYb72ITo,9560
|
|
60
|
+
sapiopycommons/eln/experiment_handler.py,sha256=1RzrWOicF9VjcWlaziV46yBEk1BOP4BuTJ0JeYdn50I,99204
|
|
61
|
+
sapiopycommons/eln/experiment_report_util.py,sha256=GLpgwSEPuUqnY1v4oJ1ao60Va-YcgXh7E-cH9YnVeAg,37256
|
|
62
|
+
sapiopycommons/eln/experiment_step_factory.py,sha256=qw9UfLslVzB6dEIZPOZ85XHKpld81RhD4-csM6TgQNg,26099
|
|
63
|
+
sapiopycommons/eln/experiment_tags.py,sha256=7-fpOiSqrjbXmWIJhEhaxMgLsVCPAtKqH8xRzpDVKoE,356
|
|
64
|
+
sapiopycommons/eln/plate_designer.py,sha256=XFazSvhTbSy47t80-jc2tyx_-fQ_IUjKd18JQKEFcsY,13939
|
|
65
|
+
sapiopycommons/eln/step_creation.py,sha256=CFkGC-SxwAQpQlcs_obqLAVgmsNxKSGMqMtO_E6IVmw,10171
|
|
66
|
+
sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
|
+
sapiopycommons/files/assay_plate_reader.py,sha256=3c2PQiiAbc2QJU9ZfNLzcTmvJrUwsbkIHO7R6R52xGU,3020
|
|
68
|
+
sapiopycommons/files/complex_data_loader.py,sha256=T39veNhvYl6j_uZjIIJ8Mk5Aa7otR5RB-g8XlAdkksA,1421
|
|
69
|
+
sapiopycommons/files/file_bridge.py,sha256=vKbqxPexi15epr_-_qLrEfYoxNxB031mXN92iVtOMqE,9511
|
|
70
|
+
sapiopycommons/files/file_bridge_handler.py,sha256=SEYDIQhSCmjI6qyLdDJE8JVKSd0WYvF7JvAq_Ahp9Do,25503
|
|
71
|
+
sapiopycommons/files/file_data_handler.py,sha256=f96MlkMuQhUCi4oLnzJK5AiuElCp5jLI8_sJkZVwpws,36779
|
|
72
|
+
sapiopycommons/files/file_text_converter.py,sha256=Gaj_divTiKXWd6flDOgrxNXpcn9fDWqxX6LUG0joePk,7516
|
|
73
|
+
sapiopycommons/files/file_util.py,sha256=WBA3FYG8R2HtfxjWSzQhZKW6_1s6JSxTo9lk3SeNDu8,37140
|
|
74
|
+
sapiopycommons/files/file_validator.py,sha256=ryg22-93csmRO_Pv0ZpWphNkB74xWZnHyJ23K56qLj0,28761
|
|
75
|
+
sapiopycommons/files/file_writer.py,sha256=hACVl0duCjP28gJ1NPljkjagNCLod0ygUlPbvUmRDNM,17605
|
|
76
|
+
sapiopycommons/files/temp_files.py,sha256=s2sGvn9uh2dTI8AVAQJWOf6RAZ0xZs7DSccCi4AGmlw,3175
|
|
77
|
+
sapiopycommons/flowcyto/flow_cyto.py,sha256=B6DFquLi-gcWfJWyP4vYfwTXXJKl6O9W5-k8FzkM0Oo,2610
|
|
78
|
+
sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
|
|
79
|
+
sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
|
+
sapiopycommons/general/accession_service.py,sha256=ZvtvZg7d_siMJUedjrF14mcqo5ZqVA5IJxDa5enlB-8,12792
|
|
81
|
+
sapiopycommons/general/aliases.py,sha256=R7c1TgkyktKOrRYeMTpzMM61A7jYojJVM8ZcNu9Ktqo,14840
|
|
82
|
+
sapiopycommons/general/audit_log.py,sha256=sQAMcJx0cNkgZm7nTZSaGPxWvHG0_x6dBtU0jESavb4,9131
|
|
83
|
+
sapiopycommons/general/custom_report_util.py,sha256=9elLEUSgfM0gli8nRPz1uYkhaXN4Vnx3piSiNHv5IBs,19156
|
|
84
|
+
sapiopycommons/general/data_structure_util.py,sha256=fbQR_Fh4Scg67IpFPbQW9wVLw1oxlYxqp4LjBRTpjgU,4702
|
|
85
|
+
sapiopycommons/general/directive_util.py,sha256=7SeQrd2Ye5JHlXZtJZaVGgtaSLdq_Vm9EObuxf44Pz8,3905
|
|
86
|
+
sapiopycommons/general/exceptions.py,sha256=aPlzK1cvxeMU5UsokYlLrIBGltUfJZ7LH8zvLh9DxpI,3233
|
|
87
|
+
sapiopycommons/general/html_formatter.py,sha256=HE3OeGgwOw6x53zGSc4-UzP4-JoOmQIz3pX-DzNVg94,17138
|
|
88
|
+
sapiopycommons/general/macros.py,sha256=Tses4g247JADC93U2OROHdDadEuUPLhPXgeQljz-fK4,7127
|
|
89
|
+
sapiopycommons/general/popup_util.py,sha256=HKILegU1uCL_6abNlNL0Wn3xgX2JNa_kJeq7e5CZu6Q,31923
|
|
90
|
+
sapiopycommons/general/sapio_links.py,sha256=YkcVKNLrSGoM7tCCXBAsIbIxylctwdcEyhePrRMODe0,2859
|
|
91
|
+
sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
|
|
92
|
+
sapiopycommons/general/time_util.py,sha256=dB8_Ot61_ILcuKXeBt1jfa01UOoCcegnjqVdfr5tyQ8,19344
|
|
93
|
+
sapiopycommons/multimodal/multimodal.py,sha256=EP9WYzx1CvidmEBlvzO6tiF4HJwsPB1FgxpnbWzxnpA,6161
|
|
94
|
+
sapiopycommons/multimodal/multimodal_data.py,sha256=0BeVPr9HaC0hNTF1v1phTIKGruvNnwerHsD994qJKBg,15099
|
|
95
|
+
sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
96
|
+
sapiopycommons/processtracking/custom_workflow_handler.py,sha256=eYKdYlwo8xx-6AkB_iPUBNV9yDoNvW2h_Sm3i8JpmRU,25844
|
|
97
|
+
sapiopycommons/processtracking/endpoints.py,sha256=5AJLbhRKQsOeeOdQa888xcCJZD5aavxD-DHZ36Qob_M,12548
|
|
98
|
+
sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
|
+
sapiopycommons/recordmodel/record_handler.py,sha256=TUkbMZEZEjO2fL77YvmA6nzRc5jCggzGApIL9AoNKOU,96916
|
|
100
|
+
sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
101
|
+
sapiopycommons/rules/eln_rule_handler.py,sha256=Ec2hvxn6gmBvZjhX9-7WCFqafxFE9JSy2zCsvFsVyS4,11565
|
|
102
|
+
sapiopycommons/rules/on_save_rule_handler.py,sha256=HLdgUkxmaoHBK3jaycZlUHWam4kk36zmw7VDuRRiAx8,11332
|
|
103
|
+
sapiopycommons/samples/aliquot.py,sha256=mWOJUqaQh0t3HklNuGdmuV7D5zzXs6fpLwtDdM6_XTo,3018
|
|
104
|
+
sapiopycommons/sftpconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
|
+
sapiopycommons/sftpconnect/sftp_builder.py,sha256=lFK3FeXk-sFLefW0hqY8WGUQDeYiGaT6yDACzT_zFgQ,3015
|
|
106
|
+
sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
107
|
+
sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
|
|
108
|
+
sapiopycommons/webhook/webhook_handlers.py,sha256=7o_wXOruhT9auNh8OfhJAh4WhhiPKij67FMBSpGPICc,39939
|
|
109
|
+
sapiopycommons/webhook/webservice_handlers.py,sha256=cvW6Mk_110BzYqkbk63Kg7jWrltBCDALOlkJRu8h4VQ,14300
|
|
110
|
+
sapiopycommons-2026.1.22a847.dist-info/METADATA,sha256=Vx4vhKjIcObBi7v_mYqHErNc-Ku0zd8YDbw2WwF5qhE,3143
|
|
111
|
+
sapiopycommons-2026.1.22a847.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
112
|
+
sapiopycommons-2026.1.22a847.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
113
|
+
sapiopycommons-2026.1.22a847.dist-info/RECORD,,
|