fairgraph 0.13.0__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.
- fairgraph/__init__.py +61 -0
- fairgraph/base.py +104 -0
- fairgraph/caching.py +52 -0
- fairgraph/client.py +867 -0
- fairgraph/collection.py +104 -0
- fairgraph/embedded.py +122 -0
- fairgraph/errors.py +47 -0
- fairgraph/fields.py +11 -0
- fairgraph/kgobject.py +1087 -0
- fairgraph/kgproxy.py +178 -0
- fairgraph/kgquery.py +151 -0
- fairgraph/node.py +488 -0
- fairgraph/openminds/__init__.py +1 -0
- fairgraph/openminds/chemicals/__init__.py +40 -0
- fairgraph/openminds/chemicals/amount_of_chemical.py +23 -0
- fairgraph/openminds/chemicals/chemical_mixture.py +72 -0
- fairgraph/openminds/chemicals/chemical_substance.py +67 -0
- fairgraph/openminds/chemicals/product_source.py +57 -0
- fairgraph/openminds/computation/__init__.py +53 -0
- fairgraph/openminds/computation/data_analysis.py +104 -0
- fairgraph/openminds/computation/data_copy.py +104 -0
- fairgraph/openminds/computation/environment.py +66 -0
- fairgraph/openminds/computation/generic_computation.py +104 -0
- fairgraph/openminds/computation/hardware_system.py +53 -0
- fairgraph/openminds/computation/launch_configuration.py +68 -0
- fairgraph/openminds/computation/local_file.py +83 -0
- fairgraph/openminds/computation/model_validation.py +107 -0
- fairgraph/openminds/computation/optimization.py +104 -0
- fairgraph/openminds/computation/simulation.py +104 -0
- fairgraph/openminds/computation/software_agent.py +81 -0
- fairgraph/openminds/computation/validation_test.py +105 -0
- fairgraph/openminds/computation/validation_test_version.py +180 -0
- fairgraph/openminds/computation/visualization.py +104 -0
- fairgraph/openminds/computation/workflow_execution.py +44 -0
- fairgraph/openminds/computation/workflow_recipe.py +95 -0
- fairgraph/openminds/computation/workflow_recipe_version.py +185 -0
- fairgraph/openminds/controlled_terms/__init__.py +116 -0
- fairgraph/openminds/controlled_terms/action_status_type.py +97 -0
- fairgraph/openminds/controlled_terms/age_category.py +89 -0
- fairgraph/openminds/controlled_terms/analysis_technique.py +108 -0
- fairgraph/openminds/controlled_terms/anatomical_axes_orientation.py +89 -0
- fairgraph/openminds/controlled_terms/anatomical_identification_type.py +89 -0
- fairgraph/openminds/controlled_terms/anatomical_plane.py +89 -0
- fairgraph/openminds/controlled_terms/annotation_criteria_type.py +89 -0
- fairgraph/openminds/controlled_terms/annotation_type.py +89 -0
- fairgraph/openminds/controlled_terms/atlas_type.py +88 -0
- fairgraph/openminds/controlled_terms/auditory_stimulus_type.py +127 -0
- fairgraph/openminds/controlled_terms/biological_order.py +117 -0
- fairgraph/openminds/controlled_terms/biological_process.py +79 -0
- fairgraph/openminds/controlled_terms/biological_sex.py +132 -0
- fairgraph/openminds/controlled_terms/breeding_type.py +127 -0
- fairgraph/openminds/controlled_terms/cell_culture_type.py +117 -0
- fairgraph/openminds/controlled_terms/cell_type.py +151 -0
- fairgraph/openminds/controlled_terms/chemical_mixture_type.py +89 -0
- fairgraph/openminds/controlled_terms/colormap.py +79 -0
- fairgraph/openminds/controlled_terms/contribution_type.py +79 -0
- fairgraph/openminds/controlled_terms/cranial_window_construction_type.py +89 -0
- fairgraph/openminds/controlled_terms/cranial_window_reinforcement_type.py +89 -0
- fairgraph/openminds/controlled_terms/criteria_quality_type.py +89 -0
- fairgraph/openminds/controlled_terms/data_type.py +89 -0
- fairgraph/openminds/controlled_terms/device_type.py +94 -0
- fairgraph/openminds/controlled_terms/difference_measure.py +89 -0
- fairgraph/openminds/controlled_terms/disease.py +142 -0
- fairgraph/openminds/controlled_terms/disease_model.py +142 -0
- fairgraph/openminds/controlled_terms/educational_level.py +79 -0
- fairgraph/openminds/controlled_terms/electrical_stimulus_type.py +137 -0
- fairgraph/openminds/controlled_terms/ethics_assessment.py +79 -0
- fairgraph/openminds/controlled_terms/experimental_approach.py +79 -0
- fairgraph/openminds/controlled_terms/file_bundle_grouping.py +99 -0
- fairgraph/openminds/controlled_terms/file_repository_type.py +89 -0
- fairgraph/openminds/controlled_terms/file_usage_role.py +89 -0
- fairgraph/openminds/controlled_terms/genetic_strain_type.py +127 -0
- fairgraph/openminds/controlled_terms/gustatory_stimulus_type.py +127 -0
- fairgraph/openminds/controlled_terms/handedness.py +127 -0
- fairgraph/openminds/controlled_terms/language.py +88 -0
- fairgraph/openminds/controlled_terms/laterality.py +94 -0
- fairgraph/openminds/controlled_terms/learning_resource_type.py +88 -0
- fairgraph/openminds/controlled_terms/measured_quantity.py +89 -0
- fairgraph/openminds/controlled_terms/measured_signal_type.py +79 -0
- fairgraph/openminds/controlled_terms/meta_data_model_type.py +88 -0
- fairgraph/openminds/controlled_terms/model_abstraction_level.py +89 -0
- fairgraph/openminds/controlled_terms/model_scope.py +89 -0
- fairgraph/openminds/controlled_terms/molecular_entity.py +142 -0
- fairgraph/openminds/controlled_terms/mri_pulse_sequence.py +98 -0
- fairgraph/openminds/controlled_terms/mri_weighting.py +98 -0
- fairgraph/openminds/controlled_terms/olfactory_stimulus_type.py +127 -0
- fairgraph/openminds/controlled_terms/operating_device.py +79 -0
- fairgraph/openminds/controlled_terms/operating_system.py +88 -0
- fairgraph/openminds/controlled_terms/optical_stimulus_type.py +127 -0
- fairgraph/openminds/controlled_terms/organ.py +161 -0
- fairgraph/openminds/controlled_terms/organism_substance.py +151 -0
- fairgraph/openminds/controlled_terms/organism_system.py +117 -0
- fairgraph/openminds/controlled_terms/patch_clamp_variation.py +89 -0
- fairgraph/openminds/controlled_terms/preparation_type.py +98 -0
- fairgraph/openminds/controlled_terms/product_accessibility.py +79 -0
- fairgraph/openminds/controlled_terms/programming_language.py +88 -0
- fairgraph/openminds/controlled_terms/qualitative_overlap.py +79 -0
- fairgraph/openminds/controlled_terms/semantic_data_type.py +79 -0
- fairgraph/openminds/controlled_terms/service.py +89 -0
- fairgraph/openminds/controlled_terms/setup_type.py +89 -0
- fairgraph/openminds/controlled_terms/software_application_category.py +79 -0
- fairgraph/openminds/controlled_terms/software_feature.py +79 -0
- fairgraph/openminds/controlled_terms/species.py +143 -0
- fairgraph/openminds/controlled_terms/stimulation_approach.py +98 -0
- fairgraph/openminds/controlled_terms/stimulation_technique.py +98 -0
- fairgraph/openminds/controlled_terms/subcellular_entity.py +143 -0
- fairgraph/openminds/controlled_terms/subject_attribute.py +89 -0
- fairgraph/openminds/controlled_terms/tactile_stimulus_type.py +127 -0
- fairgraph/openminds/controlled_terms/technique.py +108 -0
- fairgraph/openminds/controlled_terms/term_suggestion.py +121 -0
- fairgraph/openminds/controlled_terms/terminology.py +89 -0
- fairgraph/openminds/controlled_terms/tissue_sample_attribute.py +89 -0
- fairgraph/openminds/controlled_terms/tissue_sample_type.py +127 -0
- fairgraph/openminds/controlled_terms/type_of_uncertainty.py +89 -0
- fairgraph/openminds/controlled_terms/uberon_parcellation.py +153 -0
- fairgraph/openminds/controlled_terms/unit_of_measurement.py +108 -0
- fairgraph/openminds/controlled_terms/visual_stimulus_type.py +127 -0
- fairgraph/openminds/controlledterms.py +6 -0
- fairgraph/openminds/core/__init__.py +107 -0
- fairgraph/openminds/core/actors/__init__.py +7 -0
- fairgraph/openminds/core/actors/account_information.py +44 -0
- fairgraph/openminds/core/actors/affiliation.py +30 -0
- fairgraph/openminds/core/actors/consortium.py +175 -0
- fairgraph/openminds/core/actors/contact_information.py +43 -0
- fairgraph/openminds/core/actors/contribution.py +23 -0
- fairgraph/openminds/core/actors/organization.py +199 -0
- fairgraph/openminds/core/actors/person.py +236 -0
- fairgraph/openminds/core/data/__init__.py +13 -0
- fairgraph/openminds/core/data/content_type.py +107 -0
- fairgraph/openminds/core/data/content_type_pattern.py +53 -0
- fairgraph/openminds/core/data/copyright.py +23 -0
- fairgraph/openminds/core/data/file.py +275 -0
- fairgraph/openminds/core/data/file_archive.py +71 -0
- fairgraph/openminds/core/data/file_bundle.py +150 -0
- fairgraph/openminds/core/data/file_path_pattern.py +23 -0
- fairgraph/openminds/core/data/file_repository.py +99 -0
- fairgraph/openminds/core/data/file_repository_structure.py +51 -0
- fairgraph/openminds/core/data/hash.py +23 -0
- fairgraph/openminds/core/data/license.py +77 -0
- fairgraph/openminds/core/data/measurement.py +45 -0
- fairgraph/openminds/core/data/service_link.py +49 -0
- fairgraph/openminds/core/digital_identifier/__init__.py +11 -0
- fairgraph/openminds/core/digital_identifier/doi.py +98 -0
- fairgraph/openminds/core/digital_identifier/gridid.py +41 -0
- fairgraph/openminds/core/digital_identifier/handle.py +52 -0
- fairgraph/openminds/core/digital_identifier/identifiers_dot_org_id.py +41 -0
- fairgraph/openminds/core/digital_identifier/isbn.py +88 -0
- fairgraph/openminds/core/digital_identifier/issn.py +63 -0
- fairgraph/openminds/core/digital_identifier/orcid.py +41 -0
- fairgraph/openminds/core/digital_identifier/rorid.py +41 -0
- fairgraph/openminds/core/digital_identifier/rrid.py +55 -0
- fairgraph/openminds/core/digital_identifier/stock_number.py +23 -0
- fairgraph/openminds/core/digital_identifier/swhid.py +48 -0
- fairgraph/openminds/core/miscellaneous/__init__.py +7 -0
- fairgraph/openminds/core/miscellaneous/comment.py +47 -0
- fairgraph/openminds/core/miscellaneous/funding.py +70 -0
- fairgraph/openminds/core/miscellaneous/quantitative_value.py +43 -0
- fairgraph/openminds/core/miscellaneous/quantitative_value_array.py +49 -0
- fairgraph/openminds/core/miscellaneous/quantitative_value_range.py +43 -0
- fairgraph/openminds/core/miscellaneous/research_product_group.py +26 -0
- fairgraph/openminds/core/miscellaneous/web_resource.py +104 -0
- fairgraph/openminds/core/products/__init__.py +12 -0
- fairgraph/openminds/core/products/dataset.py +95 -0
- fairgraph/openminds/core/products/dataset_version.py +240 -0
- fairgraph/openminds/core/products/meta_data_model.py +95 -0
- fairgraph/openminds/core/products/meta_data_model_version.py +168 -0
- fairgraph/openminds/core/products/model.py +103 -0
- fairgraph/openminds/core/products/model_version.py +235 -0
- fairgraph/openminds/core/products/project.py +56 -0
- fairgraph/openminds/core/products/setup.py +69 -0
- fairgraph/openminds/core/products/software.py +95 -0
- fairgraph/openminds/core/products/software_version.py +226 -0
- fairgraph/openminds/core/products/web_service.py +103 -0
- fairgraph/openminds/core/products/web_service_version.py +182 -0
- fairgraph/openminds/core/research/__init__.py +17 -0
- fairgraph/openminds/core/research/behavioral_protocol.py +69 -0
- fairgraph/openminds/core/research/configuration.py +67 -0
- fairgraph/openminds/core/research/custom_property_set.py +27 -0
- fairgraph/openminds/core/research/numerical_property.py +23 -0
- fairgraph/openminds/core/research/property_value_list.py +71 -0
- fairgraph/openminds/core/research/protocol.py +67 -0
- fairgraph/openminds/core/research/protocol_execution.py +76 -0
- fairgraph/openminds/core/research/strain.py +90 -0
- fairgraph/openminds/core/research/string_property.py +23 -0
- fairgraph/openminds/core/research/subject.py +79 -0
- fairgraph/openminds/core/research/subject_group.py +91 -0
- fairgraph/openminds/core/research/subject_group_state.py +113 -0
- fairgraph/openminds/core/research/subject_state.py +138 -0
- fairgraph/openminds/core/research/tissue_sample.py +87 -0
- fairgraph/openminds/core/research/tissue_sample_collection.py +99 -0
- fairgraph/openminds/core/research/tissue_sample_collection_state.py +109 -0
- fairgraph/openminds/core/research/tissue_sample_state.py +127 -0
- fairgraph/openminds/ephys/__init__.py +39 -0
- fairgraph/openminds/ephys/activity/__init__.py +3 -0
- fairgraph/openminds/ephys/activity/cell_patching.py +73 -0
- fairgraph/openminds/ephys/activity/electrode_placement.py +67 -0
- fairgraph/openminds/ephys/activity/recording_activity.py +67 -0
- fairgraph/openminds/ephys/device/__init__.py +6 -0
- fairgraph/openminds/ephys/device/electrode.py +81 -0
- fairgraph/openminds/ephys/device/electrode_array.py +85 -0
- fairgraph/openminds/ephys/device/electrode_array_usage.py +105 -0
- fairgraph/openminds/ephys/device/electrode_usage.py +101 -0
- fairgraph/openminds/ephys/device/pipette.py +81 -0
- fairgraph/openminds/ephys/device/pipette_usage.py +123 -0
- fairgraph/openminds/ephys/entity/__init__.py +2 -0
- fairgraph/openminds/ephys/entity/channel.py +23 -0
- fairgraph/openminds/ephys/entity/recording.py +63 -0
- fairgraph/openminds/publications/__init__.py +47 -0
- fairgraph/openminds/publications/book.py +106 -0
- fairgraph/openminds/publications/chapter.py +100 -0
- fairgraph/openminds/publications/learning_resource.py +90 -0
- fairgraph/openminds/publications/live_paper.py +95 -0
- fairgraph/openminds/publications/live_paper_resource_item.py +58 -0
- fairgraph/openminds/publications/live_paper_section.py +57 -0
- fairgraph/openminds/publications/live_paper_version.py +177 -0
- fairgraph/openminds/publications/periodical.py +53 -0
- fairgraph/openminds/publications/publication_issue.py +44 -0
- fairgraph/openminds/publications/publication_volume.py +44 -0
- fairgraph/openminds/publications/scholarly_article.py +146 -0
- fairgraph/openminds/sands/__init__.py +57 -0
- fairgraph/openminds/sands/atlas/__init__.py +9 -0
- fairgraph/openminds/sands/atlas/atlas_annotation.py +52 -0
- fairgraph/openminds/sands/atlas/brain_atlas.py +113 -0
- fairgraph/openminds/sands/atlas/brain_atlas_version.py +213 -0
- fairgraph/openminds/sands/atlas/common_coordinate_space.py +121 -0
- fairgraph/openminds/sands/atlas/common_coordinate_space_version.py +243 -0
- fairgraph/openminds/sands/atlas/parcellation_entity.py +133 -0
- fairgraph/openminds/sands/atlas/parcellation_entity_version.py +162 -0
- fairgraph/openminds/sands/atlas/parcellation_terminology.py +38 -0
- fairgraph/openminds/sands/atlas/parcellation_terminology_version.py +38 -0
- fairgraph/openminds/sands/mathematical_shapes/__init__.py +3 -0
- fairgraph/openminds/sands/mathematical_shapes/circle.py +23 -0
- fairgraph/openminds/sands/mathematical_shapes/ellipse.py +27 -0
- fairgraph/openminds/sands/mathematical_shapes/rectangle.py +23 -0
- fairgraph/openminds/sands/miscellaneous/__init__.py +6 -0
- fairgraph/openminds/sands/miscellaneous/anatomical_target_position.py +40 -0
- fairgraph/openminds/sands/miscellaneous/coordinate_point.py +23 -0
- fairgraph/openminds/sands/miscellaneous/qualitative_relation_assessment.py +34 -0
- fairgraph/openminds/sands/miscellaneous/quantitative_relation_assessment.py +38 -0
- fairgraph/openminds/sands/miscellaneous/single_color.py +24 -0
- fairgraph/openminds/sands/miscellaneous/viewer_specification.py +40 -0
- fairgraph/openminds/sands/non_atlas/__init__.py +3 -0
- fairgraph/openminds/sands/non_atlas/custom_anatomical_entity.py +110 -0
- fairgraph/openminds/sands/non_atlas/custom_annotation.py +54 -0
- fairgraph/openminds/sands/non_atlas/custom_coordinate_space.py +67 -0
- fairgraph/openminds/specimen_prep/__init__.py +38 -0
- fairgraph/openminds/specimen_prep/activity/__init__.py +3 -0
- fairgraph/openminds/specimen_prep/activity/cranial_window_preparation.py +69 -0
- fairgraph/openminds/specimen_prep/activity/tissue_culture_preparation.py +67 -0
- fairgraph/openminds/specimen_prep/activity/tissue_sample_slicing.py +69 -0
- fairgraph/openminds/specimen_prep/device/__init__.py +2 -0
- fairgraph/openminds/specimen_prep/device/slicing_device.py +73 -0
- fairgraph/openminds/specimen_prep/device/slicing_device_usage.py +117 -0
- fairgraph/openminds/specimenprep.py +4 -0
- fairgraph/openminds/stimulation/__init__.py +38 -0
- fairgraph/openminds/stimulation/activity/__init__.py +1 -0
- fairgraph/openminds/stimulation/activity/stimulation_activity.py +67 -0
- fairgraph/openminds/stimulation/stimulus/__init__.py +1 -0
- fairgraph/openminds/stimulation/stimulus/ephys_stimulus.py +63 -0
- fairgraph/queries.py +499 -0
- fairgraph/registry.py +123 -0
- fairgraph/utility.py +607 -0
- fairgraph-0.13.0.dist-info/METADATA +222 -0
- fairgraph-0.13.0.dist-info/RECORD +267 -0
- fairgraph-0.13.0.dist-info/WHEEL +5 -0
- fairgraph-0.13.0.dist-info/licenses/LICENSE.txt +177 -0
- fairgraph-0.13.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import inspect
|
|
3
|
+
from fairgraph.kgobject import KGObject
|
|
4
|
+
from fairgraph.embedded import EmbeddedMetadata
|
|
5
|
+
|
|
6
|
+
from .stimulus import EphysStimulus
|
|
7
|
+
from .activity import StimulationActivity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_kg_classes():
|
|
11
|
+
"""List all KG classes defined in this module"""
|
|
12
|
+
return [
|
|
13
|
+
obj
|
|
14
|
+
for name, obj in inspect.getmembers(sys.modules[__name__])
|
|
15
|
+
if inspect.isclass(obj) and issubclass(obj, KGObject) and obj.__module__.startswith(__name__)
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def list_embedded_metadata_classes():
|
|
20
|
+
"""List all embedded metadata classes defined in this module"""
|
|
21
|
+
return [
|
|
22
|
+
obj
|
|
23
|
+
for name, obj in inspect.getmembers(sys.modules[__name__])
|
|
24
|
+
if inspect.isclass(obj) and issubclass(obj, EmbeddedMetadata) and obj.__module__.startswith(__name__)
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def set_error_handling(value):
|
|
29
|
+
"""
|
|
30
|
+
Control validation for all classes in this module.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
value (str): action to follow when there is a validation failure.
|
|
34
|
+
(e.g. if a required property is not provided).
|
|
35
|
+
Possible values: "error", "warning", "log", None
|
|
36
|
+
"""
|
|
37
|
+
for cls in list_kg_classes() + list_embedded_metadata_classes():
|
|
38
|
+
cls.set_error_handling(value)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .stimulation_activity import StimulationActivity
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
<description not available>
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# this file was auto-generated
|
|
6
|
+
|
|
7
|
+
from openminds.properties import Property
|
|
8
|
+
from openminds.v4.stimulation import StimulationActivity as OMStimulationActivity
|
|
9
|
+
from fairgraph import KGObject
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from datetime import datetime, time
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StimulationActivity(KGObject, OMStimulationActivity):
|
|
16
|
+
"""
|
|
17
|
+
<description not available>
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
type_ = "https://openminds.om-i.org/types/StimulationActivity"
|
|
21
|
+
default_space = "in-depth"
|
|
22
|
+
# forward properties are defined in the parent class (in openMINDS-Python)
|
|
23
|
+
reverse_properties = []
|
|
24
|
+
existence_query_properties = ("lookup_label",)
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
lookup_label=None,
|
|
29
|
+
custom_property_sets=None,
|
|
30
|
+
description=None,
|
|
31
|
+
end_time=None,
|
|
32
|
+
inputs=None,
|
|
33
|
+
is_part_of=None,
|
|
34
|
+
outputs=None,
|
|
35
|
+
performed_by=None,
|
|
36
|
+
preparation_design=None,
|
|
37
|
+
protocols=None,
|
|
38
|
+
setup=None,
|
|
39
|
+
start_time=None,
|
|
40
|
+
stimuli=None,
|
|
41
|
+
study_targets=None,
|
|
42
|
+
id=None,
|
|
43
|
+
data=None,
|
|
44
|
+
space=None,
|
|
45
|
+
release_status=None,
|
|
46
|
+
):
|
|
47
|
+
return KGObject.__init__(
|
|
48
|
+
self,
|
|
49
|
+
id=id,
|
|
50
|
+
space=space,
|
|
51
|
+
release_status=release_status,
|
|
52
|
+
data=data,
|
|
53
|
+
lookup_label=lookup_label,
|
|
54
|
+
custom_property_sets=custom_property_sets,
|
|
55
|
+
description=description,
|
|
56
|
+
end_time=end_time,
|
|
57
|
+
inputs=inputs,
|
|
58
|
+
is_part_of=is_part_of,
|
|
59
|
+
outputs=outputs,
|
|
60
|
+
performed_by=performed_by,
|
|
61
|
+
preparation_design=preparation_design,
|
|
62
|
+
protocols=protocols,
|
|
63
|
+
setup=setup,
|
|
64
|
+
start_time=start_time,
|
|
65
|
+
stimuli=stimuli,
|
|
66
|
+
study_targets=study_targets,
|
|
67
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .ephys_stimulus import EphysStimulus
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
<description not available>
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# this file was auto-generated
|
|
6
|
+
|
|
7
|
+
from openminds.properties import Property
|
|
8
|
+
from openminds.v4.stimulation import EphysStimulus as OMEphysStimulus
|
|
9
|
+
from fairgraph import KGObject
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EphysStimulus(KGObject, OMEphysStimulus):
|
|
13
|
+
"""
|
|
14
|
+
<description not available>
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
type_ = "https://openminds.om-i.org/types/EphysStimulus"
|
|
18
|
+
default_space = "in-depth"
|
|
19
|
+
# forward properties are defined in the parent class (in openMINDS-Python)
|
|
20
|
+
reverse_properties = [
|
|
21
|
+
Property(
|
|
22
|
+
"is_stimulus_for",
|
|
23
|
+
"openminds.v4.stimulation.StimulationActivity",
|
|
24
|
+
"stimulus",
|
|
25
|
+
reverse="stimuli",
|
|
26
|
+
multiple=True,
|
|
27
|
+
description="reverse of 'stimuli'",
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
existence_query_properties = ("lookup_label",)
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
lookup_label=None,
|
|
35
|
+
delivered_by=None,
|
|
36
|
+
description=None,
|
|
37
|
+
epoch=None,
|
|
38
|
+
generated_by=None,
|
|
39
|
+
internal_identifier=None,
|
|
40
|
+
is_stimulus_for=None,
|
|
41
|
+
specifications=None,
|
|
42
|
+
type=None,
|
|
43
|
+
id=None,
|
|
44
|
+
data=None,
|
|
45
|
+
space=None,
|
|
46
|
+
release_status=None,
|
|
47
|
+
):
|
|
48
|
+
return KGObject.__init__(
|
|
49
|
+
self,
|
|
50
|
+
id=id,
|
|
51
|
+
space=space,
|
|
52
|
+
release_status=release_status,
|
|
53
|
+
data=data,
|
|
54
|
+
lookup_label=lookup_label,
|
|
55
|
+
delivered_by=delivered_by,
|
|
56
|
+
description=description,
|
|
57
|
+
epoch=epoch,
|
|
58
|
+
generated_by=generated_by,
|
|
59
|
+
internal_identifier=internal_identifier,
|
|
60
|
+
is_stimulus_for=is_stimulus_for,
|
|
61
|
+
specifications=specifications,
|
|
62
|
+
type=type,
|
|
63
|
+
)
|
fairgraph/queries.py
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides Python classes to assist in writing Knowledge Graph queries.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Copyright 2018-2024 CNRS
|
|
6
|
+
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
from typing import Optional, List, Any, Dict, Union
|
|
21
|
+
from uuid import UUID
|
|
22
|
+
from datetime import date, datetime
|
|
23
|
+
import logging
|
|
24
|
+
from warnings import warn
|
|
25
|
+
|
|
26
|
+
from openminds.base import IRI, EmbeddedMetadata, LinkedMetadata, Node
|
|
27
|
+
from .utility import as_list, expand_uri
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("fairgraph")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Filter:
|
|
34
|
+
"""
|
|
35
|
+
A filter for querying Knowledge Graph nodes.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
operation (str): The operation for the filter. Options are:
|
|
39
|
+
("CONTAINS", "EQUALS", "IS_EMPTY", "STARTS_WITH", "ENDS_WITH", "REGEX")
|
|
40
|
+
parameter (str, optional): A parameter name for the filter.
|
|
41
|
+
value (str, optional): The value to filter on.
|
|
42
|
+
|
|
43
|
+
Methods:
|
|
44
|
+
serialize: Returns a dictionary containing the serialized filter.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, operation: str, parameter: Optional[str] = None, value: Optional[str] = None):
|
|
49
|
+
self.operation = operation
|
|
50
|
+
self.parameter = parameter
|
|
51
|
+
self.value = value
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
repr = f"Filter(operation='{self.operation}'"
|
|
55
|
+
if self.parameter:
|
|
56
|
+
repr += f", parameter='{self.parameter}'"
|
|
57
|
+
if self.value:
|
|
58
|
+
repr += f", value='{self.value}'"
|
|
59
|
+
return repr + ")"
|
|
60
|
+
|
|
61
|
+
def serialize(self):
|
|
62
|
+
data = {"op": self.operation}
|
|
63
|
+
if self.parameter:
|
|
64
|
+
data["parameter"] = self.parameter
|
|
65
|
+
if self.value:
|
|
66
|
+
data["value"] = self.value
|
|
67
|
+
return data
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class QueryProperty:
|
|
71
|
+
"""
|
|
72
|
+
A property for a Knowledge Graph query.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
path (URI): The path of the property as a URI.
|
|
76
|
+
name (str, optional): The name of the property to be used in the returned results.
|
|
77
|
+
filter (Filter, optional): A filter based on the property.
|
|
78
|
+
sorted (bool, optional): Whether to sort the results based on the property. Defaults to False.
|
|
79
|
+
required (bool, optional): Whether the property is required. Defaults to False.
|
|
80
|
+
ensure_order (bool, optional): Whether to ensure the ordering of results is maintained. Defaults to False.
|
|
81
|
+
properties (List[QueryProperty], optional): A list of sub-properties.
|
|
82
|
+
type_filter (URI, optional): Ensure that only objects that match the given type URI are returned.
|
|
83
|
+
reverse (bool, optional): Whether the link defined by the path should be followed in the reverse direction. Defaults to False.
|
|
84
|
+
expect_single (bool, optional): Whether to expect a single element in the result. Defaults to False.
|
|
85
|
+
|
|
86
|
+
Methods:
|
|
87
|
+
add_property: Adds a sub-property to the QueryProperty object.
|
|
88
|
+
serialize: Returns a dictionary containing the serialized QueryProperty.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> p = QueryProperty(
|
|
92
|
+
... "https://openminds.ebrains.eu/vocab/fullName",
|
|
93
|
+
... name="full_name",
|
|
94
|
+
... filter=Filter("CONTAINS", parameter="name"),
|
|
95
|
+
... sorted=True,
|
|
96
|
+
... required=True
|
|
97
|
+
... )
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
path: str,
|
|
103
|
+
name: Optional[str] = None,
|
|
104
|
+
filter: Optional[Filter] = None,
|
|
105
|
+
sorted: bool = False,
|
|
106
|
+
required: bool = False,
|
|
107
|
+
ensure_order: bool = False,
|
|
108
|
+
properties: Optional[List[QueryProperty]] = None,
|
|
109
|
+
type_filter: Optional[str] = None,
|
|
110
|
+
reverse: bool = False,
|
|
111
|
+
expect_single: bool = False,
|
|
112
|
+
):
|
|
113
|
+
self.path = path
|
|
114
|
+
self.name = name
|
|
115
|
+
self.filter = filter
|
|
116
|
+
self.sorted = sorted
|
|
117
|
+
self.required = required
|
|
118
|
+
self.ensure_order = ensure_order
|
|
119
|
+
self.properties = properties or []
|
|
120
|
+
self.type_filter = type_filter
|
|
121
|
+
self.reverse = reverse
|
|
122
|
+
self.expect_single = expect_single
|
|
123
|
+
|
|
124
|
+
for prop in self.properties:
|
|
125
|
+
if prop.sorted:
|
|
126
|
+
raise ValueError("Sorting is only allowed on the root level of a query.")
|
|
127
|
+
|
|
128
|
+
def __repr__(self):
|
|
129
|
+
return f"QueryProperty({self.path}, name={self.name}, reverse={self.reverse})"
|
|
130
|
+
|
|
131
|
+
def add_property(self, prop: QueryProperty):
|
|
132
|
+
assert isinstance(prop, QueryProperty)
|
|
133
|
+
if prop.sorted:
|
|
134
|
+
raise ValueError("Sorting is only allowed on the root level of a query.")
|
|
135
|
+
self.properties.append(prop)
|
|
136
|
+
|
|
137
|
+
def serialize(self) -> Dict[str, Any]:
|
|
138
|
+
data: Dict[str, Any] = {
|
|
139
|
+
"path": self.path,
|
|
140
|
+
}
|
|
141
|
+
if self.name:
|
|
142
|
+
data["propertyName"] = self.name
|
|
143
|
+
if self.filter:
|
|
144
|
+
data["filter"] = self.filter.serialize()
|
|
145
|
+
if self.sorted:
|
|
146
|
+
data["sort"] = True
|
|
147
|
+
if self.required:
|
|
148
|
+
data["required"] = True
|
|
149
|
+
if self.ensure_order:
|
|
150
|
+
data["ensureOrder"] = True
|
|
151
|
+
if self.properties:
|
|
152
|
+
data["structure"] = [prop.serialize() for prop in self.properties]
|
|
153
|
+
if self.type_filter or self.reverse:
|
|
154
|
+
if isinstance(self.path, str):
|
|
155
|
+
first_path_element = {"@id": self.path}
|
|
156
|
+
else:
|
|
157
|
+
# for now we only support specifying type filters/reverse
|
|
158
|
+
# for the first element in a multi-element path
|
|
159
|
+
assert isinstance(self.path, (list, tuple))
|
|
160
|
+
first_path_element = {"@id": self.path[0]}
|
|
161
|
+
if self.type_filter:
|
|
162
|
+
if isinstance(self.type_filter, (list, tuple)):
|
|
163
|
+
first_path_element["typeFilter"] = [{"@id": type_iri} for type_iri in self.type_filter]
|
|
164
|
+
else:
|
|
165
|
+
assert isinstance(self.type_filter, str)
|
|
166
|
+
first_path_element["typeFilter"] = {"@id": self.type_filter}
|
|
167
|
+
if self.reverse:
|
|
168
|
+
first_path_element["reverse"] = True
|
|
169
|
+
if isinstance(self.path, str):
|
|
170
|
+
data["path"] = first_path_element
|
|
171
|
+
else:
|
|
172
|
+
data["path"] = [first_path_element, *self.path[1:]]
|
|
173
|
+
if self.expect_single:
|
|
174
|
+
data["singleValue"] = "FIRST"
|
|
175
|
+
return data
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Query:
|
|
179
|
+
"""
|
|
180
|
+
A Python representation of an EBRAINS Knowledge Graph query,
|
|
181
|
+
which can be serialized to the JSON-LD used by the kg-core query API.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
node_type (URI): The URI of the node type to query.
|
|
185
|
+
label (str, optional): A label for this query.
|
|
186
|
+
space (Optional[str], optional): The KG space to query.
|
|
187
|
+
properties (Optional[List[QueryProperty]], optional): A list of QueryProperty
|
|
188
|
+
objects representing the properties to include in the results.
|
|
189
|
+
|
|
190
|
+
Methods:
|
|
191
|
+
add_property: Adds a QueryProperty object to the list of properties to include.
|
|
192
|
+
serialize: Returns a JSON-LD representation of the query, suitable for sending to the KG.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
>>> q = Query(
|
|
196
|
+
... node_type="https://openminds.ebrains.eu/core/ModelVersion",
|
|
197
|
+
... label="fg-testing-modelversion",
|
|
198
|
+
... space="model",
|
|
199
|
+
... properties=[
|
|
200
|
+
... QueryProperty("@type"),
|
|
201
|
+
... QueryProperty(
|
|
202
|
+
... "https://openminds.ebrains.eu/vocab/fullName",
|
|
203
|
+
... name="vocab:fullName",
|
|
204
|
+
... filter=Filter("CONTAINS", parameter="name"),
|
|
205
|
+
... sorted=True,
|
|
206
|
+
... required=True,
|
|
207
|
+
... ),
|
|
208
|
+
... QueryProperty(
|
|
209
|
+
... "https://openminds.ebrains.eu/vocab/versionIdentifier",
|
|
210
|
+
... name="vocab:versionIdentifier",
|
|
211
|
+
... filter=Filter("EQUALS", parameter="version"),
|
|
212
|
+
... required=True,
|
|
213
|
+
... ),
|
|
214
|
+
... QueryProperty(
|
|
215
|
+
... "https://openminds.ebrains.eu/vocab/format",
|
|
216
|
+
... name="vocab:format",
|
|
217
|
+
... ensure_order=True,
|
|
218
|
+
... properties=[
|
|
219
|
+
... QueryProperty("@id", filter=Filter("EQUALS", parameter="format")),
|
|
220
|
+
... QueryProperty("@type"),
|
|
221
|
+
... ],
|
|
222
|
+
... )
|
|
223
|
+
... )
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
node_type: str,
|
|
229
|
+
label: Optional[str] = None,
|
|
230
|
+
space: Optional[str] = None,
|
|
231
|
+
properties: Optional[List[QueryProperty]] = None,
|
|
232
|
+
):
|
|
233
|
+
self.node_type = node_type
|
|
234
|
+
self.label = label
|
|
235
|
+
self.space = space
|
|
236
|
+
self.properties = [QueryProperty("@id", filter=Filter("EQUALS", parameter="id"))]
|
|
237
|
+
if properties:
|
|
238
|
+
self.properties.extend(properties)
|
|
239
|
+
if space:
|
|
240
|
+
found = False
|
|
241
|
+
for property in self.properties:
|
|
242
|
+
if property.path == "https://core.kg.ebrains.eu/vocab/meta/space":
|
|
243
|
+
property.filter = Filter("EQUALS", value=self.space)
|
|
244
|
+
found = True
|
|
245
|
+
if not found:
|
|
246
|
+
self.properties.append(
|
|
247
|
+
QueryProperty(
|
|
248
|
+
"https://core.kg.ebrains.eu/vocab/meta/space",
|
|
249
|
+
name="query:space",
|
|
250
|
+
filter=Filter("EQUALS", value=self.space),
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def add_property(self, prop: QueryProperty):
|
|
255
|
+
assert isinstance(prop, QueryProperty)
|
|
256
|
+
self.properties.append(prop)
|
|
257
|
+
|
|
258
|
+
def serialize(self) -> Dict[str, Any]:
|
|
259
|
+
query = {
|
|
260
|
+
"@context": {
|
|
261
|
+
"@vocab": "https://core.kg.ebrains.eu/vocab/query/",
|
|
262
|
+
"query": "https://schema.hbp.eu/myQuery/",
|
|
263
|
+
"propertyName": {"@id": "propertyName", "@type": "@id"},
|
|
264
|
+
"merge": {"@type": "@id", "@id": "merge"},
|
|
265
|
+
"path": {"@id": "path", "@type": "@id"},
|
|
266
|
+
},
|
|
267
|
+
"meta": {
|
|
268
|
+
"type": self.node_type,
|
|
269
|
+
"description": "Automatically generated by fairgraph",
|
|
270
|
+
},
|
|
271
|
+
"structure": [prop.serialize() for prop in self.properties],
|
|
272
|
+
}
|
|
273
|
+
if self.label:
|
|
274
|
+
query["meta"]["name"] = self.label
|
|
275
|
+
return query
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# todo: I think only one property can have "sort": True - need to check this
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _get_query_property_name(property, possible_classes):
|
|
282
|
+
if isinstance(property.path, str):
|
|
283
|
+
property_name = property.path
|
|
284
|
+
else:
|
|
285
|
+
assert isinstance(property.path, list)
|
|
286
|
+
found_match = False
|
|
287
|
+
for cls in possible_classes:
|
|
288
|
+
for path in property.path:
|
|
289
|
+
for prop in cls.properties:
|
|
290
|
+
if path == prop.path:
|
|
291
|
+
property_name = path
|
|
292
|
+
found_match = True
|
|
293
|
+
break
|
|
294
|
+
if found_match:
|
|
295
|
+
break
|
|
296
|
+
if found_match:
|
|
297
|
+
break
|
|
298
|
+
assert found_match
|
|
299
|
+
return property_name
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_query_properties(
|
|
303
|
+
property, context, follow_links: Optional[Dict[str, Any]] = None, with_reverse_properties: Optional[bool] = False
|
|
304
|
+
) -> List[QueryProperty]:
|
|
305
|
+
"""
|
|
306
|
+
Generate one or more QueryProperty instances for this property,
|
|
307
|
+
for use in constructing a KG query definition.
|
|
308
|
+
"""
|
|
309
|
+
expanded_path = expand_uri(property.path, context)
|
|
310
|
+
properties = []
|
|
311
|
+
|
|
312
|
+
if any(issubclass(_type, EmbeddedMetadata) for _type in property.types):
|
|
313
|
+
if not all(issubclass(_type, EmbeddedMetadata) for _type in property.types):
|
|
314
|
+
warn(f"Mixed types in {property}")
|
|
315
|
+
return properties
|
|
316
|
+
for cls in property.types:
|
|
317
|
+
if len(property.types) > 1:
|
|
318
|
+
property_name = f"{property.path}__{cls.__name__}"
|
|
319
|
+
assert isinstance(cls.type_, str)
|
|
320
|
+
type_filter = cls.type_
|
|
321
|
+
else:
|
|
322
|
+
property_name = property.path
|
|
323
|
+
type_filter = None
|
|
324
|
+
properties.append(
|
|
325
|
+
QueryProperty(
|
|
326
|
+
expanded_path,
|
|
327
|
+
name=property_name,
|
|
328
|
+
reverse=bool(property.reverse),
|
|
329
|
+
type_filter=type_filter,
|
|
330
|
+
ensure_order=property.multiple,
|
|
331
|
+
expect_single=property.is_link and not property.multiple,
|
|
332
|
+
properties=cls.generate_query_properties(follow_links, with_reverse_properties),
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
elif any(issubclass(_type, LinkedMetadata) for _type in property.types):
|
|
336
|
+
assert all(issubclass(_type, LinkedMetadata) for _type in property.types)
|
|
337
|
+
if follow_links is not None:
|
|
338
|
+
for cls in property.types:
|
|
339
|
+
property_name = _get_query_property_name(property, possible_classes=[cls])
|
|
340
|
+
if len(property.types) > 1:
|
|
341
|
+
property_name = f"{property_name}__{cls.__name__}"
|
|
342
|
+
assert isinstance(cls.type_, str)
|
|
343
|
+
type_filter = cls.type_
|
|
344
|
+
else:
|
|
345
|
+
type_filter = None
|
|
346
|
+
properties.append(
|
|
347
|
+
QueryProperty(
|
|
348
|
+
expanded_path,
|
|
349
|
+
name=property_name,
|
|
350
|
+
reverse=bool(property.reverse),
|
|
351
|
+
type_filter=type_filter,
|
|
352
|
+
ensure_order=property.multiple,
|
|
353
|
+
expect_single=property.is_link and not property.multiple,
|
|
354
|
+
properties=[
|
|
355
|
+
QueryProperty("@id"),
|
|
356
|
+
*cls.generate_query_properties(follow_links, with_reverse_properties),
|
|
357
|
+
],
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
else:
|
|
361
|
+
if isinstance(property.path, str):
|
|
362
|
+
property_name = property.path
|
|
363
|
+
properties.append(
|
|
364
|
+
QueryProperty(
|
|
365
|
+
expanded_path,
|
|
366
|
+
name=property_name,
|
|
367
|
+
reverse=bool(property.reverse),
|
|
368
|
+
type_filter=None,
|
|
369
|
+
ensure_order=property.multiple,
|
|
370
|
+
expect_single=property.is_link and not property.multiple,
|
|
371
|
+
properties=[
|
|
372
|
+
QueryProperty("@id"),
|
|
373
|
+
QueryProperty("@type"),
|
|
374
|
+
],
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
else:
|
|
378
|
+
assert isinstance(property.path, list)
|
|
379
|
+
logger.warning(f"Cannot yet handle case where property.path is a list: {property}")
|
|
380
|
+
else:
|
|
381
|
+
assert not property.reverse
|
|
382
|
+
properties.append(
|
|
383
|
+
QueryProperty(
|
|
384
|
+
expanded_path,
|
|
385
|
+
name=property.path,
|
|
386
|
+
reverse=bool(property.reverse),
|
|
387
|
+
ensure_order=property.multiple,
|
|
388
|
+
expect_single=property.is_link and not property.multiple,
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
return properties
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def get_query_filter_property(property, context, filter: Any) -> QueryProperty:
|
|
395
|
+
"""
|
|
396
|
+
Generate a QueryProperty instance containing a filter,
|
|
397
|
+
for use in constructing a KG query definition.
|
|
398
|
+
"""
|
|
399
|
+
assert filter is not None
|
|
400
|
+
if isinstance(filter, dict):
|
|
401
|
+
# we pass the filter through to the next level
|
|
402
|
+
filter_obj = None
|
|
403
|
+
else:
|
|
404
|
+
# we have a filter value for this property
|
|
405
|
+
if property.types[0] in (int, float, bool, datetime, date):
|
|
406
|
+
op = "EQUALS"
|
|
407
|
+
else:
|
|
408
|
+
op = "CONTAINS"
|
|
409
|
+
filter_obj = Filter(op, value=filter)
|
|
410
|
+
|
|
411
|
+
expanded_path = expand_uri(property.path, context)
|
|
412
|
+
|
|
413
|
+
if any(issubclass(_type, Node) for _type in property.types):
|
|
414
|
+
assert all(issubclass(_type, Node) for _type in property.types)
|
|
415
|
+
prop = QueryProperty(expanded_path, name=f"Q{property.name}", required=True, reverse=property.reverse)
|
|
416
|
+
if filter_obj:
|
|
417
|
+
if filter_obj.value.startswith("https://kg.ebrains.eu/api/instances"):
|
|
418
|
+
filter_path = "@id"
|
|
419
|
+
else:
|
|
420
|
+
filter_path = "http://schema.org/identifier"
|
|
421
|
+
prop.properties.append(QueryProperty(filter_path, filter=filter_obj))
|
|
422
|
+
else:
|
|
423
|
+
for cls in property.types:
|
|
424
|
+
child_properties = cls.generate_query_filter_properties(filter)
|
|
425
|
+
if child_properties:
|
|
426
|
+
# if the class has properties with the appropriate name
|
|
427
|
+
# we add them, then break to avoid adding the same
|
|
428
|
+
# prop twice
|
|
429
|
+
prop.properties.extend(child_properties)
|
|
430
|
+
break
|
|
431
|
+
else:
|
|
432
|
+
prop = QueryProperty(expanded_path, name=f"Q{property.name}", filter=filter_obj, required=True)
|
|
433
|
+
return prop
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def get_filter_value(property, value: Any) -> Union[str, List[str]]:
|
|
437
|
+
"""
|
|
438
|
+
Normalize a value for use in a KG query
|
|
439
|
+
|
|
440
|
+
Example:
|
|
441
|
+
>>> import fairgraph.openminds.core as omcore
|
|
442
|
+
>>> person = omcore.Person.from_uuid("045f846f-f010-4db8-97b9-b95b20970bf2", kg_client)
|
|
443
|
+
>>> prop = Property(name='custodians', types=(omcore.Organization, omcore.Person),
|
|
444
|
+
... path="vocab:custodian", multiple=True)
|
|
445
|
+
>>> get_filter_value(prop, person)
|
|
446
|
+
https://kg.ebrains.eu/api/instances/045f846f-f010-4db8-97b9-b95b20970bf2
|
|
447
|
+
"""
|
|
448
|
+
from .kgproxy import KGProxy
|
|
449
|
+
|
|
450
|
+
def is_valid(val):
|
|
451
|
+
if isinstance(val, str):
|
|
452
|
+
try:
|
|
453
|
+
val = UUID(val)
|
|
454
|
+
except ValueError:
|
|
455
|
+
pass
|
|
456
|
+
return isinstance(val, (IRI, UUID, *property.types)) or (
|
|
457
|
+
isinstance(val, KGProxy) and not set(val.classes).isdisjoint(property.types)
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if isinstance(value, list) and len(value) > 0:
|
|
461
|
+
valid_type = all(is_valid(item) for item in value)
|
|
462
|
+
have_multiple = True
|
|
463
|
+
else:
|
|
464
|
+
valid_type = is_valid(value)
|
|
465
|
+
have_multiple = False
|
|
466
|
+
if not valid_type:
|
|
467
|
+
if property.name == "hash": # bit of a hack
|
|
468
|
+
filter_value = value
|
|
469
|
+
elif isinstance(value, str) and value.startswith("http"): # for @id
|
|
470
|
+
filter_value = value
|
|
471
|
+
else:
|
|
472
|
+
raise TypeError("{} must be of type {}, not {}".format(property.name, property.types, type(value)))
|
|
473
|
+
|
|
474
|
+
filter_items = []
|
|
475
|
+
for item in as_list(value):
|
|
476
|
+
if isinstance(item, IRI):
|
|
477
|
+
filter_item = item.value
|
|
478
|
+
elif isinstance(item, (date, datetime)):
|
|
479
|
+
filter_item = item.isoformat()
|
|
480
|
+
elif hasattr(item, "id"):
|
|
481
|
+
filter_item = item.id
|
|
482
|
+
elif isinstance(item, UUID):
|
|
483
|
+
# todo: consider using client.uri_from_uuid()
|
|
484
|
+
# would require passing client as arg
|
|
485
|
+
filter_item = f"https://kg.ebrains.eu/api/instances/{item}"
|
|
486
|
+
elif isinstance(item, str) and "+" in item: # workaround for KG bug
|
|
487
|
+
invalid_char_index = item.index("+")
|
|
488
|
+
if invalid_char_index < 3:
|
|
489
|
+
raise ValueError(f"Cannot use {item} as filter, contains invalid characters")
|
|
490
|
+
filter_item = item[:invalid_char_index]
|
|
491
|
+
warn(f"Truncating filter value {item} --> {filter_item}")
|
|
492
|
+
else:
|
|
493
|
+
filter_item = item
|
|
494
|
+
filter_items.append(filter_item)
|
|
495
|
+
|
|
496
|
+
if have_multiple:
|
|
497
|
+
return filter_items
|
|
498
|
+
else:
|
|
499
|
+
return filter_items[0]
|