sapiopycommons 2024.10.24a342__py3-none-any.whl → 2024.10.29a346__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- sapiopycommons/chem/IndigoMolecules.py +0 -1
- sapiopycommons/chem/Molecules.py +19 -77
- sapiopycommons/customreport/column_builder.py +2 -2
- sapiopycommons/customreport/custom_report_builder.py +9 -4
- sapiopycommons/datatype/data_fields.py +61 -0
- sapiopycommons/datatype/pseudo_data_types.py +440 -0
- sapiopycommons/eln/experiment_handler.py +57 -23
- sapiopycommons/eln/experiment_report_util.py +563 -28
- sapiopycommons/general/aliases.py +66 -12
- sapiopycommons/general/audit_log.py +40 -47
- sapiopycommons/general/time_util.py +6 -40
- sapiopycommons/multimodal/multimodal_data.py +3 -6
- sapiopycommons/processtracking/custom_workflow_handler.py +406 -0
- sapiopycommons/sftpconnect/__init__.py +0 -0
- sapiopycommons/sftpconnect/sftp_builder.py +69 -0
- sapiopycommons/webhook/webhook_context.py +39 -0
- {sapiopycommons-2024.10.24a342.dist-info → sapiopycommons-2024.10.29a346.dist-info}/METADATA +1 -1
- {sapiopycommons-2024.10.24a342.dist-info → sapiopycommons-2024.10.29a346.dist-info}/RECORD +20 -16
- sapiopycommons/flowcyto/flow_cyto.py +0 -77
- sapiopycommons/flowcyto/flowcyto_data.py +0 -75
- {sapiopycommons-2024.10.24a342.dist-info → sapiopycommons-2024.10.29a346.dist-info}/WHEEL +0 -0
- {sapiopycommons-2024.10.24a342.dist-info → sapiopycommons-2024.10.29a346.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,8 +5,10 @@ from sapiopylib.rest.User import SapioUser
|
|
|
5
5
|
from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
6
6
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
7
7
|
from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment
|
|
8
|
+
from sapiopylib.rest.pojo.eln.ExperimentEntry import ExperimentEntry
|
|
9
|
+
from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
|
|
8
10
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
9
|
-
from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol
|
|
11
|
+
from sapiopylib.rest.utils.Protocols import ElnExperimentProtocol, ElnEntryStep
|
|
10
12
|
from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
|
|
11
13
|
from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedRecordModel, WrappedType, WrapperField
|
|
12
14
|
|
|
@@ -31,8 +33,10 @@ dictionaries.."""
|
|
|
31
33
|
HasFieldWrappers = type[WrappedType] | WrappedRecordModel
|
|
32
34
|
"""An identifier for classes that have wrapper fields."""
|
|
33
35
|
ExperimentIdentifier = ElnExperimentProtocol | ElnExperiment | int
|
|
34
|
-
"""An ExperimentIdentifier is either an experiment protocol, experiment, or an integer for
|
|
36
|
+
"""An ExperimentIdentifier is either an experiment protocol, experiment, or an integer for the experiment's notebook
|
|
35
37
|
ID."""
|
|
38
|
+
ExperimentEntryIdentifier = ElnEntryStep | ExperimentEntry | int
|
|
39
|
+
"""An ExperimentEntryIdentifier is either an ELN entry step, experiment entry, or an integer for the entry's ID."""
|
|
36
40
|
FieldMap = dict[str, FieldValue]
|
|
37
41
|
"""A field map is simply a dict of data field names to values. The purpose of aliasing this is to help distinguish
|
|
38
42
|
any random dict in a webhook from one which is explicitly used for record fields."""
|
|
@@ -86,44 +90,50 @@ class AliasUtil:
|
|
|
86
90
|
return record if isinstance(record, int) else record.record_id
|
|
87
91
|
|
|
88
92
|
@staticmethod
|
|
89
|
-
def to_data_type_name(value: DataTypeIdentifier) -> str:
|
|
93
|
+
def to_data_type_name(value: DataTypeIdentifier, convert_eln_dts: bool = True) -> str:
|
|
90
94
|
"""
|
|
91
95
|
Convert a given value to a data type name.
|
|
92
96
|
|
|
93
97
|
:param value: A value which is a string, record, or record model type.
|
|
98
|
+
:param convert_eln_dts: If true, convert ELN data types to their base data type name.
|
|
94
99
|
:return: A string of the data type name of the input value.
|
|
95
100
|
"""
|
|
96
|
-
if isinstance(value, str):
|
|
97
|
-
return value
|
|
98
101
|
if isinstance(value, SapioRecord):
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
value = value.data_type_name
|
|
103
|
+
elif not isinstance(value, str):
|
|
104
|
+
value = value.get_wrapper_data_type_name()
|
|
105
|
+
if convert_eln_dts and ElnBaseDataType.is_eln_type(value):
|
|
106
|
+
return ElnBaseDataType.get_base_type(value).data_type_name
|
|
107
|
+
return value
|
|
101
108
|
|
|
102
109
|
@staticmethod
|
|
103
|
-
def to_data_type_names(values: Iterable[DataTypeIdentifier], return_set: bool = False
|
|
110
|
+
def to_data_type_names(values: Iterable[DataTypeIdentifier], return_set: bool = False,
|
|
111
|
+
convert_eln_dts: bool = True) -> list[str] | set[str]:
|
|
104
112
|
"""
|
|
105
113
|
Convert a given iterable of values to a list or set of data type names.
|
|
106
114
|
|
|
107
115
|
:param values: An iterable of values which are strings, records, or record model types.
|
|
108
116
|
:param return_set: If true, return a set instead of a list.
|
|
117
|
+
:param convert_eln_dts: If true, convert ELN data types to their base data type name.
|
|
109
118
|
:return: A list or set of strings of the data type name of the input value.
|
|
110
119
|
"""
|
|
111
|
-
values = [AliasUtil.to_data_type_name(x) for x in values]
|
|
120
|
+
values = [AliasUtil.to_data_type_name(x, convert_eln_dts) for x in values]
|
|
112
121
|
return set(values) if return_set else values
|
|
113
122
|
|
|
114
123
|
@staticmethod
|
|
115
|
-
def to_singular_data_type_name(values: Iterable[DataTypeIdentifier]) -> str:
|
|
124
|
+
def to_singular_data_type_name(values: Iterable[DataTypeIdentifier], convert_eln_dts: bool = True) -> str:
|
|
116
125
|
"""
|
|
117
126
|
Convert a given iterable of values to a singular data type name that they share. Throws an exception if more
|
|
118
127
|
than one data type name exists in the provided list of identifiers.
|
|
119
128
|
|
|
120
129
|
:param values: An iterable of values which are strings, records, or record model types.
|
|
130
|
+
:param convert_eln_dts: If true, convert ELN data types to their base data type name.
|
|
121
131
|
:return: The single data type name that the input vales share. Returns an empty string if an empty iterable
|
|
122
132
|
was provided.
|
|
123
133
|
"""
|
|
124
134
|
if not values:
|
|
125
135
|
return ""
|
|
126
|
-
data_types: set[str] = AliasUtil.to_data_type_names(values, True)
|
|
136
|
+
data_types: set[str] = AliasUtil.to_data_type_names(values, True, convert_eln_dts)
|
|
127
137
|
if len(data_types) > 1:
|
|
128
138
|
raise SapioException(f"Provided values contain multiple data types: {data_types}. "
|
|
129
139
|
f"Only expecting a single data type.")
|
|
@@ -223,7 +233,51 @@ class AliasUtil:
|
|
|
223
233
|
if isinstance(experiment, ElnExperiment):
|
|
224
234
|
return experiment.notebook_experiment_id
|
|
225
235
|
return experiment.get_id()
|
|
226
|
-
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def to_notebook_ids(experiments: list[ExperimentIdentifier]) -> list[int]:
|
|
239
|
+
"""
|
|
240
|
+
Convert a list of objects that identify ELN experiments to their notebook IDs.
|
|
241
|
+
|
|
242
|
+
:return: The list of notebook IDs for the experiment identifiers.
|
|
243
|
+
"""
|
|
244
|
+
notebook_ids: list[int] = []
|
|
245
|
+
for experiment in experiments:
|
|
246
|
+
notebook_ids.append(AliasUtil.to_notebook_id(experiment))
|
|
247
|
+
return notebook_ids
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def to_entry_id(entry: ExperimentEntryIdentifier) -> int:
|
|
251
|
+
"""
|
|
252
|
+
Convert an object that identifies an experiment entry to its entry ID.
|
|
253
|
+
|
|
254
|
+
:return: The entry ID for the entry identifier.
|
|
255
|
+
"""
|
|
256
|
+
if isinstance(entry, int):
|
|
257
|
+
return entry
|
|
258
|
+
elif isinstance(entry, ExperimentEntry):
|
|
259
|
+
return entry.entry_id
|
|
260
|
+
elif isinstance(entry, ElnEntryStep):
|
|
261
|
+
return entry.get_id()
|
|
262
|
+
raise SapioException(f"Unrecognized entry identifier of type {type(entry)}")
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def to_entry_ids(entries: list[ExperimentEntryIdentifier]) -> list[int]:
|
|
266
|
+
"""
|
|
267
|
+
Convert a list of objects that identify experiment entries to their entry IDs.
|
|
268
|
+
|
|
269
|
+
:return: The list of entry IDs for the entry identifiers.
|
|
270
|
+
"""
|
|
271
|
+
entry_ids: list[int] = []
|
|
272
|
+
for entry in entries:
|
|
273
|
+
entry_ids.append(AliasUtil.to_entry_id(entry))
|
|
274
|
+
return entry_ids
|
|
275
|
+
|
|
227
276
|
@staticmethod
|
|
228
277
|
def to_sapio_user(context: UserIdentifier) -> SapioUser:
|
|
278
|
+
"""
|
|
279
|
+
Convert an object that could be either a SapioUser or SapioWebhookContext to just a SapioUser.
|
|
280
|
+
|
|
281
|
+
:return: A SapioUser object.
|
|
282
|
+
"""
|
|
229
283
|
return context if isinstance(context, SapioUser) else context.user
|
|
@@ -2,24 +2,13 @@ from enum import Enum
|
|
|
2
2
|
|
|
3
3
|
from sapiopylib.rest.User import SapioUser
|
|
4
4
|
from sapiopylib.rest.pojo.CustomReport import ReportColumn, CustomReportCriteria
|
|
5
|
-
from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
|
|
6
5
|
|
|
6
|
+
from sapiopycommons.customreport.column_builder import ColumnBuilder
|
|
7
7
|
from sapiopycommons.customreport.term_builder import TermBuilder
|
|
8
|
+
from sapiopycommons.datatype.pseudo_data_types import AuditLogPseudoDef
|
|
8
9
|
from sapiopycommons.general.aliases import RecordIdentifier, AliasUtil, UserIdentifier, FieldIdentifier, FieldValue
|
|
9
10
|
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
10
11
|
|
|
11
|
-
EVENTTYPE_COLUMN = "EVENTTYPE"
|
|
12
|
-
TIMESTAMP_COLUMN = "TIMESTAMP"
|
|
13
|
-
DATATYPENAME_COLUMN = "DATATYPENAME"
|
|
14
|
-
RECORDID_COLUMN = "RECORDID"
|
|
15
|
-
DESCRIPTION_COLUMN = "DESCRIPTION"
|
|
16
|
-
USERNAME_COLUMN = "USERNAME"
|
|
17
|
-
USERCOMMENT_COLUMN = "USERCOMMENT"
|
|
18
|
-
RECORDNAME_COLUMN = "RECORDNAME"
|
|
19
|
-
DATAFIELDNAME_COLUMN = "DATAFIELDNAME"
|
|
20
|
-
ORIGINALVALUE_COLUMN = "ORIGINALVALUE"
|
|
21
|
-
NEWVALUE_COLUMN = "NEWVALUE"
|
|
22
|
-
|
|
23
12
|
|
|
24
13
|
class EventType(Enum):
|
|
25
14
|
"""An enum to represent the possible event type values with the event type column in the audit log table."""
|
|
@@ -39,7 +28,6 @@ class EventType(Enum):
|
|
|
39
28
|
|
|
40
29
|
|
|
41
30
|
class AuditLogEntry:
|
|
42
|
-
|
|
43
31
|
__event_type: EventType
|
|
44
32
|
__date: int
|
|
45
33
|
__data_type_name: str
|
|
@@ -97,40 +85,41 @@ class AuditLogEntry:
|
|
|
97
85
|
return self.__new_value
|
|
98
86
|
|
|
99
87
|
def __init__(self, report_row: dict[str, FieldValue]):
|
|
100
|
-
self.__event_type = EventType((report_row[
|
|
101
|
-
self.__date = report_row[
|
|
102
|
-
self.__data_type_name = report_row[
|
|
103
|
-
self.__record_id = report_row[
|
|
104
|
-
self.__description = report_row[
|
|
105
|
-
self.__users_login_name = report_row[
|
|
106
|
-
self.__comment = report_row[
|
|
107
|
-
self.__data_record_name = report_row[
|
|
108
|
-
self.__data_field_name = report_row[
|
|
109
|
-
self.__original_value = report_row[
|
|
110
|
-
self.__new_value = report_row[
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class
|
|
114
|
-
AUDIT_LOG_PSEUDO_DATATYPE: str = "AUDITLOG"
|
|
115
|
-
EVENT_TYPE: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, EVENTTYPE_COLUMN, FieldType.ENUM)
|
|
116
|
-
DATE: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, TIMESTAMP_COLUMN, FieldType.DATE)
|
|
117
|
-
DATA_TYPE_NAME: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, DATATYPENAME_COLUMN, FieldType.STRING)
|
|
118
|
-
RECORD_ID: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, RECORDID_COLUMN, FieldType.LONG)
|
|
119
|
-
DESCRIPTION: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, DESCRIPTION_COLUMN, FieldType.STRING)
|
|
120
|
-
USERS_LOGIN_NAME: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, USERNAME_COLUMN, FieldType.STRING)
|
|
121
|
-
COMMENT: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, USERCOMMENT_COLUMN, FieldType.STRING)
|
|
122
|
-
DATA_RECORD_NAME: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, RECORDNAME_COLUMN, FieldType.STRING)
|
|
123
|
-
DATA_FIELD_NAME: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, DATAFIELDNAME_COLUMN, FieldType.STRING)
|
|
124
|
-
ORIGINAL_VALUE: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, ORIGINALVALUE_COLUMN, FieldType.STRING)
|
|
125
|
-
NEW_VALUE: ReportColumn = ReportColumn(AUDIT_LOG_PSEUDO_DATATYPE, NEWVALUE_COLUMN, FieldType.STRING)
|
|
126
|
-
|
|
127
|
-
AUDIT_LOG_COLUMNS = [EVENT_TYPE, DATE, DATA_TYPE_NAME, RECORD_ID, DESCRIPTION, USERS_LOGIN_NAME, COMMENT,
|
|
128
|
-
DATA_RECORD_NAME, DATA_FIELD_NAME, ORIGINAL_VALUE, NEW_VALUE]
|
|
88
|
+
self.__event_type = EventType((report_row[AuditLogPseudoDef.EVENT_TYPE__FIELD_NAME.field_name]))
|
|
89
|
+
self.__date = report_row[AuditLogPseudoDef.TIME_STAMP__FIELD_NAME.field_name]
|
|
90
|
+
self.__data_type_name = report_row[AuditLogPseudoDef.DATA_TYPE_NAME__FIELD_NAME.field_name]
|
|
91
|
+
self.__record_id = report_row[AuditLogPseudoDef.RECORD_ID__FIELD_NAME.field_name]
|
|
92
|
+
self.__description = report_row[AuditLogPseudoDef.DESCRIPTION__FIELD_NAME.field_name]
|
|
93
|
+
self.__users_login_name = report_row[AuditLogPseudoDef.USER_NAME__FIELD_NAME.field_name]
|
|
94
|
+
self.__comment = report_row[AuditLogPseudoDef.USER_COMMENT__FIELD_NAME.field_name]
|
|
95
|
+
self.__data_record_name = report_row[AuditLogPseudoDef.RECORD_NAME__FIELD_NAME]
|
|
96
|
+
self.__data_field_name = report_row[AuditLogPseudoDef.DATA_FIELD_NAME__FIELD_NAME.field_name]
|
|
97
|
+
self.__original_value = report_row[AuditLogPseudoDef.ORIGINAL_VALUE__FIELD_NAME.field_name]
|
|
98
|
+
self.__new_value = report_row[AuditLogPseudoDef.NEW_VALUE__FIELD_NAME.field_name]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AuditLogUtil:
|
|
129
102
|
user: SapioUser
|
|
130
103
|
|
|
131
104
|
def __init__(self, context: UserIdentifier):
|
|
132
105
|
self.user = AliasUtil.to_sapio_user(context)
|
|
133
106
|
|
|
107
|
+
@staticmethod
|
|
108
|
+
def report_columns() -> list[ReportColumn]:
|
|
109
|
+
return [
|
|
110
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.EVENT_TYPE__FIELD_NAME),
|
|
111
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.TIME_STAMP__FIELD_NAME),
|
|
112
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.DATA_TYPE_NAME__FIELD_NAME),
|
|
113
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.RECORD_ID__FIELD_NAME),
|
|
114
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.DESCRIPTION__FIELD_NAME),
|
|
115
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.USER_NAME__FIELD_NAME),
|
|
116
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.USER_COMMENT__FIELD_NAME),
|
|
117
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.RECORD_NAME__FIELD_NAME),
|
|
118
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.DATA_FIELD_NAME__FIELD_NAME),
|
|
119
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.ORIGINAL_VALUE__FIELD_NAME),
|
|
120
|
+
ColumnBuilder.build_column(AuditLogPseudoDef.DATA_TYPE_NAME, AuditLogPseudoDef.NEW_VALUE__FIELD_NAME)
|
|
121
|
+
]
|
|
122
|
+
|
|
134
123
|
@staticmethod
|
|
135
124
|
def create_data_record_audit_log_report(records: list[RecordIdentifier],
|
|
136
125
|
fields: list[FieldIdentifier] | None = None) -> CustomReportCriteria:
|
|
@@ -148,15 +137,19 @@ class AuditLog:
|
|
|
148
137
|
# Build the raw report term querying for any entry with a matching record ID value to the record ID's
|
|
149
138
|
# passed in.
|
|
150
139
|
record_ids = AliasUtil.to_record_ids(records)
|
|
151
|
-
root_term = TermBuilder.is_term(
|
|
140
|
+
root_term = TermBuilder.is_term(AuditLogPseudoDef.DATA_TYPE_NAME,
|
|
141
|
+
AuditLogPseudoDef.RECORD_ID__FIELD_NAME,
|
|
142
|
+
record_ids)
|
|
152
143
|
|
|
153
144
|
# If the user passed in any specific fields, then we should limit the query to those fields.
|
|
154
145
|
if fields:
|
|
155
146
|
fields: list[str] = AliasUtil.to_data_field_names(fields)
|
|
156
|
-
field_term = TermBuilder.is_term(
|
|
147
|
+
field_term = TermBuilder.is_term(AuditLogPseudoDef.DATA_TYPE_NAME,
|
|
148
|
+
AuditLogPseudoDef.DATA_FIELD_NAME__FIELD_NAME,
|
|
149
|
+
fields)
|
|
157
150
|
root_term = TermBuilder.and_terms(root_term, field_term)
|
|
158
151
|
|
|
159
|
-
return CustomReportCriteria(
|
|
152
|
+
return CustomReportCriteria(AuditLogUtil.report_columns(), root_term)
|
|
160
153
|
|
|
161
154
|
def run_data_record_audit_log_report(self, records: list[RecordIdentifier],
|
|
162
155
|
fields: list[FieldIdentifier] | None = None) \
|
|
@@ -172,7 +165,7 @@ class AuditLog:
|
|
|
172
165
|
"""
|
|
173
166
|
fields: list[str] = AliasUtil.to_data_field_names(fields)
|
|
174
167
|
# First, we must build our report criteria for running the Custom Report.
|
|
175
|
-
criteria =
|
|
168
|
+
criteria = AuditLogUtil.create_data_record_audit_log_report(records, fields)
|
|
176
169
|
|
|
177
170
|
# Then we must run the custom report using that criteria.
|
|
178
171
|
raw_report_data: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(self.user, criteria)
|
|
@@ -5,8 +5,6 @@ from datetime import datetime
|
|
|
5
5
|
|
|
6
6
|
import pytz
|
|
7
7
|
|
|
8
|
-
from sapiopycommons.general.exceptions import SapioException
|
|
9
|
-
|
|
10
8
|
__timezone = None
|
|
11
9
|
"""The default timezone. Use TimeUtil.set_default_timezone in a global context before making use of TimeUtil."""
|
|
12
10
|
|
|
@@ -96,9 +94,10 @@ class TimeUtil:
|
|
|
96
94
|
return TimeUtil.current_time(timezone).strftime(time_format)
|
|
97
95
|
|
|
98
96
|
@staticmethod
|
|
99
|
-
def millis_to_format(millis: int, time_format: str, timezone: str | int = None) -> str:
|
|
97
|
+
def millis_to_format(millis: int, time_format: str, timezone: str | int = None) -> str | None:
|
|
100
98
|
"""
|
|
101
|
-
Convert the input time in milliseconds to the provided format.
|
|
99
|
+
Convert the input time in milliseconds to the provided format. If None is passed to the millis parameter,
|
|
100
|
+
None will be returned
|
|
102
101
|
|
|
103
102
|
:param millis: The time in milliseconds to convert from.
|
|
104
103
|
:param time_format: The format to display the input time in. Documentation for how the time formatting works
|
|
@@ -107,6 +106,9 @@ class TimeUtil:
|
|
|
107
106
|
timezone variable set by the TimeUtil. A list of valid timezones can be found at
|
|
108
107
|
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. May also accept a UTC offset in seconds.
|
|
109
108
|
"""
|
|
109
|
+
if millis is None:
|
|
110
|
+
return None
|
|
111
|
+
|
|
110
112
|
tz = TimeUtil.__to_tz(timezone)
|
|
111
113
|
return datetime.fromtimestamp(millis / 1000, tz).strftime(time_format)
|
|
112
114
|
|
|
@@ -141,39 +143,3 @@ class TimeUtil:
|
|
|
141
143
|
return True
|
|
142
144
|
except Exception:
|
|
143
145
|
return False
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class DateRange:
|
|
147
|
-
start: int | None
|
|
148
|
-
end: int | None
|
|
149
|
-
|
|
150
|
-
@staticmethod
|
|
151
|
-
def from_string(value: str | None) -> DateRange:
|
|
152
|
-
"""
|
|
153
|
-
Construct a DateRange object from a string. The field value of date range fields is a string of the form
|
|
154
|
-
<start timestamp>/<end timestamp>.
|
|
155
|
-
|
|
156
|
-
:param value: A date range field value.
|
|
157
|
-
:return: A DateRange object matching the input field value.
|
|
158
|
-
"""
|
|
159
|
-
if not value:
|
|
160
|
-
return DateRange(None, None)
|
|
161
|
-
values: list[str] = value.split("/")
|
|
162
|
-
return DateRange(int(values[0]), int(values[1]))
|
|
163
|
-
|
|
164
|
-
def __init__(self, start: int | None, end: int | None):
|
|
165
|
-
"""
|
|
166
|
-
:param start: The timestamp for the start of the date range.
|
|
167
|
-
:param end: The timestamp for the end of the date rate.
|
|
168
|
-
"""
|
|
169
|
-
if (start and end is None) or (end and start is None):
|
|
170
|
-
raise SapioException("Both start and end values must be present in a date range.")
|
|
171
|
-
if start and end and end < start:
|
|
172
|
-
raise SapioException(f"End timestamp {end} is earlier than the start timestamp {start}.")
|
|
173
|
-
self.start = start
|
|
174
|
-
self.end = end
|
|
175
|
-
|
|
176
|
-
def __str__(self) -> str | None:
|
|
177
|
-
if not self.start and not self.end:
|
|
178
|
-
return None
|
|
179
|
-
return f"{self.start}/{self.end}"
|
|
@@ -38,9 +38,6 @@ class PyMolecule:
|
|
|
38
38
|
normError: str | None
|
|
39
39
|
desaltError: str | None
|
|
40
40
|
desaltedList: list[str] | None
|
|
41
|
-
registrationHash: str | None
|
|
42
|
-
hasOrGroup: bool
|
|
43
|
-
CXSMILESHash: str | None
|
|
44
41
|
|
|
45
42
|
|
|
46
43
|
@dataclass
|
|
@@ -103,9 +100,9 @@ class PyMoleculeLoaderResult:
|
|
|
103
100
|
compoundList: the compounds successfully loaded.
|
|
104
101
|
errorList: an error record is added here for each one we failed to load in Sapio.
|
|
105
102
|
"""
|
|
106
|
-
compoundByStr: dict[str, PyCompound]
|
|
107
|
-
compoundList: list[PyCompound]
|
|
108
|
-
errorList: list[ChemLoadingError]
|
|
103
|
+
compoundByStr: dict[str, PyCompound]
|
|
104
|
+
compoundList: list[PyCompound]
|
|
105
|
+
errorList: list[ChemLoadingError]
|
|
109
106
|
|
|
110
107
|
|
|
111
108
|
@dataclass
|