sapiopycommons 2025.6.19a564__py3-none-any.whl → 2026.1.22a847__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.
- sapiopycommons/ai/__init__.py +0 -0
- sapiopycommons/ai/agent_service_base.py +2051 -0
- sapiopycommons/ai/converter_service_base.py +163 -0
- sapiopycommons/ai/external_credentials.py +131 -0
- sapiopycommons/ai/protoapi/agent/agent_pb2.py +87 -0
- sapiopycommons/ai/protoapi/agent/agent_pb2.pyi +282 -0
- sapiopycommons/ai/protoapi/agent/agent_pb2_grpc.py +154 -0
- sapiopycommons/ai/protoapi/agent/entry_pb2.py +49 -0
- sapiopycommons/ai/protoapi/agent/entry_pb2.pyi +40 -0
- sapiopycommons/ai/protoapi/agent/entry_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/agent/item/item_container_pb2.py +61 -0
- sapiopycommons/ai/protoapi/agent/item/item_container_pb2.pyi +181 -0
- sapiopycommons/ai/protoapi/agent/item/item_container_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.py +41 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2.pyi +36 -0
- sapiopycommons/ai/protoapi/externalcredentials/external_credentials_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.py +51 -0
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2.pyi +59 -0
- sapiopycommons/ai/protoapi/fielddefinitions/fields_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.py +123 -0
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2.pyi +599 -0
- sapiopycommons/ai/protoapi/fielddefinitions/velox_field_def_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2.py +59 -0
- sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2.pyi +68 -0
- sapiopycommons/ai/protoapi/pipeline/converter/converter_pb2_grpc.py +149 -0
- sapiopycommons/ai/protoapi/pipeline/script/script_pb2.py +69 -0
- sapiopycommons/ai/protoapi/pipeline/script/script_pb2.pyi +109 -0
- sapiopycommons/ai/protoapi/pipeline/script/script_pb2_grpc.py +153 -0
- sapiopycommons/ai/protoapi/pipeline/step_output_pb2.py +49 -0
- sapiopycommons/ai/protoapi/pipeline/step_output_pb2.pyi +56 -0
- sapiopycommons/ai/protoapi/pipeline/step_output_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/pipeline/step_pb2.py +43 -0
- sapiopycommons/ai/protoapi/pipeline/step_pb2.pyi +44 -0
- sapiopycommons/ai/protoapi/pipeline/step_pb2_grpc.py +24 -0
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.py +39 -0
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2.pyi +33 -0
- sapiopycommons/ai/protoapi/session/sapio_conn_info_pb2_grpc.py +24 -0
- sapiopycommons/ai/protobuf_utils.py +583 -0
- sapiopycommons/ai/request_validation.py +561 -0
- sapiopycommons/ai/server.py +152 -0
- sapiopycommons/ai/test_client.py +534 -0
- sapiopycommons/callbacks/callback_util.py +53 -24
- sapiopycommons/eln/experiment_handler.py +12 -5
- sapiopycommons/files/assay_plate_reader.py +93 -0
- sapiopycommons/files/file_text_converter.py +207 -0
- sapiopycommons/files/file_util.py +128 -1
- sapiopycommons/files/temp_files.py +82 -0
- sapiopycommons/flowcyto/flow_cyto.py +2 -24
- sapiopycommons/general/accession_service.py +2 -28
- sapiopycommons/general/aliases.py +4 -1
- sapiopycommons/general/macros.py +172 -0
- sapiopycommons/general/time_util.py +199 -4
- sapiopycommons/multimodal/multimodal.py +2 -24
- sapiopycommons/recordmodel/record_handler.py +200 -111
- sapiopycommons/rules/eln_rule_handler.py +3 -0
- sapiopycommons/rules/on_save_rule_handler.py +3 -0
- sapiopycommons/webhook/webhook_handlers.py +6 -4
- sapiopycommons/webhook/webservice_handlers.py +1 -1
- {sapiopycommons-2025.6.19a564.dist-info → sapiopycommons-2026.1.22a847.dist-info}/METADATA +2 -2
- sapiopycommons-2026.1.22a847.dist-info/RECORD +113 -0
- sapiopycommons-2025.6.19a564.dist-info/RECORD +0 -68
- {sapiopycommons-2025.6.19a564.dist-info → sapiopycommons-2026.1.22a847.dist-info}/WHEEL +0 -0
- {sapiopycommons-2025.6.19a564.dist-info → sapiopycommons-2026.1.22a847.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
|
|
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,16 @@ 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
|
|
31
|
+
from sapiopylib.rest.utils.recordmodel.properties import Parents, Parent, Children, Child, ForwardSideLink, \
|
|
32
|
+
ReverseSideLink
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
# CR-47717: Use TypeVars in the type hints of certain functions to prevent PyCharm from erroneously flagging certain
|
|
35
|
+
# return type hints as incorrect.
|
|
36
|
+
IsRecordModel = TypeVar('IsRecordModel', bound=RecordModel)
|
|
37
|
+
"""A PyRecordModel or AbstractRecordModel."""
|
|
38
|
+
IsSapioRecord = TypeVar('IsSapioRecord', bound=SapioRecord)
|
|
39
|
+
"""A DataRecord, PyRecordModel, or AbstractRecordModel."""
|
|
33
40
|
|
|
34
|
-
# Aliases for longer name.
|
|
35
|
-
_PropertyGetter = AbstractRecordModelPropertyGetter
|
|
36
|
-
_PropertyAdder = AbstractRecordModelPropertyAdder
|
|
37
|
-
_PropertyRemover = AbstractRecordModelPropertyRemover
|
|
38
|
-
_PropertySetter = AbstractRecordModelPropertySetter
|
|
39
|
-
_PropertyType = RecordModelPropertyType
|
|
40
41
|
|
|
41
42
|
# FR-46064 - Initial port of PyWebhookUtils to sapiopycommons.
|
|
42
43
|
# FR-47575 - Reordered functions so that the Java and Python versions are as close to each other as possible.
|
|
@@ -92,6 +93,10 @@ class RecordHandler:
|
|
|
92
93
|
PyRecordModel instead of a WrappedRecordModel.
|
|
93
94
|
:return: The record model for the input.
|
|
94
95
|
"""
|
|
96
|
+
# PR-47792: Set the wrapper_type to None if a str was provided instead of a type[WrappedType]. The type hints
|
|
97
|
+
# say this shouldn't be done anyway, but using this as a safeguard against user error.
|
|
98
|
+
if isinstance(wrapper_type, str):
|
|
99
|
+
wrapper_type = None
|
|
95
100
|
if wrapper_type is not None:
|
|
96
101
|
self.__verify_data_type(record, wrapper_type)
|
|
97
102
|
if isinstance(record, PyRecordModel):
|
|
@@ -524,9 +529,11 @@ class RecordHandler:
|
|
|
524
529
|
"""
|
|
525
530
|
warnings.warn("Deprecated in favor of the [System/Custom/Quick]ReportRecordAutoPager classes.", DeprecationWarning)
|
|
526
531
|
if isinstance(report_name, str):
|
|
532
|
+
# noinspection PyDeprecation
|
|
527
533
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_system_report(self.user, report_name, filters,
|
|
528
534
|
page_limit, page_size, page_number)
|
|
529
535
|
elif isinstance(report_name, RawReportTerm):
|
|
536
|
+
# noinspection PyDeprecation
|
|
530
537
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_quick_report(self.user, report_name, filters,
|
|
531
538
|
page_limit, page_size, page_number)
|
|
532
539
|
elif isinstance(report_name, CustomReportCriteria):
|
|
@@ -539,6 +546,7 @@ class RecordHandler:
|
|
|
539
546
|
# Enforce that the given custom report has a record ID column.
|
|
540
547
|
if not any([x.data_type_name == dt and x.data_field_name == "RecordId" for x in report_name.column_list]):
|
|
541
548
|
report_name.column_list.append(ReportColumn(dt, "RecordId", FieldType.LONG))
|
|
549
|
+
# noinspection PyDeprecation
|
|
542
550
|
results: list[dict[str, FieldValue]] = CustomReportUtil.run_custom_report(self.user, report_name, filters,
|
|
543
551
|
page_limit, page_size, page_number)
|
|
544
552
|
else:
|
|
@@ -551,7 +559,7 @@ class RecordHandler:
|
|
|
551
559
|
return self.query_models_by_id(wrapper_type, ids)
|
|
552
560
|
|
|
553
561
|
@staticmethod
|
|
554
|
-
def map_by_id(models: Iterable[
|
|
562
|
+
def map_by_id(models: Iterable[IsSapioRecord]) -> dict[int, IsSapioRecord]:
|
|
555
563
|
"""
|
|
556
564
|
Map the given records their record IDs.
|
|
557
565
|
|
|
@@ -560,12 +568,12 @@ class RecordHandler:
|
|
|
560
568
|
"""
|
|
561
569
|
ret_dict: dict[int, SapioRecord] = {}
|
|
562
570
|
for model in models:
|
|
563
|
-
ret_dict.update({model
|
|
571
|
+
ret_dict.update({AliasUtil.to_record_id(model): model})
|
|
564
572
|
return ret_dict
|
|
565
573
|
|
|
566
574
|
@staticmethod
|
|
567
|
-
def map_by_field(models: Iterable[
|
|
568
|
-
-> dict[FieldValue, list[
|
|
575
|
+
def map_by_field(models: Iterable[IsSapioRecord], field_name: FieldIdentifier) \
|
|
576
|
+
-> dict[FieldValue, list[IsSapioRecord]]:
|
|
569
577
|
"""
|
|
570
578
|
Map the given records by one of their fields. If any two records share the same field value, they'll appear in
|
|
571
579
|
the same value list.
|
|
@@ -582,8 +590,8 @@ class RecordHandler:
|
|
|
582
590
|
return ret_dict
|
|
583
591
|
|
|
584
592
|
@staticmethod
|
|
585
|
-
def map_by_unique_field(models: Iterable[
|
|
586
|
-
-> dict[FieldValue,
|
|
593
|
+
def map_by_unique_field(models: Iterable[IsSapioRecord], field_name: FieldIdentifier) \
|
|
594
|
+
-> dict[FieldValue, IsSapioRecord]:
|
|
587
595
|
"""
|
|
588
596
|
Uniquely map the given records by one of their fields. If any two records share the same field value, throws
|
|
589
597
|
an exception.
|
|
@@ -631,6 +639,40 @@ class RecordHandler:
|
|
|
631
639
|
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as stream:
|
|
632
640
|
self.dr_man.set_record_image(record, stream)
|
|
633
641
|
|
|
642
|
+
def get_file_blob_data(self, record: SapioRecord, field_name: FieldIdentifier) -> bytes:
|
|
643
|
+
"""
|
|
644
|
+
Retrieve file blob data for a given record from one of its file blob fields.
|
|
645
|
+
|
|
646
|
+
:param record: The record model to retrieve from.
|
|
647
|
+
:param field_name: The name of the file blob field to retrieve the data from.
|
|
648
|
+
:return: The file bytes of the given record's file blob data for the input field.
|
|
649
|
+
"""
|
|
650
|
+
record: DataRecord = AliasUtil.to_data_record(record)
|
|
651
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
652
|
+
with io.BytesIO() as data_sink:
|
|
653
|
+
def consume_data(chunk: bytes):
|
|
654
|
+
data_sink.write(chunk)
|
|
655
|
+
|
|
656
|
+
self.dr_man.get_file_blob_data(record, field_name, consume_data)
|
|
657
|
+
data_sink.flush()
|
|
658
|
+
data_sink.seek(0)
|
|
659
|
+
file_bytes = data_sink.read()
|
|
660
|
+
return file_bytes
|
|
661
|
+
|
|
662
|
+
def set_file_blob_data(self, record: SapioRecord, field_name: FieldIdentifier, file_name: str, file_data: str | bytes) -> None:
|
|
663
|
+
"""
|
|
664
|
+
Set the file blob data for a given record on one of its file blob fields.
|
|
665
|
+
|
|
666
|
+
:param record: The record model to set the file blob data of.
|
|
667
|
+
:param field_name: The name of the file blob field to set the data for.
|
|
668
|
+
:param file_name: The name of the file being stored in the file blob field.
|
|
669
|
+
:param file_data: The file data of the blob to set on the record.
|
|
670
|
+
"""
|
|
671
|
+
record: DataRecord = AliasUtil.to_data_record(record)
|
|
672
|
+
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
673
|
+
with io.BytesIO(file_data.encode() if isinstance(file_data, str) else file_data) as stream:
|
|
674
|
+
self.dr_man.set_file_blob_data(record, field_name, file_name, stream)
|
|
675
|
+
|
|
634
676
|
@staticmethod
|
|
635
677
|
def sum_of_field(models: Iterable[SapioRecord], field_name: FieldIdentifier) -> float:
|
|
636
678
|
"""
|
|
@@ -662,7 +704,7 @@ class RecordHandler:
|
|
|
662
704
|
return RecordHandler.sum_of_field(models, field_name) / len(models)
|
|
663
705
|
|
|
664
706
|
@staticmethod
|
|
665
|
-
def get_newest_record(records: Iterable[
|
|
707
|
+
def get_newest_record(records: Iterable[IsSapioRecord]) -> IsSapioRecord:
|
|
666
708
|
"""
|
|
667
709
|
Get the newest record from a list of records.
|
|
668
710
|
|
|
@@ -673,7 +715,7 @@ class RecordHandler:
|
|
|
673
715
|
|
|
674
716
|
# FR-46696: Add a function for getting the oldest record in a list, just like we have one for the newest record.
|
|
675
717
|
@staticmethod
|
|
676
|
-
def get_oldest_record(records: Iterable[
|
|
718
|
+
def get_oldest_record(records: Iterable[IsSapioRecord]) -> IsSapioRecord:
|
|
677
719
|
"""
|
|
678
720
|
Get the oldest record from a list of records.
|
|
679
721
|
|
|
@@ -683,7 +725,7 @@ class RecordHandler:
|
|
|
683
725
|
return min(records, key=lambda x: x.record_id)
|
|
684
726
|
|
|
685
727
|
@staticmethod
|
|
686
|
-
def get_min_record(records: list[
|
|
728
|
+
def get_min_record(records: list[IsSapioRecord], field: FieldIdentifier) -> IsSapioRecord:
|
|
687
729
|
"""
|
|
688
730
|
Get the record model with the minimum value of a given field from a list of record models.
|
|
689
731
|
|
|
@@ -695,7 +737,7 @@ class RecordHandler:
|
|
|
695
737
|
return min(records, key=lambda x: x.get_field_value(field))
|
|
696
738
|
|
|
697
739
|
@staticmethod
|
|
698
|
-
def get_max_record(records: list[
|
|
740
|
+
def get_max_record(records: list[IsSapioRecord], field: FieldIdentifier) -> IsSapioRecord:
|
|
699
741
|
"""
|
|
700
742
|
Get the record model with the maximum value of a given field from a list of record models.
|
|
701
743
|
|
|
@@ -772,7 +814,8 @@ class RecordHandler:
|
|
|
772
814
|
return [{field_name: value} for value in values]
|
|
773
815
|
|
|
774
816
|
@staticmethod
|
|
775
|
-
def get_from_all(records: Iterable[RecordModel],
|
|
817
|
+
def get_from_all(records: Iterable[RecordModel],
|
|
818
|
+
getter: AbstractRecordModelPropertyGetter[RecordModelPropertyType]) \
|
|
776
819
|
-> list[RecordModelPropertyType]:
|
|
777
820
|
"""
|
|
778
821
|
Use a getter property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -787,7 +830,8 @@ class RecordHandler:
|
|
|
787
830
|
return [x.get(getter) for x in records]
|
|
788
831
|
|
|
789
832
|
@staticmethod
|
|
790
|
-
def set_on_all(records: Iterable[RecordModel],
|
|
833
|
+
def set_on_all(records: Iterable[RecordModel],
|
|
834
|
+
setter: AbstractRecordModelPropertySetter[RecordModelPropertyType]) \
|
|
791
835
|
-> list[RecordModelPropertyType]:
|
|
792
836
|
"""
|
|
793
837
|
Use a setter property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -802,7 +846,8 @@ class RecordHandler:
|
|
|
802
846
|
return [x.set(setter) for x in records]
|
|
803
847
|
|
|
804
848
|
@staticmethod
|
|
805
|
-
def add_to_all(records: Iterable[RecordModel],
|
|
849
|
+
def add_to_all(records: Iterable[RecordModel],
|
|
850
|
+
adder: AbstractRecordModelPropertyAdder[RecordModelPropertyType]) \
|
|
806
851
|
-> list[RecordModelPropertyType]:
|
|
807
852
|
"""
|
|
808
853
|
Use an adder property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -816,7 +861,8 @@ class RecordHandler:
|
|
|
816
861
|
return [x.add(adder) for x in records]
|
|
817
862
|
|
|
818
863
|
@staticmethod
|
|
819
|
-
def remove_from_all(records: Iterable[RecordModel],
|
|
864
|
+
def remove_from_all(records: Iterable[RecordModel],
|
|
865
|
+
remover: AbstractRecordModelPropertyRemover[RecordModelPropertyType]) \
|
|
820
866
|
-> list[RecordModelPropertyType]:
|
|
821
867
|
"""
|
|
822
868
|
Use a remover property on all records in a list of record models. For example, you can iterate over a list of
|
|
@@ -870,7 +916,7 @@ class RecordHandler:
|
|
|
870
916
|
parent_dt: str = AliasUtil.to_data_type_name(parent_type)
|
|
871
917
|
wrapper: type[WrappedType] | None = parent_type if isinstance(parent_type, type) else None
|
|
872
918
|
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
873
|
-
parent: PyRecordModel | None = record.
|
|
919
|
+
parent: PyRecordModel | None = record.get(Parent.of_type_name(parent_dt))
|
|
874
920
|
if parent is not None:
|
|
875
921
|
return self.wrap_model(parent, wrapper) if wrapper else parent
|
|
876
922
|
return record.add(Parent.create(wrapper)) if wrapper else record.add(Parent.create_by_name(parent_dt))
|
|
@@ -888,7 +934,7 @@ class RecordHandler:
|
|
|
888
934
|
child_dt: str = AliasUtil.to_data_type_name(child_type)
|
|
889
935
|
wrapper: type[WrappedType] | None = child_type if isinstance(child_type, type) else None
|
|
890
936
|
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
891
|
-
child: PyRecordModel | None = record.
|
|
937
|
+
child: PyRecordModel | None = record.get(Child.of_type_name(child_dt))
|
|
892
938
|
if child is not None:
|
|
893
939
|
return self.wrap_model(child, wrapper) if wrapper else child
|
|
894
940
|
return record.add(Child.create(wrapper)) if wrapper else record.add(Child.create_by_name(child_dt))
|
|
@@ -908,7 +954,7 @@ class RecordHandler:
|
|
|
908
954
|
side_link_field: str = AliasUtil.to_data_field_name(side_link_field)
|
|
909
955
|
wrapper: type[WrappedType] | None = side_link_type if isinstance(side_link_type, type) else None
|
|
910
956
|
record: PyRecordModel = RecordModelInstanceManager.unwrap(record)
|
|
911
|
-
side_link: PyRecordModel | None = record.
|
|
957
|
+
side_link: PyRecordModel | None = record.get(ForwardSideLink.of(side_link_field))
|
|
912
958
|
if side_link is not None:
|
|
913
959
|
return self.wrap_model(side_link, wrapper) if wrapper else side_link
|
|
914
960
|
side_link: WrappedType | PyRecordModel = self.add_model(side_link_type)
|
|
@@ -955,52 +1001,63 @@ class RecordHandler:
|
|
|
955
1001
|
if child not in children:
|
|
956
1002
|
record.remove(Child.ref(child))
|
|
957
1003
|
|
|
1004
|
+
# CR-47717: Update the map_[to/by]_[relationship] functions to allow PyRecordModels to be provided and returned
|
|
1005
|
+
# instead of only using WrappedRecordModels and wrapper types.
|
|
958
1006
|
@staticmethod
|
|
959
|
-
def map_to_parent(models: Iterable[
|
|
960
|
-
-> dict[
|
|
1007
|
+
def map_to_parent(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
1008
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
961
1009
|
"""
|
|
962
1010
|
Map a list of record models to a single parent of a given type. The parents must already be loaded.
|
|
963
1011
|
|
|
964
1012
|
:param models: A list of record models.
|
|
965
|
-
:param parent_type: The record model wrapper of the
|
|
1013
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
1014
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
966
1015
|
:return: A dict[ModelType, ParentType]. If an input model doesn't have a parent of the given parent type, then
|
|
967
1016
|
it will map to None.
|
|
968
1017
|
"""
|
|
969
|
-
return_dict: dict[
|
|
1018
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
970
1019
|
for model in models:
|
|
971
|
-
|
|
1020
|
+
if isinstance(parent_type, str):
|
|
1021
|
+
return_dict[model] = model.get(Parent.of_type_name(parent_type))
|
|
1022
|
+
else:
|
|
1023
|
+
return_dict[model] = model.get(Parent.of_type(parent_type))
|
|
972
1024
|
return return_dict
|
|
973
1025
|
|
|
974
1026
|
@staticmethod
|
|
975
|
-
def map_to_parents(models: Iterable[
|
|
976
|
-
-> dict[
|
|
1027
|
+
def map_to_parents(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
1028
|
+
-> dict[IsRecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
977
1029
|
"""
|
|
978
1030
|
Map a list of record models to a list parents of a given type. The parents must already be loaded.
|
|
979
1031
|
|
|
980
1032
|
:param models: A list of record models.
|
|
981
|
-
:param parent_type: The record model wrapper of the parents.
|
|
1033
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
1034
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
982
1035
|
:return: A dict[ModelType, list[ParentType]]. If an input model doesn't have a parent of the given parent type,
|
|
983
1036
|
then it will map to an empty list.
|
|
984
1037
|
"""
|
|
985
|
-
return_dict: dict[WrappedRecordModel, list[WrappedType]] = {}
|
|
1038
|
+
return_dict: dict[WrappedRecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
986
1039
|
for model in models:
|
|
987
|
-
|
|
1040
|
+
if isinstance(parent_type, str):
|
|
1041
|
+
return_dict[model] = model.get(Parents.of_type_name(parent_type))
|
|
1042
|
+
else:
|
|
1043
|
+
return_dict[model] = model.get(Parents.of_type(parent_type))
|
|
988
1044
|
return return_dict
|
|
989
1045
|
|
|
990
1046
|
@staticmethod
|
|
991
|
-
def map_by_parent(models: Iterable[
|
|
992
|
-
-> dict[WrappedType,
|
|
1047
|
+
def map_by_parent(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
1048
|
+
-> dict[WrappedType | PyRecordModel, IsRecordModel]:
|
|
993
1049
|
"""
|
|
994
1050
|
Take a list of record models and map them by their parent. Essentially an inversion of map_to_parent.
|
|
995
1051
|
If two records share the same parent, an exception will be thrown. The parents must already be loaded.
|
|
996
1052
|
|
|
997
1053
|
:param models: A list of record models.
|
|
998
|
-
:param parent_type: The record model wrapper of the parents.
|
|
1054
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
1055
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
999
1056
|
:return: A dict[ParentType, ModelType]. If an input model doesn't have a parent of the given parent type,
|
|
1000
1057
|
then it will not be in the resulting dictionary.
|
|
1001
1058
|
"""
|
|
1002
|
-
to_parent: dict[
|
|
1003
|
-
by_parent: dict[WrappedType,
|
|
1059
|
+
to_parent: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler.map_to_parent(models, parent_type)
|
|
1060
|
+
by_parent: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1004
1061
|
for record, parent in to_parent.items():
|
|
1005
1062
|
if parent is None:
|
|
1006
1063
|
continue
|
|
@@ -1011,70 +1068,81 @@ class RecordHandler:
|
|
|
1011
1068
|
return by_parent
|
|
1012
1069
|
|
|
1013
1070
|
@staticmethod
|
|
1014
|
-
def map_by_parents(models: Iterable[
|
|
1015
|
-
-> dict[WrappedType, list[
|
|
1071
|
+
def map_by_parents(models: Iterable[IsRecordModel], parent_type: type[WrappedType] | str) \
|
|
1072
|
+
-> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1016
1073
|
"""
|
|
1017
1074
|
Take a list of record models and map them by their parents. Essentially an inversion of map_to_parents. Input
|
|
1018
1075
|
models that share a parent will end up in the same list. The parents must already be loaded.
|
|
1019
1076
|
|
|
1020
1077
|
:param models: A list of record models.
|
|
1021
|
-
:param parent_type: The record model wrapper of the parents.
|
|
1078
|
+
:param parent_type: The record model wrapper or data type name of the parents. If a data type name is
|
|
1079
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1022
1080
|
:return: A dict[ParentType, list[ModelType]]. If an input model doesn't have a parent of the given parent type,
|
|
1023
1081
|
then it will not be in the resulting dictionary.
|
|
1024
1082
|
"""
|
|
1025
|
-
to_parents: dict[
|
|
1026
|
-
|
|
1083
|
+
to_parents: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = RecordHandler\
|
|
1084
|
+
.map_to_parents(models, parent_type)
|
|
1085
|
+
by_parents: dict[WrappedType | PyRecordModel, list[RecordModel]] = {}
|
|
1027
1086
|
for record, parents in to_parents.items():
|
|
1028
1087
|
for parent in parents:
|
|
1029
1088
|
by_parents.setdefault(parent, []).append(record)
|
|
1030
1089
|
return by_parents
|
|
1031
1090
|
|
|
1032
1091
|
@staticmethod
|
|
1033
|
-
def map_to_child(models: Iterable[
|
|
1034
|
-
-> dict[
|
|
1092
|
+
def map_to_child(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1093
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
1035
1094
|
"""
|
|
1036
1095
|
Map a list of record models to a single child of a given type. The children must already be loaded.
|
|
1037
1096
|
|
|
1038
1097
|
:param models: A list of record models.
|
|
1039
|
-
:param child_type: The record model wrapper of the
|
|
1098
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1099
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1040
1100
|
:return: A dict[ModelType, ChildType]. If an input model doesn't have a child of the given child type, then
|
|
1041
1101
|
it will map to None.
|
|
1042
1102
|
"""
|
|
1043
|
-
return_dict: dict[
|
|
1103
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
1044
1104
|
for model in models:
|
|
1045
|
-
|
|
1105
|
+
if isinstance(child_type, str):
|
|
1106
|
+
return_dict[model] = model.get(Child.of_type_name(child_type))
|
|
1107
|
+
else:
|
|
1108
|
+
return_dict[model] = model.get(Child.of_type(child_type))
|
|
1046
1109
|
return return_dict
|
|
1047
1110
|
|
|
1048
1111
|
@staticmethod
|
|
1049
|
-
def map_to_children(models: Iterable[
|
|
1050
|
-
-> dict[
|
|
1112
|
+
def map_to_children(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1113
|
+
-> dict[IsRecordModel, list[WrappedType] | PyRecordModel]:
|
|
1051
1114
|
"""
|
|
1052
1115
|
Map a list of record models to a list children of a given type. The children must already be loaded.
|
|
1053
1116
|
|
|
1054
1117
|
:param models: A list of record models.
|
|
1055
|
-
:param child_type: The record model wrapper of the children.
|
|
1118
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1119
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1056
1120
|
:return: A dict[ModelType, list[ChildType]]. If an input model doesn't have children of the given child type,
|
|
1057
1121
|
then it will map to an empty list.
|
|
1058
1122
|
"""
|
|
1059
|
-
return_dict: dict[
|
|
1123
|
+
return_dict: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
1060
1124
|
for model in models:
|
|
1061
|
-
|
|
1125
|
+
if isinstance(child_type, str):
|
|
1126
|
+
return_dict[model] = model.get(Children.of_type_name(child_type))
|
|
1127
|
+
else:
|
|
1128
|
+
return_dict[model] = model.get(Children.of_type(child_type))
|
|
1062
1129
|
return return_dict
|
|
1063
1130
|
|
|
1064
1131
|
@staticmethod
|
|
1065
|
-
def map_by_child(models: Iterable[
|
|
1066
|
-
-> dict[WrappedType,
|
|
1132
|
+
def map_by_child(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1133
|
+
-> dict[WrappedType | str, IsRecordModel]:
|
|
1067
1134
|
"""
|
|
1068
1135
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_child.
|
|
1069
1136
|
If two records share the same child, an exception will be thrown. The children must already be loaded.
|
|
1070
1137
|
|
|
1071
1138
|
:param models: A list of record models.
|
|
1072
|
-
:param child_type: The record model wrapper of the children.
|
|
1139
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1140
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1073
1141
|
:return: A dict[ChildType, ModelType]. If an input model doesn't have a child of the given child type,
|
|
1074
1142
|
then it will not be in the resulting dictionary.
|
|
1075
1143
|
"""
|
|
1076
|
-
to_child: dict[
|
|
1077
|
-
by_child: dict[WrappedType,
|
|
1144
|
+
to_child: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler.map_to_child(models, child_type)
|
|
1145
|
+
by_child: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1078
1146
|
for record, child in to_child.items():
|
|
1079
1147
|
if child is None:
|
|
1080
1148
|
continue
|
|
@@ -1085,45 +1153,50 @@ class RecordHandler:
|
|
|
1085
1153
|
return by_child
|
|
1086
1154
|
|
|
1087
1155
|
@staticmethod
|
|
1088
|
-
def map_by_children(models: Iterable[
|
|
1089
|
-
-> dict[WrappedType, list[
|
|
1156
|
+
def map_by_children(models: Iterable[IsRecordModel], child_type: type[WrappedType] | str) \
|
|
1157
|
+
-> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1090
1158
|
"""
|
|
1091
1159
|
Take a list of record models and map them by their children. Essentially an inversion of map_to_children. Input
|
|
1092
1160
|
models that share a child will end up in the same list. The children must already be loaded.
|
|
1093
1161
|
|
|
1094
1162
|
:param models: A list of record models.
|
|
1095
|
-
:param child_type: The record model wrapper of the children.
|
|
1163
|
+
:param child_type: The record model wrapper or data type name of the children. If a data type name is
|
|
1164
|
+
provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1096
1165
|
:return: A dict[ChildType, list[ModelType]]. If an input model doesn't have children of the given child type,
|
|
1097
1166
|
then it will not be in the resulting dictionary.
|
|
1098
1167
|
"""
|
|
1099
|
-
to_children: dict[
|
|
1100
|
-
|
|
1168
|
+
to_children: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = RecordHandler\
|
|
1169
|
+
.map_to_children(models, child_type)
|
|
1170
|
+
by_children: dict[WrappedType | PyRecordModel, list[RecordModel]] = {}
|
|
1101
1171
|
for record, children in to_children.items():
|
|
1102
1172
|
for child in children:
|
|
1103
1173
|
by_children.setdefault(child, []).append(record)
|
|
1104
1174
|
return by_children
|
|
1105
1175
|
|
|
1106
1176
|
@staticmethod
|
|
1107
|
-
def map_to_forward_side_link(models: Iterable[
|
|
1108
|
-
side_link_type: type[WrappedType]
|
|
1177
|
+
def map_to_forward_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1178
|
+
side_link_type: type[WrappedType] | None) \
|
|
1179
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
1109
1180
|
"""
|
|
1110
1181
|
Map a list of record models to their forward side link. The forward side link must already be loaded.
|
|
1111
1182
|
|
|
1112
1183
|
:param models: A list of record models.
|
|
1113
1184
|
: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.
|
|
1185
|
+
:param side_link_type: The record model wrapper of the forward side link. If None, the side links will
|
|
1186
|
+
be returned as PyRecordModels instead of WrappedRecordModels.
|
|
1115
1187
|
:return: A dict[ModelType, SlideLink]. If an input model doesn't have a forward side link of the given type,
|
|
1116
1188
|
then it will map to None.
|
|
1117
1189
|
"""
|
|
1118
1190
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1119
|
-
return_dict: dict[
|
|
1191
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
1120
1192
|
for model in models:
|
|
1121
|
-
return_dict[model] = model.
|
|
1193
|
+
return_dict[model] = model.get(ForwardSideLink.of(field_name, side_link_type))
|
|
1122
1194
|
return return_dict
|
|
1123
1195
|
|
|
1124
1196
|
@staticmethod
|
|
1125
|
-
def map_by_forward_side_link(models: Iterable[
|
|
1126
|
-
side_link_type: type[WrappedType]
|
|
1197
|
+
def map_by_forward_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1198
|
+
side_link_type: type[WrappedType] | None) \
|
|
1199
|
+
-> dict[WrappedType | PyRecordModel, IsRecordModel]:
|
|
1127
1200
|
"""
|
|
1128
1201
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
1129
1202
|
map_to_forward_side_link, but if two records share the same forward link, an exception is thrown.
|
|
@@ -1131,14 +1204,15 @@ class RecordHandler:
|
|
|
1131
1204
|
|
|
1132
1205
|
:param models: A list of record models.
|
|
1133
1206
|
: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.
|
|
1207
|
+
:param side_link_type: The record model wrapper of the forward side links. If None, the side links will
|
|
1208
|
+
be returned as PyRecordModels instead of WrappedRecordModels.
|
|
1135
1209
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a forward side link of the given type
|
|
1136
1210
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1137
1211
|
"""
|
|
1138
1212
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1139
|
-
to_side_link: dict[
|
|
1213
|
+
to_side_link: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler\
|
|
1140
1214
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
1141
|
-
by_side_link: dict[WrappedType,
|
|
1215
|
+
by_side_link: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1142
1216
|
for record, side_link in to_side_link.items():
|
|
1143
1217
|
if side_link is None:
|
|
1144
1218
|
continue
|
|
@@ -1149,8 +1223,9 @@ class RecordHandler:
|
|
|
1149
1223
|
return by_side_link
|
|
1150
1224
|
|
|
1151
1225
|
@staticmethod
|
|
1152
|
-
def map_by_forward_side_links(models: Iterable[
|
|
1153
|
-
side_link_type: type[WrappedType]
|
|
1226
|
+
def map_by_forward_side_links(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1227
|
+
side_link_type: type[WrappedType] | None) \
|
|
1228
|
+
-> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1154
1229
|
"""
|
|
1155
1230
|
Take a list of record models and map them by their forward side link. Essentially an inversion of
|
|
1156
1231
|
map_to_forward_side_link. Input models that share a forward side link will end up in the same list.
|
|
@@ -1158,14 +1233,15 @@ class RecordHandler:
|
|
|
1158
1233
|
|
|
1159
1234
|
:param models: A list of record models.
|
|
1160
1235
|
: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.
|
|
1236
|
+
:param side_link_type: The record model wrapper of the forward side links. If None, the side links will
|
|
1237
|
+
be returned as PyRecordModels instead of WrappedRecordModels.
|
|
1162
1238
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have a forward side link of the given type
|
|
1163
1239
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1164
1240
|
"""
|
|
1165
1241
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1166
|
-
to_side_link: dict[
|
|
1242
|
+
to_side_link: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler\
|
|
1167
1243
|
.map_to_forward_side_link(models, field_name, side_link_type)
|
|
1168
|
-
by_side_link: dict[WrappedType, list[
|
|
1244
|
+
by_side_link: dict[WrappedType | PyRecordModel, list[RecordModel]] = {}
|
|
1169
1245
|
for record, side_link in to_side_link.items():
|
|
1170
1246
|
if side_link is None:
|
|
1171
1247
|
continue
|
|
@@ -1173,8 +1249,9 @@ class RecordHandler:
|
|
|
1173
1249
|
return by_side_link
|
|
1174
1250
|
|
|
1175
1251
|
@staticmethod
|
|
1176
|
-
def map_to_reverse_side_link(models: Iterable[
|
|
1177
|
-
side_link_type: type[WrappedType]
|
|
1252
|
+
def map_to_reverse_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1253
|
+
side_link_type: type[WrappedType] | str) \
|
|
1254
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel]:
|
|
1178
1255
|
"""
|
|
1179
1256
|
Map a list of record models to the reverse side link of a given type. If a given record has more than one
|
|
1180
1257
|
reverse side link of this type, an exception is thrown. The reverse side links must already be loaded.
|
|
@@ -1182,14 +1259,18 @@ class RecordHandler:
|
|
|
1182
1259
|
:param models: A list of record models.
|
|
1183
1260
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1184
1261
|
located.
|
|
1185
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1262
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1263
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1186
1264
|
:return: A dict[ModelType, SideLink]. If an input model doesn't have reverse side links of the given type,
|
|
1187
1265
|
then it will map to None.
|
|
1188
1266
|
"""
|
|
1189
1267
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1190
|
-
return_dict: dict[
|
|
1268
|
+
return_dict: dict[RecordModel, WrappedType | PyRecordModel] = {}
|
|
1191
1269
|
for model in models:
|
|
1192
|
-
|
|
1270
|
+
if isinstance(side_link_type, str):
|
|
1271
|
+
links: list[WrappedType] = model.get(ReverseSideLink.of(side_link_type, field_name))
|
|
1272
|
+
else:
|
|
1273
|
+
links: list[WrappedType] = model.get(ReverseSideLink.of_type(side_link_type, field_name))
|
|
1193
1274
|
if len(links) > 1:
|
|
1194
1275
|
raise SapioException(f"Model {model.data_type_name} {model.record_id} has more than one reverse link "
|
|
1195
1276
|
f"of type {side_link_type.get_wrapper_data_type_name()}.")
|
|
@@ -1197,8 +1278,9 @@ class RecordHandler:
|
|
|
1197
1278
|
return return_dict
|
|
1198
1279
|
|
|
1199
1280
|
@staticmethod
|
|
1200
|
-
def map_to_reverse_side_links(models: Iterable[
|
|
1201
|
-
side_link_type: type[WrappedType]
|
|
1281
|
+
def map_to_reverse_side_links(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1282
|
+
side_link_type: type[WrappedType] | str) \
|
|
1283
|
+
-> dict[IsRecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
1202
1284
|
"""
|
|
1203
1285
|
Map a list of record models to a list reverse side links of a given type. The reverse side links must already
|
|
1204
1286
|
be loaded.
|
|
@@ -1206,19 +1288,24 @@ class RecordHandler:
|
|
|
1206
1288
|
:param models: A list of record models.
|
|
1207
1289
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1208
1290
|
located.
|
|
1209
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1291
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1292
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1210
1293
|
:return: A dict[ModelType, list[SideLink]]. If an input model doesn't have reverse side links of the given type,
|
|
1211
1294
|
then it will map to an empty list.
|
|
1212
1295
|
"""
|
|
1213
1296
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1214
|
-
return_dict: dict[
|
|
1297
|
+
return_dict: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
1215
1298
|
for model in models:
|
|
1216
|
-
|
|
1299
|
+
if isinstance(side_link_type, str):
|
|
1300
|
+
return_dict[model] = model.get(ReverseSideLink.of(side_link_type, field_name))
|
|
1301
|
+
else:
|
|
1302
|
+
return_dict[model] = model.get(ReverseSideLink.of_type(side_link_type, field_name))
|
|
1217
1303
|
return return_dict
|
|
1218
1304
|
|
|
1219
1305
|
@staticmethod
|
|
1220
|
-
def map_by_reverse_side_link(models: Iterable[
|
|
1221
|
-
side_link_type: type[WrappedType]
|
|
1306
|
+
def map_by_reverse_side_link(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1307
|
+
side_link_type: type[WrappedType] | str) \
|
|
1308
|
+
-> dict[WrappedType | PyRecordModel, IsRecordModel]:
|
|
1222
1309
|
"""
|
|
1223
1310
|
Take a list of record models and map them by their reverse side link. Essentially an inversion of
|
|
1224
1311
|
map_to_reverse_side_link. If two records share the same reverse side link, an exception is thrown.
|
|
@@ -1227,14 +1314,15 @@ class RecordHandler:
|
|
|
1227
1314
|
:param models: A list of record models.
|
|
1228
1315
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1229
1316
|
located.
|
|
1230
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1317
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1318
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1231
1319
|
:return: A dict[SideLink, ModelType]. If an input model doesn't have a reverse side link of the given type
|
|
1232
1320
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1233
1321
|
"""
|
|
1234
1322
|
field_name: str = AliasUtil.to_data_field_name(field_name)
|
|
1235
|
-
to_side_link: dict[
|
|
1323
|
+
to_side_link: dict[RecordModel, WrappedType | PyRecordModel] = RecordHandler\
|
|
1236
1324
|
.map_to_reverse_side_link(models, field_name, side_link_type)
|
|
1237
|
-
by_side_link: dict[WrappedType,
|
|
1325
|
+
by_side_link: dict[WrappedType | PyRecordModel, RecordModel] = {}
|
|
1238
1326
|
for record, side_link in to_side_link.items():
|
|
1239
1327
|
if side_link is None:
|
|
1240
1328
|
continue
|
|
@@ -1245,8 +1333,8 @@ class RecordHandler:
|
|
|
1245
1333
|
return by_side_link
|
|
1246
1334
|
|
|
1247
1335
|
@staticmethod
|
|
1248
|
-
def map_by_reverse_side_links(models: Iterable[
|
|
1249
|
-
side_link_type: type[WrappedType]) -> dict[WrappedType, list[
|
|
1336
|
+
def map_by_reverse_side_links(models: Iterable[IsRecordModel], field_name: FieldIdentifier,
|
|
1337
|
+
side_link_type: type[WrappedType] | str) -> dict[WrappedType | PyRecordModel, list[IsRecordModel]]:
|
|
1250
1338
|
"""
|
|
1251
1339
|
Take a list of record models and map them by their reverse side links. Essentially an inversion of
|
|
1252
1340
|
map_to_reverse_side_links. Input models that share a reverse side link will end up in the same list.
|
|
@@ -1255,7 +1343,8 @@ class RecordHandler:
|
|
|
1255
1343
|
:param models: A list of record models.
|
|
1256
1344
|
:param field_name: The field name on the side linked model where the side link to the given record models is
|
|
1257
1345
|
located.
|
|
1258
|
-
:param side_link_type: The record model wrapper of the reverse side links.
|
|
1346
|
+
:param side_link_type: The record model wrapper or data type name of the reverse side links. If a data type
|
|
1347
|
+
name is provided, the returned records will be PyRecordModels instead of WrappedRecordModels.
|
|
1259
1348
|
:return: A dict[SideLink, list[ModelType]]. If an input model doesn't have reverse side links of the given type
|
|
1260
1349
|
pointing to it, then it will not be in the resulting dictionary.
|
|
1261
1350
|
"""
|
|
@@ -1270,9 +1359,9 @@ class RecordHandler:
|
|
|
1270
1359
|
|
|
1271
1360
|
# FR-46155: Update relationship path traversing functions to be non-static and take in a wrapper type so that the
|
|
1272
1361
|
# output can be wrapped instead of requiring the user to wrap the output.
|
|
1273
|
-
def get_linear_path(self, models: Iterable[
|
|
1362
|
+
def get_linear_path(self, models: Iterable[IsRecordModel], path: RelationshipPath,
|
|
1274
1363
|
wrapper_type: type[WrappedType] | None = None) \
|
|
1275
|
-
-> dict[
|
|
1364
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel | None]:
|
|
1276
1365
|
"""
|
|
1277
1366
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1278
1367
|
path, if any. The hierarchy must be linear (1:1 relationship between data types at every step) and the
|
|
@@ -1285,7 +1374,7 @@ class RecordHandler:
|
|
|
1285
1374
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1286
1375
|
path couldn't be reached, the record will map to None.
|
|
1287
1376
|
"""
|
|
1288
|
-
ret_dict: dict[RecordModel, WrappedType | None] = {}
|
|
1377
|
+
ret_dict: dict[RecordModel, WrappedType | PyRecordModel | None] = {}
|
|
1289
1378
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
1290
1379
|
path: list[RelationshipNode] = path.path
|
|
1291
1380
|
for model in models:
|
|
@@ -1332,9 +1421,9 @@ class RecordHandler:
|
|
|
1332
1421
|
ret_dict.update({model: self.wrap_model(current, wrapper_type) if current else None})
|
|
1333
1422
|
return ret_dict
|
|
1334
1423
|
|
|
1335
|
-
def get_branching_path(self, models: Iterable[
|
|
1424
|
+
def get_branching_path(self, models: Iterable[IsRecordModel], path: RelationshipPath,
|
|
1336
1425
|
wrapper_type: type[WrappedType] | None = None)\
|
|
1337
|
-
-> dict[
|
|
1426
|
+
-> dict[IsRecordModel, list[WrappedType] | list[PyRecordModel]]:
|
|
1338
1427
|
"""
|
|
1339
1428
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1340
1429
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1347,7 +1436,7 @@ class RecordHandler:
|
|
|
1347
1436
|
:return: Each record model mapped to the records at the end of the path starting from itself. If the end of the
|
|
1348
1437
|
path couldn't be reached, the record will map to an empty list.
|
|
1349
1438
|
"""
|
|
1350
|
-
ret_dict: dict[RecordModel, list[WrappedType]] = {}
|
|
1439
|
+
ret_dict: dict[RecordModel, list[WrappedType] | list[PyRecordModel]] = {}
|
|
1351
1440
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
1352
1441
|
path: list[RelationshipNode] = path.path
|
|
1353
1442
|
for model in models:
|
|
@@ -1383,9 +1472,9 @@ class RecordHandler:
|
|
|
1383
1472
|
|
|
1384
1473
|
# FR-46155: Create a relationship traversing function that returns a single function at the end of the path like
|
|
1385
1474
|
# 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[
|
|
1475
|
+
def get_flat_path(self, models: Iterable[IsRecordModel], path: RelationshipPath,
|
|
1387
1476
|
wrapper_type: type[WrappedType] | None = None) \
|
|
1388
|
-
-> dict[
|
|
1477
|
+
-> dict[IsRecordModel, WrappedType | PyRecordModel | None]:
|
|
1389
1478
|
"""
|
|
1390
1479
|
Given a relationship path, travel the path starting from the input models. Returns the record at the end of the
|
|
1391
1480
|
path, if any. The hierarchy may be non-linear (1:Many relationships between data types are allowed) and the
|
|
@@ -1402,7 +1491,7 @@ class RecordHandler:
|
|
|
1402
1491
|
:return: Each record model mapped to the record at the end of the path starting from itself. If the end of the
|
|
1403
1492
|
path couldn't be reached, the record will map to None.
|
|
1404
1493
|
"""
|
|
1405
|
-
ret_dict: dict[RecordModel, WrappedType | None] = {}
|
|
1494
|
+
ret_dict: dict[RecordModel, WrappedType | PyRecordModel | None] = {}
|
|
1406
1495
|
# PR-46832: Update path traversal to account for changes to RelationshipPath in Sapiopylib.
|
|
1407
1496
|
path: list[RelationshipNode] = path.path
|
|
1408
1497
|
for model in models:
|