sapiopycommons 2025.7.14a610__py3-none-any.whl → 2025.7.15a611__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/ai/__init__.py +0 -0
- sapiopycommons/ai/tool_of_tools.py +917 -0
- sapiopycommons/callbacks/callback_util.py +22 -15
- sapiopycommons/chem/IndigoMolecules.py +0 -2
- sapiopycommons/files/file_text_converter.py +207 -0
- sapiopycommons/recordmodel/record_handler.py +161 -107
- sapiopycommons/webhook/webhook_handlers.py +6 -4
- {sapiopycommons-2025.7.14a610.dist-info → sapiopycommons-2025.7.15a611.dist-info}/METADATA +1 -1
- {sapiopycommons-2025.7.14a610.dist-info → sapiopycommons-2025.7.15a611.dist-info}/RECORD +11 -9
- sapiopycommons/chem/ps_commons.py +0 -763
- {sapiopycommons-2025.7.14a610.dist-info → sapiopycommons-2025.7.15a611.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.7.14a610.dist-info → sapiopycommons-2025.7.15a611.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,9 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
import io
|
|
4
4
|
import warnings
|
|
5
5
|
from collections.abc import Iterable
|
|
6
|
-
from typing import Collection
|
|
6
|
+
from typing import Collection, TypeVar, TypeAlias
|
|
7
7
|
from weakref import WeakValueDictionary
|
|
8
8
|
|
|
9
|
+
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap, FieldIdentifier, AliasUtil, \
|
|
10
|
+
FieldIdentifierMap, FieldValue, UserIdentifier, FieldIdentifierKey, DataTypeIdentifier
|
|
11
|
+
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
12
|
+
from sapiopycommons.general.exceptions import SapioException
|
|
9
13
|
from sapiopylib.rest.DataRecordManagerService import DataRecordManager
|
|
10
14
|
from sapiopylib.rest.User import SapioUser
|
|
11
15
|
from sapiopylib.rest.pojo.CustomReport import CustomReportCriteria, RawReportTerm, ReportColumn
|
|
@@ -24,19 +28,23 @@ from sapiopylib.rest.utils.recordmodel.RecordModelWrapper import WrappedType, Wr
|
|
|
24
28
|
from sapiopylib.rest.utils.recordmodel.RelationshipPath import RelationshipPath, RelationshipNode, \
|
|
25
29
|
RelationshipNodeType
|
|
26
30
|
from sapiopylib.rest.utils.recordmodel.ancestry import RecordModelAncestorManager
|
|
27
|
-
from sapiopylib.rest.utils.recordmodel.properties import Parents, Parent, Children, Child, ForwardSideLink
|
|
28
|
-
|
|
29
|
-
from sapiopycommons.general.aliases import RecordModel, SapioRecord, FieldMap, FieldIdentifier, AliasUtil, \
|
|
30
|
-
FieldIdentifierMap, FieldValue, UserIdentifier, FieldIdentifierKey, DataTypeIdentifier
|
|
31
|
-
from sapiopycommons.general.custom_report_util import CustomReportUtil
|
|
32
|
-
from sapiopycommons.general.exceptions import SapioException
|
|
31
|
+
from sapiopylib.rest.utils.recordmodel.properties import Parents, Parent, Children, Child, ForwardSideLink, \
|
|
32
|
+
ReverseSideLink
|
|
33
33
|
|
|
34
34
|
# Aliases for longer name.
|
|
35
|
-
_PropertyGetter = AbstractRecordModelPropertyGetter
|
|
36
|
-
_PropertyAdder = AbstractRecordModelPropertyAdder
|
|
37
|
-
_PropertyRemover = AbstractRecordModelPropertyRemover
|
|
38
|
-
_PropertySetter = AbstractRecordModelPropertySetter
|
|
39
|
-
_PropertyType = RecordModelPropertyType
|
|
35
|
+
_PropertyGetter: TypeAlias = AbstractRecordModelPropertyGetter
|
|
36
|
+
_PropertyAdder: TypeAlias = AbstractRecordModelPropertyAdder
|
|
37
|
+
_PropertyRemover: TypeAlias = AbstractRecordModelPropertyRemover
|
|
38
|
+
_PropertySetter: TypeAlias = AbstractRecordModelPropertySetter
|
|
39
|
+
_PropertyType: TypeAlias = RecordModelPropertyType
|
|
40
|
+
|
|
41
|
+
# CR-47717: Use TypeVars in the type hints of certain functions to prevent PyCharm from erroneously flagging certain
|
|
42
|
+
# return type hints as incorrect.
|
|
43
|
+
IsRecordModel = TypeVar('IsRecordModel', bound=RecordModel)
|
|
44
|
+
"""A PyRecordModel or AbstractRecordModel."""
|
|
45
|
+
IsSapioRecord = TypeVar('IsSapioRecord', bound=SapioRecord)
|
|
46
|
+
"""A DataRecord, PyRecordModel, or AbstractRecordModel."""
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
42
50
|
# FR-47575 - Reordered functions so that the Java and Python versions are as close to each other as possible.
|
|
@@ -524,9 +532,11 @@ class RecordHandler:
|
|
|
524
532
|
"""
|
|
525
533
|
warnings.warn("Deprecated in favor of the [System/Custom/Quick]ReportRecordAutoPager classes.", DeprecationWarning)
|
|
526
534
|
if isinstance(report_name, str):
|
|
535
|
+
# noinspection PyDeprecation
|
|
527
536
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_system_report(self.user, report_name, filters,
|
|
528
537
|
page_limit, page_size, page_number)
|
|
529
538
|
elif isinstance(report_name, RawReportTerm):
|
|
539
|
+
# noinspection PyDeprecation
|
|
530
540
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
531
541
|
page_limit, page_size, page_number)
|
|
532
542
|
elif isinstance(report_name, CustomReportCriteria):
|
|
@@ -539,6 +549,7 @@ class RecordHandler:
|
|
|
539
549
|
# Enforce that the given custom report has a record ID column.
|
|
540
550
|
if not any([x.data_type_name == dt and x.data_field_name == "RecordId" for x in report_name.column_list]):
|
|
541
551
|
report_name.column_list.append(ReportColumn(dt, "RecordId", FieldType.LONG))
|
|
552
|
+
# noinspection PyDeprecation
|
|
542
553
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(self.user, report_name, filters,
|
|
543
554
|
page_limit, page_size, page_number)
|
|
544
555
|
else:
|
|
@@ -551,7 +562,7 @@ class RecordHandler:
|
|
|
551
562
|
return self.query_models_by_id(wrapper_type, ids)
|
|
552
563
|
|
|
553
564
|
@staticmethod
|
|
554
|
-
def map_by_id(models: Iterable[
|
|
565
|
+
def map_by_id(models: Iterable[IsSapioRecord]) -> dict[int, IsSapioRecord]:
|
|
555
566
|
"""
|
|
556
567
|
Map the given records their record IDs.
|
|
557
568
|
|
|
@@ -560,12 +571,12 @@ class RecordHandler:
|
|
|
560
571
|
"""
|
|
561
572
|
ret_dict: dict[int, SapioRecord] = {}
|
|
562
573
|
for model in models:
|
|
563
|
-
ret_dict.update({model
|
|
574
|
+
ret_dict.update({AliasUtil.to_record_id(model): model})
|
|
564
575
|
return ret_dict
|
|
565
576
|
|
|
566
577
|
@staticmethod
|
|
567
|
-
def map_by_field(models: Iterable[
|
|
568
|
-
-> dict[FieldValue, list[
|
|
578
|
+
def map_by_field(models: Iterable[IsSapioRecord], field_name: FieldIdentifier) \
|
|
579
|
+
-> dict[FieldValue, list[IsSapioRecord]]:
|
|
569
580
|
"""
|
|
570
581
|
Map the given records by one of their fields. If any two records share the same field value, they'll appear in
|
|
571
582
|
the same value list.
|
|
@@ -582,8 +593,8 @@ class RecordHandler:
|
|
|
582
593
|
return ret_dict
|
|
583
594
|
|
|
584
595
|
@staticmethod
|
|
585
|
-
def map_by_unique_field(models: Iterable[
|
|
586
|
-
-> dict[FieldValue,
|
|
596
|
+
def map_by_unique_field(models: Iterable[IsSapioRecord], field_name: FieldIdentifier) \
|
|
597
|
+
-> dict[FieldValue, IsSapioRecord]:
|
|
587
598
|
"""
|
|
588
599
|
Uniquely map the given records by one of their fields. If any two records share the same field value, throws
|
|
589
600
|
an exception.
|
|
@@ -662,7 +673,7 @@ class RecordHandler:
|
|
|
662
673
|
return RecordHandler.sum_of_field(models, field_name) / len(models)
|
|
663
674
|
|
|
664
675
|
@staticmethod
|
|
665
|
-
def get_newest_record(records: Iterable[
|
|
676
|
+
def get_newest_record(records: Iterable[IsSapioRecord]) -> IsSapioRecord:
|
|
666
677
|
"""
|
|
667
678
|
Get the newest record from a list of records.
|
|
668
679
|
|
|
@@ -673,7 +684,7 @@ class RecordHandler:
|
|
|
673
684
|
|
|
674
685
|
# FR-46696: Add a function for getting the oldest record in a list, just like we have one for the newest record.
|
|
675
686
|
@staticmethod
|
|
676
|
-
def get_oldest_record(records: Iterable[
|
|
687
|
+
def get_oldest_record(records: Iterable[IsSapioRecord]) -> IsSapioRecord:
|
|
677
688
|
"""
|
|
678
689
|
Get the oldest record from a list of records.
|
|
679
690
|
|
|
@@ -683,7 +694,7 @@ class RecordHandler:
|
|
|
683
694
|
return min(records, key=lambda x: x.record_id)
|
|
684
695
|
|
|
685
696
|
@staticmethod
|
|
686
|
-
def get_min_record(records: list[
|
|
697
|
+
def get_min_record(records: list[IsSapioRecord], field: FieldIdentifier) -> IsSapioRecord:
|
|
687
698
|
"""
|
|
688
699
|
Get the record model with the minimum value of a given field from a list of record models.
|
|
689
700
|
|
|
@@ -695,7 +706,7 @@ class RecordHandler:
|
|
|
695
706
|
return min(records, key=lambda x: x.get_field_value(field))
|
|
696
707
|
|
|
697
708
|
@staticmethod
|
|
698
|
-
def get_max_record(records: list[
|
|
709
|
+
def get_max_record(records: list[IsSapioRecord], field: FieldIdentifier) -> IsSapioRecord:
|
|
699
710
|
"""
|
|
700
711
|
Get the record model with the maximum value of a given field from a list of record models.
|
|
701
712
|
|
|
@@ -870,7 +881,7 @@ class RecordHandler:
|
|
|
870
881
|
parent_dt: str = AliasUtil.to_data_type_name(parent_type)
|
|
871
882
|
wrapper: type[WrappedType] | None = parent_type if isinstance(parent_type, type) else None
|
|
872
883
|
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
873
|
-
parent: PyRecordModel | None = record.
|
|
884
|
+
parent: PyRecordModel | None = record.get(Parent.of_type_name(parent_dt))
|
|
874
885
|
if parent is not None:
|
|
875
886
|
return self.wrap_model(parent, wrapper) if wrapper else parent
|
|
876
887
|
return record.add(Parent.create(wrapper)) if wrapper else record.add(Parent.create_by_name(parent_dt))
|
|
@@ -888,7 +899,7 @@ class RecordHandler:
|
|
|
888
899
|
child_dt: str = AliasUtil.to_data_type_name(child_type)
|
|
889
900
|
wrapper: type[WrappedType] | None = child_type if isinstance(child_type, type) else None
|
|
890
901
|
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
891
|
-
child: PyRecordModel | None = record.
|
|
902
|
+
child: PyRecordModel | None = record.get(Child.of_type_name(child_dt))
|
|
892
903
|
if child is not None:
|
|
893
904
|
return self.wrap_model(child, wrapper) if wrapper else child
|
|
894
905
|
return record.add(Child.create(wrapper)) if wrapper else record.add(Child.create_by_name(child_dt))
|
|
@@ -908,7 +919,7 @@ class RecordHandler:
|
|
|
908
919
|
side_link_field: str = AliasUtil.to_data_field_name(side_link_field)
|
|
909
920
|
wrapper: type[WrappedType] | None = side_link_type if isinstance(side_link_type, type) else None
|
|
910
921
|
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
911
|
-
side_link: PyRecordModel | None = record.
|
|
922
|
+
side_link: PyRecordModel | None = record.get(ForwardSideLink.of(side_link_field))
|
|
912
923
|
if side_link is not None:
|
|
913
924
|
return self.wrap_model(side_link, wrapper) if wrapper else side_link
|
|
914
925
|
side_link: WrappedType | PyRecordModel = self.add_model(side_link_type)
|
|
@@ -955,52 +966,63 @@ class RecordHandler:
|
|
|
955
966
|
if child not in children:
|
|
956
967
|
record.remove(Child.ref(child))
|
|
957
968
|
|
|
969
|
+
# CR-47717: Update the map_[to/by]_[relationship] functions to allow PyRecordModels to be provided and returned
|
|
970
|
+
# instead of only using WrappedRecordModels and wrapper types.
|
|
958
971
|
@staticmethod
|
|
959
|
-
def map_to_parent(models: Iterable[
|
|
960
|
-
-> dict[
|
|
972
|
+
def map_to_parent(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
973
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
961
974
|
"""
|
|
962
975
|
Map a list of record models to a single parent of a given type. The parents must already be loaded.
|
|
963
976
|
|
|
964
977
|
:param models: A list of record models.
|
|
965
|
-
:param parent_type: The record model wrapper of the
|
|
978
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
979
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
966
980
|
:return: A dict[ModelType, ParentType]. If an input model doesn't have a parent of the given parent type, then
|
|
967
981
|
it will map to None.
|
|
968
982
|
"""
|
|
969
|
-
return_dict: dict[
|
|
983
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
970
984
|
for model in models:
|
|
971
|
-
|
|
985
|
+
if isinstance(parent_type, str):
|
|
986
|
+
return_dict[model] = model.get(Parent.of_type_name(parent_type))
|
|
987
|
+
else:
|
|
988
|
+
return_dict[model] = model.get(Parent.of_type(parent_type))
|
|
972
989
|
return return_dict
|
|
973
990
|
|
|
974
991
|
@staticmethod
|
|
975
|
-
def map_to_parents(models: Iterable[
|
|
976
|
-
-> dict[
|
|
992
|
+
def map_to_parents(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
993
|
+
-> dict[IsRecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
977
994
|
"""
|
|
978
995
|
Map a list of record models to a list parents of a given type. The parents must already be loaded.
|
|
979
996
|
|
|
980
997
|
:param models: A list of record models.
|
|
981
|
-
:param parent_type: The record model wrapper of the parents.
|
|
998
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
999
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
982
1000
|
:return: A dict[ModelType, list[ParentType]]. If an input model doesn't have a parent of the given parent type,
|
|
983
1001
|
then it will map to an empty list.
|
|
984
1002
|
"""
|
|
985
|
-
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
1003
|
+
return_dict: dict[WrappedRecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
986
1004
|
for model in models:
|
|
987
|
-
|
|
1005
|
+
if isinstance(parent_type, str):
|
|
1006
|
+
return_dict[model] = model.get(Parents.of_type_name(parent_type))
|
|
1007
|
+
else:
|
|
1008
|
+
return_dict[model] = model.get(Parents.of_type(parent_type))
|
|
988
1009
|
return return_dict
|
|
989
1010
|
|
|
990
1011
|
@staticmethod
|
|
991
|
-
def map_by_parent(models: Iterable[
|
|
992
|
-
-> dict[WrappedType,
|
|
1012
|
+
def map_by_parent(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
1013
|
+
-> dict[WrappedType | PyRecordModel, IsRecordModel]:
|
|
993
1014
|
"""
|
|
994
1015
|
Take a list of record models and map them by their parent. Essentially an inversion of map_to_parent.
|
|
995
1016
|
If two records share the same parent, an exception will be thrown. The parents must already be loaded.
|
|
996
1017
|
|
|
997
1018
|
:param models: A list of record models.
|
|
998
|
-
:param parent_type: The record model wrapper of the parents.
|
|
1019
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
1020
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
999
1021
|
:return: A dict[ParentType, ModelType]. If an input model doesn't have a parent of the given parent type,
|
|
1000
1022
|
then it will not be in the resulting dictionary.
|
|
1001
1023
|
"""
|
|
1002
|
-
to_parent: dict[
|
|
1003
|
-
by_parent: dict[WrappedType,
|
|
1024
|
+
to_parent: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler.map_to_parent(models, parent_type)
|
|
1025
|
+
by_parent: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1004
1026
|
for record, parent in to_parent.items():
|
|
1005
1027
|
if parent is None:
|
|
1006
1028
|
continue
|
|
@@ -1011,70 +1033,81 @@ class RecordHandler:
|
|
|
1011
1033
|
return by_parent
|
|
1012
1034
|
|
|
1013
1035
|
@staticmethod
|
|
1014
|
-
def map_by_parents(models: Iterable[
|
|
1015
|
-
-> dict[WrappedType, list[
|
|
1036
|
+
def map_by_parents(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
1037
|
+
-> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1016
1038
|
"""
|
|
1017
1039
|
Take a list of record models and map them by their parents. Essentially an inversion of map_to_parents. Input
|
|
1018
1040
|
models that share a parent will end up in the same list. The parents must already be loaded.
|
|
1019
1041
|
|
|
1020
1042
|
:param models: A list of record models.
|
|
1021
|
-
:param parent_type: The record model wrapper of the parents.
|
|
1043
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
1044
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1022
1045
|
:return: A dict[ParentType, list[ModelType]]. If an input model doesn't have a parent of the given parent type,
|
|
1023
1046
|
then it will not be in the resulting dictionary.
|
|
1024
1047
|
"""
|
|
1025
|
-
to_parents: dict[
|
|
1026
|
-
|
|
1048
|
+
to_parents: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = RecordHandler\
|
|
1049
|
+
.map_to_parents(models, parent_type)
|
|
1050
|
+
by_parents: dict[WrappedType | PyRecordModel, list[RecordModel]] = {}
|
|
1027
1051
|
for record, parents in to_parents.items():
|
|
1028
1052
|
for parent in parents:
|
|
1029
1053
|
by_parents.setdefault(parent, []).append(record)
|
|
1030
1054
|
return by_parents
|
|
1031
1055
|
|
|
1032
1056
|
@staticmethod
|
|
1033
|
-
def map_to_child(models: Iterable[
|
|
1034
|
-
-> dict[
|
|
1057
|
+
def map_to_child(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1058
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
1035
1059
|
"""
|
|
1036
1060
|
Map a list of record models to a single child of a given type. The children must already be loaded.
|
|
1037
1061
|
|
|
1038
1062
|
:param models: A list of record models.
|
|
1039
|
-
:param child_type: The record model wrapper of the
|
|
1063
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1064
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1040
1065
|
:return: A dict[ModelType, ChildType]. If an input model doesn't have a child of the given child type, then
|
|
1041
1066
|
it will map to None.
|
|
1042
1067
|
"""
|
|
1043
|
-
return_dict: dict[
|
|
1068
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
1044
1069
|
for model in models:
|
|
1045
|
-
|
|
1070
|
+
if isinstance(child_type, str):
|
|
1071
|
+
return_dict[model] = model.get(Child.of_type_name(child_type))
|
|
1072
|
+
else:
|
|
1073
|
+
return_dict[model] = model.get(Child.of_type(child_type))
|
|
1046
1074
|
return return_dict
|
|
1047
1075
|
|
|
1048
1076
|
@staticmethod
|
|
1049
|
-
def map_to_children(models: Iterable[
|
|
1050
|
-
-> dict[
|
|
1077
|
+
def map_to_children(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1078
|
+
-> dict[IsRecordModel, list[WrappedType] | PyRecordModel]:
|
|
1051
1079
|
"""
|
|
1052
1080
|
Map a list of record models to a list children of a given type. The children must already be loaded.
|
|
1053
1081
|
|
|
1054
1082
|
:param models: A list of record models.
|
|
1055
|
-
:param child_type: The record model wrapper of the children.
|
|
1083
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1084
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1056
1085
|
:return: A dict[ModelType, list[ChildType]]. If an input model doesn't have children of the given child type,
|
|
1057
1086
|
then it will map to an empty list.
|
|
1058
1087
|
"""
|
|
1059
|
-
return_dict: dict[
|
|
1088
|
+
return_dict: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
1060
1089
|
for model in models:
|
|
1061
|
-
|
|
1090
|
+
if isinstance(child_type, str):
|
|
1091
|
+
return_dict[model] = model.get(Children.of_type_name(child_type))
|
|
1092
|
+
else:
|
|
1093
|
+
return_dict[model] = model.get(Children.of_type(child_type))
|
|
1062
1094
|
return return_dict
|
|
1063
1095
|
|
|
1064
1096
|
@staticmethod
|
|
1065
|
-
def map_by_child(models: Iterable[
|
|
1066
|
-
-> dict[WrappedType,
|
|
1097
|
+
def map_by_child(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1098
|
+
-> dict[WrappedType | str, IsRecordModel]:
|
|
1067
1099
|
"""
|
|
1068
1100
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_child.
|
|
1069
1101
|
If two records share the same child, an exception will be thrown. The children must already be loaded.
|
|
1070
1102
|
|
|
1071
1103
|
:param models: A list of record models.
|
|
1072
|
-
:param child_type: The record model wrapper of the children.
|
|
1104
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1105
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1073
1106
|
:return: A dict[ChildType, ModelType]. If an input model doesn't have a child of the given child type,
|
|
1074
1107
|
then it will not be in the resulting dictionary.
|
|
1075
1108
|
"""
|
|
1076
|
-
to_child: dict[
|
|
1077
|
-
by_child: dict[WrappedType,
|
|
1109
|
+
to_child: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler.map_to_child(models, child_type)
|
|
1110
|
+
by_child: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1078
1111
|
for record, child in to_child.items():
|
|
1079
1112
|
if child is None:
|
|
1080
1113
|
continue
|
|
@@ -1085,45 +1118,50 @@ class RecordHandler:
|
|
|
1085
1118
|
return by_child
|
|
1086
1119
|
|
|
1087
1120
|
@staticmethod
|
|
1088
|
-
def map_by_children(models: Iterable[
|
|
1089
|
-
-> dict[WrappedType, list[
|
|
1121
|
+
def map_by_children(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1122
|
+
-> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1090
1123
|
"""
|
|
1091
1124
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_children. Input
|
|
1092
1125
|
models that share a child will end up in the same list. The children must already be loaded.
|
|
1093
1126
|
|
|
1094
1127
|
:param models: A list of record models.
|
|
1095
|
-
:param child_type: The record model wrapper of the children.
|
|
1128
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1129
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1096
1130
|
:return: A dict[ChildType, list[ModelType]]. If an input model doesn't have children of the given child type,
|
|
1097
1131
|
then it will not be in the resulting dictionary.
|
|
1098
1132
|
"""
|
|
1099
|
-
to_children: dict[
|
|
1100
|
-
|
|
1133
|
+
to_children: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = RecordHandler\
|
|
1134
|
+
.map_to_children(models, child_type)
|
|
1135
|
+
by_children: dict[WrappedType | PyRecordModel, list[RecordModel]] = {}
|
|
1101
1136
|
for record, children in to_children.items():
|
|
1102
1137
|
for child in children:
|
|
1103
1138
|
by_children.setdefault(child, []).append(record)
|
|
1104
1139
|
return by_children
|
|
1105
1140
|
|
|
1106
1141
|
@staticmethod
|
|
1107
|
-
def map_to_forward_side_link(models: Iterable[
|
|
1108
|
-
side_link_type: type[WrappedType]
|
|
1142
|
+
def map_to_forward_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1143
|
+
side_link_type: type[WrappedType] | None) \
|
|
1144
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
1109
1145
|
"""
|
|
1110
1146
|
Map a list of record models to their forward side link. The forward side link must already be loaded.
|
|
1111
1147
|
|
|
1112
1148
|
:param models: A list of record models.
|
|
1113
1149
|
:param field_name: The field name on the record models where the side link is located.
|
|
1114
|
-
:param side_link_type: The record model wrapper of the forward side link.
|
|
1150
|
+
:param side_link_type: The record model wrapper of the forward side link. If None, the side links will
|
|
1151
|
+
be returned as PyRecordModels instead of WrappedRecordModels.
|
|
1115
1152
|
:return: A dict[ModelType, SlideLink]. If an input model doesn't have a forward side link of the given type,
|
|
1116
1153
|
then it will map to None.
|
|
1117
1154
|
"""
|
|
1118
1155
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1119
|
-
return_dict: dict[
|
|
1156
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
1120
1157
|
for model in models:
|
|
1121
|
-
return_dict[model] = model.
|
|
1158
|
+
return_dict[model] = model.get(ForwardSideLink.of(field_name, side_link_type))
|
|
1122
1159
|
return return_dict
|
|
1123
1160
|
|
|
1124
1161
|
@staticmethod
|
|
1125
|
-
def map_by_forward_side_link(models: Iterable[
|
|
1126
|
-
side_link_type: type[WrappedType]
|
|
1162
|
+
def map_by_forward_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1163
|
+
side_link_type: type[WrappedType] | None) \
|
|
1164
|
+
-> dict[WrappedType | PyRecordModel, IsRecordModel]:
|
|
1127
1165
|
"""
|
|
1128
1166
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
1129
1167
|
map_to_forward_side_link, but if two records share the same forward link, an exception is thrown.
|
|
@@ -1131,14 +1169,15 @@ class RecordHandler:
|
|
|
1131
1169
|
|
|
1132
1170
|
:param models: A list of record models.
|
|
1133
1171
|
:param field_name: The field name on the record models where the side link is located.
|
|
1134
|
-
:param side_link_type: The record model wrapper of the forward side links.
|
|
1172
|
+
:param side_link_type: The record model wrapper of the forward side links. If None, the side links will
|
|
1173
|
+
be returned as PyRecordModels instead of WrappedRecordModels.
|
|
1135
1174
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a forward side link of the given type
|
|
1136
1175
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1137
1176
|
"""
|
|
1138
1177
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1139
|
-
to_side_link: dict[
|
|
1178
|
+
to_side_link: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler\
|
|
1140
1179
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
1141
|
-
by_side_link: dict[WrappedType,
|
|
1180
|
+
by_side_link: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1142
1181
|
for record, side_link in to_side_link.items():
|
|
1143
1182
|
if side_link is None:
|
|
1144
1183
|
continue
|
|
@@ -1149,8 +1188,9 @@ class RecordHandler:
|
|
|
1149
1188
|
return by_side_link
|
|
1150
1189
|
|
|
1151
1190
|
@staticmethod
|
|
1152
|
-
def map_by_forward_side_links(models: Iterable[
|
|
1153
|
-
side_link_type: type[WrappedType]
|
|
1191
|
+
def map_by_forward_side_links(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1192
|
+
side_link_type: type[WrappedType] | None) \
|
|
1193
|
+
-> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1154
1194
|
"""
|
|
1155
1195
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
1156
1196
|
map_to_forward_side_link. Input models that share a forward side link will end up in the same list.
|
|
@@ -1158,14 +1198,15 @@ class RecordHandler:
|
|
|
1158
1198
|
|
|
1159
1199
|
:param models: A list of record models.
|
|
1160
1200
|
:param field_name: The field name on the record models where the side link is located.
|
|
1161
|
-
:param side_link_type: The record model wrapper of the forward side links.
|
|
1201
|
+
:param side_link_type: The record model wrapper of the forward side links. If None, the side links will
|
|
1202
|
+
be returned as PyRecordModels instead of WrappedRecordModels.
|
|
1162
1203
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have a forward side link of the given type
|
|
1163
1204
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1164
1205
|
"""
|
|
1165
1206
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1166
|
-
to_side_link: dict[
|
|
1207
|
+
to_side_link: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler\
|
|
1167
1208
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
1168
|
-
by_side_link: dict[WrappedType, list[
|
|
1209
|
+
by_side_link: dict[WrappedType | PyRecordModel, list[RecordModel]] = {}
|
|
1169
1210
|
for record, side_link in to_side_link.items():
|
|
1170
1211
|
if side_link is None:
|
|
1171
1212
|
continue
|
|
@@ -1173,8 +1214,9 @@ class RecordHandler:
|
|
|
1173
1214
|
return by_side_link
|
|
1174
1215
|
|
|
1175
1216
|
@staticmethod
|
|
1176
|
-
def map_to_reverse_side_link(models: Iterable[
|
|
1177
|
-
side_link_type: type[WrappedType]
|
|
1217
|
+
def map_to_reverse_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1218
|
+
side_link_type: type[WrappedType] | str) \
|
|
1219
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
1178
1220
|
"""
|
|
1179
1221
|
Map a list of record models to the reverse side link of a given type. If a given record has more than one
|
|
1180
1222
|
reverse side link of this type, an exception is thrown. The reverse side links must already be loaded.
|
|
@@ -1182,14 +1224,18 @@ class RecordHandler:
|
|
|
1182
1224
|
:param models: A list of record models.
|
|
1183
1225
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1184
1226
|
located.
|
|
1185
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1227
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1228
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1186
1229
|
:return: A dict[ModelType, SideLink]. If an input model doesn't have reverse side links of the given type,
|
|
1187
1230
|
then it will map to None.
|
|
1188
1231
|
"""
|
|
1189
1232
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1190
|
-
return_dict: dict[
|
|
1233
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
1191
1234
|
for model in models:
|
|
1192
|
-
|
|
1235
|
+
if isinstance(side_link_type, str):
|
|
1236
|
+
links: list[WrappedType] = model.get(ReverseSideLink.of(side_link_type, field_name))
|
|
1237
|
+
else:
|
|
1238
|
+
links: list[WrappedType] = model.get(ReverseSideLink.of_type(side_link_type, field_name))
|
|
1193
1239
|
if len(links) > 1:
|
|
1194
1240
|
raise SapioException(f"Model {model.data_type_name} {model.record_id} has more than one reverse link "
|
|
1195
1241
|
f"of type {side_link_type.get_wrapper_data_type_name()}.")
|
|
@@ -1197,8 +1243,9 @@ class RecordHandler:
|
|
|
1197
1243
|
return return_dict
|
|
1198
1244
|
|
|
1199
1245
|
@staticmethod
|
|
1200
|
-
def map_to_reverse_side_links(models: Iterable[
|
|
1201
|
-
side_link_type: type[WrappedType]
|
|
1246
|
+
def map_to_reverse_side_links(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1247
|
+
side_link_type: type[WrappedType] | str) \
|
|
1248
|
+
-> dict[IsRecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
1202
1249
|
"""
|
|
1203
1250
|
Map a list of record models to a list reverse side links of a given type. The reverse side links must already
|
|
1204
1251
|
be loaded.
|
|
@@ -1206,19 +1253,24 @@ class RecordHandler:
|
|
|
1206
1253
|
:param models: A list of record models.
|
|
1207
1254
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1208
1255
|
located.
|
|
1209
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1256
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1257
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1210
1258
|
:return: A dict[ModelType, list[SideLink]]. If an input model doesn't have reverse side links of the given type,
|
|
1211
1259
|
then it will map to an empty list.
|
|
1212
1260
|
"""
|
|
1213
1261
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1214
|
-
return_dict: dict[
|
|
1262
|
+
return_dict: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
1215
1263
|
for model in models:
|
|
1216
|
-
|
|
1264
|
+
if isinstance(side_link_type, str):
|
|
1265
|
+
return_dict[model] = model.get(ReverseSideLink.of(side_link_type, field_name))
|
|
1266
|
+
else:
|
|
1267
|
+
return_dict[model] = model.get(ReverseSideLink.of_type(side_link_type, field_name))
|
|
1217
1268
|
return return_dict
|
|
1218
1269
|
|
|
1219
1270
|
@staticmethod
|
|
1220
|
-
def map_by_reverse_side_link(models: Iterable[
|
|
1221
|
-
side_link_type: type[WrappedType]
|
|
1271
|
+
def map_by_reverse_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1272
|
+
side_link_type: type[WrappedType] | str) \
|
|
1273
|
+
-> dict[WrappedType | PyRecordModel, IsRecordModel]:
|
|
1222
1274
|
"""
|
|
1223
1275
|
Take a list of record models and map them by their reverse side link. Essentially an inversion of
|
|
1224
1276
|
map_to_reverse_side_link. If two records share the same reverse side link, an exception is thrown.
|
|
@@ -1227,14 +1279,15 @@ class RecordHandler:
|
|
|
1227
1279
|
:param models: A list of record models.
|
|
1228
1280
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1229
1281
|
located.
|
|
1230
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1282
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1283
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1231
1284
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a reverse side link of the given type
|
|
1232
1285
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1233
1286
|
"""
|
|
1234
1287
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1235
|
-
to_side_link: dict[
|
|
1288
|
+
to_side_link: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler\
|
|
1236
1289
|
.map_to_reverse_side_link(models, field_name, side_link_type)
|
|
1237
|
-
by_side_link: dict[WrappedType,
|
|
1290
|
+
by_side_link: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1238
1291
|
for record, side_link in to_side_link.items():
|
|
1239
1292
|
if side_link is None:
|
|
1240
1293
|
continue
|
|
@@ -1245,8 +1298,8 @@ class RecordHandler:
|
|
|
1245
1298
|
return by_side_link
|
|
1246
1299
|
|
|
1247
1300
|
@staticmethod
|
|
1248
|
-
def map_by_reverse_side_links(models: Iterable[
|
|
1249
|
-
side_link_type: type[WrappedType]) -> dict[WrappedType, list[
|
|
1301
|
+
def map_by_reverse_side_links(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1302
|
+
side_link_type: type[WrappedType] | str) -> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1250
1303
|
"""
|
|
1251
1304
|
Take a list of record models and map them by their reverse side links. Essentially an inversion of
|
|
1252
1305
|
map_to_reverse_side_links. Input models that share a reverse side link will end up in the same list.
|
|
@@ -1255,7 +1308,8 @@ class RecordHandler:
|
|
|
1255
1308
|
:param models: A list of record models.
|
|
1256
1309
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1257
1310
|
located.
|
|
1258
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1311
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1312
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1259
1313
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have reverse side links of the given type
|
|
1260
1314
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1261
1315
|
"""
|
|
@@ -1270,9 +1324,9 @@ class RecordHandler:
|
|
|
1270
1324
|
|
|
1271
1325
|
# FR-46155: Update relationship path traversing functions to be non-static and take in a wrapper type so that the
|
|
1272
1326
|
# output can be wrapped instead of requiring the user to wrap the output.
|
|
1273
|
-
def get_linear_path(self, models: Iterable[
|
|
1327
|
+
def get_linear_path(self, models: Iterable[IsRecordModel], path: RelationshipPath,
|
|
1274
1328
|
wrapper_type: type[WrappedType] | None = None) \
|
|
1275
|
-
-> dict[
|
|
1329
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel | None]:
|
|
1276
1330
|
"""
|
|
1277
1331
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1278
1332
|
path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
|
|
@@ -1285,7 +1339,7 @@ class RecordHandler:
|
|
|
1285
1339
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1286
1340
|
path couldn't be reached, the record will map to None.
|
|
1287
1341
|
"""
|
|
1288
|
-
ret_dict: dict[RecordModel, WrappedType | None] = {}
|
|
1342
|
+
ret_dict: dict[RecordModel, WrappedType | PyRecordModel | None] = {}
|
|
1289
1343
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
1290
1344
|
path: list[RelationshipNode] = path.path
|
|
1291
1345
|
for model in models:
|
|
@@ -1332,9 +1386,9 @@ class RecordHandler:
|
|
|
1332
1386
|
ret_dict.update({model: self.wrap_model(current, wrapper_type) if current else None})
|
|
1333
1387
|
return ret_dict
|
|
1334
1388
|
|
|
1335
|
-
def get_branching_path(self, models: Iterable[
|
|
1389
|
+
def get_branching_path(self, models: Iterable[IsRecordModel], path: RelationshipPath,
|
|
1336
1390
|
wrapper_type: type[WrappedType] | None = None)\
|
|
1337
|
-
-> dict[
|
|
1391
|
+
-> dict[IsRecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
1338
1392
|
"""
|
|
1339
1393
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1340
1394
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1347,7 +1401,7 @@ class RecordHandler:
|
|
|
1347
1401
|
:return: Each record model mapped to the records at the end of the path starting from itself. If the end of the
|
|
1348
1402
|
path couldn't be reached, the record will map to an empty list.
|
|
1349
1403
|
"""
|
|
1350
|
-
ret_dict: dict[RecordModel, list[WrappedType]] = {}
|
|
1404
|
+
ret_dict: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
1351
1405
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
1352
1406
|
path: list[RelationshipNode] = path.path
|
|
1353
1407
|
for model in models:
|
|
@@ -1383,9 +1437,9 @@ class RecordHandler:
|
|
|
1383
1437
|
|
|
1384
1438
|
# FR-46155: Create a relationship traversing function that returns a single function at the end of the path like
|
|
1385
1439
|
# get_linear_path but can handle branching paths in the middle of the search like get_branching_path.
|
|
1386
|
-
def get_flat_path(self, models: Iterable[
|
|
1440
|
+
def get_flat_path(self, models: Iterable[IsRecordModel], path: RelationshipPath,
|
|
1387
1441
|
wrapper_type: type[WrappedType] | None = None) \
|
|
1388
|
-
-> dict[
|
|
1442
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel | None]:
|
|
1389
1443
|
"""
|
|
1390
1444
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1391
1445
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1402,7 +1456,7 @@ class RecordHandler:
|
|
|
1402
1456
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1403
1457
|
path couldn't be reached, the record will map to None.
|
|
1404
1458
|
"""
|
|
1405
|
-
ret_dict: dict[RecordModel, WrappedType | None] = {}
|
|
1459
|
+
ret_dict: dict[RecordModel, WrappedType | PyRecordModel | None] = {}
|
|
1406
1460
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
1407
1461
|
path: list[RelationshipNode] = path.path
|
|
1408
1462
|
for model in models:
|