sapiopycommons 2025.4.8a474__py3-none-any.whl → 2025.4.9a150__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 (42) hide show
  1. sapiopycommons/callbacks/callback_util.py +392 -1262
  2. sapiopycommons/callbacks/field_builder.py +0 -2
  3. sapiopycommons/chem/Molecules.py +2 -0
  4. sapiopycommons/customreport/term_builder.py +1 -1
  5. sapiopycommons/datatype/attachment_util.py +2 -4
  6. sapiopycommons/datatype/data_fields.py +1 -23
  7. sapiopycommons/eln/experiment_handler.py +279 -933
  8. sapiopycommons/eln/experiment_report_util.py +10 -15
  9. sapiopycommons/eln/plate_designer.py +59 -159
  10. sapiopycommons/files/file_bridge.py +0 -76
  11. sapiopycommons/files/file_bridge_handler.py +110 -325
  12. sapiopycommons/files/file_data_handler.py +2 -2
  13. sapiopycommons/files/file_util.py +15 -40
  14. sapiopycommons/files/file_validator.py +5 -6
  15. sapiopycommons/files/file_writer.py +1 -1
  16. sapiopycommons/flowcyto/flow_cyto.py +1 -1
  17. sapiopycommons/general/accession_service.py +3 -3
  18. sapiopycommons/general/aliases.py +28 -51
  19. sapiopycommons/general/audit_log.py +2 -2
  20. sapiopycommons/general/custom_report_util.py +1 -24
  21. sapiopycommons/general/exceptions.py +2 -41
  22. sapiopycommons/general/popup_util.py +2 -2
  23. sapiopycommons/multimodal/multimodal.py +0 -1
  24. sapiopycommons/processtracking/custom_workflow_handler.py +30 -46
  25. sapiopycommons/recordmodel/record_handler.py +159 -547
  26. sapiopycommons/rules/eln_rule_handler.py +30 -41
  27. sapiopycommons/rules/on_save_rule_handler.py +30 -41
  28. sapiopycommons/webhook/webhook_handlers.py +55 -448
  29. sapiopycommons/webhook/webservice_handlers.py +2 -2
  30. {sapiopycommons-2025.4.8a474.dist-info → sapiopycommons-2025.4.9a150.dist-info}/METADATA +1 -1
  31. sapiopycommons-2025.4.9a150.dist-info/RECORD +59 -0
  32. sapiopycommons/customreport/auto_pagers.py +0 -281
  33. sapiopycommons/eln/experiment_cache.py +0 -173
  34. sapiopycommons/eln/experiment_step_factory.py +0 -474
  35. sapiopycommons/eln/experiment_tags.py +0 -7
  36. sapiopycommons/eln/step_creation.py +0 -235
  37. sapiopycommons/general/data_structure_util.py +0 -115
  38. sapiopycommons/general/directive_util.py +0 -86
  39. sapiopycommons/samples/aliquot.py +0 -48
  40. sapiopycommons-2025.4.8a474.dist-info/RECORD +0 -67
  41. {sapiopycommons-2025.4.8a474.dist-info → sapiopycommons-2025.4.9a150.dist-info}/WHEEL +0 -0
  42. {sapiopycommons-2025.4.8a474.dist-info → sapiopycommons-2025.4.9a150.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sapiopycommons
3
- Version: 2025.4.8a474
3
+ Version: 2025.4.9a150
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>
@@ -0,0 +1,59 @@
1
+ sapiopycommons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sapiopycommons/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ sapiopycommons/callbacks/callback_util.py,sha256=2pGr14f_FVCqeF6dsbYP1e4cOsaUTQyQ7XVwZuUkM_k,67952
4
+ sapiopycommons/callbacks/field_builder.py,sha256=p2XacN99MuKk3ite8GAqstUMpixqugul2CsC4gB83-o,38620
5
+ sapiopycommons/chem/IndigoMolecules.py,sha256=slM2y39zZFHc468c366EqR8T-GYJ24UnM9HWAqWFEwQ,3900
6
+ sapiopycommons/chem/Molecules.py,sha256=9B4sbwbvYs50XHRn0TZiu-D1Oa3pKrI9qE5vNW8zv-U,12464
7
+ sapiopycommons/chem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ sapiopycommons/customreport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ sapiopycommons/customreport/column_builder.py,sha256=0RO53e9rKPZ07C--KcepN6_tpRw_FxF3O9vdG0ilKG8,3014
10
+ sapiopycommons/customreport/custom_report_builder.py,sha256=BlTxZ4t1sfZA2Ciur1EfYvkZxHxJ7ADwYNAe2zwiN0c,7176
11
+ sapiopycommons/customreport/term_builder.py,sha256=PNp71NF1vFxidk5v6uQNi9oQR9KJIk8WfhyntvvZN-U,18573
12
+ sapiopycommons/datatype/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ sapiopycommons/datatype/attachment_util.py,sha256=_l2swuP8noIGAl4bwzBUEhr6YlN_OVZl3-gi1XqFHYA,3364
14
+ sapiopycommons/datatype/data_fields.py,sha256=g8Ib6LH8ikNu9AzeVJs8Z2qS8A-cplACeFU7vYguNEY,4063
15
+ sapiopycommons/datatype/pseudo_data_types.py,sha256=6TG7aJxgmUZ8FQkWBcgmbK5oy7AFFNtKOPpi1w1OOYA,27657
16
+ sapiopycommons/eln/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ sapiopycommons/eln/experiment_handler.py,sha256=ZSx0uZy-2OtH_ArHy2OVwoNI3BYQLXSHGBmjviZl1Fw,69283
18
+ sapiopycommons/eln/experiment_report_util.py,sha256=bs9zSUgRo40q2VYtjDCVPsT-D1umdNfl5s4oZrY4uuc,36597
19
+ sapiopycommons/eln/plate_designer.py,sha256=FYJfhhNq8hdfuXgDYOYHy6g0m2zNwQXZWF_MTPzElDg,7184
20
+ sapiopycommons/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ sapiopycommons/files/complex_data_loader.py,sha256=T39veNhvYl6j_uZjIIJ8Mk5Aa7otR5RB-g8XlAdkksA,1421
22
+ sapiopycommons/files/file_bridge.py,sha256=WwCVegk0OGA8eqho8chsOsLlqg1nXctO75zfh-rHF-g,5950
23
+ sapiopycommons/files/file_bridge_handler.py,sha256=bt2IfIsxJ4lcJYo_NHvCQ17ZV6C4fSAEa8Zcgixh7B4,14263
24
+ sapiopycommons/files/file_data_handler.py,sha256=SCsjODMJIPEBSsahzXUeOM7CfSCmYwPPoGAM6aOfelo,36743
25
+ sapiopycommons/files/file_util.py,sha256=wbL3rxcFc-t2mXaPWWkoFWYGopvTcQts9Wf-L5GkhT8,29498
26
+ sapiopycommons/files/file_validator.py,sha256=4OvY98ueJWPJdpndwnKv2nqVvLP9S2W7Il_dM0Y0ojo,28709
27
+ sapiopycommons/files/file_writer.py,sha256=96Xl8TTT46Krxe_J8rmmlEMtel4nzZB961f5Yqtl1-I,17616
28
+ sapiopycommons/flowcyto/flow_cyto.py,sha256=YlkKJR_zEHYRuNW0bnTqlTyZeXs0lOaeSCfG2fnfD7E,3227
29
+ sapiopycommons/flowcyto/flowcyto_data.py,sha256=mYKFuLbtpJ-EsQxLGtu4tNHVlygTxKixgJxJqD68F58,2596
30
+ sapiopycommons/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ sapiopycommons/general/accession_service.py,sha256=HYgyOsH_UaoRnoury-c2yTW8SeG4OtjLemdpCzoV4R8,13484
32
+ sapiopycommons/general/aliases.py,sha256=tdDBNWSGx6s39eHJ3n2kscc4xxW3ZYaUfDftct6FmJE,12910
33
+ sapiopycommons/general/audit_log.py,sha256=pvXPfLr4Rw2X52wAXJGsX5fsPllswnCqaFZQ181j_GU,8727
34
+ sapiopycommons/general/custom_report_util.py,sha256=6ZIg_Jl02TYUygfc5xqBoI1srPsSxLyxaJ9jwTolGcM,16671
35
+ sapiopycommons/general/exceptions.py,sha256=GY7fe0qOgoy4kQVn_Pn3tdzHsJZyNIpa6VCChg6tzuM,1813
36
+ sapiopycommons/general/popup_util.py,sha256=L-4qpTemSZdlD6_6oEsDYIzLOCiZgDK6wC6DqUwzOYA,31925
37
+ sapiopycommons/general/sapio_links.py,sha256=o9Z-8y2rz6AI0Cy6tq58ElPge9RBnisGc9NyccbaJxs,2610
38
+ sapiopycommons/general/storage_util.py,sha256=ovmK_jN7v09BoX07XxwShpBUC5WYQOM7dbKV_VeLXJU,8892
39
+ sapiopycommons/general/time_util.py,sha256=jU1urPoZRv6evNucR0-288EyT4PrsDpCr-H1-7BKq9A,12363
40
+ sapiopycommons/multimodal/multimodal.py,sha256=A1QsC8QTPmgZyPr7KtMbPRedn2Ie4WIErodUvQ9otgU,6724
41
+ sapiopycommons/multimodal/multimodal_data.py,sha256=0BeVPr9HaC0hNTF1v1phTIKGruvNnwerHsD994qJKBg,15099
42
+ sapiopycommons/processtracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ sapiopycommons/processtracking/custom_workflow_handler.py,sha256=B8KxQlwlLGkxnEid2jb9igGyGqrtcsBMhcOb9MfnO9E,23465
44
+ sapiopycommons/processtracking/endpoints.py,sha256=w5bziI2xC7450M95rCF8JpRwkoni1kEDibyAux9B12Q,10848
45
+ sapiopycommons/recordmodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ sapiopycommons/recordmodel/record_handler.py,sha256=5kCO5zsSg0kp3uYpgw1vf0WLHw30pMNC_6Bn3G7iQkI,64796
47
+ sapiopycommons/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
+ sapiopycommons/rules/eln_rule_handler.py,sha256=JYzDA_14D2nLnlqwbpIxVOrfKWzbOS27AYf4TQfGr4Q,10469
49
+ sapiopycommons/rules/on_save_rule_handler.py,sha256=Rkqvph20RbNq6m-RF4fbvCP-YfD2CZYBM2iTr3nl0eY,10236
50
+ sapiopycommons/sftpconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ sapiopycommons/sftpconnect/sftp_builder.py,sha256=lFK3FeXk-sFLefW0hqY8WGUQDeYiGaT6yDACzT_zFgQ,3015
52
+ sapiopycommons/webhook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ sapiopycommons/webhook/webhook_context.py,sha256=D793uLsb1691SalaPnBUk3rOSxn_hYLhdvkaIxjNXss,1909
54
+ sapiopycommons/webhook/webhook_handlers.py,sha256=MdsVK4bHffkMNmNWl0_qvu-5Lz8-qGu4Ryi7lZO1BZs,18586
55
+ sapiopycommons/webhook/webservice_handlers.py,sha256=Y5dHx_UFWFuSqaoPL6Re-fsKYRuxvCWZ8bj6KSZ3jfM,14285
56
+ sapiopycommons-2025.4.9a150.dist-info/METADATA,sha256=5O99CwAKXRdS1K_CNvmlDcEB68rcd-FkTOf-uzkKDUg,3142
57
+ sapiopycommons-2025.4.9a150.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
58
+ sapiopycommons-2025.4.9a150.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
59
+ sapiopycommons-2025.4.9a150.dist-info/RECORD,,
@@ -1,281 +0,0 @@
1
- from abc import ABC
2
- from copy import copy
3
- from queue import Queue
4
-
5
- from sapiopylib.rest.CustomReportService import CustomReportManager
6
- from sapiopylib.rest.DataMgmtService import DataMgmtServer
7
- from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, CustomReport, RawReportTerm, ReportColumn
8
- from sapiopylib.rest.pojo.datatype.FieldDefinition import FieldType
9
- # noinspection PyProtectedMember
10
- from sapiopylib.rest.utils.autopaging import SapioPyAutoPager, PagerResultCriteriaType, _default_report_page_size, \
11
- _default_record_page_size
12
- from sapiopylib.rest.utils.recordmodel.PyRecordModel import PyRecordModel
13
- from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType
14
-
15
- from sapiopycommons.general.aliases import FieldValue, UserIdentifier, AliasUtil, RecordModel
16
- from sapiopycommons.general.custom_report_util import CustomReportUtil
17
- from sapiopycommons.general.exceptions import SapioException
18
- from sapiopycommons.recordmodel.record_handler import RecordHandler
19
-
20
-
21
- # FR-47389: Create auto pagers for running custom/system/quick reports that return dictionaries or records for each row.
22
- class _DictReportPagerBase(SapioPyAutoPager[CustomReportCriteria, dict[str, FieldValue]], ABC):
23
- """
24
- A base class for automatically paging through a report and returning the results as a list of dictionaries.
25
- """
26
- _columns: list[ReportColumn]
27
- _report_man: CustomReportManager
28
-
29
- def __init__(self, user: UserIdentifier, first_page_criteria: CustomReportCriteria):
30
- self._columns = first_page_criteria.column_list
31
- super().__init__(AliasUtil.to_sapio_user(user), first_page_criteria)
32
- self._report_man = DataMgmtServer.get_custom_report_manager(self.user)
33
-
34
- def get_all_at_once(self) -> list[dict[str, FieldValue]]:
35
- """
36
- Get the results of all pages. Be cautious of client memory usage.
37
- """
38
- if self.has_iterated:
39
- raise BrokenPipeError("Cannot use this method if the iterator has already been used.")
40
- return [x for x in self]
41
-
42
- def default_first_page_criteria(self) -> PagerResultCriteriaType:
43
- raise ValueError("Cannot generate a default first page criteria for custom reports.")
44
-
45
- def get_next_page_result(self) -> tuple[CustomReportCriteria | None, Queue[dict[str, FieldValue]]]:
46
- report: CustomReport = self._report_man.run_custom_report(self.next_page_criteria)
47
- queue: Queue[dict[str, FieldValue]] = Queue()
48
- for row in _process_results(report.result_table, self._columns):
49
- queue.put(row)
50
- if report.has_next_page:
51
- next_page_criteria = copy(self.next_page_criteria)
52
- next_page_criteria.page_number += 1
53
- return next_page_criteria, queue
54
- else:
55
- return None, queue
56
-
57
-
58
- class CustomReportDictAutoPager(_DictReportPagerBase):
59
- """
60
- A class that automatically pages through a custom report and returns the results as a list of dictionaries.
61
- """
62
- def __init__(self, user: UserIdentifier, report_criteria: CustomReportCriteria,
63
- page_number: int = 0, page_size: int = _default_report_page_size):
64
- """
65
- :param user: The current webhook context or a user object to send requests from.
66
- :param report_criteria: The custom report criteria to run.
67
- :param page_number: The page number to start on. The first page is page 0.
68
- :param page_size: The number of results to return per page.
69
- """
70
- first_page_criteria: CustomReportCriteria = copy(report_criteria)
71
- first_page_criteria.page_number = page_number
72
- first_page_criteria.page_size = page_size
73
- super().__init__(user, first_page_criteria)
74
-
75
-
76
- class SystemReportDictAutoPager(_DictReportPagerBase):
77
- """
78
- A class that automatically pages through a system report and returns the results as a list of dictionaries.
79
-
80
- System reports are also known as predefined searches in the system and must be defined in the data designer for
81
- a specific data type. That is, saved searches created by users cannot be run using this function.
82
- """
83
- def __init__(self, user: UserIdentifier, report_name: str,
84
- page_number: int = 0, page_size: int = _default_report_page_size):
85
- """
86
- :param user: The current webhook context or a user object to send requests from.
87
- :param report_name: The name of the system report to run.
88
- :param page_number: The page number to start on. The first page is page 0.
89
- :param page_size: The number of results to return per page.
90
- """
91
- first_page_criteria: CustomReportCriteria = CustomReportUtil.get_system_report_criteria(user, report_name)
92
- first_page_criteria.page_number = page_number
93
- first_page_criteria.page_size = page_size
94
- super().__init__(user, first_page_criteria)
95
-
96
-
97
- class QuickReportDictAutoPager(_DictReportPagerBase):
98
- """
99
- A class that automatically pages through a quick report and returns the results as a list of dictionaries.
100
- """
101
- def __init__(self, user: UserIdentifier, report_term: RawReportTerm,
102
- page_number: int = 0, page_size: int = _default_report_page_size):
103
- """
104
- :param user: The current webhook context or a user object to send requests from.
105
- :param report_term: The raw report term to use for the quick report.
106
- :param page_number: The page number to start on. The first page is page 0.
107
- :param page_size: The number of results to return per page.
108
- """
109
- first_page_criteria: CustomReportCriteria = CustomReportUtil.get_quick_report_criteria(user, report_term)
110
- first_page_criteria.page_number = page_number
111
- first_page_criteria.page_size = page_size
112
- super().__init__(user, first_page_criteria)
113
-
114
-
115
- # CR-47491: Support providing a data type name string to receive PyRecordModels instead of requiring a WrapperType.
116
- class _RecordReportPagerBase(SapioPyAutoPager[CustomReportCriteria, WrappedType | PyRecordModel], ABC):
117
- """
118
- A base class for automatically paging through a report and returning the results as a list of records.
119
- """
120
- _columns: list[ReportColumn]
121
- _query_type: type[WrappedType] | str
122
- _data_type: str
123
- _rec_handler: RecordHandler
124
- _report_man: CustomReportManager
125
-
126
- def __init__(self, user: UserIdentifier, first_page_criteria: CustomReportCriteria,
127
- wrapper_type: type[WrappedType] | str):
128
- self._columns = first_page_criteria.column_list
129
- self._query_type = wrapper_type
130
- self._data_type = AliasUtil.to_data_type_name(wrapper_type)
131
- self._rec_handler = RecordHandler(user)
132
- super().__init__(AliasUtil.to_sapio_user(user), first_page_criteria)
133
- self._report_man = DataMgmtServer.get_custom_report_manager(self.user)
134
-
135
- def get_all_at_once(self) -> list[RecordModel]:
136
- """
137
- Get the results of all pages. Be cautious of client memory usage.
138
- """
139
- if self.has_iterated:
140
- raise BrokenPipeError("Cannot use this method if the iterator has already been used.")
141
- return [x for x in self]
142
-
143
- def default_first_page_criteria(self) -> PagerResultCriteriaType:
144
- raise ValueError("Cannot generate a default first page criteria for custom reports.")
145
-
146
- def get_next_page_result(self) -> tuple[CustomReportCriteria | None, Queue[WrappedType] | Queue[PyRecordModel]]:
147
- report: CustomReport = self._report_man.run_custom_report(self.next_page_criteria)
148
- queue = Queue()
149
- id_index: int = -1
150
- for i, column in enumerate(self._columns):
151
- if column.data_type_name == self._data_type and column.data_field_name == "RecordId":
152
- id_index = i
153
- break
154
- if id_index == -1:
155
- raise SapioException(f"This report does not contain a Record ID column for the given record model type "
156
- f"{self._data_type}.")
157
- ids: set[int] = {row[id_index] for row in report.result_table}
158
- for row in self._rec_handler.query_models_by_id(self._query_type, ids, page_size=report.page_size):
159
- queue.put(row)
160
- if report.has_next_page:
161
- next_page_criteria = copy(self.next_page_criteria)
162
- next_page_criteria.page_number += 1
163
- return next_page_criteria, queue
164
- else:
165
- return None, queue
166
-
167
-
168
- class CustomReportRecordAutoPager(_RecordReportPagerBase):
169
- """
170
- A class that automatically pages through a custom report and returns the results as a list of records.
171
- """
172
- def __init__(self, user: UserIdentifier, report_criteria: CustomReportCriteria,
173
- wrapper_type: type[WrappedType] | str, page_number: int = 0,
174
- page_size: int = _default_record_page_size):
175
- """
176
- :param user: The current webhook context or a user object to send requests from.
177
- :param report_criteria: The custom report criteria to run.
178
- :param wrapper_type: The record model wrapper type or data type name of the records being searched for.
179
- If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
180
- instead of WrappedRecordModels.
181
- :param page_number: The page number to start on. The first page is page 0.
182
- :param page_size: The number of results to return per page.
183
- """
184
- first_page_criteria: CustomReportCriteria = copy(report_criteria)
185
- _add_record_id_column(first_page_criteria, wrapper_type)
186
- first_page_criteria.page_number = page_number
187
- first_page_criteria.page_size = page_size
188
- super().__init__(user, first_page_criteria, wrapper_type)
189
-
190
-
191
- class SystemReportRecordAutoPager(_RecordReportPagerBase):
192
- """
193
- A class that automatically pages through a system report and returns the results as a list of records.
194
-
195
- System reports are also known as predefined searches in the system and must be defined in the data designer for
196
- a specific data type. That is, saved searches created by users cannot be run using this function.
197
- """
198
- def __init__(self, user: UserIdentifier, report_name: str, wrapper_type: type[WrappedType] | str,
199
- page_number: int = 0, page_size: int = _default_record_page_size):
200
- """
201
- :param user: The current webhook context or a user object to send requests from.
202
- :param report_name: The name of the system report to run.
203
- :param wrapper_type: The record model wrapper type or data type name of the records being searched for.
204
- If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
205
- instead of WrappedRecordModels.
206
- :param page_number: The page number to start on. The first page is page 0.
207
- :param page_size: The number of results to return per page.
208
- """
209
- first_page_criteria: CustomReportCriteria = CustomReportUtil.get_system_report_criteria(user, report_name)
210
- _add_record_id_column(first_page_criteria, wrapper_type)
211
- first_page_criteria.page_number = page_number
212
- first_page_criteria.page_size = page_size
213
- super().__init__(user, first_page_criteria, wrapper_type)
214
-
215
-
216
- class QuickReportRecordAutoPager(_RecordReportPagerBase):
217
- """
218
- A class that automatically pages through a quick report and returns the results as a list of records.
219
- """
220
- def __init__(self, user: UserIdentifier, report_term: RawReportTerm, wrapper_type: type[WrappedType] | str,
221
- page_number: int = 0, page_size: int = _default_record_page_size):
222
- """
223
- :param user: The current webhook context or a user object to send requests from.
224
- :param report_term: The raw report term to use for the quick report.
225
- :param wrapper_type: The record model wrapper type or data type name of the records being searched for.
226
- If a data type name was used instead of a model wrapper, then the returned records will be PyRecordModels
227
- instead of WrappedRecordModels.
228
- :param page_number: The page number to start on. The first page is page 0.
229
- :param page_size: The number of results to return per page.
230
- """
231
- if report_term.data_type_name != AliasUtil.to_data_type_name(wrapper_type):
232
- raise SapioException("The data type name of the report term must match the data type name of the wrapper type.")
233
- first_page_criteria: CustomReportCriteria = CustomReportUtil.get_quick_report_criteria(user, report_term)
234
- first_page_criteria.page_number = page_number
235
- first_page_criteria.page_size = page_size
236
- super().__init__(user, first_page_criteria, wrapper_type)
237
-
238
-
239
- def _add_record_id_column(report: CustomReportCriteria, wrapper_type: type[WrappedType] | str) -> None:
240
- """
241
- Given a custom report criteria, ensure that the report contains a Record ID column for the given record model's
242
- data type. Add one if it is missing.
243
- """
244
- dt: str = AliasUtil.to_data_type_name(wrapper_type)
245
- # Ensure that the root data type is the one we're looking for.
246
- report.root_data_type = dt
247
- # Enforce that the given custom report has a record ID column.
248
- if not any([x.data_type_name == dt and x.data_field_name == "RecordId" for x in report.column_list]):
249
- report.column_list.append(ReportColumn(dt, "RecordId", FieldType.LONG))
250
-
251
-
252
- def _process_results(rows: list[list[FieldValue]], columns: list[ReportColumn]) -> list[dict[str, FieldValue]]:
253
- """
254
- Given the results of a report as a list of row values and the report's columns, combine these lists to
255
- result in a singular list of dictionaries for each row in the results.
256
- """
257
- # It may be the case that two columns have the same data field name but differing data type names.
258
- # If this occurs, then we need to be able to differentiate these columns in the resulting dictionary.
259
- prepend_dt: set[str] = set()
260
- encountered_names: list[str] = []
261
- for column in columns:
262
- field_name: str = column.data_field_name
263
- if field_name in encountered_names:
264
- prepend_dt.add(field_name)
265
- else:
266
- encountered_names.append(field_name)
267
-
268
- ret: list[dict[str, FieldValue]] = []
269
- for row in rows:
270
- row_data: dict[str, FieldValue] = {}
271
- filter_row: bool = False
272
- for value, column in zip(row, columns):
273
- header: str = column.data_field_name
274
- # If two columns share the same data field name, prepend the data type name of the column to the
275
- # data field name.
276
- if header in prepend_dt:
277
- header = column.data_type_name + "." + header
278
- row_data.update({header: value})
279
- if filter_row is False:
280
- ret.append(row_data)
281
- return ret
@@ -1,173 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from weakref import WeakValueDictionary
4
-
5
- from sapiopylib.rest.ELNService import ElnManager
6
- from sapiopylib.rest.User import SapioUser
7
- from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition
8
- from sapiopylib.rest.pojo.eln.ElnExperiment import ElnTemplate, TemplateExperimentQuery
9
- from sapiopylib.rest.pojo.eln.SapioELNEnums import ElnBaseDataType
10
- from sapiopylib.rest.pojo.eln.field_set import ElnFieldSetInfo
11
- from sapiopylib.rest.pojo.eln.protocol_template import ProtocolTemplateInfo, ProtocolTemplateQuery
12
-
13
- from sapiopycommons.general.aliases import UserIdentifier, AliasUtil
14
- from sapiopycommons.general.exceptions import SapioException
15
-
16
-
17
- # FR-47530: Created a class that caches experiment template and predefined field information.
18
- class ExperimentCacheManager:
19
- """
20
- A class to manage the caching of experiment-related information.
21
- """
22
- user: SapioUser
23
- eln_man: ElnManager
24
-
25
- _templates: list[ElnTemplate]
26
- """A list of experiment templates. Only cached when first accessed."""
27
- _protocols: list[ProtocolTemplateInfo]
28
- """A list of protocol templates. Only cached when first accessed."""
29
- _field_sets: dict[str, ElnFieldSetInfo]
30
- """A dictionary of field set name to field set. Only cached when first accessed."""
31
- _field_set_fields: dict[int, list[AbstractVeloxFieldDefinition]]
32
- """A dictionary of field set ID to field definitions. Only cached when first accessed."""
33
- _predefined_fields: dict[str, dict[str, AbstractVeloxFieldDefinition]]
34
- """A dictionary of ELN data type name to predefined field definitions. Only cached when first accessed."""
35
-
36
- __instances: WeakValueDictionary[SapioUser, ExperimentCacheManager] = WeakValueDictionary()
37
- __initialized: bool
38
-
39
- def __new__(cls, context: UserIdentifier):
40
- """
41
- :param context: The current webhook context or a user object to send requests from.
42
- """
43
- user = AliasUtil.to_sapio_user(context)
44
- obj = cls.__instances.get(user)
45
- if not obj:
46
- obj = object.__new__(cls)
47
- obj.__initialized = False
48
- cls.__instances[user] = obj
49
- return obj
50
-
51
- def __init__(self, context: UserIdentifier):
52
- """
53
- :param context: The current webhook context or a user object to send requests from.
54
- """
55
- if self.__initialized:
56
- return
57
- self.__initialized = True
58
-
59
- self.user = AliasUtil.to_sapio_user(context)
60
- self.eln_man = ElnManager(self.user)
61
-
62
- self._field_set_fields = {}
63
- self._predefined_fields = {}
64
-
65
- def get_experiment_template(self, name: str, active: bool = True, version: int | None = None,
66
- first_match: bool = False) -> ElnTemplate:
67
- """
68
- Get the experiment template with the given information.
69
-
70
- :param name: The name of the template.
71
- :param active: Whether the template is marked as active.
72
- :param version: The version of the template to get. If None, the latest version will be returned.
73
- :param first_match: If true, returns the first match found. If false, raises an exception.
74
- :return: The experiment template with the given information.
75
- """
76
- if not hasattr(self, "_templates"):
77
- query = TemplateExperimentQuery()
78
- query.active_templates_only = False
79
- query.latest_version_only = False
80
- self._templates = self.eln_man.get_template_experiment_list(query)
81
- return self._find_template(self._templates, name, active, version, first_match)
82
-
83
-
84
- def get_protocol_template(self, name: str, active: bool = True, version: int | None = None,
85
- first_match: bool = False) -> ProtocolTemplateInfo:
86
- """
87
- Get the protocol template with the given information. Will throw an exception if multiple templates match
88
- the given information.
89
-
90
- :param name: The name of the template.
91
- :param active: Whether the template is marked as active.
92
- :param version: The version of the template to get. If None, the latest version will be returned.
93
- :param first_match: If true, returns the first match found. If false, raises an exception.
94
- :return: The protocol template with the given information.
95
- """
96
- if not hasattr(self, "_protocols"):
97
- query = ProtocolTemplateQuery()
98
- query.active_templates_only = False
99
- query.latest_version_only = False
100
- self._protocols = self.eln_man.get_protocol_template_info_list(query)
101
- return self._find_template(self._protocols, name, active, version, first_match)
102
-
103
- @staticmethod
104
- def _find_template(templates: list[ElnTemplate] | list[ProtocolTemplateInfo], name: str, active: bool,
105
- version: int, first_match: bool) -> ElnTemplate | ProtocolTemplateInfo:
106
- """
107
- Find the experiment or protocol template with the given information.
108
- """
109
- matches = []
110
- for template in templates:
111
- if template.template_name != name:
112
- continue
113
- if template.active != active:
114
- continue
115
- if version is not None and template.template_version != version:
116
- continue
117
- matches.append(template)
118
- if not matches:
119
- raise SapioException(f"No template with the name \"{name}\"" +
120
- ("" if version is None else f" and the version {version}") +
121
- f" found.")
122
- if not version:
123
- return max(matches, key=lambda x: x.template_version)
124
- if len(matches) > 1 and not first_match:
125
- raise SapioException(f"Multiple templates with the name \"{name}\" found.")
126
- return matches[0]
127
-
128
- def get_predefined_field(self, field_name: str, data_type: ElnBaseDataType) -> AbstractVeloxFieldDefinition:
129
- """
130
- Get the predefined field of the given name for the given ELN data type.
131
-
132
- :param field_name: The name of the field.
133
- :param data_type: The ELN data type of the field.
134
- :return: The predefined field of the given name for the given ELN data type.
135
- """
136
- return self.get_predefined_fields(data_type)[field_name]
137
-
138
- def get_predefined_fields(self, data_type: ElnBaseDataType) -> dict[str, AbstractVeloxFieldDefinition]:
139
- """
140
- Get the predefined fields for the given ELN data type.
141
-
142
- :param data_type: The ELN data type to get the predefined fields for.
143
- :return: A dictionary of field name to field definition for the given ELN data type.
144
- """
145
- if data_type.data_type_name not in self._predefined_fields:
146
- fields: list[AbstractVeloxFieldDefinition] = self.eln_man.get_predefined_fields(data_type)
147
- self._predefined_fields[data_type.data_type_name] = {x.data_field_name: x for x in fields}
148
- return self._predefined_fields[data_type.data_type_name]
149
-
150
- def get_field_set(self, name: str) -> ElnFieldSetInfo:
151
- """
152
- Get the field set with the given name.
153
-
154
- :param name: The name of the field set.
155
- :return: The field set with the given name.
156
- """
157
- if not hasattr(self, "_field_sets"):
158
- self._field_sets = {x.field_set_name: x for x in self.eln_man.get_field_set_info_list()}
159
- return self._field_sets[name]
160
-
161
- def get_field_set_fields(self, field_set: ElnFieldSetInfo | int) -> list[AbstractVeloxFieldDefinition]:
162
- """
163
- Get the fields of the given field set.
164
-
165
- :param field_set: The field set to get the fields from. Can be either an ElnFieldSetInfo object or an integer
166
- for the set ID.
167
- :return: The fields of the given field set.
168
- """
169
- field_set: int = field_set if isinstance(field_set, int) else field_set.field_set_id
170
- if field_set in self._field_set_fields:
171
- return self._field_set_fields[field_set]
172
- self._field_set_fields[field_set] = self.eln_man.get_predefined_fields_from_field_set_id(field_set)
173
- return self._field_set_fields[field_set]