sapiopycommons 2025.2.12a432__tar.gz → 2025.2.12a433__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.12a433}/PKG-INFO +1 -1
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/pyproject.toml +1 -1
- sapiopycommons-2025.2.12a432/src/sapiopycommons/elain/tool_of_tools.py +0 -632
- sapiopycommons-2025.2.12a432/src/sapiopycommons/webhook/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/.gitignore +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/LICENSE +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/README.md +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/callbacks/callback_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/callbacks/field_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/chem/IndigoMolecules.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/chem/Molecules.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/customreport/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/customreport/auto_pagers.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/customreport/column_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/customreport/custom_report_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/customreport/term_builder.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/datatype/attachment_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/datatype/data_fields.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/datatype/pseudo_data_types.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/elain → sapiopycommons-2025.2.12a433/src/sapiopycommons/eln}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/eln/experiment_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/eln/experiment_report_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/eln → sapiopycommons-2025.2.12a433/src/sapiopycommons/files}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/complex_data_loader.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/file_bridge.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/file_bridge_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/file_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/file_validator.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/flowcyto/flow_cyto.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/flowcyto/flowcyto_data.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/files → sapiopycommons-2025.2.12a433/src/sapiopycommons/general}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/accession_service.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/aliases.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/audit_log.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/custom_report_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/directive_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/sapio_links.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/multimodal/multimodal.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/multimodal/multimodal_data.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/general → sapiopycommons-2025.2.12a433/src/sapiopycommons/processtracking}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/processtracking/custom_workflow_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/processtracking/endpoints.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/processtracking → sapiopycommons-2025.2.12a433/src/sapiopycommons/recordmodel}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/recordmodel/record_handler.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/recordmodel → sapiopycommons-2025.2.12a433/src/sapiopycommons/rules}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/samples/aliquot.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/rules → sapiopycommons-2025.2.12a433/src/sapiopycommons/sftpconnect}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/sftpconnect/sftp_builder.py +0 -0
- {sapiopycommons-2025.2.12a432/src/sapiopycommons/sftpconnect → sapiopycommons-2025.2.12a433/src/sapiopycommons/webhook}/__init__.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/webhook/webhook_context.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/webhook/webhook_handlers.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/webhook/webservice_handlers.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/AF-A0A009IHW8-F1-model_v4.cif +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/_do_not_add_init_py_here +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/accession_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/aliquot_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/bio_reg_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/chem_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/chem_test_curation_queue.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/curation_queue_test.sdf +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/data_type_models.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto/101_DEN084Y5_15_E01_008_clean.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto/101_DEN084Y5_15_E03_009_clean.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto/101_DEN084Y5_15_E05_010_clean.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto/8_color_ICS.wsp +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto/COVID19_W_001_O.fcs +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/kappa.chains.fasta +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/mafft_test.py +0 -0
- {sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/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.12a433
|
|
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>
|
|
@@ -1,632 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import math
|
|
3
|
-
import re
|
|
4
|
-
from typing import Final, Mapping, Any
|
|
5
|
-
|
|
6
|
-
from pandas import DataFrame
|
|
7
|
-
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
8
|
-
from sapiopylib.rest.ELNService import ElnManager
|
|
9
|
-
from sapiopylib.rest.User import SapioUser
|
|
10
|
-
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
11
|
-
from sapiopylib.rest.pojo.chartdata.DashboardDefinition import GaugeChartDefinition
|
|
12
|
-
from sapiopylib.rest.pojo.chartdata.DashboardEnums import ChartGroupingType, ChartOperationType
|
|
13
|
-
from sapiopylib.rest.pojo.chartdata.DashboardSeries import GaugeChartSeries
|
|
14
|
-
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition, FieldType
|
|
15
|
-
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
|
|
16
|
-
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
17
|
-
from sapiopylib.rest.pojo.eln.ExperimentEntryCriteria import ElnEntryCriteria, ElnFormEntryUpdateCriteria, \
|
|
18
|
-
ElnDashboardEntryUpdateCriteria, ElnTextEntryUpdateCriteria
|
|
19
|
-
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnEntryType, ElnBaseDataType
|
|
20
|
-
from sapiopylib.rest.pojo.eln.eln_headings import ElnExperimentTabAddCriteria, ElnExperimentTab
|
|
21
|
-
from sapiopylib.rest.pojo.eln.field_set import ElnFieldSetInfo
|
|
22
|
-
from sapiopylib.rest.utils.ProtocolUtils import ELNStepFactory
|
|
23
|
-
from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
|
|
24
|
-
|
|
25
|
-
from sapiopycommons.callbacks.field_builder import FieldBuilder
|
|
26
|
-
from sapiopycommons.general.aliases import AliasUtil, SapioRecord
|
|
27
|
-
from sapiopycommons.general.exceptions import SapioException
|
|
28
|
-
from sapiopycommons.general.time_util import TimeUtil
|
|
29
|
-
|
|
30
|
-
CREDENTIALS_HEADER: Final[str] = "SAPIO_APP_API_KEY"
|
|
31
|
-
API_URL_HEADER: Final[str] = "SAPIO_APP_API_URL"
|
|
32
|
-
EXP_ID_HEADER: Final[str] = "EXPERIMENT_ID"
|
|
33
|
-
TAB_PREFIX_HEADER: Final[str] = "TAB_PREFIX"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# TODO: There's a lot of duplication between the static functions and the ToT Helper. This should just be one class
|
|
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.
|
|
40
|
-
def create_tot_headers(url: str, username: str, password: str, experiment_id: int, tab_prefix: str) \
|
|
41
|
-
-> tuple[str, dict[str, str]]:
|
|
42
|
-
"""
|
|
43
|
-
Create the headers to be passed to a tool of tools endpoint.
|
|
44
|
-
|
|
45
|
-
:param url: The webservice URL of the system to make the changes in.
|
|
46
|
-
:param username: The username of the user making the changes.
|
|
47
|
-
:param password: The password of the user making the changes.
|
|
48
|
-
:param experiment_id: The ID of the experiment to make the changes in.
|
|
49
|
-
:param tab_prefix: The prefix to use for the tab name that will be created by the tool.
|
|
50
|
-
:return: The encoded credentials and the headers to be passed to the endpoint.
|
|
51
|
-
"""
|
|
52
|
-
# Combine the credentials into the format "username:password"
|
|
53
|
-
credentials: str = f"{username}:{password}"
|
|
54
|
-
# Encode the credentials to bytes, then encode them using base64,
|
|
55
|
-
# and finally convert the result back into a string.
|
|
56
|
-
encoded_credentials: str = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
|
|
57
|
-
headers: dict[str, str] = {
|
|
58
|
-
CREDENTIALS_HEADER: f"Basic {encoded_credentials}",
|
|
59
|
-
API_URL_HEADER: url,
|
|
60
|
-
EXP_ID_HEADER: str(experiment_id),
|
|
61
|
-
TAB_PREFIX_HEADER: tab_prefix
|
|
62
|
-
}
|
|
63
|
-
return encoded_credentials, headers
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def create_user_from_tot_headers(headers: Mapping[str, str]) -> SapioUser:
|
|
67
|
-
"""
|
|
68
|
-
Create a SapioUser object from the headers passed to a tool of tools endpoint.
|
|
69
|
-
|
|
70
|
-
:param headers: The headers that were passed to the endpoint.
|
|
71
|
-
:return: A SapioUser object created from the headers that can be used to communicate with the Sapio server.
|
|
72
|
-
"""
|
|
73
|
-
headers: dict[str, str] = format_tot_headers(headers)
|
|
74
|
-
credentials = (base64.b64decode(headers[CREDENTIALS_HEADER.lower()].removeprefix("Basic "))
|
|
75
|
-
.decode("utf-8").split(":", 1))
|
|
76
|
-
return SapioUser(headers[API_URL_HEADER.lower()], username=credentials[0], password=credentials[1])
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def format_tot_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
80
|
-
"""
|
|
81
|
-
Format the headers passed to a tool of tools endpoint to guarantee that the keys are lowercase.
|
|
82
|
-
|
|
83
|
-
:param headers: The headers that were passed to the endpoint.
|
|
84
|
-
:return: The headers with all keys converted to lowercase. (Conflicting keys will cause one to overwrite the other,
|
|
85
|
-
but there should not be any conflicting keys in the headers passed to a tool of tools endpoint.)
|
|
86
|
-
"""
|
|
87
|
-
return {k.lower(): v for k, v in headers.items()}
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
class HtmlFormatter:
|
|
222
|
-
"""
|
|
223
|
-
A class for formatting text in HTML with classes supported by the client.
|
|
224
|
-
"""
|
|
225
|
-
TIMESTAMP_TEXT__CSS_CLASS_NAME: Final[str] = "timestamp-text"
|
|
226
|
-
HEADER_1_TEXT__CSS_CLASS_NAME: Final[str] = "header1-text"
|
|
227
|
-
HEADER_2_TEXT__CSS_CLASS_NAME: Final[str] = "header2-text"
|
|
228
|
-
HEADER_3_TEXT__CSS_CLASS_NAME: Final[str] = "header3-text"
|
|
229
|
-
BODY_TEXT__CSS_CLASS_NAME: Final[str] = "body-text"
|
|
230
|
-
CAPTION_TEXT__CSS_CLASS_NAME: Final[str] = "caption-text"
|
|
231
|
-
|
|
232
|
-
@staticmethod
|
|
233
|
-
def timestamp(text: str) -> str:
|
|
234
|
-
"""
|
|
235
|
-
Given a text string, return that same text string HTML formatted using the timestamp CSS class.
|
|
236
|
-
|
|
237
|
-
:param text: The text to format.
|
|
238
|
-
:return: The HTML formatted text.
|
|
239
|
-
"""
|
|
240
|
-
return f"<span class=\"{HtmlFormatter.TIMESTAMP_TEXT__CSS_CLASS_NAME}\">{text}</span>"
|
|
241
|
-
|
|
242
|
-
@staticmethod
|
|
243
|
-
def header_1(text: str) -> str:
|
|
244
|
-
"""
|
|
245
|
-
Given a text string, return that same text string HTML formatted using the header 1 CSS class.
|
|
246
|
-
|
|
247
|
-
:param text: The text to format.
|
|
248
|
-
:return: The HTML formatted text.
|
|
249
|
-
"""
|
|
250
|
-
return f"<span class=\"{HtmlFormatter.HEADER_1_TEXT__CSS_CLASS_NAME}\">{text}</span>"
|
|
251
|
-
|
|
252
|
-
@staticmethod
|
|
253
|
-
def header_2(text: str) -> str:
|
|
254
|
-
"""
|
|
255
|
-
Given a text string, return that same text string HTML formatted using the header 2 CSS class.
|
|
256
|
-
|
|
257
|
-
:param text: The text to format.
|
|
258
|
-
:return: The HTML formatted text.
|
|
259
|
-
"""
|
|
260
|
-
return f"<span class=\"{HtmlFormatter.HEADER_2_TEXT__CSS_CLASS_NAME}\">{text}</span>"
|
|
261
|
-
|
|
262
|
-
@staticmethod
|
|
263
|
-
def header_3(text: str) -> str:
|
|
264
|
-
"""
|
|
265
|
-
Given a text string, return that same text string HTML formatted using the header 3 CSS class.
|
|
266
|
-
|
|
267
|
-
:param text: The text to format.
|
|
268
|
-
:return: The HTML formatted text.
|
|
269
|
-
"""
|
|
270
|
-
return f"<span class=\"{HtmlFormatter.HEADER_3_TEXT__CSS_CLASS_NAME}\">{text}</span>"
|
|
271
|
-
|
|
272
|
-
@staticmethod
|
|
273
|
-
def body(text: str) -> str:
|
|
274
|
-
"""
|
|
275
|
-
Given a text string, return that same text string HTML formatted using the body CSS class.
|
|
276
|
-
|
|
277
|
-
:param text: The text to format.
|
|
278
|
-
:return: The HTML formatted text.
|
|
279
|
-
"""
|
|
280
|
-
return f"<span class=\"{HtmlFormatter.BODY_TEXT__CSS_CLASS_NAME}\">{text}</span>"
|
|
281
|
-
|
|
282
|
-
@staticmethod
|
|
283
|
-
def caption(text: str) -> str:
|
|
284
|
-
"""
|
|
285
|
-
Given a text string, return that same text string HTML formatted using the caption CSS class.
|
|
286
|
-
|
|
287
|
-
:param text: The text to format.
|
|
288
|
-
:return: The HTML formatted text.
|
|
289
|
-
"""
|
|
290
|
-
return f"<span class=\"{HtmlFormatter.CAPTION_TEXT__CSS_CLASS_NAME}\">{text}</span>"
|
|
291
|
-
|
|
292
|
-
@staticmethod
|
|
293
|
-
def replace_newlines(text: str) -> str:
|
|
294
|
-
"""
|
|
295
|
-
Given a text string, return that same text string HTML formatted with newlines replaced by HTML line breaks.
|
|
296
|
-
|
|
297
|
-
:param text: The text to format.
|
|
298
|
-
:return: The HTML formatted text.
|
|
299
|
-
"""
|
|
300
|
-
return re.sub("\r?\n", "<br>", text)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
class ToolOfToolsHelper:
|
|
304
|
-
"""
|
|
305
|
-
A class with helper methods utilized by the Tool of Tools for the creation and updating of experiment tabs that
|
|
306
|
-
track a tool's progress and results.
|
|
307
|
-
"""
|
|
308
|
-
# Contextual info.
|
|
309
|
-
user: SapioUser
|
|
310
|
-
tab_prefix: str
|
|
311
|
-
exp_id: int
|
|
312
|
-
_protocol: ElnExperimentProtocol
|
|
313
|
-
|
|
314
|
-
# Tool info.
|
|
315
|
-
name: str
|
|
316
|
-
description: str
|
|
317
|
-
results_data_type: str | None
|
|
318
|
-
|
|
319
|
-
# Managers.
|
|
320
|
-
eln_man: ElnManager
|
|
321
|
-
dr_man: DataRecordManager
|
|
322
|
-
|
|
323
|
-
# Stuff created by this helper.
|
|
324
|
-
_initialized: bool
|
|
325
|
-
"""Whether a tab for this tool has been initialized."""
|
|
326
|
-
tab: ElnExperimentTab
|
|
327
|
-
"""The tab that contains the tool's entries."""
|
|
328
|
-
description_entry: ElnEntryStep | None
|
|
329
|
-
"""The text entry that displays the description of the tool."""
|
|
330
|
-
description_record: DataRecord | None
|
|
331
|
-
"""The record that stores the description of the tool."""
|
|
332
|
-
progress_entry: ElnEntryStep | None
|
|
333
|
-
"""A hidden entry for tracking the progress of the tool."""
|
|
334
|
-
progress_record: DataRecord | None
|
|
335
|
-
"""The record that stores the progress of the tool."""
|
|
336
|
-
progress_gauge_entry: ElnEntryStep | None
|
|
337
|
-
"""A chart entry that displays the progress of the tool using the hidden progress entry."""
|
|
338
|
-
results_entry: ElnEntryStep | None
|
|
339
|
-
"""An entry for displaying the results of the tool. If None, the tool does not produce result records."""
|
|
340
|
-
|
|
341
|
-
def __init__(self, headers: Mapping[str, str], name: str, description: str,
|
|
342
|
-
results_data_type: str | None = None):
|
|
343
|
-
"""
|
|
344
|
-
:param headers: The headers that were passed to the endpoint.
|
|
345
|
-
:param name: The name of the tool.
|
|
346
|
-
:param description: A description of the tool.
|
|
347
|
-
:param results_data_type: The data type name for the results of the tool. If None, the tool does not produce
|
|
348
|
-
result records.
|
|
349
|
-
"""
|
|
350
|
-
headers: dict[str, str] = format_tot_headers(headers)
|
|
351
|
-
self.user = create_user_from_tot_headers(headers)
|
|
352
|
-
self.exp_id = int(headers[EXP_ID_HEADER.lower()])
|
|
353
|
-
self.tab_prefix = headers[TAB_PREFIX_HEADER.lower()]
|
|
354
|
-
# The experiment name and record ID aren't necessary to know.
|
|
355
|
-
self._protocol = ElnExperimentProtocol(ElnExperiment(self.exp_id, "", 0), self.user)
|
|
356
|
-
|
|
357
|
-
self.name = name
|
|
358
|
-
self.description = description
|
|
359
|
-
self.results_data_type = results_data_type
|
|
360
|
-
|
|
361
|
-
self.eln_man = ElnManager(self.user)
|
|
362
|
-
self.dr_man = DataRecordManager(self.user)
|
|
363
|
-
|
|
364
|
-
self._initialized = False
|
|
365
|
-
|
|
366
|
-
def initialize_tab(self) -> ElnExperimentTab:
|
|
367
|
-
if self._initialized:
|
|
368
|
-
return self.tab
|
|
369
|
-
self._initialized = True
|
|
370
|
-
|
|
371
|
-
# Determine if a previous call to this endpoint already created a tab for these results. If so, grab the entries
|
|
372
|
-
# from that tab.
|
|
373
|
-
tab_name: str = f"{self.tab_prefix.strip()} {self.name.strip()}"
|
|
374
|
-
tabs: list[ElnExperimentTab] = self.eln_man.get_tabs_for_experiment(self.exp_id)
|
|
375
|
-
for tab in tabs:
|
|
376
|
-
if tab.tab_name != tab_name:
|
|
377
|
-
continue
|
|
378
|
-
|
|
379
|
-
for entry in self._protocol.get_sorted_step_list():
|
|
380
|
-
if entry.eln_entry.notebook_experiment_tab_id != tab.tab_id:
|
|
381
|
-
continue
|
|
382
|
-
|
|
383
|
-
dt: str = entry.get_data_type_names()[0] if entry.get_data_type_names() else None
|
|
384
|
-
if (entry.eln_entry.entry_type == ElnEntryType.Form
|
|
385
|
-
and ElnBaseDataType.get_base_type(dt) == ElnBaseDataType.EXPERIMENT_DETAIL
|
|
386
|
-
and not hasattr(self, "progress_entry")):
|
|
387
|
-
self.progress_entry = entry
|
|
388
|
-
self.progress_record = entry.get_records()[0]
|
|
389
|
-
elif (entry.eln_entry.entry_type == ElnEntryType.Dashboard
|
|
390
|
-
and not hasattr(self, "progress_gauge_entry")):
|
|
391
|
-
self.progress_gauge_entry = entry
|
|
392
|
-
elif (entry.eln_entry.entry_type == ElnEntryType.Text
|
|
393
|
-
and not hasattr(self, "description_entry")):
|
|
394
|
-
self.description_entry = entry
|
|
395
|
-
self.description_record = entry.get_records()[0]
|
|
396
|
-
elif (entry.eln_entry.entry_type == ElnEntryType.Table
|
|
397
|
-
and dt == self.results_data_type
|
|
398
|
-
and not hasattr(self, "results_entry")):
|
|
399
|
-
self.results_entry = entry
|
|
400
|
-
|
|
401
|
-
if not hasattr(self, "progress_entry"):
|
|
402
|
-
self.progress_entry = None
|
|
403
|
-
self.progress_record = None
|
|
404
|
-
if not hasattr(self, "progress_gauge_entry"):
|
|
405
|
-
self.progress_gauge_entry = None
|
|
406
|
-
if not hasattr(self, "description_entry"):
|
|
407
|
-
self.description_entry = None
|
|
408
|
-
self.description_record = None
|
|
409
|
-
if not hasattr(self, "results_entry"):
|
|
410
|
-
self.results_entry = None
|
|
411
|
-
|
|
412
|
-
self.tab = tab
|
|
413
|
-
return tab
|
|
414
|
-
|
|
415
|
-
# Otherwise, create the tab for the tool progress and results.
|
|
416
|
-
tab_crit = ElnExperimentTabAddCriteria(tab_name, [])
|
|
417
|
-
tab: ElnExperimentTab = self.eln_man.add_tab_for_experiment(self.exp_id, tab_crit)
|
|
418
|
-
self.tab = tab
|
|
419
|
-
|
|
420
|
-
# Create a hidden entry for tracking the progress of the tool.
|
|
421
|
-
field_sets: list[ElnFieldSetInfo] = self.eln_man.get_field_set_info_list()
|
|
422
|
-
progress_field_set: list[ElnFieldSetInfo] = [x for x in field_sets if
|
|
423
|
-
x.field_set_name == "Tool of Tools Progress"]
|
|
424
|
-
if not progress_field_set:
|
|
425
|
-
raise SapioException("Unable to locate the field set for the Tool of Tools progress.")
|
|
426
|
-
progress_entry_crit = ElnEntryCriteria(ElnEntryType.Form, f"ELaiN: {self.name} Progress",
|
|
427
|
-
ElnBaseDataType.EXPERIMENT_DETAIL.data_type_name, 1,
|
|
428
|
-
notebook_experiment_tab_id=tab.tab_id,
|
|
429
|
-
enb_field_set_id=progress_field_set[0].field_set_id)
|
|
430
|
-
progress_entry = ElnEntryStep(self._protocol,
|
|
431
|
-
self.eln_man.add_experiment_entry(self.exp_id, progress_entry_crit))
|
|
432
|
-
self.progress_entry = progress_entry
|
|
433
|
-
self.progress_record = progress_entry.get_records()[0]
|
|
434
|
-
|
|
435
|
-
# Hide the progress entry.
|
|
436
|
-
form_update_crit = ElnFormEntryUpdateCriteria()
|
|
437
|
-
form_update_crit.is_hidden = True
|
|
438
|
-
self.eln_man.update_experiment_entry(self.exp_id, self.progress_entry.get_id(), form_update_crit)
|
|
439
|
-
|
|
440
|
-
# Create the text entry that displays the description of the tool. Include the timestamp of when the
|
|
441
|
-
# tool started and format the description so that the text isn't too small to read.
|
|
442
|
-
# TODO: Get the UTC offset in seconds from the header once that's being sent.
|
|
443
|
-
now: str = TimeUtil.now_in_format("%Y-%m-%d %H:%M:%S UTC", "UTC")
|
|
444
|
-
description: str = f"<p>{HtmlFormatter.timestamp(now)}<br>{HtmlFormatter.body(self.description)}</p>"
|
|
445
|
-
text_entry: ElnEntryStep = ELNStepFactory.create_text_entry(self._protocol, description)
|
|
446
|
-
self.description_entry = text_entry
|
|
447
|
-
self.description_record = text_entry.get_records()[0]
|
|
448
|
-
|
|
449
|
-
# Shrink the text entry by one column.
|
|
450
|
-
text_update_crit = ElnTextEntryUpdateCriteria()
|
|
451
|
-
text_update_crit.column_span = 2
|
|
452
|
-
self.eln_man.update_experiment_entry(self.exp_id, self.description_entry.get_id(), text_update_crit)
|
|
453
|
-
|
|
454
|
-
# Create a gauge entry to display the progress.
|
|
455
|
-
gauge_entry: ElnEntryStep = self._create_gauge_chart(self._protocol, progress_entry,
|
|
456
|
-
f"{self.name} Progress", "Progress", "StatusMsg")
|
|
457
|
-
self.progress_gauge_entry = gauge_entry
|
|
458
|
-
|
|
459
|
-
# Make sure the gauge entry isn't too big and stick it to the right of the text entry.
|
|
460
|
-
dash_update_crit = ElnDashboardEntryUpdateCriteria()
|
|
461
|
-
dash_update_crit.entry_height = 250
|
|
462
|
-
dash_update_crit.column_span = 2
|
|
463
|
-
dash_update_crit.column_order = 2
|
|
464
|
-
self.eln_man.update_experiment_entry(self.exp_id, self.progress_gauge_entry.get_id(), dash_update_crit)
|
|
465
|
-
|
|
466
|
-
# TODO: Bulk updates aren't working?
|
|
467
|
-
# self.eln_man.update_experiment_entries(self.exp_id, {
|
|
468
|
-
# self.progress_entry.get_id(): form_update_crit,
|
|
469
|
-
# self.progress_gauge_entry.get_id(): dash_update_crit,
|
|
470
|
-
# self.description_entry.get_id(): text_update_crit
|
|
471
|
-
# })
|
|
472
|
-
|
|
473
|
-
# Create a results entry if this tool produces result records.
|
|
474
|
-
if self.results_data_type:
|
|
475
|
-
results_entry = ELNStepFactory.create_table_step(self._protocol, f"{self.name} Results",
|
|
476
|
-
self.results_data_type)
|
|
477
|
-
self.results_entry = results_entry
|
|
478
|
-
else:
|
|
479
|
-
self.results_entry = None
|
|
480
|
-
|
|
481
|
-
return tab
|
|
482
|
-
|
|
483
|
-
def add_to_description(self, description: str, auto_format: bool = True) -> None:
|
|
484
|
-
"""
|
|
485
|
-
Add to the description entry of the tool.
|
|
486
|
-
|
|
487
|
-
:param description: The text to add to the description.
|
|
488
|
-
:param auto_format: Whether to automatically format the text to be added.
|
|
489
|
-
"""
|
|
490
|
-
if not self._initialized:
|
|
491
|
-
raise SapioException("The tab for this tool has not been initialized.")
|
|
492
|
-
field: str = ElnBaseDataType.get_text_entry_data_field_name()
|
|
493
|
-
update: str = self.description_record.get_field_value(field)
|
|
494
|
-
if auto_format:
|
|
495
|
-
description = HtmlFormatter.body(description)
|
|
496
|
-
update += f"<p style=\"padding-top: 10px;\">{description}</p>"
|
|
497
|
-
self.description_record.set_field_value(field, update)
|
|
498
|
-
self.dr_man.commit_data_records([self.description_record])
|
|
499
|
-
|
|
500
|
-
def update_progress(self, progress: float, status_msg: str | None = None) -> None:
|
|
501
|
-
"""
|
|
502
|
-
Updates the progress of the tool.
|
|
503
|
-
|
|
504
|
-
:param progress: A value between 0 and 100 representing the progress of the tool.
|
|
505
|
-
:param status_msg: A status message to display to the user alongside the progress gauge.
|
|
506
|
-
"""
|
|
507
|
-
if not self._initialized:
|
|
508
|
-
raise SapioException("The tab for this tool has not been initialized.")
|
|
509
|
-
self.progress_record.set_field_value("Progress", progress)
|
|
510
|
-
self.progress_record.set_field_value("StatusMsg", status_msg)
|
|
511
|
-
self.dr_man.commit_data_records([self.progress_record])
|
|
512
|
-
|
|
513
|
-
def add_results(self, results: list[SapioRecord]) -> None:
|
|
514
|
-
"""
|
|
515
|
-
Add the results of the tool to the results entry.
|
|
516
|
-
|
|
517
|
-
:param results: The result records to add to the results entry.
|
|
518
|
-
"""
|
|
519
|
-
if not self._initialized:
|
|
520
|
-
raise SapioException("The tab for this tool has not been initialized.")
|
|
521
|
-
self.results_entry.add_records(AliasUtil.to_data_records(results))
|
|
522
|
-
|
|
523
|
-
def add_results_bar_chart(self, x_axis: str, y_axis: str) -> ExperimentEntry:
|
|
524
|
-
"""
|
|
525
|
-
Create a bar chart entry for the results of the tool.
|
|
526
|
-
|
|
527
|
-
:param x_axis: The data field to use for the x-axis of the chart.
|
|
528
|
-
:param y_axis: The data field to use for the y-axis of the chart.
|
|
529
|
-
:return: The newly created chart entry.
|
|
530
|
-
"""
|
|
531
|
-
if not self._initialized:
|
|
532
|
-
raise SapioException("The tab for this tool has not been initialized.")
|
|
533
|
-
if not self.results_entry:
|
|
534
|
-
raise SapioException("This tool does not produce result records.")
|
|
535
|
-
return ELNStepFactory.create_bar_chart_step(self._protocol, self.results_entry, f"{self.name} Results Chart",
|
|
536
|
-
x_axis, y_axis)[0].eln_entry
|
|
537
|
-
|
|
538
|
-
def add_attachment_entry(self, file_name: str, file_data: str | bytes, entry_name: str,
|
|
539
|
-
tab: ElnExperimentTab | None = None) -> ExperimentEntry:
|
|
540
|
-
"""
|
|
541
|
-
Add a new attachment entry to the experiment with the provided attachment data.
|
|
542
|
-
|
|
543
|
-
:param file_name: The name of the attachment.
|
|
544
|
-
:param file_data: The data of the attachment. This can be a string or bytes.
|
|
545
|
-
:param entry_name: Name of the attachment entry to create in the experiment.
|
|
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.
|
|
549
|
-
"""
|
|
550
|
-
# Check if the tab has been initialized or a tab has been provided.
|
|
551
|
-
if not self._initialized and tab is None:
|
|
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
|
-
)
|
|
568
|
-
|
|
569
|
-
# Return the entry object for further use.
|
|
570
|
-
return attachment_entry
|
|
571
|
-
|
|
572
|
-
def add_attachment_entry_from_file_system(self, file_path: str, entry_name: str,
|
|
573
|
-
tab: ElnExperimentTab | None = None) -> ExperimentEntry:
|
|
574
|
-
"""
|
|
575
|
-
Add a new attachment entry to the experiment with the provided file path to a file in the file system.
|
|
576
|
-
|
|
577
|
-
:param file_path: The path to a file in the system to attach to the experiment.
|
|
578
|
-
:param entry_name: Name of the attachment entry to create in the experiment.
|
|
579
|
-
:param tab: The tab where the attachment will be added. If not provided, the tab initialized by this helper
|
|
580
|
-
will be used.
|
|
581
|
-
:return: The created entry object.
|
|
582
|
-
"""
|
|
583
|
-
# Check if the tab has been initialized or a tab has been provided.
|
|
584
|
-
# This is redundant with the same check in the add_attachment_entry function, but it's duplicated here as to
|
|
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.")
|
|
589
|
-
|
|
590
|
-
with open(file_path, 'rb') as f:
|
|
591
|
-
file_contents: bytes = f.read()
|
|
592
|
-
return self.add_attachment_entry(file_path, file_contents, entry_name, tab)
|
|
593
|
-
|
|
594
|
-
# TODO: Remove this once pylib's gauge chart definition is up to date.
|
|
595
|
-
@staticmethod
|
|
596
|
-
def _create_gauge_chart(protocol: ElnExperimentProtocol, data_source_step: ElnEntryStep, step_name: str,
|
|
597
|
-
field_name: str, status_field: str, group_by_field_name: str = "DataRecordName") \
|
|
598
|
-
-> ElnEntryStep:
|
|
599
|
-
"""
|
|
600
|
-
Create a gauge chart step in the experiment protocol.
|
|
601
|
-
"""
|
|
602
|
-
if not data_source_step.get_data_type_names():
|
|
603
|
-
raise ValueError("The data source step did not declare a data type name.")
|
|
604
|
-
data_type_name: str = data_source_step.get_data_type_names()[0]
|
|
605
|
-
series = GaugeChartSeries(data_type_name, field_name)
|
|
606
|
-
series.operation_type = ChartOperationType.VALUE
|
|
607
|
-
chart = _GaugeChartDefinition()
|
|
608
|
-
chart.main_data_type_name = data_type_name
|
|
609
|
-
chart.status_field = status_field
|
|
610
|
-
chart.minimum_value = 0.
|
|
611
|
-
chart.maximum_value = 100.
|
|
612
|
-
chart.series_list = [series]
|
|
613
|
-
chart.grouping_type = ChartGroupingType.GROUP_BY_FIELD
|
|
614
|
-
chart.grouping_type_data_type_name = data_type_name
|
|
615
|
-
chart.grouping_type_data_field_name = group_by_field_name
|
|
616
|
-
dashboard, step = ELNStepFactory._create_dashboard_step_from_chart(chart, data_source_step, protocol, step_name,
|
|
617
|
-
None)
|
|
618
|
-
protocol.invalidate()
|
|
619
|
-
return step
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
# TODO: Using this to set the new status field setting.
|
|
623
|
-
class _GaugeChartDefinition(GaugeChartDefinition):
|
|
624
|
-
status_field: str
|
|
625
|
-
|
|
626
|
-
def to_json(self) -> dict[str, Any]:
|
|
627
|
-
result = super().to_json()
|
|
628
|
-
result["statusValueField"] = {
|
|
629
|
-
"dataTypeName": self.main_data_type_name,
|
|
630
|
-
"dataFieldName": self.status_field
|
|
631
|
-
}
|
|
632
|
-
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/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.12a433}/src/sapiopycommons/chem/Molecules.py
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/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
|
|
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.12a433}/src/sapiopycommons/files/file_util.py
RENAMED
|
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.12a433}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/src/sapiopycommons/samples/aliquot.py
RENAMED
|
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.12a433}/tests/AF-A0A009IHW8-F1-model_v4.cif
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/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.12a433}/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.12a433}/tests/flowcyto/8_color_ICS.wsp
RENAMED
|
File without changes
|
{sapiopycommons-2025.2.12a432 → sapiopycommons-2025.2.12a433}/tests/flowcyto/COVID19_W_001_O.fcs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|