sapiopycommons 2024.11.8a355__py3-none-any.whl → 2024.11.9a360__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.

Files changed (47) hide show
  1. sapiopycommons/callbacks/callback_util.py +83 -532
  2. sapiopycommons/chem/IndigoMolecules.py +0 -2
  3. sapiopycommons/chem/Molecules.py +18 -77
  4. sapiopycommons/datatype/attachment_util.py +10 -11
  5. sapiopycommons/eln/experiment_handler.py +70 -272
  6. sapiopycommons/files/complex_data_loader.py +4 -5
  7. sapiopycommons/files/file_bridge.py +24 -31
  8. sapiopycommons/files/file_data_handler.py +5 -2
  9. sapiopycommons/files/file_util.py +9 -59
  10. sapiopycommons/files/file_validator.py +6 -92
  11. sapiopycommons/files/file_writer.py +15 -44
  12. sapiopycommons/general/aliases.py +6 -207
  13. sapiopycommons/general/custom_report_util.py +37 -212
  14. sapiopycommons/general/exceptions.py +8 -21
  15. sapiopycommons/general/popup_util.py +0 -21
  16. sapiopycommons/general/time_util.py +2 -8
  17. sapiopycommons/processtracking/endpoints.py +22 -22
  18. sapiopycommons/recordmodel/record_handler.py +97 -481
  19. sapiopycommons/rules/eln_rule_handler.py +25 -34
  20. sapiopycommons/rules/on_save_rule_handler.py +31 -34
  21. sapiopycommons/webhook/webhook_handlers.py +42 -201
  22. {sapiopycommons-2024.11.8a355.dist-info → sapiopycommons-2024.11.9a360.dist-info}/METADATA +2 -4
  23. sapiopycommons-2024.11.9a360.dist-info/RECORD +38 -0
  24. sapiopycommons/callbacks/field_builder.py +0 -537
  25. sapiopycommons/customreport/__init__.py +0 -0
  26. sapiopycommons/customreport/column_builder.py +0 -60
  27. sapiopycommons/customreport/custom_report_builder.py +0 -130
  28. sapiopycommons/customreport/term_builder.py +0 -299
  29. sapiopycommons/datatype/data_fields.py +0 -61
  30. sapiopycommons/datatype/pseudo_data_types.py +0 -440
  31. sapiopycommons/eln/experiment_report_util.py +0 -653
  32. sapiopycommons/files/file_bridge_handler.py +0 -340
  33. sapiopycommons/flowcyto/flow_cyto.py +0 -77
  34. sapiopycommons/flowcyto/flowcyto_data.py +0 -75
  35. sapiopycommons/general/accession_service.py +0 -375
  36. sapiopycommons/general/audit_log.py +0 -189
  37. sapiopycommons/general/sapio_links.py +0 -50
  38. sapiopycommons/multimodal/multimodal.py +0 -146
  39. sapiopycommons/multimodal/multimodal_data.py +0 -489
  40. sapiopycommons/processtracking/custom_workflow_handler.py +0 -406
  41. sapiopycommons/sftpconnect/__init__.py +0 -0
  42. sapiopycommons/sftpconnect/sftp_builder.py +0 -69
  43. sapiopycommons/webhook/webhook_context.py +0 -39
  44. sapiopycommons/webhook/webservice_handlers.py +0 -67
  45. sapiopycommons-2024.11.8a355.dist-info/RECORD +0 -59
  46. {sapiopycommons-2024.11.8a355.dist-info → sapiopycommons-2024.11.9a360.dist-info}/WHEEL +0 -0
  47. {sapiopycommons-2024.11.8a355.dist-info → sapiopycommons-2024.11.9a360.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,7 @@
1
- from __future__ import annotations
2
-
3
1
  import time
4
2
  from collections.abc import Mapping, Iterable
5
- from weakref import WeakValueDictionary
6
3
 
7
- from sapiopylib.rest.DataMgmtService import DataMgmtServer
8
4
  from sapiopylib.rest.ELNService import ElnManager
9
- from sapiopylib.rest.User import SapioUser
10
5
  from sapiopylib.rest.pojo.DataRecord import DataRecord
11
6
  from sapiopylib.rest.pojo.eln.ElnExperiment import ElnExperiment, TemplateExperimentQueryPojo, ElnTemplate, \
12
7
  InitializeNotebookExperimentPojo, ElnExperimentUpdateCriteria
@@ -21,11 +16,8 @@ from sapiopylib.rest.utils.Protocols import ElnEntryStep, ElnExperimentProtocol
21
16
  from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
22
17
  from sapiopylib.rest.utils.recordmodel.RecordModelManager import RecordModelInstanceManager, RecordModelManager
23
18
  from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
24
- from sapiopylib.rest.utils.recordmodel.properties import Child
25
19
 
26
- from sapiopycommons.eln.experiment_report_util import ExperimentReportUtil
27
- from sapiopycommons.general.aliases import AliasUtil, SapioRecord, ExperimentIdentifier, UserIdentifier, \
28
- DataTypeIdentifier, RecordModel
20
+ from sapiopycommons.general.aliases import AliasUtil, SapioRecord
29
21
  from sapiopycommons.general.exceptions import SapioException
30
22
 
31
23
  Step = str | ElnEntryStep
@@ -35,8 +27,7 @@ itself."""
35
27
 
36
28
  # FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
37
29
  class ExperimentHandler:
38
- user: SapioUser
39
- context: SapioWebhookContext | None
30
+ context: SapioWebhookContext
40
31
  """The context that this handler is working from."""
41
32
 
42
33
  # Basic experiment info from the context.
@@ -57,9 +48,9 @@ class ExperimentHandler:
57
48
  # additional queries to obtain, but may also be repeatedly accessed. In such cases, cache the information after it
58
49
  # has been requested so that the user doesn't need to worry about caching it themselves.
59
50
  # CR-46341: Replace class variables with instance variables.
60
- __exp_record: DataRecord | None
51
+ __exp_record: DataRecord
61
52
  """The data record for this experiment. Only cached when first accessed."""
62
- __exp_template: ElnTemplate | None
53
+ __exp_template: ElnTemplate
63
54
  """The template for this experiment. Only cached when first accessed."""
64
55
  __exp_options: dict[str, str]
65
56
  """Experiment options for this experiment. Only cached when first accessed."""
@@ -68,9 +59,9 @@ class ExperimentHandler:
68
59
  """Whether this ExperimentHandler has queried the system for all steps in the experiment."""
69
60
  __steps: dict[str, ElnEntryStep]
70
61
  """Steps from this experiment. All steps are cached the first time any individual step is accessed."""
71
- __step_options: dict[int, dict[str, str]]
72
- """Entry options for each step in this experiment. All entry options are cached the first time any individual step's
73
- options are queried. The cache is updated whenever the entry options for a step are changed by this handler."""
62
+ __step_options: dict[ElnEntryStep, dict[str, str]]
63
+ """Entry options for each step in this experiment. Only cached for each individual step when they are first accessed.
64
+ The cache is updated whenever the entry options for a step are changed by this handler."""
74
65
 
75
66
  # Constants
76
67
  __ENTRY_COMPLETE_STATUSES = [ExperimentEntryStatus.Completed, ExperimentEntryStatus.CompletedApproved]
@@ -86,103 +77,44 @@ class ExperimentHandler:
86
77
  ElnExperimentStatus.Canceled]
87
78
  """The set of statuses that an ELN experiment could have and be considered locked."""
88
79
 
89
- __instances: WeakValueDictionary[str, ExperimentHandler] = WeakValueDictionary()
90
- __initialized: bool
91
-
92
- def __new__(cls, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
93
- """
94
- :param context: The current webhook context or a user object to send requests from.
95
- :param experiment: If an experiment is provided that is separate from the experiment that is in the context,
96
- that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
97
- forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
98
- """
99
- param_results = cls.__parse_params(context, experiment)
100
- user = param_results[0]
101
- experiment = param_results[2]
102
- key = f"{user.__hash__()}:{experiment.notebook_experiment_id}"
103
- obj = cls.__instances.get(key)
104
- if not obj:
105
- obj = object.__new__(cls)
106
- obj.__initialized = False
107
- cls.__instances[key] = obj
108
- return obj
109
-
110
- def __init__(self, context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None):
80
+ def __init__(self, context: SapioWebhookContext, experiment: ElnExperiment | None = None):
111
81
  """
112
82
  Initialization will throw an exception if there is no ELN Experiment in the provided context and no experiment
113
83
  is provided.
114
84
 
115
- :param context: The current webhook context or a user object to send requests from.
85
+ :param context: The current webhook context.
116
86
  :param experiment: If an experiment is provided that is separate from the experiment that is in the context,
117
- that experiment will be used by this ExperimentHandler instead. An experiment can be provided in various
118
- forms, including an ElnExperiment, ElnExperimentProtocol, an experiment record, or a notebook experiment ID.
87
+ that experiment will be used by this ExperimentHandler instead.
119
88
  """
120
- param_results = self.__parse_params(context, experiment)
121
- self.user = param_results[0]
122
- self.context = param_results[1]
123
- experiment = param_results[2]
89
+ # FR-46495 - Allow the init function of ExperimentHandler to take in an ElnExperiment that is separate from the
90
+ # context.
91
+ if context.eln_experiment is None and experiment is None:
92
+ raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment in the context.")
93
+ if context.eln_experiment == experiment:
94
+ experiment = None
95
+ self.context = context
124
96
 
125
97
  # Get the basic information about this experiment that already exists in the context and is often used.
126
- self.__eln_exp = experiment
127
- self.__protocol = ElnExperimentProtocol(experiment, self.user)
98
+ self.__eln_exp = experiment if experiment else context.eln_experiment
99
+ self.__protocol = ElnExperimentProtocol(experiment, context.user) if experiment else context.active_protocol
128
100
  self.__exp_id = self.__protocol.get_id()
129
101
 
130
102
  # Grab various managers that may be used.
131
- self.__eln_man = DataMgmtServer.get_eln_manager(self.user)
132
- self.__inst_man = RecordModelManager(self.user).instance_manager
103
+ self.__eln_man = context.eln_manager
104
+ self.__inst_man = RecordModelManager(context.user).instance_manager
133
105
 
134
106
  # Create empty caches to fill when necessary.
135
107
  self.__steps = {}
136
108
  self.__step_options = {}
137
109
  # CR-46330: Cache any experiment entry information that might already exist in the context.
138
110
  self.__queried_all_steps = False
139
- # We can only trust the entries in the context if the experiment that this handler is for is the same as the
140
- # one from the context.
141
- if self.context is not None and self.context.eln_experiment == experiment:
142
- if self.context.experiment_entry is not None:
143
- self.__steps.update({self.context.active_step.get_name(): self.context.active_step})
144
- if self.context.experiment_entry_list is not None:
145
- for entry in self.context.experiment_entry_list:
146
- self.__steps.update({entry.entry_name: ElnEntryStep(self.__protocol, entry)})
147
-
148
- @staticmethod
149
- def __parse_params(context: UserIdentifier, experiment: ExperimentIdentifier | SapioRecord | None = None) \
150
- -> tuple[SapioUser, SapioWebhookContext | None, ElnExperiment]:
151
- if isinstance(context, SapioWebhookContext):
152
- user = context.user
153
- context = context
154
- else:
155
- user = context
156
- context = None
157
- if context is not None and context.eln_experiment is not None and experiment is None:
158
- experiment = context.eln_experiment
159
- # FR-46495 - Allow the init function of ExperimentHandler to take in an ElnExperiment that is separate from the
160
- # context.
161
- # CR-37038 - Allow other experiment object types to be provided. Convert them all down to ElnExperiment.
162
- if (context is None or context.eln_experiment is None) and experiment is not None:
163
- eln_manager = DataMgmtServer.get_eln_manager(user)
164
- # If this object is already an ElnExperiment, do nothing.
165
- if isinstance(experiment, ElnExperiment):
166
- pass
167
- # If this object is an ElnExperimentProtocol, then we can get the ElnExperiment from the object.
168
- elif isinstance(experiment, ElnExperimentProtocol):
169
- experiment: ElnExperiment = experiment.eln_experiment
170
- # If this object is an integer, assume it is a notebook ID that we can query the system with.
171
- elif isinstance(experiment, int):
172
- notebook_id: int = experiment
173
- experiment: ElnExperiment = eln_manager.get_eln_experiment_by_id(notebook_id)
174
- if not experiment:
175
- raise SapioException(f"No experiment with notebook ID {notebook_id} located in the system.")
176
- # If this object is a record, assume it is an experiment record that we can query the system with.
177
- else:
178
- record_id: int = AliasUtil.to_record_ids([experiment])[0]
179
- experiment: ElnExperiment = eln_manager.get_eln_experiment_by_record_id(record_id)
180
- if not experiment:
181
- raise SapioException(f"No experiment with record ID {record_id} located in the system.")
111
+ # We can only trust the entries in the context if no experiment was given as an input parameter.
182
112
  if experiment is None:
183
- raise SapioException("Cannot initialize ExperimentHandler. No ELN Experiment found in the provided parameters.")
184
-
185
- return user, context, experiment
113
+ if context.experiment_entry is not None:
114
+ self.__steps.update({context.active_step.get_name(): context.active_step})
115
+ if context.experiment_entry_list is not None:
116
+ for entry in context.experiment_entry_list:
117
+ self.__steps.update({entry.entry_name: ElnEntryStep(self.__protocol, entry)})
186
118
 
187
119
  # FR-46495: Split the creation of the experiment in launch_experiment into a create_experiment function.
188
120
  @staticmethod
@@ -277,7 +209,6 @@ class ExperimentHandler:
277
209
  """
278
210
  template_id: int | None = self.__eln_exp.template_id
279
211
  if template_id is None:
280
- self.__exp_template = None
281
212
  if exception_on_none:
282
213
  raise SapioException(f"Experiment with ID {self.__exp_id} has no template ID.")
283
214
  return None
@@ -332,7 +263,11 @@ class ExperimentHandler:
332
263
  :return: The data record for this experiment. None if it has no record.
333
264
  """
334
265
  if not hasattr(self, "_ExperimentHandler__exp_record"):
335
- self.__exp_record = self.__protocol.get_record()
266
+ drm = self.context.data_record_manager
267
+ dt = self.__eln_exp.experiment_data_type_name
268
+ results = drm.query_data_records_by_id(dt, [self.__eln_exp.experiment_record_id]).result_list
269
+ # PR-46504: Set the exp_record to None if there are no results.
270
+ self.__exp_record = results[0] if results else None
336
271
  if self.__exp_record is None and exception_on_none:
337
272
  raise SapioException(f"Experiment record not found for experiment with ID {self.__exp_id}.")
338
273
  return self.__exp_record
@@ -532,7 +467,7 @@ class ExperimentHandler:
532
467
  ret_list.append(step)
533
468
  return ret_list
534
469
 
535
- def get_all_steps(self, data_type: DataTypeIdentifier | None = None) -> list[ElnEntryStep]:
470
+ def get_all_steps(self, data_type: str | type[WrappedType] | None = None) -> list[ElnEntryStep]:
536
471
  """
537
472
  Get a list of every entry in the experiment. Optionally filter the returned entries by a data type.
538
473
 
@@ -548,42 +483,10 @@ class ExperimentHandler:
548
483
  all_steps: list[ElnEntryStep] = self.__protocol.get_sorted_step_list()
549
484
  if data_type is None:
550
485
  return all_steps
551
- data_type: str = AliasUtil.to_data_type_name(data_type)
486
+ if not isinstance(data_type, str):
487
+ data_type: str = data_type.get_wrapper_data_type_name()
552
488
  return [x for x in all_steps if data_type in x.get_data_type_names()]
553
489
 
554
- def get_step_by_option(self, key: str, value: str | None = None) -> ElnEntryStep:
555
- """
556
- Retrieve the step in this experiment that contains an entry option with the provided key and value.
557
- Throws an exception if no entries or multiple entries in the experiment match.
558
-
559
- :param key: The key of the entry option to match on.
560
- :param value: The value of the entry option to match on. If not provided, then only matches on key.
561
- :return: The entry in this experiment that matches the provided entry option key and value.
562
- """
563
- steps: list[ElnEntryStep] = self.get_steps_by_option(key, value)
564
- count: int = len(steps)
565
- if count != 1:
566
- option = key + ("::" + value if value is not None else "")
567
- raise SapioException(f"{('No' if count == 0 else 'Multiple')} entries in this experiment match the "
568
- f"provided option: {option}")
569
- return steps[0]
570
-
571
- def get_steps_by_option(self, key: str, value: str | None = None) -> list[ElnEntryStep]:
572
- """
573
- Retrieve every step in this experiment that contains an entry option with the provided key and value.
574
-
575
- :param key: The key of the entry option to match on.
576
- :param value: The value of the entry option to match on. If not provided, then only matches on key.
577
- :return: The entries in this experiment that match the provided entry option key and value.
578
- """
579
- ret_list: list[ElnEntryStep] = []
580
- for step in self.get_all_steps():
581
- options: dict[str, str] = self.get_step_options(step)
582
- if key in options:
583
- if value is None or options[key] == value:
584
- ret_list.append(step)
585
- return ret_list
586
-
587
490
  def get_step_records(self, step: Step) -> list[DataRecord]:
588
491
  """
589
492
  Query for the data records for the given step. The returned records are not cached by the ExperimentHandler.
@@ -633,15 +536,6 @@ class ExperimentHandler:
633
536
  The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
634
537
  """
635
538
  step = self.__to_eln_step(step)
636
- if not records:
637
- return
638
- dt: str = AliasUtil.to_singular_data_type_name(records)
639
- if ElnBaseDataType.is_base_data_type(dt):
640
- raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
641
- f"Use add_eln_rows or add_sample_details instead.")
642
- if dt != step.get_data_type_names()[0]:
643
- raise SapioException(f"Cannot add {dt} records to entry {step.get_name()} of type "
644
- f"{step.get_data_type_names()[0]}.")
645
539
  step.add_records(AliasUtil.to_data_records(records))
646
540
 
647
541
  def remove_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
@@ -660,15 +554,6 @@ class ExperimentHandler:
660
554
  The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
661
555
  """
662
556
  step = self.__to_eln_step(step)
663
- if not records:
664
- return
665
- dt: str = AliasUtil.to_singular_data_type_name(records)
666
- if ElnBaseDataType.is_base_data_type(dt):
667
- raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
668
- f"Use remove_eln_rows or remove_sample_details instead.")
669
- if dt != step.get_data_type_names()[0]:
670
- raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
671
- f"{step.get_data_type_names()[0]}.")
672
557
  step.remove_records(AliasUtil.to_data_records(records))
673
558
 
674
559
  def set_step_records(self, step: Step, records: Iterable[SapioRecord]) -> None:
@@ -692,17 +577,11 @@ class ExperimentHandler:
692
577
  The records may be provided as either DataRecords, PyRecordModels, or WrappedRecordModels.
693
578
  """
694
579
  step = self.__to_eln_step(step)
695
- if records:
696
- dt: str = AliasUtil.to_singular_data_type_name(records)
697
- if ElnBaseDataType.is_base_data_type(dt):
698
- raise SapioException(f"{dt} is an ELN data type. This function call has no effect on ELN data types. "
699
- f"Use add_eln_rows or add_sample_details instead.")
700
- if dt != step.get_data_type_names()[0]:
701
- raise SapioException(f"Cannot set {dt} records for entry {step.get_name()} of type "
702
- f"{step.get_data_type_names()[0]}.")
703
580
  step.set_records(AliasUtil.to_data_records(records))
704
581
 
705
582
  # FR-46496 - Provide alias of set_step_records for use with form entries.
583
+ # TODO: Provide a similar aliased function for attachment entries once sapiopylib allows setting multiple
584
+ # attachments to an attachment step.
706
585
  def set_form_record(self, step: Step, record: SapioRecord) -> None:
707
586
  """
708
587
  Sets the record for a form entry.
@@ -720,8 +599,7 @@ class ExperimentHandler:
720
599
  self.set_step_records(step, [record])
721
600
 
722
601
  # FR-46496 - Provide functions for adding and removing rows from an ELN data type entry.
723
- def add_eln_rows(self, step: Step, count: int, wrapper_type: type[WrappedType] | None = None) \
724
- -> list[PyRecordModel | WrappedType]:
602
+ def add_eln_rows(self, step: Step, count: int) -> list[PyRecordModel]:
725
603
  """
726
604
  Add rows to an ELNExperimentDetail or ELNSampleDetail table entry. The rows will not appear in the system
727
605
  until a record manager store and commit has occurred.
@@ -733,37 +611,15 @@ class ExperimentHandler:
733
611
  The step may be provided as either a string for the name of the step or an ElnEntryStep.
734
612
  If given a name, throws an exception if no step of the given name exists in the experiment.
735
613
  :param count: The number of new rows to add to the entry.
736
- :param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
737
- an unwrapped PyRecordModel.
738
614
  :return: A list of the newly created rows.
739
615
  """
740
616
  step = self.__to_eln_step(step)
741
617
  if step.eln_entry.entry_type != ElnEntryType.Table:
742
618
  raise SapioException("The provided step is not a table entry.")
743
619
  dt: str = step.get_data_type_names()[0]
744
- if not ElnBaseDataType.is_eln_type(dt):
620
+ if not self.__is_eln_type(dt):
745
621
  raise SapioException("The provided step is not an ELN data type entry.")
746
- records: list[PyRecordModel] = self.__inst_man.add_new_records(dt, count)
747
- if wrapper_type:
748
- return self.__inst_man.wrap_list(records, wrapper_type)
749
- return records
750
-
751
- def add_eln_row(self, step: Step, wrapper_type: type[WrappedType] | None = None) -> PyRecordModel | WrappedType:
752
- """
753
- Add a row to an ELNExperimentDetail or ELNSampleDetail table entry. The row will not appear in the system
754
- until a record manager store and commit has occurred.
755
-
756
- If no step functions have been called before and a step is being searched for by name, queries for the
757
- list of steps in the experiment and caches them.
758
-
759
- :param step:
760
- The step may be provided as either a string for the name of the step or an ElnEntryStep.
761
- If given a name, throws an exception if no step of the given name exists in the experiment.
762
- :param wrapper_type: Optionally wrap the ELN data type in a record model wrapper. If not provided, returns
763
- an unwrapped PyRecordModel.
764
- :return: The newly created row.
765
- """
766
- return self.add_eln_rows(step, 1, wrapper_type)[0]
622
+ return self.__inst_man.add_new_records(dt, count)
767
623
 
768
624
  def remove_eln_rows(self, step: Step, records: list[SapioRecord]) -> None:
769
625
  """
@@ -785,14 +641,10 @@ class ExperimentHandler:
785
641
  """
786
642
  step = self.__to_eln_step(step)
787
643
  dt: str = step.get_data_type_names()[0]
788
- if not ElnBaseDataType.is_eln_type(dt):
644
+ if not self.__is_eln_type(dt):
789
645
  raise SapioException("The provided step is not an ELN data type entry.")
790
- if not records:
791
- return
792
- record_dt: str = AliasUtil.to_singular_data_type_name(records, False)
793
- if record_dt != dt:
794
- raise SapioException(f"Cannot remove {dt} records from entry {step.get_name()} of type "
795
- f"{step.get_data_type_names()[0]}.")
646
+ if any([x.data_type_name != dt for x in records]):
647
+ raise SapioException("Not all of the provided records match the data type of the step.")
796
648
  # If any rows were provided as data records, turn them into record models before deleting them, as otherwise
797
649
  # this function would need to make a webservice call to do the deletion.
798
650
  data_records: list[DataRecord] = []
@@ -806,59 +658,16 @@ class ExperimentHandler:
806
658
  for record in record_models:
807
659
  record.delete()
808
660
 
809
- def remove_eln_row(self, step: Step, record: SapioRecord) -> None:
810
- """
811
- Remove a row from an ELNExperimentDetail or ELNSampleDetail table entry. ELN data type table entries display all
812
- records in the system that match the entry's data type. This means that removing rows from an ELN data type
813
- table entry is equivalent to deleting the records for the rows.
814
-
815
- The row will not be deleted in the system until a record manager store and commit has occurred.
816
-
817
- If no step functions have been called before and a step is being searched for by name, queries for the
818
- list of steps in the experiment and caches them.
819
-
820
- :param step:
821
- The step may be provided as either a string for the name of the step or an ElnEntryStep.
822
- If given a name, throws an exception if no step of the given name exists in the experiment.
823
- :param record:
824
- The record to remove from the given step.
825
- The record may be provided as either a DataRecord, PyRecordModel, or WrappedRecordModel.
826
- """
827
- self.remove_eln_row(step, [record])
828
-
829
- def add_sample_details(self, step: Step, samples: list[RecordModel], wrapper_type: type[WrappedType]) \
830
- -> list[PyRecordModel | WrappedType]:
831
- """
832
- Add sample details to a sample details entry while relating them to the input sample records.
833
-
834
- :param step:
835
- The step may be provided as either a string for the name of the step or an ElnEntryStep.
836
- If given a name, throws an exception if no step of the given name exists in the experiment.
837
- :param samples: The sample records to add the sample details to.
838
- :param wrapper_type: Optionally wrap the sample details in a record model wrapper. If not provided, returns
839
- an unwrapped PyRecordModel.
840
- :return: The newly created sample details. The indices of the samples in the input list match the index of the
841
- sample details in this list that they are related to.
842
- """
843
- step = self.__to_eln_step(step)
844
- if step.eln_entry.entry_type != ElnEntryType.Table:
845
- raise SapioException("The provided step is not a table entry.")
846
- dt: str = step.get_data_type_names()[0]
847
- if not ElnBaseDataType.is_eln_type(dt) or ElnBaseDataType.get_base_type(dt) != ElnBaseDataType.SAMPLE_DETAIL:
848
- raise SapioException("The provided step is not an ELNSampleDetail entry.")
849
- records: list[PyRecordModel] = []
850
- for sample in samples:
851
- if sample.data_type_name != "Sample":
852
- raise SapioException(f"Received a {sample.data_type_name} record when Sample records were expected.")
853
- detail: PyRecordModel = sample.add(Child.create_by_name(dt))
854
- detail.set_field_values({
855
- "SampleId": sample.get_field_value("SampleId"),
856
- "OtherSampleId": sample.get_field_value("OtherSampleId")
857
- })
858
- records.append(detail)
859
- if wrapper_type:
860
- return self.__inst_man.wrap_list(records, wrapper_type)
861
- return records
661
+ # TODO: Remove and use the function of the same name in ElnBaseDataType in the future. Currently this function is
662
+ # bugged in sapiopylib and is comparing against base_type.name instead of base_type.value.
663
+ @staticmethod
664
+ def __is_eln_type(data_type: str):
665
+ if data_type is None or not data_type:
666
+ return False
667
+ for base_type in ElnBaseDataType:
668
+ if data_type.lower().startswith(base_type.value.lower()):
669
+ return True
670
+ return False
862
671
 
863
672
  def update_step(self, step: Step,
864
673
  entry_name: str | None = None,
@@ -1010,7 +819,7 @@ class ExperimentHandler:
1010
819
  if clear_template_item_fulfilled_timestamp is True:
1011
820
  entry.template_item_fulfilled_timestamp = None
1012
821
  if entry_options_map is not None:
1013
- self.__step_options.update({step.get_id(): entry_options_map})
822
+ self.__step_options.update({step: entry_options_map})
1014
823
 
1015
824
  def get_step_option(self, step: Step, option: str) -> str:
1016
825
  """
@@ -1040,8 +849,8 @@ class ExperimentHandler:
1040
849
  list of steps in the experiment and caches them.
1041
850
 
1042
851
  Getting the step options requires a webservice query, which is made the first time any step option
1043
- method is called for any step in this experiment. The step options are cached so that subsequent calls of this
1044
- method don't make a webservice call.
852
+ method is called for a specific step. The step options are cached so that subsequent calls of this
853
+ method for that step don't make a webservice call.
1045
854
 
1046
855
  :param step:
1047
856
  The step to get the options of.
@@ -1049,10 +858,7 @@ class ExperimentHandler:
1049
858
  If given a name, throws an exception if no step of the given name exists in the experiment.
1050
859
  :return: The map of options for the input step.
1051
860
  """
1052
- step = self.__to_eln_step(step)
1053
- if step not in self.__step_options:
1054
- self.__step_options.update(ExperimentReportUtil.get_experiment_entry_options(self.user, self.get_all_steps()))
1055
- return self.__step_options[step.get_id()]
861
+ return self.__get_step_options(step)
1056
862
 
1057
863
  def add_step_options(self, step: Step, mapping: Mapping[str, str]):
1058
864
  """
@@ -1150,27 +956,6 @@ class ExperimentHandler:
1150
956
  step.unlock_step()
1151
957
  step.eln_entry.entry_status = ExperimentEntryStatus.UnlockedChangesRequired
1152
958
 
1153
- def disable_step(self, step: Step) -> None:
1154
- """
1155
- Set the status of the input step to Disabled. This is the state that entries are in when they are waiting for
1156
- entries that they are dependent upon to be submitted before they can be enabled. If you have unsubmitted an
1157
- entry and want its dependent entries to be locked again, then you would use this to set their status to
1158
- disabled.
1159
-
1160
- Makes a webservice call to update the step. Checks if the step is already unlocked, and does nothing if so.
1161
-
1162
- If no step functions have been called before and a step is being searched for by name, queries for the
1163
- list of steps in the experiment and caches them.
1164
-
1165
- :param step:
1166
- The step to disable.
1167
- The step may be provided as either a string for the name of the step or an ElnEntryStep.
1168
- If given a name, throws an exception if no step of the given name exists in the experiment.
1169
- """
1170
- step = self.__to_eln_step(step)
1171
- if step.eln_entry.entry_status in self.__ENTRY_LOCKED_STATUSES:
1172
- self.update_step(step, entry_status=ExperimentEntryStatus.Disabled)
1173
-
1174
959
  def step_is_submitted(self, step: Step) -> bool:
1175
960
  """
1176
961
  Determine if the input step has already been submitted.
@@ -1222,3 +1007,16 @@ class ExperimentHandler:
1222
1007
  return self.__exp_options
1223
1008
  self.__exp_options = self.__eln_man.get_notebook_experiment_options(self.__exp_id)
1224
1009
  return self.__exp_options
1010
+
1011
+ def __get_step_options(self, step: Step) -> dict[str, str]:
1012
+ """
1013
+ Cache the options for the input step if they haven't been cached yet.
1014
+
1015
+ :return: The entry options for the input step.
1016
+ """
1017
+ step = self.__to_eln_step(step)
1018
+ if step in self.__step_options:
1019
+ return self.__step_options.get(step)
1020
+ options: dict[str, str] = step.get_options()
1021
+ self.__step_options.update({step: options})
1022
+ return options
@@ -1,13 +1,12 @@
1
1
  import io
2
2
 
3
3
  from sapiopylib.rest.User import SapioUser
4
-
5
- from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
4
+ from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
6
5
 
7
6
 
8
7
  class CDL:
9
8
  @staticmethod
10
- def load_cdl(context: UserIdentifier, config_name: str, file_name: str, file_data: bytes | str) \
9
+ def load_cdl(context: SapioWebhookContext | SapioUser, config_name: str, file_name: str, file_data: bytes | str) \
11
10
  -> list[int]:
12
11
  """
13
12
  Create data records from a file using one of the complex data loader (CDL) configurations in the system.
@@ -23,8 +22,8 @@ class CDL:
23
22
  "configName": config_name,
24
23
  "fileName": file_name
25
24
  }
26
- user: SapioUser = AliasUtil.to_sapio_user(context)
27
- with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as data_stream:
25
+ user: SapioUser = context if isinstance(context, SapioUser) else context.user
26
+ with io.StringIO(file_data) if isinstance(file_data, str) else io.BytesIO(file_data) as data_stream:
28
27
  response = user.post_data_stream(sub_path, params=params, data_stream=data_stream)
29
28
  user.raise_for_status(response)
30
29
  # The response content is returned as bytes for a comma separated string of record IDs.