sapiopycommons 2025.2.12a432__tar.gz → 2025.2.14a434__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.2.12a432 → sapiopycommons-2025.2.14a434}/PKG-INFO +1 -1
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/pyproject.toml +1 -1
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/elain/tool_of_tools.py +256 -199
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/.gitignore +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/LICENSE +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/README.md +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/callbacks/callback_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/callbacks/field_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/chem/Molecules.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/customreport/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/customreport/column_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/customreport/term_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/datatype/attachment_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/datatype/data_fields.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/elain/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/eln/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/eln/experiment_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/complex_data_loader.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_bridge.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_validator.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/accession_service.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/aliases.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/audit_log.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/custom_report_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/directive_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/sapio_links.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/multimodal/multimodal.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/processtracking/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/processtracking/endpoints.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/recordmodel/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/rules/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/samples/aliquot.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/sftpconnect/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/webhook/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/webhook/webhook_context.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/_do_not_add_init_py_here +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/accession_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/aliquot_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/bio_reg_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/chem_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/chem_test_curation_queue.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/curation_queue_test.sdf +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/data_type_models.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/8_color_ICS.wsp +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/kappa.chains.fasta +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/mafft_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/test.gb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2025.2.
|
|
3
|
+
Version: 2025.2.14a434
|
|
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>
|
|
@@ -12,6 +12,7 @@ from sapiopylib.rest.pojo.chartdata.DashboardDefinition import GaugeChartDefinit
|
|
|
12
12
|
from sapiopylib.rest.pojo.chartdata.DashboardEnums import ChartGroupingType, ChartOperationType
|
|
13
13
|
from sapiopylib.rest.pojo.chartdata.DashboardSeries import GaugeChartSeries
|
|
14
14
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition, FieldType
|
|
15
|
+
from sapiopylib.rest.pojo.eln.ElnEntryPosition import ElnEntryPosition
|
|
15
16
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
|
|
16
17
|
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
17
18
|
from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import ElnEntryCriteria, ElnFormEntryUpdateCriteria, \
|
|
@@ -33,12 +34,9 @@ EXP_ID_HEADER: Final[str] = "EXPERIMENT_ID"
|
|
|
33
34
|
TAB_PREFIX_HEADER: Final[str] = "TAB_PREFIX"
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
#
|
|
37
|
-
# that both the tool endpoints and the AI instantiate, or two classes where one is a subclass of the other. Maybe work
|
|
38
|
-
# on that after the initial implementation is done.
|
|
39
|
-
# FR-47422: Create utility methods to assist the tool of tools.
|
|
37
|
+
# FR-47422: Create utility classes and methods to assist the tool of tools.
|
|
40
38
|
def create_tot_headers(url: str, username: str, password: str, experiment_id: int, tab_prefix: str) \
|
|
41
|
-
->
|
|
39
|
+
-> dict[str, str]:
|
|
42
40
|
"""
|
|
43
41
|
Create the headers to be passed to a tool of tools endpoint.
|
|
44
42
|
|
|
@@ -47,7 +45,7 @@ def create_tot_headers(url: str, username: str, password: str, experiment_id: in
|
|
|
47
45
|
:param password: The password of the user making the changes.
|
|
48
46
|
:param experiment_id: The ID of the experiment to make the changes in.
|
|
49
47
|
:param tab_prefix: The prefix to use for the tab name that will be created by the tool.
|
|
50
|
-
:return: The
|
|
48
|
+
:return: The headers to be passed to the endpoint.
|
|
51
49
|
"""
|
|
52
50
|
# Combine the credentials into the format "username:password"
|
|
53
51
|
credentials: str = f"{username}:{password}"
|
|
@@ -60,7 +58,7 @@ def create_tot_headers(url: str, username: str, password: str, experiment_id: in
|
|
|
60
58
|
EXP_ID_HEADER: str(experiment_id),
|
|
61
59
|
TAB_PREFIX_HEADER: tab_prefix
|
|
62
60
|
}
|
|
63
|
-
return
|
|
61
|
+
return headers
|
|
64
62
|
|
|
65
63
|
|
|
66
64
|
def create_user_from_tot_headers(headers: Mapping[str, str]) -> SapioUser:
|
|
@@ -87,140 +85,9 @@ def format_tot_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
|
87
85
|
return {k.lower(): v for k, v in headers.items()}
|
|
88
86
|
|
|
89
87
|
|
|
90
|
-
def create_experiment_details_from_data_frame(user: SapioUser,
|
|
91
|
-
exp_id: int,
|
|
92
|
-
entry_name: str,
|
|
93
|
-
tab: ElnExperimentTab,
|
|
94
|
-
df: DataFrame) -> ExperimentEntry | None:
|
|
95
|
-
"""
|
|
96
|
-
Create an experiment detail entry from a DataFrame.
|
|
97
|
-
|
|
98
|
-
:param user: The user to send the request from.
|
|
99
|
-
:param exp_id: The ID of the experiment to create the entry in.
|
|
100
|
-
:param entry_name: The name of the entry.
|
|
101
|
-
:param tab: The tab that the entry should be added to.
|
|
102
|
-
:param df: The DataFrame to create the entry from.
|
|
103
|
-
:return: The created entry object.
|
|
104
|
-
"""
|
|
105
|
-
json_list: list[dict[str, Any]] = []
|
|
106
|
-
for _, row in df.iterrows():
|
|
107
|
-
json_list.append(row.to_dict())
|
|
108
|
-
return create_experiment_details_from_json(user, exp_id, entry_name, tab, json_list)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def create_experiment_details_from_json(user: SapioUser,
|
|
112
|
-
exp_id: int,
|
|
113
|
-
entry_name: str,
|
|
114
|
-
tab: ElnExperimentTab,
|
|
115
|
-
json_list: list[dict[str, Any]]) -> ExperimentEntry | None:
|
|
116
|
-
"""
|
|
117
|
-
Create an experiment detail entry from a list of JSON dictionaries.
|
|
118
|
-
|
|
119
|
-
:param user: The user to send the request from.
|
|
120
|
-
:param exp_id: The ID of the experiment to create the entry in.
|
|
121
|
-
:param entry_name: The name of the entry.
|
|
122
|
-
:param tab: The tab that the entry should be added to.
|
|
123
|
-
:param json_list: The list of JSON dictionaries to create the entry from. Each dictionary is expected to have the
|
|
124
|
-
same keys.
|
|
125
|
-
:return: The created entry object.
|
|
126
|
-
"""
|
|
127
|
-
if not json_list:
|
|
128
|
-
return None
|
|
129
|
-
|
|
130
|
-
# Determine which fields in the JSON can be used to create field definitions.
|
|
131
|
-
fb = FieldBuilder()
|
|
132
|
-
fields: list[AbstractVeloxFieldDefinition] = []
|
|
133
|
-
fields_by_name: dict[str, AbstractVeloxFieldDefinition] = {}
|
|
134
|
-
for key, value in json_list[0].items():
|
|
135
|
-
field_name: str = key.replace(" ", "_")
|
|
136
|
-
if isinstance(value, str):
|
|
137
|
-
field = fb.string_field(field_name, display_name=key)
|
|
138
|
-
fields.append(field)
|
|
139
|
-
fields_by_name[key] = field
|
|
140
|
-
elif isinstance(value, (int, float)):
|
|
141
|
-
field = fb.double_field(field_name, display_name=key, precision=3)
|
|
142
|
-
fields.append(field)
|
|
143
|
-
fields_by_name[key] = field
|
|
144
|
-
|
|
145
|
-
# Extract the valid field values from the JSON.
|
|
146
|
-
field_maps: list[dict[str, Any]] = []
|
|
147
|
-
for json_dict in json_list:
|
|
148
|
-
field_map: dict[str, Any] = {}
|
|
149
|
-
for key, field in fields_by_name.items():
|
|
150
|
-
# Watch out for NaN values or other special values.
|
|
151
|
-
val: Any = json_dict.get(key)
|
|
152
|
-
if (field.data_field_type == FieldType.DOUBLE
|
|
153
|
-
and (not isinstance(val, (int, float))) or (isinstance(val, float) and math.isnan(val))):
|
|
154
|
-
val = None
|
|
155
|
-
field_map[field.data_field_name] = val
|
|
156
|
-
field_maps.append(field_map)
|
|
157
|
-
|
|
158
|
-
detail_entry = ElnEntryCriteria(ElnEntryType.Table, entry_name,
|
|
159
|
-
ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name,
|
|
160
|
-
tab_next_entry_order(user, exp_id, tab),
|
|
161
|
-
notebook_experiment_tab_id=tab.tab_id,
|
|
162
|
-
field_definition_list=fields)
|
|
163
|
-
entry = ElnManager(user).add_experiment_entry(exp_id, detail_entry)
|
|
164
|
-
DataRecordManager(user).add_data_records_with_data(entry.data_type_name, field_maps)
|
|
165
|
-
return entry
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def tab_next_entry_order(user: SapioUser, exp_id: int, tab: ElnExperimentTab) -> int:
|
|
169
|
-
max_order: int = 0
|
|
170
|
-
for step in ElnExperimentProtocol(ElnExperiment(exp_id, "", 0), user).get_sorted_step_list():
|
|
171
|
-
if step.eln_entry.notebook_experiment_tab_id == tab.tab_id and step.eln_entry.order > max_order:
|
|
172
|
-
max_order = step.eln_entry.order
|
|
173
|
-
return max_order + 1
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def set_text_entry(timestamp: str, description: str, text_entry: ExperimentEntry, exp_id: int, user: SapioUser,
|
|
177
|
-
auto_format: bool = True) -> None:
|
|
178
|
-
"""
|
|
179
|
-
Set the text of a text entry.
|
|
180
|
-
|
|
181
|
-
:param timestamp: The timestamp to display at the top of the text entry.
|
|
182
|
-
:param description: The description to display in the text entry.
|
|
183
|
-
:param text_entry: The text entry to set the text of.
|
|
184
|
-
:param exp_id: The ID of the experiment that the text entry is in.
|
|
185
|
-
:param user: The user to send the request from.
|
|
186
|
-
:param auto_format: Whether to automatically format the text to be added.
|
|
187
|
-
"""
|
|
188
|
-
if auto_format:
|
|
189
|
-
timestamp = HtmlFormatter.timestamp(timestamp)
|
|
190
|
-
description = HtmlFormatter.body(description)
|
|
191
|
-
description: str = f"<p>{timestamp}<br>{description}</p>"
|
|
192
|
-
protocol = ElnExperimentProtocol(ElnExperiment(exp_id, "", 0), user)
|
|
193
|
-
step = ElnEntryStep(protocol, text_entry)
|
|
194
|
-
text_record: DataRecord = step.get_records()[0]
|
|
195
|
-
text_record.set_field_value(ElnBaseDataType.get_text_entry_data_field_name(), description)
|
|
196
|
-
DataRecordManager(user).commit_data_records([text_record])
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def add_to_text_entry(description: str, text_entry: ExperimentEntry, exp_id: int, user: SapioUser,
|
|
200
|
-
auto_format: bool = True) -> None:
|
|
201
|
-
"""
|
|
202
|
-
Add to the text of a text entry.
|
|
203
|
-
|
|
204
|
-
:param description: The text to add to the text entry.
|
|
205
|
-
:param text_entry: The text entry to add the text to.
|
|
206
|
-
:param exp_id: The ID of the experiment that the text entry is in.
|
|
207
|
-
:param user: The user to send the request from.
|
|
208
|
-
:param auto_format: Whether to automatically format the text to be added.
|
|
209
|
-
"""
|
|
210
|
-
protocol = ElnExperimentProtocol(ElnExperiment(exp_id, "", 0), user)
|
|
211
|
-
step = ElnEntryStep(protocol, text_entry)
|
|
212
|
-
text_record: DataRecord = step.get_records()[0]
|
|
213
|
-
update: str = text_record.get_field_value(ElnBaseDataType.get_text_entry_data_field_name())
|
|
214
|
-
if auto_format:
|
|
215
|
-
description = HtmlFormatter.body(description)
|
|
216
|
-
update += f"<p style=\"padding-top: 10px;\">{description}</p>"
|
|
217
|
-
text_record.set_field_value(ElnBaseDataType.get_text_entry_data_field_name(), update)
|
|
218
|
-
DataRecordManager(user).commit_data_records([text_record])
|
|
219
|
-
|
|
220
|
-
|
|
221
88
|
class HtmlFormatter:
|
|
222
89
|
"""
|
|
223
|
-
A class for formatting text in HTML with classes supported by the client.
|
|
90
|
+
A class for formatting text in HTML with tag classes supported by the client.
|
|
224
91
|
"""
|
|
225
92
|
TIMESTAMP_TEXT__CSS_CLASS_NAME: Final[str] = "timestamp-text"
|
|
226
93
|
HEADER_1_TEXT__CSS_CLASS_NAME: Final[str] = "header1-text"
|
|
@@ -300,6 +167,228 @@ class HtmlFormatter:
|
|
|
300
167
|
return re.sub("\r?\n", "<br>", text)
|
|
301
168
|
|
|
302
169
|
|
|
170
|
+
class AiHelper:
|
|
171
|
+
"""
|
|
172
|
+
A class with helper methods for the AI to make use of when creating/updating experiment tabs and entries.
|
|
173
|
+
"""
|
|
174
|
+
# Contextual info.
|
|
175
|
+
user: SapioUser
|
|
176
|
+
exp_id: int
|
|
177
|
+
|
|
178
|
+
# Managers.
|
|
179
|
+
dr_man: DataRecordManager
|
|
180
|
+
eln_man: ElnManager
|
|
181
|
+
|
|
182
|
+
def __init__(self, user: SapioUser, exp_id: int):
|
|
183
|
+
"""
|
|
184
|
+
:param user: The user to send the requests from.
|
|
185
|
+
:param exp_id: The ID of the experiment to create the entries in.
|
|
186
|
+
"""
|
|
187
|
+
self.user = user
|
|
188
|
+
self.exp_id = exp_id
|
|
189
|
+
|
|
190
|
+
self.dr_man = DataRecordManager(self.user)
|
|
191
|
+
self.eln_man = ElnManager(self.user)
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def protocol(self) -> ElnExperimentProtocol:
|
|
195
|
+
"""
|
|
196
|
+
:return: An experiment protocol object for this helper's experiment. (Recreating a new protocol object every
|
|
197
|
+
time this is called since the protocol's cache could be invalidated by things that the AI is doing.)
|
|
198
|
+
"""
|
|
199
|
+
# The experiment name and record ID aren't necessary to know for our purposes.
|
|
200
|
+
return ElnExperimentProtocol(ElnExperiment(self.exp_id, "", 0), self.user)
|
|
201
|
+
|
|
202
|
+
def create_tab(self, name: str) -> ElnExperimentTab:
|
|
203
|
+
"""
|
|
204
|
+
Create a new tab in the experiment.
|
|
205
|
+
|
|
206
|
+
:param name: The name of the tab to create.
|
|
207
|
+
:return: The newly created tab.
|
|
208
|
+
"""
|
|
209
|
+
tab_crit = ElnExperimentTabAddCriteria(name, [])
|
|
210
|
+
return self.eln_man.add_tab_for_experiment(self.exp_id, tab_crit)
|
|
211
|
+
|
|
212
|
+
def tab_next_entry_order(self, tab: ElnExperimentTab) -> int:
|
|
213
|
+
"""
|
|
214
|
+
:param tab: A tab in this helper's experiment.
|
|
215
|
+
:return: The order that the next entry that gets created in the tab should have.
|
|
216
|
+
"""
|
|
217
|
+
max_order: int = 0
|
|
218
|
+
for step in self.protocol.get_sorted_step_list():
|
|
219
|
+
if step.eln_entry.notebook_experiment_tab_id == tab.tab_id and step.eln_entry.order > max_order:
|
|
220
|
+
max_order = step.eln_entry.order
|
|
221
|
+
return max_order + 1
|
|
222
|
+
|
|
223
|
+
def create_experiment_details_from_data_frame(self,
|
|
224
|
+
tab: ElnExperimentTab,
|
|
225
|
+
entry_name: str,
|
|
226
|
+
df: DataFrame) -> ExperimentEntry | None:
|
|
227
|
+
"""
|
|
228
|
+
Create an experiment detail entry from a DataFrame.
|
|
229
|
+
|
|
230
|
+
:param tab: The tab that the entry should be added to.
|
|
231
|
+
:param entry_name: The name of the entry.
|
|
232
|
+
:param df: The DataFrame to create the entry from.
|
|
233
|
+
:return: The newly created experiment detail entry.
|
|
234
|
+
"""
|
|
235
|
+
json_list: list[dict[str, Any]] = []
|
|
236
|
+
for _, row in df.iterrows():
|
|
237
|
+
json_list.append(row.to_dict())
|
|
238
|
+
return self.create_experiment_details_from_json(tab, entry_name, json_list)
|
|
239
|
+
|
|
240
|
+
def create_experiment_details_from_json(self,
|
|
241
|
+
tab: ElnExperimentTab,
|
|
242
|
+
entry_name: str,
|
|
243
|
+
json_list: list[dict[str, Any]]) -> ExperimentEntry | None:
|
|
244
|
+
"""
|
|
245
|
+
Create an experiment detail entry from a list of JSON dictionaries.
|
|
246
|
+
|
|
247
|
+
:param tab: The tab that the entry should be added to.
|
|
248
|
+
:param entry_name: The name of the entry.
|
|
249
|
+
:param json_list: The list of JSON dictionaries to create the entry from. Each dictionary is expected to have the
|
|
250
|
+
same keys.
|
|
251
|
+
:return: The newly created experiment detail entry.
|
|
252
|
+
"""
|
|
253
|
+
if not json_list:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
# Determine which fields in the JSON can be used to create field definitions.
|
|
257
|
+
fb = FieldBuilder()
|
|
258
|
+
fields: list[AbstractVeloxFieldDefinition] = []
|
|
259
|
+
fields_by_name: dict[str, AbstractVeloxFieldDefinition] = {}
|
|
260
|
+
for key, value in json_list[0].items():
|
|
261
|
+
field_name: str = key.replace(" ", "_")
|
|
262
|
+
if isinstance(value, str):
|
|
263
|
+
field = fb.string_field(field_name, display_name=key)
|
|
264
|
+
fields.append(field)
|
|
265
|
+
fields_by_name[key] = field
|
|
266
|
+
elif isinstance(value, (int, float)):
|
|
267
|
+
field = fb.double_field(field_name, display_name=key, precision=3)
|
|
268
|
+
fields.append(field)
|
|
269
|
+
fields_by_name[key] = field
|
|
270
|
+
|
|
271
|
+
# Extract the valid field values from the JSON.
|
|
272
|
+
field_maps: list[dict[str, Any]] = []
|
|
273
|
+
for json_dict in json_list:
|
|
274
|
+
field_map: dict[str, Any] = {}
|
|
275
|
+
for key, field in fields_by_name.items():
|
|
276
|
+
# Watch out for NaN values or other special values.
|
|
277
|
+
val: Any = json_dict.get(key)
|
|
278
|
+
if (field.data_field_type == FieldType.DOUBLE
|
|
279
|
+
and (not isinstance(val, (int, float))) or (isinstance(val, float) and math.isnan(val))):
|
|
280
|
+
val = None
|
|
281
|
+
field_map[field.data_field_name] = val
|
|
282
|
+
field_maps.append(field_map)
|
|
283
|
+
|
|
284
|
+
detail_entry = ElnEntryCriteria(ElnEntryType.Table, entry_name,
|
|
285
|
+
ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name,
|
|
286
|
+
self.tab_next_entry_order(tab),
|
|
287
|
+
notebook_experiment_tab_id=tab.tab_id,
|
|
288
|
+
field_definition_list=fields)
|
|
289
|
+
entry = self.eln_man.add_experiment_entry(self.exp_id, detail_entry)
|
|
290
|
+
self.dr_man.add_data_records_with_data(entry.data_type_name, field_maps)
|
|
291
|
+
return entry
|
|
292
|
+
|
|
293
|
+
def create_text_entry(self, tab: ElnExperimentTab, timestamp: str, description: str, auto_format: bool = True) \
|
|
294
|
+
-> ExperimentEntry:
|
|
295
|
+
"""
|
|
296
|
+
Create a new text entry in the experiment.
|
|
297
|
+
|
|
298
|
+
:param tab: The tab to create the text entry in.
|
|
299
|
+
:param timestamp: The timestamp to display at the top of the text entry.
|
|
300
|
+
:param description: The description to display in the text entry.
|
|
301
|
+
:param auto_format: Whether to automatically format the text to be added.
|
|
302
|
+
:return: The newly created text entry.
|
|
303
|
+
"""
|
|
304
|
+
if auto_format:
|
|
305
|
+
description: str = f"<p>{HtmlFormatter.timestamp(timestamp)}<br>{HtmlFormatter.body(description)}</p>"
|
|
306
|
+
else:
|
|
307
|
+
description: str = f"<p>{timestamp}<br>{description}</p>"
|
|
308
|
+
position = ElnEntryPosition(tab.tab_id, self.tab_next_entry_order(tab))
|
|
309
|
+
text_entry: ElnEntryStep = ELNStepFactory.create_text_entry(self.protocol, description, position)
|
|
310
|
+
return text_entry.eln_entry
|
|
311
|
+
|
|
312
|
+
def set_text_entry(self, text_entry: ExperimentEntry, timestamp: str, description: str,
|
|
313
|
+
auto_format: bool = True) -> None:
|
|
314
|
+
"""
|
|
315
|
+
Set the text of a text entry.
|
|
316
|
+
|
|
317
|
+
:param text_entry: The text entry to set the text of.
|
|
318
|
+
:param timestamp: The timestamp to display at the top of the text entry.
|
|
319
|
+
:param description: The description to display in the text entry.
|
|
320
|
+
:param auto_format: Whether to automatically format the text to be added.
|
|
321
|
+
"""
|
|
322
|
+
if auto_format:
|
|
323
|
+
timestamp = HtmlFormatter.timestamp(timestamp)
|
|
324
|
+
description = HtmlFormatter.body(description)
|
|
325
|
+
description: str = f"<p>{timestamp}<br>{description}</p>"
|
|
326
|
+
step = ElnEntryStep(self.protocol, text_entry)
|
|
327
|
+
text_record: DataRecord = step.get_records()[0]
|
|
328
|
+
text_record.set_field_value(ElnBaseDataType.get_text_entry_data_field_name(), description)
|
|
329
|
+
self.dr_man.commit_data_records([text_record])
|
|
330
|
+
|
|
331
|
+
def add_to_text_entry(self, text_entry: ExperimentEntry, description: str, auto_format: bool = True) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Add to the text of a text entry.
|
|
334
|
+
|
|
335
|
+
:param text_entry: The text entry to add the text to.
|
|
336
|
+
:param description: The text to add to the text entry.
|
|
337
|
+
:param auto_format: Whether to automatically format the text to be added.
|
|
338
|
+
"""
|
|
339
|
+
step = ElnEntryStep(self.protocol, text_entry)
|
|
340
|
+
text_record: DataRecord = step.get_records()[0]
|
|
341
|
+
update: str = text_record.get_field_value(ElnBaseDataType.get_text_entry_data_field_name())
|
|
342
|
+
if auto_format:
|
|
343
|
+
description = HtmlFormatter.body(description)
|
|
344
|
+
update += f"<p style=\"padding-top: 10px;\">{description}</p>"
|
|
345
|
+
text_record.set_field_value(ElnBaseDataType.get_text_entry_data_field_name(), update)
|
|
346
|
+
self.dr_man.commit_data_records([text_record])
|
|
347
|
+
|
|
348
|
+
def create_attachment_entry(self, tab: ElnExperimentTab, entry_name: str, file_name: str, file_data: str | bytes) \
|
|
349
|
+
-> ExperimentEntry:
|
|
350
|
+
"""
|
|
351
|
+
Add a new attachment entry to the experiment with the provided attachment data.
|
|
352
|
+
|
|
353
|
+
:param tab: The tab where the attachment entry will be added.
|
|
354
|
+
:param entry_name: Name of the attachment entry to create in the experiment.
|
|
355
|
+
:param file_name: The name of the attachment.
|
|
356
|
+
:param file_data: The data of the attachment. This can be a string or bytes.
|
|
357
|
+
:return: The newly created attachment entry.
|
|
358
|
+
"""
|
|
359
|
+
tab_id: int = tab.tab_id
|
|
360
|
+
|
|
361
|
+
# Encode the file contents in base64.
|
|
362
|
+
if isinstance(file_data, str):
|
|
363
|
+
file_data: bytes = file_data.encode("utf-8")
|
|
364
|
+
base64_encoded: str = base64.b64encode(file_data).decode("utf-8")
|
|
365
|
+
|
|
366
|
+
# Crete an attachment entry with the provided data.
|
|
367
|
+
attachment_entry = self.eln_man.add_experiment_entry(
|
|
368
|
+
self.exp_id,
|
|
369
|
+
ElnEntryCriteria(ElnEntryType.Attachment, entry_name, "Attachment", order=2,
|
|
370
|
+
notebook_experiment_tab_id=tab_id, attachment_file_name=file_name,
|
|
371
|
+
attachment_data_base64=base64_encoded)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Return the entry object for further use.
|
|
375
|
+
return attachment_entry
|
|
376
|
+
|
|
377
|
+
def create_attachment_entry_from_file(self, tab: ElnExperimentTab, entry_name: str, file_path: str) \
|
|
378
|
+
-> ExperimentEntry:
|
|
379
|
+
"""
|
|
380
|
+
Add a new attachment entry to the experiment with the provided file path to a file in the file system.
|
|
381
|
+
|
|
382
|
+
:param tab: The tab where the attachment entry will be added.
|
|
383
|
+
:param entry_name: Name of the attachment entry to create in the experiment.
|
|
384
|
+
:param file_path: The path to a file in the system to attach to the experiment.
|
|
385
|
+
:return: The newly created attachment entry.
|
|
386
|
+
"""
|
|
387
|
+
with open(file_path, 'rb') as f:
|
|
388
|
+
file_contents: bytes = f.read()
|
|
389
|
+
return self.create_attachment_entry(tab, entry_name, file_path, file_contents)
|
|
390
|
+
|
|
391
|
+
|
|
303
392
|
class ToolOfToolsHelper:
|
|
304
393
|
"""
|
|
305
394
|
A class with helper methods utilized by the Tool of Tools for the creation and updating of experiment tabs that
|
|
@@ -309,7 +398,7 @@ class ToolOfToolsHelper:
|
|
|
309
398
|
user: SapioUser
|
|
310
399
|
tab_prefix: str
|
|
311
400
|
exp_id: int
|
|
312
|
-
|
|
401
|
+
helper: AiHelper
|
|
313
402
|
|
|
314
403
|
# Tool info.
|
|
315
404
|
name: str
|
|
@@ -317,8 +406,8 @@ class ToolOfToolsHelper:
|
|
|
317
406
|
results_data_type: str | None
|
|
318
407
|
|
|
319
408
|
# Managers.
|
|
320
|
-
eln_man: ElnManager
|
|
321
409
|
dr_man: DataRecordManager
|
|
410
|
+
eln_man: ElnManager
|
|
322
411
|
|
|
323
412
|
# Stuff created by this helper.
|
|
324
413
|
_initialized: bool
|
|
@@ -351,15 +440,14 @@ class ToolOfToolsHelper:
|
|
|
351
440
|
self.user = create_user_from_tot_headers(headers)
|
|
352
441
|
self.exp_id = int(headers[EXP_ID_HEADER.lower()])
|
|
353
442
|
self.tab_prefix = headers[TAB_PREFIX_HEADER.lower()]
|
|
354
|
-
|
|
355
|
-
self._protocol = ElnExperimentProtocol(ElnExperiment(self.exp_id, "", 0), self.user)
|
|
443
|
+
self.helper = AiHelper(self.user, self.exp_id)
|
|
356
444
|
|
|
357
445
|
self.name = name
|
|
358
446
|
self.description = description
|
|
359
447
|
self.results_data_type = results_data_type
|
|
360
448
|
|
|
361
|
-
self.eln_man = ElnManager(self.user)
|
|
362
449
|
self.dr_man = DataRecordManager(self.user)
|
|
450
|
+
self.eln_man = ElnManager(self.user)
|
|
363
451
|
|
|
364
452
|
self._initialized = False
|
|
365
453
|
|
|
@@ -376,7 +464,7 @@ class ToolOfToolsHelper:
|
|
|
376
464
|
if tab.tab_name != tab_name:
|
|
377
465
|
continue
|
|
378
466
|
|
|
379
|
-
for entry in self.
|
|
467
|
+
for entry in self.helper.protocol.get_sorted_step_list():
|
|
380
468
|
if entry.eln_entry.notebook_experiment_tab_id != tab.tab_id:
|
|
381
469
|
continue
|
|
382
470
|
|
|
@@ -413,9 +501,7 @@ class ToolOfToolsHelper:
|
|
|
413
501
|
return tab
|
|
414
502
|
|
|
415
503
|
# Otherwise, create the tab for the tool progress and results.
|
|
416
|
-
|
|
417
|
-
tab: ElnExperimentTab = self.eln_man.add_tab_for_experiment(self.exp_id, tab_crit)
|
|
418
|
-
self.tab = tab
|
|
504
|
+
self.tab = self.helper.create_tab(tab_name)
|
|
419
505
|
|
|
420
506
|
# Create a hidden entry for tracking the progress of the tool.
|
|
421
507
|
field_sets: list[ElnFieldSetInfo] = self.eln_man.get_field_set_info_list()
|
|
@@ -425,9 +511,9 @@ class ToolOfToolsHelper:
|
|
|
425
511
|
raise SapioException("Unable to locate the field set for the Tool of Tools progress.")
|
|
426
512
|
progress_entry_crit = ElnEntryCriteria(ElnEntryType.Form, f"ELaiN: {self.name} Progress",
|
|
427
513
|
ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name, 1,
|
|
428
|
-
notebook_experiment_tab_id=tab.tab_id,
|
|
514
|
+
notebook_experiment_tab_id=self.tab.tab_id,
|
|
429
515
|
enb_field_set_id=progress_field_set[0].field_set_id)
|
|
430
|
-
progress_entry = ElnEntryStep(self.
|
|
516
|
+
progress_entry = ElnEntryStep(self.helper.protocol,
|
|
431
517
|
self.eln_man.add_experiment_entry(self.exp_id, progress_entry_crit))
|
|
432
518
|
self.progress_entry = progress_entry
|
|
433
519
|
self.progress_record = progress_entry.get_records()[0]
|
|
@@ -441,26 +527,26 @@ class ToolOfToolsHelper:
|
|
|
441
527
|
# tool started and format the description so that the text isn't too small to read.
|
|
442
528
|
# TODO: Get the UTC offset in seconds from the header once that's being sent.
|
|
443
529
|
now: str = TimeUtil.now_in_format("%Y-%m-%d %H:%M:%S UTC", "UTC")
|
|
444
|
-
|
|
445
|
-
text_entry: ElnEntryStep = ELNStepFactory.create_text_entry(self._protocol, description)
|
|
530
|
+
text_entry = ElnEntryStep(self.helper.protocol, self.helper.create_text_entry(self.tab, now, self.description))
|
|
446
531
|
self.description_entry = text_entry
|
|
447
532
|
self.description_record = text_entry.get_records()[0]
|
|
448
533
|
|
|
449
534
|
# Shrink the text entry by one column.
|
|
450
535
|
text_update_crit = ElnTextEntryUpdateCriteria()
|
|
536
|
+
text_update_crit.column_order = 0
|
|
451
537
|
text_update_crit.column_span = 2
|
|
452
538
|
self.eln_man.update_experiment_entry(self.exp_id, self.description_entry.get_id(), text_update_crit)
|
|
453
539
|
|
|
454
540
|
# Create a gauge entry to display the progress.
|
|
455
|
-
gauge_entry: ElnEntryStep = self._create_gauge_chart(self.
|
|
541
|
+
gauge_entry: ElnEntryStep = self._create_gauge_chart(self.helper.protocol, progress_entry,
|
|
456
542
|
f"{self.name} Progress", "Progress", "StatusMsg")
|
|
457
543
|
self.progress_gauge_entry = gauge_entry
|
|
458
544
|
|
|
459
545
|
# Make sure the gauge entry isn't too big and stick it to the right of the text entry.
|
|
460
546
|
dash_update_crit = ElnDashboardEntryUpdateCriteria()
|
|
461
547
|
dash_update_crit.entry_height = 250
|
|
462
|
-
dash_update_crit.column_span = 2
|
|
463
548
|
dash_update_crit.column_order = 2
|
|
549
|
+
dash_update_crit.column_span = 2
|
|
464
550
|
self.eln_man.update_experiment_entry(self.exp_id, self.progress_gauge_entry.get_id(), dash_update_crit)
|
|
465
551
|
|
|
466
552
|
# TODO: Bulk updates aren't working?
|
|
@@ -472,13 +558,13 @@ class ToolOfToolsHelper:
|
|
|
472
558
|
|
|
473
559
|
# Create a results entry if this tool produces result records.
|
|
474
560
|
if self.results_data_type:
|
|
475
|
-
results_entry = ELNStepFactory.create_table_step(self.
|
|
561
|
+
results_entry = ELNStepFactory.create_table_step(self.helper.protocol, f"{self.name} Results",
|
|
476
562
|
self.results_data_type)
|
|
477
563
|
self.results_entry = results_entry
|
|
478
564
|
else:
|
|
479
565
|
self.results_entry = None
|
|
480
566
|
|
|
481
|
-
return tab
|
|
567
|
+
return self.tab
|
|
482
568
|
|
|
483
569
|
def add_to_description(self, description: str, auto_format: bool = True) -> None:
|
|
484
570
|
"""
|
|
@@ -532,64 +618,35 @@ class ToolOfToolsHelper:
|
|
|
532
618
|
raise SapioException("The tab for this tool has not been initialized.")
|
|
533
619
|
if not self.results_entry:
|
|
534
620
|
raise SapioException("This tool does not produce result records.")
|
|
535
|
-
return ELNStepFactory.create_bar_chart_step(self.
|
|
536
|
-
x_axis, y_axis)[0].eln_entry
|
|
621
|
+
return ELNStepFactory.create_bar_chart_step(self.helper.protocol, self.results_entry,
|
|
622
|
+
f"{self.name} Results Chart", x_axis, y_axis)[0].eln_entry
|
|
537
623
|
|
|
538
|
-
def add_attachment_entry(self, file_name: str, file_data: str | bytes
|
|
539
|
-
tab: ElnExperimentTab | None = None) -> ExperimentEntry:
|
|
624
|
+
def add_attachment_entry(self, entry_name: str, file_name: str, file_data: str | bytes) -> ExperimentEntry:
|
|
540
625
|
"""
|
|
541
626
|
Add a new attachment entry to the experiment with the provided attachment data.
|
|
542
627
|
|
|
628
|
+
:param entry_name: Name of the attachment entry to create in the experiment.
|
|
543
629
|
:param file_name: The name of the attachment.
|
|
544
630
|
:param file_data: The data of the attachment. This can be a string or bytes.
|
|
545
|
-
:
|
|
546
|
-
:param tab: The tab where the attachment will be added. If not provided, the tab initialized by this helper
|
|
547
|
-
will be used.
|
|
548
|
-
:return: The created entry object.
|
|
631
|
+
:return: The newly created attachment entry.
|
|
549
632
|
"""
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
raise SapioException("The tab for this tool has not been initialized. Either initialize a tab for this "
|
|
553
|
-
"tool or provide the tab to this function to add the attachment entry to.")
|
|
554
|
-
tab_id: int = self.tab.tab_id if tab is None else tab.tab_id
|
|
555
|
-
|
|
556
|
-
# Encode the file contents in base64.
|
|
557
|
-
if isinstance(file_data, str):
|
|
558
|
-
file_data: bytes = file_data.encode("utf-8")
|
|
559
|
-
base64_encoded: str = base64.b64encode(file_data).decode("utf-8")
|
|
560
|
-
|
|
561
|
-
# Crete an attachment entry with the provided data.
|
|
562
|
-
attachment_entry = self.eln_man.add_experiment_entry(
|
|
563
|
-
self.exp_id,
|
|
564
|
-
ElnEntryCriteria(ElnEntryType.Attachment, entry_name, "Attachment", order=2,
|
|
565
|
-
notebook_experiment_tab_id=tab_id, attachment_file_name=file_name,
|
|
566
|
-
attachment_data_base64=base64_encoded)
|
|
567
|
-
)
|
|
633
|
+
if not self._initialized:
|
|
634
|
+
raise SapioException("The tab for this tool has not been initialized.")
|
|
568
635
|
|
|
569
|
-
|
|
570
|
-
return attachment_entry
|
|
636
|
+
return self.helper.create_attachment_entry(self.tab, entry_name, file_name, file_data)
|
|
571
637
|
|
|
572
|
-
def
|
|
573
|
-
tab: ElnExperimentTab | None = None) -> ExperimentEntry:
|
|
638
|
+
def add_attachment_entry_from_file(self, entry_name: str, file_path: str) -> ExperimentEntry:
|
|
574
639
|
"""
|
|
575
640
|
Add a new attachment entry to the experiment with the provided file path to a file in the file system.
|
|
576
641
|
|
|
577
|
-
:param file_path: The path to a file in the system to attach to the experiment.
|
|
578
642
|
:param entry_name: Name of the attachment entry to create in the experiment.
|
|
579
|
-
:param
|
|
580
|
-
|
|
581
|
-
:return: The created entry object.
|
|
643
|
+
:param file_path: The path to a file in the system to attach to the experiment.
|
|
644
|
+
:return: The newly created attachment entry.
|
|
582
645
|
"""
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
# not read the provided file and then find out we can't do anything with it anyway.
|
|
586
|
-
if not self._initialized and tab is None:
|
|
587
|
-
raise SapioException("The tab for this tool has not been initialized. Either initialize a tab for this "
|
|
588
|
-
"tool or provide the tab to this function to add the attachment entry to.")
|
|
646
|
+
if not self._initialized:
|
|
647
|
+
raise SapioException("The tab for this tool has not been initialized.")
|
|
589
648
|
|
|
590
|
-
|
|
591
|
-
file_contents: bytes = f.read()
|
|
592
|
-
return self.add_attachment_entry(file_path, file_contents, entry_name, tab)
|
|
649
|
+
return self.helper.create_attachment_entry_from_file(self.tab, entry_name, file_path)
|
|
593
650
|
|
|
594
651
|
# TODO: Remove this once pylib's gauge chart definition is up to date.
|
|
595
652
|
@staticmethod
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/chem/Molecules.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/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
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/elain/__init__.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/eln/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/files/file_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/general/aliases.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
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/rules/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/samples/aliquot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/src/sapiopycommons/webhook/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/AF-A0A009IHW8-F1-model_v4.cif
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/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.2.12a432 → sapiopycommons-2025.2.14a434}/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.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/8_color_ICS.wsp
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.14a434}/tests/flowcyto/COVID19_W_001_O.fcs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|