sapiopycommons 2025.6.16a562__tar.gz → 2025.6.19a564__tar.gz
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 sapiopycommons might be problematic. Click here for more details.
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/PKG-INFO +1 -1
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/pyproject.toml +1 -1
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/customreport/auto_pagers.py +24 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/experiment_handler.py +89 -65
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/experiment_report_util.py +11 -6
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_util.py +3 -1
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/aliases.py +0 -1
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/audit_log.py +7 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/custom_report_util.py +12 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/processtracking/custom_workflow_handler.py +9 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/processtracking/endpoints.py +27 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/.gitignore +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/LICENSE +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/README.md +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/callbacks/callback_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/callbacks/field_builder.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/chem/Molecules.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/customreport/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/customreport/column_builder.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/customreport/term_builder.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/datatype/attachment_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/datatype/data_fields.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/experiment_cache.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/experiment_step_factory.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/experiment_tags.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/step_creation.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/complex_data_loader.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_bridge.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_validator.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/accession_service.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/data_structure_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/directive_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/html_formatter.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/sapio_links.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/multimodal/multimodal.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/processtracking/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/recordmodel/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/rules/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/samples/aliquot.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/webhook/__init__.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/webhook/webhook_context.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/_do_not_add_init_py_here +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/accession_test.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/aliquot_test.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/bio_reg_test.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/chem_test.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/chem_test_curation_queue.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/curation_queue_test.sdf +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/data_type_models.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/8_color_ICS.wsp +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto_test.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/kappa.chains.fasta +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/mafft_test.py +0 -0
- {sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/test.gb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.6.
|
|
3
|
+
Version: 2025.6.19a564
|
|
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>
|
|
@@ -62,6 +62,12 @@ class CustomReportDictAutoPager(_DictReportPagerBase):
|
|
|
62
62
|
def __init__(self, user: UserIdentifier, report_criteria: CustomReportCriteria,
|
|
63
63
|
page_number: int = 0, page_size: int = _default_report_page_size):
|
|
64
64
|
"""
|
|
65
|
+
IMPORTANT NOTICE: Custom reports that are not single data type (i.e. they have terms or columns from multiple
|
|
66
|
+
data types) may not be 100% time accurate. Such reports use the system's ancestor table to retrieve the
|
|
67
|
+
relationships, and this table takes some time to update after relationships are updated, especially for more
|
|
68
|
+
populous data types. If you need 100% time accurate results to the current state of the records and
|
|
69
|
+
relationships in the database, you should query for the records directly instead of using a custom report.
|
|
70
|
+
|
|
65
71
|
:param user: The current webhook context or a user object to send requests from.
|
|
66
72
|
:param report_criteria: The custom report criteria to run.
|
|
67
73
|
:param page_number: The page number to start on. The first page is page 0.
|
|
@@ -83,6 +89,12 @@ class SystemReportDictAutoPager(_DictReportPagerBase):
|
|
|
83
89
|
def __init__(self, user: UserIdentifier, report_name: str,
|
|
84
90
|
page_number: int = 0, page_size: int = _default_report_page_size):
|
|
85
91
|
"""
|
|
92
|
+
IMPORTANT NOTICE: Custom reports that are not single data type (i.e. they have terms or columns from multiple
|
|
93
|
+
data types) may not be 100% time accurate. Such reports use the system's ancestor table to retrieve the
|
|
94
|
+
relationships, and this table takes some time to update after relationships are updated, especially for more
|
|
95
|
+
populous data types. If you need 100% time accurate results to the current state of the records and
|
|
96
|
+
relationships in the database, you should query for the records directly instead of using a custom report.
|
|
97
|
+
|
|
86
98
|
:param user: The current webhook context or a user object to send requests from.
|
|
87
99
|
:param report_name: The name of the system report to run.
|
|
88
100
|
:param page_number: The page number to start on. The first page is page 0.
|
|
@@ -173,6 +185,12 @@ class CustomReportRecordAutoPager(_RecordReportPagerBase):
|
|
|
173
185
|
wrapper_type: type[WrappedType] | str, page_number: int = 0,
|
|
174
186
|
page_size: int = _default_record_page_size):
|
|
175
187
|
"""
|
|
188
|
+
IMPORTANT NOTICE: Custom reports that are not single data type (i.e. they have terms or columns from multiple
|
|
189
|
+
data types) may not be 100% time accurate. Such reports use the system's ancestor table to retrieve the
|
|
190
|
+
relationships, and this table takes some time to update after relationships are updated, especially for more
|
|
191
|
+
populous data types. If you need 100% time accurate results to the current state of the records and
|
|
192
|
+
relationships in the database, you should query for the records directly instead of using a custom report.
|
|
193
|
+
|
|
176
194
|
:param user: The current webhook context or a user object to send requests from.
|
|
177
195
|
:param report_criteria: The custom report criteria to run.
|
|
178
196
|
:param wrapper_type: The record model wrapper type or data type name of the records being searched for.
|
|
@@ -198,6 +216,12 @@ class SystemReportRecordAutoPager(_RecordReportPagerBase):
|
|
|
198
216
|
def __init__(self, user: UserIdentifier, report_name: str, wrapper_type: type[WrappedType] | str,
|
|
199
217
|
page_number: int = 0, page_size: int = _default_record_page_size):
|
|
200
218
|
"""
|
|
219
|
+
IMPORTANT NOTICE: Custom reports that are not single data type (i.e. they have terms or columns from multiple
|
|
220
|
+
data types) may not be 100% time accurate. Such reports use the system's ancestor table to retrieve the
|
|
221
|
+
relationships, and this table takes some time to update after relationships are updated, especially for more
|
|
222
|
+
populous data types. If you need 100% time accurate results to the current state of the records and
|
|
223
|
+
relationships in the database, you should query for the records directly instead of using a custom report.
|
|
224
|
+
|
|
201
225
|
:param user: The current webhook context or a user object to send requests from.
|
|
202
226
|
:param report_name: The name of the system report to run.
|
|
203
227
|
:param wrapper_type: The record model wrapper type or data type name of the records being searched for.
|
|
@@ -290,12 +290,19 @@ class ExperimentHandler:
|
|
|
290
290
|
:param entry: The entry to add to the cache.
|
|
291
291
|
:return: The entry that was added to the cache as an ElnEntryStep.
|
|
292
292
|
"""
|
|
293
|
+
# ExperimentEntries are stored as ElnEntrySteps in the cache.
|
|
293
294
|
if isinstance(entry, ExperimentEntry):
|
|
294
295
|
entry = ElnEntryStep(self._protocol, entry)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
# PR-47699: Confirm that this entry is part of the experiment that this handler is for.
|
|
297
|
+
if entry.eln_entry.parent_experiment_id != self._exp_id:
|
|
298
|
+
raise SapioException(f"Entry with ID {entry.get_id()} is not part of the experiment with ID "
|
|
299
|
+
f"{self._exp_id}.")
|
|
300
|
+
# PR-47699: Don't add the entry if it is already in the cache.
|
|
301
|
+
if entry.get_id() not in self._steps_by_id:
|
|
302
|
+
self._steps.append(entry)
|
|
303
|
+
self._steps_by_name.update({entry.get_name(): entry})
|
|
304
|
+
self._steps_by_id.update({entry.get_id(): entry})
|
|
305
|
+
# Skipping the options cache. The get_step_options method will update that cache when necessary.
|
|
299
306
|
return entry
|
|
300
307
|
|
|
301
308
|
def add_entries_to_caches(self, entries: list[ExperimentEntry | ElnEntryStep]) -> list[ElnEntryStep]:
|
|
@@ -326,9 +333,11 @@ class ExperimentHandler:
|
|
|
326
333
|
self._tabs_by_name[tab.tab_name] = tab
|
|
327
334
|
|
|
328
335
|
# FR-46495: Split the creation of the experiment in launch_experiment into a create_experiment function.
|
|
336
|
+
# CR-47703: Allow create_experiment and launch_experiment to accept None as a template_name to create a blank
|
|
337
|
+
# experiment. Also allow a SapioUser object to be provided as context instead of a full SapioWebhookContext object.
|
|
329
338
|
@staticmethod
|
|
330
|
-
def create_experiment(context:
|
|
331
|
-
template_name: str,
|
|
339
|
+
def create_experiment(context: UserIdentifier,
|
|
340
|
+
template_name: str | None = None,
|
|
332
341
|
experiment_name: str | None = None,
|
|
333
342
|
parent_record: SapioRecord | None = None, *,
|
|
334
343
|
template_version: int | None = None, active_templates_only: bool = True) -> ElnExperiment:
|
|
@@ -339,8 +348,9 @@ class ExperimentHandler:
|
|
|
339
348
|
templates match the same criteria, the first template that is encountered in the query is used. Throws an
|
|
340
349
|
exception if no template is found. Also makes a webservice request to create the experiment.
|
|
341
350
|
|
|
342
|
-
:param context: The current webhook context.
|
|
343
|
-
:param template_name: The name of the template to create the experiment from.
|
|
351
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
352
|
+
:param template_name: The name of the template to create the experiment from. If None, a blank experiment
|
|
353
|
+
is created.
|
|
344
354
|
:param experiment_name: The name to give to the experiment after it is created. If not provided, defaults to the
|
|
345
355
|
display name of the template.
|
|
346
356
|
:param parent_record: The parent record to attach this experiment under. This record must be an eligible
|
|
@@ -352,20 +362,27 @@ class ExperimentHandler:
|
|
|
352
362
|
:param active_templates_only: Whether only active templates should be queried for.
|
|
353
363
|
:return: The newly created experiment.
|
|
354
364
|
"""
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
365
|
+
user = AliasUtil.to_sapio_user(context)
|
|
366
|
+
cache = ExperimentCacheManager(user)
|
|
367
|
+
eln_manager: ElnManager = DataMgmtServer.get_eln_manager(user)
|
|
368
|
+
|
|
369
|
+
template_id: int | None = None
|
|
370
|
+
if template_name:
|
|
371
|
+
launch_template: ElnTemplate = cache.get_experiment_template(template_name, active_templates_only,
|
|
372
|
+
template_version, first_match=True)
|
|
373
|
+
template_id = launch_template.template_id
|
|
374
|
+
if experiment_name is None:
|
|
375
|
+
experiment_name: str = launch_template.display_name
|
|
376
|
+
elif experiment_name is None:
|
|
377
|
+
experiment_name = f"{user.username}'s Experiment"
|
|
361
378
|
if parent_record is not None:
|
|
362
379
|
parent_record: DataRecord = AliasUtil.to_data_record(parent_record)
|
|
363
|
-
notebook_init = InitializeNotebookExperimentPojo(experiment_name,
|
|
364
|
-
return
|
|
380
|
+
notebook_init = InitializeNotebookExperimentPojo(experiment_name, template_id, parent_record)
|
|
381
|
+
return eln_manager.create_notebook_experiment(notebook_init)
|
|
365
382
|
|
|
366
383
|
@staticmethod
|
|
367
|
-
def launch_experiment(context:
|
|
368
|
-
template_name: str,
|
|
384
|
+
def launch_experiment(context: UserIdentifier,
|
|
385
|
+
template_name: str | None = None,
|
|
369
386
|
experiment_name: str | None = None,
|
|
370
387
|
parent_record: SapioRecord | None = None, *,
|
|
371
388
|
template_version: int | None = None,
|
|
@@ -378,8 +395,9 @@ class ExperimentHandler:
|
|
|
378
395
|
templates match the same criteria, the first template that is encountered in the query is used. Throws an
|
|
379
396
|
exception if no template is found. Also makes a webservice request to create the experiment.
|
|
380
397
|
|
|
381
|
-
:param context: The current webhook context.
|
|
382
|
-
:param template_name: The name of the template to create the experiment from.
|
|
398
|
+
:param context: The current webhook context or a user object to send requests from.
|
|
399
|
+
:param template_name: The name of the template to create the experiment from. If None, a blank experiment
|
|
400
|
+
is created.
|
|
383
401
|
:param experiment_name: The name to give to the experiment after it is created. If not provided, defaults to the
|
|
384
402
|
display name of the template.
|
|
385
403
|
:param parent_record: The parent record to attach this experiment under. This record must be an eligible
|
|
@@ -629,14 +647,14 @@ class ExperimentHandler:
|
|
|
629
647
|
"""
|
|
630
648
|
return all([x is not None for x in self.get_steps(step_names, False)])
|
|
631
649
|
|
|
632
|
-
def get_step(self, step_name:
|
|
650
|
+
def get_step(self, step_name: Step, exception_on_none: bool = True) -> ElnEntryStep | None:
|
|
633
651
|
"""
|
|
634
652
|
Get the step of the given name from the experiment.
|
|
635
653
|
|
|
636
654
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
637
655
|
list of steps in the experiment and caches them.
|
|
638
656
|
|
|
639
|
-
:param step_name: The
|
|
657
|
+
:param step_name: The identifier for the step to return.
|
|
640
658
|
:param exception_on_none: If false, returns None if the entry can't be found. If true, raises an exception
|
|
641
659
|
when the named entry doesn't exist in the experiment.
|
|
642
660
|
:return: An ElnEntrySteps matching the provided name. If there is no match and no exception is to be thrown,
|
|
@@ -644,26 +662,43 @@ class ExperimentHandler:
|
|
|
644
662
|
"""
|
|
645
663
|
return self.get_steps([step_name], exception_on_none)[0]
|
|
646
664
|
|
|
647
|
-
def get_steps(self, step_names: Iterable[
|
|
665
|
+
def get_steps(self, step_names: Iterable[Step], exception_on_none: bool = True) -> list[ElnEntryStep | None]:
|
|
648
666
|
"""
|
|
649
667
|
Get a list of steps of the given names from the experiment, sorted in the same order as the names are provided.
|
|
650
668
|
|
|
651
669
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
652
670
|
list of steps in the experiment and caches them.
|
|
653
671
|
|
|
654
|
-
:param step_names: A list of
|
|
672
|
+
:param step_names: A list of identifiers for the entries to return and the order to return them in.
|
|
655
673
|
:param exception_on_none: If false, returns None for entries that can't be found. If true, raises an exception
|
|
656
674
|
when the named entry doesn't exist in the experiment.
|
|
657
675
|
:return: A list of ElnEntrySteps matching the provided names in the order they were provided in. If there is no
|
|
658
676
|
match for a given step and no exception is to be thrown, returns None for that step.
|
|
659
677
|
"""
|
|
678
|
+
# CR-47700: Support getting steps from entry objects.
|
|
679
|
+
step_ids: list[str | int] = []
|
|
680
|
+
for step in step_names:
|
|
681
|
+
# Convert any ElnEntrySteps into ExperimentEntries.
|
|
682
|
+
if isinstance(step, ElnEntryStep):
|
|
683
|
+
step: ExperimentEntry = step.eln_entry
|
|
684
|
+
# If an ExperimentEntry is provided, then add its ID to the list of steps to return.
|
|
685
|
+
if isinstance(step, ExperimentEntry):
|
|
686
|
+
step_ids.append(step.entry_id)
|
|
687
|
+
# Add this entry to the caches so that it doesn't need to be queried for. This will also verify that
|
|
688
|
+
# this entry belongs to the experiment that this handler is for.
|
|
689
|
+
self.add_entry_to_caches(step)
|
|
690
|
+
elif isinstance(step, (str, int)):
|
|
691
|
+
step_ids.append(step)
|
|
692
|
+
|
|
660
693
|
ret_list: list[ElnEntryStep | None] = []
|
|
661
|
-
for step_id in
|
|
694
|
+
for step_id in step_ids:
|
|
662
695
|
# If we haven't queried the system for all steps in the experiment yet, then the reason that a step is
|
|
663
696
|
# missing may be because it wasn't in the webhook context. Therefore, query all steps and then check
|
|
664
697
|
# if the step name is still missing from the experiment before potentially throwing an exception.
|
|
665
|
-
if
|
|
666
|
-
self.
|
|
698
|
+
if not self._queried_all_steps:
|
|
699
|
+
if ((isinstance(step_id, str) and step_id not in self._steps_by_name)
|
|
700
|
+
or (isinstance(step_id, int) and step_id not in self._steps_by_id)):
|
|
701
|
+
self._query_all_steps()
|
|
667
702
|
if isinstance(step_id, str):
|
|
668
703
|
step: ElnEntryStep = self._steps_by_name.get(step_id)
|
|
669
704
|
else:
|
|
@@ -758,7 +793,7 @@ class ExperimentHandler:
|
|
|
758
793
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
759
794
|
:return: The data records for the given step.
|
|
760
795
|
"""
|
|
761
|
-
return self.
|
|
796
|
+
return self.get_step(step).get_records()
|
|
762
797
|
|
|
763
798
|
def get_step_models(self, step: Step, wrapper_type: type[WrappedType] | None = None) \
|
|
764
799
|
-> list[WrappedType] | list[PyRecordModel]:
|
|
@@ -795,7 +830,7 @@ class ExperimentHandler:
|
|
|
795
830
|
A list of records to add to the given step.
|
|
796
831
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
797
832
|
"""
|
|
798
|
-
step = self.
|
|
833
|
+
step: ElnEntryStep = self.get_step(step)
|
|
799
834
|
if not records:
|
|
800
835
|
return
|
|
801
836
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
@@ -810,8 +845,7 @@ class ExperimentHandler:
|
|
|
810
845
|
|
|
811
846
|
def remove_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
|
|
812
847
|
"""
|
|
813
|
-
Make a webservice call to remove a list of records from a step.
|
|
814
|
-
entries. For removing from an ELN data type table entry, see remove_eln_rows.
|
|
848
|
+
Make a webservice call to remove a list of records from a step.
|
|
815
849
|
|
|
816
850
|
If no step functions have been called before and a step is being searched for by name, queries for the
|
|
817
851
|
list of steps in the experiment and caches them.
|
|
@@ -823,7 +857,7 @@ class ExperimentHandler:
|
|
|
823
857
|
A list of records to remove from the given step.
|
|
824
858
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
825
859
|
"""
|
|
826
|
-
step = self.
|
|
860
|
+
step: ElnEntryStep = self.get_step(step)
|
|
827
861
|
if not records:
|
|
828
862
|
return
|
|
829
863
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
@@ -856,7 +890,7 @@ class ExperimentHandler:
|
|
|
856
890
|
A list of records to set for the given step,
|
|
857
891
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
858
892
|
"""
|
|
859
|
-
step = self.
|
|
893
|
+
step: ElnEntryStep = self.get_step(step)
|
|
860
894
|
if records:
|
|
861
895
|
dt: str = AliasUtil.to_singular_data_type_name(records)
|
|
862
896
|
# CR-47532: Add set_step_records support for Experiment Detail and Sample Detail entries.
|
|
@@ -888,7 +922,7 @@ class ExperimentHandler:
|
|
|
888
922
|
The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
|
|
889
923
|
"""
|
|
890
924
|
self.set_step_records(step, [record])
|
|
891
|
-
step = self.
|
|
925
|
+
step: ElnEntryStep = self.get_step(step)
|
|
892
926
|
if isinstance(step.eln_entry, ExperimentFormEntry):
|
|
893
927
|
step.eln_entry.record_id = AliasUtil.to_data_record(record).record_id
|
|
894
928
|
|
|
@@ -927,7 +961,7 @@ class ExperimentHandler:
|
|
|
927
961
|
an unwrapped PyRecordModel.
|
|
928
962
|
:return: A list of the newly created rows.
|
|
929
963
|
"""
|
|
930
|
-
step = self.
|
|
964
|
+
step: ElnEntryStep = self.get_step(step)
|
|
931
965
|
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
932
966
|
raise SapioException("The provided step is not a table entry.")
|
|
933
967
|
dt: str = step.get_data_type_names()[0]
|
|
@@ -969,7 +1003,7 @@ class ExperimentHandler:
|
|
|
969
1003
|
:return: The newly created sample details. The indices of the samples in the input list match the index of the
|
|
970
1004
|
sample details in this list that they are related to.
|
|
971
1005
|
"""
|
|
972
|
-
step = self.
|
|
1006
|
+
step: ElnEntryStep = self.get_step(step)
|
|
973
1007
|
if step.eln_entry.entry_type != ElnEntryType.Table:
|
|
974
1008
|
raise SapioException("The provided step is not a table entry.")
|
|
975
1009
|
dt: str = step.get_data_type_names()[0]
|
|
@@ -1027,7 +1061,7 @@ class ExperimentHandler:
|
|
|
1027
1061
|
A list of records to remove from the given step.
|
|
1028
1062
|
The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
|
|
1029
1063
|
"""
|
|
1030
|
-
step = self.
|
|
1064
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1031
1065
|
dt: str = step.get_data_type_names()[0]
|
|
1032
1066
|
if not ElnBaseDataType.is_eln_type(dt):
|
|
1033
1067
|
raise SapioException("The provided step is not an ELN data type entry.")
|
|
@@ -1120,7 +1154,7 @@ class ExperimentHandler:
|
|
|
1120
1154
|
# FR-47468: Deprecating this since the entry-specific update criteria should be used instead.
|
|
1121
1155
|
warnings.warn("Update step is deprecated. Use force_entry_update instead.",
|
|
1122
1156
|
DeprecationWarning)
|
|
1123
|
-
step: ElnEntryStep = self.
|
|
1157
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1124
1158
|
update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
1125
1159
|
|
|
1126
1160
|
# These two variables could be iterables that aren't lists. Convert them to plain
|
|
@@ -1171,7 +1205,7 @@ class ExperimentHandler:
|
|
|
1171
1205
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1172
1206
|
:param update: The update to make to the step.
|
|
1173
1207
|
"""
|
|
1174
|
-
step = self.
|
|
1208
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1175
1209
|
self._eln_man.update_experiment_entry(self._exp_id, step.get_id(), update)
|
|
1176
1210
|
self._update_entry_details(step, update)
|
|
1177
1211
|
|
|
@@ -1191,7 +1225,7 @@ class ExperimentHandler:
|
|
|
1191
1225
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1192
1226
|
:param update: The update to make to the step.
|
|
1193
1227
|
"""
|
|
1194
|
-
step = self.
|
|
1228
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1195
1229
|
if step.eln_entry.entry_type != update.entry_type:
|
|
1196
1230
|
raise SapioException(f"The provided step and update criteria are not of the same entry type. "
|
|
1197
1231
|
f"The step is of type {step.eln_entry.entry_type} and the update criteria is of type "
|
|
@@ -1222,6 +1256,8 @@ class ExperimentHandler:
|
|
|
1222
1256
|
Commit all the stored updates to the entries in this experiment. The updates are made in the order that they
|
|
1223
1257
|
were stored.
|
|
1224
1258
|
"""
|
|
1259
|
+
if not self._step_updates:
|
|
1260
|
+
return
|
|
1225
1261
|
self._eln_man.update_experiment_entries(self._exp_id, self._step_updates)
|
|
1226
1262
|
for step_id, criteria in self._step_updates.items():
|
|
1227
1263
|
self._update_entry_details(self._steps_by_id[step_id], criteria)
|
|
@@ -1388,7 +1424,7 @@ class ExperimentHandler:
|
|
|
1388
1424
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1389
1425
|
:return: The map of options for the input step.
|
|
1390
1426
|
"""
|
|
1391
|
-
step = self.
|
|
1427
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1392
1428
|
if step not in self._step_options:
|
|
1393
1429
|
self._step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user,
|
|
1394
1430
|
self.get_all_steps()))
|
|
@@ -1414,6 +1450,8 @@ class ExperimentHandler:
|
|
|
1414
1450
|
(e.g. a dictionary). If an option key already exists and is provided in the mapping, overwrites the existing
|
|
1415
1451
|
value for that key.
|
|
1416
1452
|
"""
|
|
1453
|
+
# PR-47698: Convert the given step to an ElnEntryStep if it is not already one.
|
|
1454
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1417
1455
|
options: dict[str, str] = self.get_step_options(step)
|
|
1418
1456
|
options.update(mapping)
|
|
1419
1457
|
update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
@@ -1434,7 +1472,7 @@ class ExperimentHandler:
|
|
|
1434
1472
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1435
1473
|
"""
|
|
1436
1474
|
# Avoid unnecessary calls if the step is already initialized.
|
|
1437
|
-
step: ElnEntryStep = self.
|
|
1475
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1438
1476
|
if step.eln_entry.template_item_fulfilled_timestamp is None:
|
|
1439
1477
|
update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
1440
1478
|
update.template_item_fulfilled_timestamp = TimeUtil.now_in_millis()
|
|
@@ -1454,7 +1492,7 @@ class ExperimentHandler:
|
|
|
1454
1492
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1455
1493
|
"""
|
|
1456
1494
|
# Avoid unnecessary calls if the step is already uninitialized.
|
|
1457
|
-
step: ElnEntryStep = self.
|
|
1495
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1458
1496
|
if step.eln_entry.template_item_fulfilled_timestamp is not None:
|
|
1459
1497
|
update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
1460
1498
|
update.clear_template_item_fulfilled_timestamp = True
|
|
@@ -1473,7 +1511,7 @@ class ExperimentHandler:
|
|
|
1473
1511
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1474
1512
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1475
1513
|
"""
|
|
1476
|
-
step = self.
|
|
1514
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1477
1515
|
if step.eln_entry.entry_status not in self._ENTRY_COMPLETE_STATUSES:
|
|
1478
1516
|
step.complete_step()
|
|
1479
1517
|
step.eln_entry.entry_status = ExperimentEntryStatus.Completed
|
|
@@ -1491,7 +1529,7 @@ class ExperimentHandler:
|
|
|
1491
1529
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1492
1530
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1493
1531
|
"""
|
|
1494
|
-
step = self.
|
|
1532
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1495
1533
|
if step.eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES:
|
|
1496
1534
|
step.unlock_step()
|
|
1497
1535
|
step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
|
|
@@ -1513,7 +1551,7 @@ class ExperimentHandler:
|
|
|
1513
1551
|
The step may be provided as either a string for the name of the step or an ElnEntryStep.
|
|
1514
1552
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1515
1553
|
"""
|
|
1516
|
-
step = self.
|
|
1554
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1517
1555
|
if step.eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES:
|
|
1518
1556
|
update = AbstractElnEntryUpdateCriteria(step.eln_entry.entry_type)
|
|
1519
1557
|
update.entry_status = ExperimentEntryStatus.Disabled
|
|
@@ -1532,7 +1570,7 @@ class ExperimentHandler:
|
|
|
1532
1570
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1533
1571
|
:return: True if the step's status is Completed or CompletedApproved. False otherwise.
|
|
1534
1572
|
"""
|
|
1535
|
-
return self.
|
|
1573
|
+
return self.get_step(step).eln_entry.entry_status in self._ENTRY_COMPLETE_STATUSES
|
|
1536
1574
|
|
|
1537
1575
|
def step_is_locked(self, step: Step) -> bool:
|
|
1538
1576
|
"""
|
|
@@ -1548,7 +1586,7 @@ class ExperimentHandler:
|
|
|
1548
1586
|
:return: True if the step's status is Completed, CompletedApproved, Disabled, LockedAwaitingApproval,
|
|
1549
1587
|
or LockedRejected. False otherwise.
|
|
1550
1588
|
"""
|
|
1551
|
-
return self.
|
|
1589
|
+
return self.get_step(step).eln_entry.entry_status in self._ENTRY_LOCKED_STATUSES
|
|
1552
1590
|
|
|
1553
1591
|
# FR-47464: Some functions that can help with entry placement.
|
|
1554
1592
|
def get_all_tabs(self) -> list[ElnExperimentTab]:
|
|
@@ -1660,7 +1698,7 @@ class ExperimentHandler:
|
|
|
1660
1698
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1661
1699
|
:return: The tab that the input step is located in.
|
|
1662
1700
|
"""
|
|
1663
|
-
step = self.
|
|
1701
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1664
1702
|
tab_id = step.eln_entry.notebook_experiment_tab_id
|
|
1665
1703
|
if tab_id not in self._tabs_by_id:
|
|
1666
1704
|
self.get_all_tabs()
|
|
@@ -1697,7 +1735,7 @@ class ExperimentHandler:
|
|
|
1697
1735
|
If given a name, throws an exception if no step of the given name exists in the experiment.
|
|
1698
1736
|
:return: The position of the input step in the experiment.
|
|
1699
1737
|
"""
|
|
1700
|
-
step: ElnEntryStep = self.
|
|
1738
|
+
step: ElnEntryStep = self.get_step(step)
|
|
1701
1739
|
entry: ExperimentEntry = step.eln_entry
|
|
1702
1740
|
return ElnEntryPosition(entry.notebook_experiment_tab_id,
|
|
1703
1741
|
entry.order,
|
|
@@ -1741,21 +1779,7 @@ class ExperimentHandler:
|
|
|
1741
1779
|
new_entries: list[ExperimentEntry] = self._eln_man.add_protocol_template(self._exp_id, protocol, position)
|
|
1742
1780
|
return self.add_entries_to_caches(new_entries)
|
|
1743
1781
|
|
|
1744
|
-
|
|
1745
|
-
"""
|
|
1746
|
-
Convert a variable that could be either a string or an ElnEntryStep to just an ElnEntryStep.
|
|
1747
|
-
This will query and cache the steps for the experiment if the input step is a name and the steps have not been
|
|
1748
|
-
cached before.
|
|
1749
|
-
|
|
1750
|
-
:return: The input step as an ElnEntryStep.
|
|
1751
|
-
"""
|
|
1752
|
-
if isinstance(step, str):
|
|
1753
|
-
return self.get_step(step)
|
|
1754
|
-
if isinstance(step, int):
|
|
1755
|
-
return self._steps_by_id.get(step)
|
|
1756
|
-
if isinstance(step, ExperimentEntry):
|
|
1757
|
-
return self.add_entry_to_caches(step)
|
|
1758
|
-
return step
|
|
1782
|
+
# CR-47700: Deleted __to_eln_step since it became redundant with the get_step method.
|
|
1759
1783
|
|
|
1760
1784
|
def __to_eln_tab(self, tab: Tab) -> ElnExperimentTab:
|
|
1761
1785
|
"""
|
|
@@ -2,7 +2,7 @@ from sapiopylib.rest.ELNService import ElnManager
|
|
|
2
2
|
from sapiopylib.rest.User import SapioUser
|
|
3
3
|
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, AbstractReportTerm, RawReportTerm
|
|
4
4
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
5
|
-
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, ElnExperimentQueryCriteria
|
|
5
|
+
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, ElnExperimentQueryCriteria, ElnTemplate
|
|
6
6
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnExperimentStatus, ElnBaseDataType
|
|
7
7
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
8
8
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
|
|
@@ -250,17 +250,22 @@ class ExperimentReportUtil:
|
|
|
250
250
|
return {exp: list(records) for exp, records in exp_to_records.items()}
|
|
251
251
|
|
|
252
252
|
@staticmethod
|
|
253
|
-
def get_experiment_options(context: UserIdentifier, experiments: list[ExperimentIdentifier]) \
|
|
253
|
+
def get_experiment_options(context: UserIdentifier, experiments: list[ExperimentIdentifier | ElnTemplate]) \
|
|
254
254
|
-> dict[int, dict[str, str]]:
|
|
255
255
|
"""
|
|
256
|
-
Run a custom report to retrieve the experiment options for all the provided experiments
|
|
257
|
-
version of the get_notebook_experiment_options function of ElnManager.
|
|
256
|
+
Run a custom report to retrieve the experiment options for all the provided experiments or experiment templates.
|
|
257
|
+
Effectively a batched version of the get_notebook_experiment_options function of ElnManager.
|
|
258
258
|
|
|
259
259
|
:param context: The current webhook context or a user object to send requests from.
|
|
260
|
-
:param experiments: The experiment identifiers to retrieve the experiment options for.
|
|
260
|
+
:param experiments: The experiment/template identifiers to retrieve the experiment options for.
|
|
261
261
|
:return: A dictionary mapping the notebook experiment ID to the options for that experiment.
|
|
262
262
|
"""
|
|
263
|
-
exp_ids: list[int] =
|
|
263
|
+
exp_ids: list[int] = []
|
|
264
|
+
for exp in experiments:
|
|
265
|
+
if isinstance(exp, ElnTemplate):
|
|
266
|
+
exp_ids.append(exp.template_id)
|
|
267
|
+
else:
|
|
268
|
+
exp_ids.append(AliasUtil.to_notebook_id(exp))
|
|
264
269
|
|
|
265
270
|
report_builder = CustomReportBuilder(NotebookExperimentOptionPseudoDef.DATA_TYPE_NAME)
|
|
266
271
|
tb = report_builder.get_term_builder()
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/file_util.py
RENAMED
|
@@ -331,7 +331,9 @@ class FileUtil:
|
|
|
331
331
|
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
332
332
|
for file_name, file_data in files.items():
|
|
333
333
|
zip_file.writestr(file_name, file_data)
|
|
334
|
-
|
|
334
|
+
# PR-47697: Indent the getvalue call into the outer with block. Making the call outside of the with block
|
|
335
|
+
# throws an I/O exception.
|
|
336
|
+
return zip_buffer.getvalue()
|
|
335
337
|
|
|
336
338
|
# Deprecated functions:
|
|
337
339
|
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/aliases.py
RENAMED
|
@@ -7,7 +7,6 @@ from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType, AbstractVel
|
|
|
7
7
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
|
|
8
8
|
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
9
9
|
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
10
|
-
from sapiopylib.rest.pojo.eln.eln_headings import ElnExperimentTab
|
|
11
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
12
11
|
from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol, ElnEntryStep
|
|
13
12
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel, AbstractRecordModel
|
|
@@ -8,6 +8,7 @@ from sapiopycommons.customreport.column_builder import ColumnBuilder
|
|
|
8
8
|
from sapiopycommons.customreport.term_builder import TermBuilder
|
|
9
9
|
from sapiopycommons.datatype.pseudo_data_types import AuditLogPseudoDef
|
|
10
10
|
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, UserIdentifier, FieldIdentifier, FieldValue
|
|
11
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class EventType(Enum):
|
|
@@ -134,6 +135,9 @@ class AuditLogUtil:
|
|
|
134
135
|
:param fields: The data field names to include changes for.
|
|
135
136
|
:return: The constructed CustomReportCriteria object, which can be used to run a report on the audit log.
|
|
136
137
|
"""
|
|
138
|
+
# PR-47701: Throw an exception if no records are provided.
|
|
139
|
+
if not records:
|
|
140
|
+
raise SapioException("No records provided. Please provide at least one record to build a report for.")
|
|
137
141
|
# Build the raw report term querying for any entry with a matching record ID value to the record ID's
|
|
138
142
|
# passed in.
|
|
139
143
|
record_ids = AliasUtil.to_record_ids(records)
|
|
@@ -160,6 +164,9 @@ class AuditLogUtil:
|
|
|
160
164
|
:return: A dictionary where the keys are the record identifiers passed in, and the values are a list of
|
|
161
165
|
AuditLogEntry objects which match the record id value of those records.
|
|
162
166
|
"""
|
|
167
|
+
# PR-47701: Return an empty dictionary if no records are provided.
|
|
168
|
+
if not records:
|
|
169
|
+
return {}
|
|
163
170
|
# First, we must build our report criteria for running the Custom Report.
|
|
164
171
|
criteria = AuditLogUtil.create_data_record_audit_log_report(records, fields)
|
|
165
172
|
|
|
@@ -24,6 +24,12 @@ class CustomReportUtil:
|
|
|
24
24
|
System reports are also known as predefined searches in the system and must be defined in the data designer for
|
|
25
25
|
a specific data type. That is, saved searches created by users cannot be run using this function.
|
|
26
26
|
|
|
27
|
+
IMPORTANT NOTICE: Custom reports that are not single data type (i.e. they have terms or columns from multiple
|
|
28
|
+
data types) may not be 100% time accurate. Such reports use the system's ancestor table to retrieve the
|
|
29
|
+
relationships, and this table takes some time to update after relationships are updated, especially for more
|
|
30
|
+
populous data types. If you need 100% time accurate results to the current state of the records and
|
|
31
|
+
relationships in the database, you should query for the records directly instead of using a custom report.
|
|
32
|
+
|
|
27
33
|
:param context: The current webhook context or a user object to send requests from.
|
|
28
34
|
:param report_name: The name of the system report to run.
|
|
29
35
|
:param filters: If provided, filter the results of the report using the given mapping of headers to values to
|
|
@@ -63,6 +69,12 @@ class CustomReportUtil:
|
|
|
63
69
|
results. They are like advanced or predefined searches from the system, except they are constructed from
|
|
64
70
|
within the webhook instead of from within the system.
|
|
65
71
|
|
|
72
|
+
IMPORTANT NOTICE: Custom reports that are not single data type (i.e. they have terms or columns from multiple
|
|
73
|
+
data types) may not be 100% time accurate. Such reports use the system's ancestor table to retrieve the
|
|
74
|
+
relationships, and this table takes some time to update after relationships are updated, especially for more
|
|
75
|
+
populous data types. If you need 100% time accurate results to the current state of the records and
|
|
76
|
+
relationships in the database, you should query for the records directly instead of using a custom report.
|
|
77
|
+
|
|
66
78
|
:param context: The current webhook context or a user object to send requests from.
|
|
67
79
|
:param report_criteria: The custom report criteria to run.
|
|
68
80
|
:param filters: If provided, filter the results of the report using the given mapping of headers to values to
|
|
@@ -94,6 +94,9 @@ class QueueItemHandler:
|
|
|
94
94
|
"""
|
|
95
95
|
A class used for handling the display of records in custom process queues, which are controlled in the system by
|
|
96
96
|
ProcessQueueItem data types.
|
|
97
|
+
|
|
98
|
+
IMPORTANT NOTICE: This is only for custom processes that make use of ProcessQueueItem records. For experiment
|
|
99
|
+
processes that use AssignedProcess records, see the ProcessTracking class.
|
|
97
100
|
"""
|
|
98
101
|
user: SapioUser
|
|
99
102
|
context: ProcessQueueContext | None
|
|
@@ -261,6 +264,9 @@ class QueueItemHandler:
|
|
|
261
264
|
Given a list of records, create process queue item records for them at the provided process and step names.
|
|
262
265
|
You must store and commit using the record model manager in order for these changes to take effect.
|
|
263
266
|
|
|
267
|
+
IMPORTANT NOTICE: This is only for custom processes that make use of ProcessQueueItem records. For experiment
|
|
268
|
+
processes that use AssignedProcess records, see the ProcessTracking class.
|
|
269
|
+
|
|
264
270
|
:param records: The records to create process queue items for.
|
|
265
271
|
:param process: The name of the process to queue for.
|
|
266
272
|
:param step: The name of the step in the above process to queue for. This is the "Workflow Name" field of the
|
|
@@ -293,6 +299,9 @@ class QueueItemHandler:
|
|
|
293
299
|
criteria and remove them from the queue by setting the ShowInQueue field on the process queue items to false.
|
|
294
300
|
You must store and commit using the record model manager in order for these changes to take effect.
|
|
295
301
|
|
|
302
|
+
IMPORTANT NOTICE: This is only for custom processes that make use of ProcessQueueItem records. For experiment
|
|
303
|
+
processes that use AssignedProcess records, see the ProcessTracking class.
|
|
304
|
+
|
|
296
305
|
:param records: The records to remove from the queue.
|
|
297
306
|
:param wrapper: The record model wrapper for the process queue items being updated. If not provided, the
|
|
298
307
|
returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
@@ -5,6 +5,12 @@ from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, Experime
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ProcessTracking:
|
|
8
|
+
"""
|
|
9
|
+
A class for calling the Foundations process tracking endpoints.
|
|
10
|
+
|
|
11
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
12
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
13
|
+
"""
|
|
8
14
|
@staticmethod
|
|
9
15
|
def assign_to_process(context: UserIdentifier, data_type: DataTypeIdentifier, records: list[RecordIdentifier],
|
|
10
16
|
process_name: str, step_number: int | None = None, branch_id: int | None = None,
|
|
@@ -14,6 +20,9 @@ class ProcessTracking:
|
|
|
14
20
|
at that step.
|
|
15
21
|
Synonymous with what occurs during request creation or when using the assign to process button.
|
|
16
22
|
|
|
23
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
24
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
25
|
+
|
|
17
26
|
:param context: The current webhook context or a user object to send requests from.
|
|
18
27
|
:param data_type: The data type of the tracked records.
|
|
19
28
|
:param records: A list of the tracked records.
|
|
@@ -46,6 +55,9 @@ class ProcessTracking:
|
|
|
46
55
|
tracked records from "Ready for -" to "In Process -" for their current step.
|
|
47
56
|
Synonymous with what occurs when starting a process step in the system.
|
|
48
57
|
|
|
58
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
59
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
60
|
+
|
|
49
61
|
:param context: The current webhook context or a user object to send requests from.
|
|
50
62
|
:param data_type: The data type of the tracked records.
|
|
51
63
|
:param records: A list of the tracked records.
|
|
@@ -73,6 +85,9 @@ class ProcessTracking:
|
|
|
73
85
|
Moves the assigned process down to the descendant sample(s) if both samples are provided in the records list.
|
|
74
86
|
Synonymous with what occurs when completing an experiment in the system.
|
|
75
87
|
|
|
88
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
89
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
90
|
+
|
|
76
91
|
:param context: The current webhook context or a user object to send requests from.
|
|
77
92
|
:param data_type: The data type of the tracked records.
|
|
78
93
|
:param records: A list of the tracked records.
|
|
@@ -96,6 +111,9 @@ class ProcessTracking:
|
|
|
96
111
|
records must be "In Process -" for the given step.
|
|
97
112
|
Synonymous with what occurs when failing a sample due to a QC failure in a process in the system.
|
|
98
113
|
|
|
114
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
115
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
116
|
+
|
|
99
117
|
:param context: The current webhook context or a user object to send requests from.
|
|
100
118
|
:param data_type: The data type of the tracked records.
|
|
101
119
|
:param records: A list of the tracked records.
|
|
@@ -122,6 +140,9 @@ class ProcessTracking:
|
|
|
122
140
|
will move their status to "Ready for -" for the next step in the process, or "Completed -" if this was the
|
|
123
141
|
last step.
|
|
124
142
|
|
|
143
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
144
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
145
|
+
|
|
125
146
|
:param context: The current webhook context or a user object to send requests from.
|
|
126
147
|
:param data_type: The data type of the tracked records.
|
|
127
148
|
:param records: A list of the tracked records.
|
|
@@ -149,6 +170,9 @@ class ProcessTracking:
|
|
|
149
170
|
step will move their status to "Ready for -" for the next step in the process, or "Completed -" if this was the
|
|
150
171
|
last step.
|
|
151
172
|
|
|
173
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
174
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
175
|
+
|
|
152
176
|
:param context: The current webhook context or a user object to send requests from.
|
|
153
177
|
:param data_type: The data type of the tracked records.
|
|
154
178
|
:param records: A list of the tracked records.
|
|
@@ -180,6 +204,9 @@ class ProcessTracking:
|
|
|
180
204
|
Synonymous with what occurs when reprocessing records to a previous step due to QC failures in a process in the
|
|
181
205
|
system.
|
|
182
206
|
|
|
207
|
+
IMPORTANT NOTICE: This is only for experiment processes that make use of AssignedProcess records. For custom
|
|
208
|
+
processes that use ProcessQueueItem records, see the QueueItemHandler class.
|
|
209
|
+
|
|
183
210
|
:param context: The current webhook context or a user object to send requests from.
|
|
184
211
|
:param records: A list of ReturnPoint records to reprocess to.
|
|
185
212
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/chem/Molecules.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/chem/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/eln/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/files/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/general/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/rules/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/samples/aliquot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/src/sapiopycommons/webhook/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/AF-A0A009IHW8-F1-model_v4.cif
RENAMED
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/_do_not_add_init_py_here
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/chem_test_curation_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/8_color_ICS.wsp
RENAMED
|
File without changes
|
{sapiopycommons-2025.6.16a562 → sapiopycommons-2025.6.19a564}/tests/flowcyto/COVID19_W_001_O.fcs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|