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.
Files changed (267) hide show
  1. fairgraph/__init__.py +61 -0
  2. fairgraph/base.py +104 -0
  3. fairgraph/caching.py +52 -0
  4. fairgraph/client.py +867 -0
  5. fairgraph/collection.py +104 -0
  6. fairgraph/embedded.py +122 -0
  7. fairgraph/errors.py +47 -0
  8. fairgraph/fields.py +11 -0
  9. fairgraph/kgobject.py +1087 -0
  10. fairgraph/kgproxy.py +178 -0
  11. fairgraph/kgquery.py +151 -0
  12. fairgraph/node.py +488 -0
  13. fairgraph/openminds/__init__.py +1 -0
  14. fairgraph/openminds/chemicals/__init__.py +40 -0
  15. fairgraph/openminds/chemicals/amount_of_chemical.py +23 -0
  16. fairgraph/openminds/chemicals/chemical_mixture.py +72 -0
  17. fairgraph/openminds/chemicals/chemical_substance.py +67 -0
  18. fairgraph/openminds/chemicals/product_source.py +57 -0
  19. fairgraph/openminds/computation/__init__.py +53 -0
  20. fairgraph/openminds/computation/data_analysis.py +104 -0
  21. fairgraph/openminds/computation/data_copy.py +104 -0
  22. fairgraph/openminds/computation/environment.py +66 -0
  23. fairgraph/openminds/computation/generic_computation.py +104 -0
  24. fairgraph/openminds/computation/hardware_system.py +53 -0
  25. fairgraph/openminds/computation/launch_configuration.py +68 -0
  26. fairgraph/openminds/computation/local_file.py +83 -0
  27. fairgraph/openminds/computation/model_validation.py +107 -0
  28. fairgraph/openminds/computation/optimization.py +104 -0
  29. fairgraph/openminds/computation/simulation.py +104 -0
  30. fairgraph/openminds/computation/software_agent.py +81 -0
  31. fairgraph/openminds/computation/validation_test.py +105 -0
  32. fairgraph/openminds/computation/validation_test_version.py +180 -0
  33. fairgraph/openminds/computation/visualization.py +104 -0
  34. fairgraph/openminds/computation/workflow_execution.py +44 -0
  35. fairgraph/openminds/computation/workflow_recipe.py +95 -0
  36. fairgraph/openminds/computation/workflow_recipe_version.py +185 -0
  37. fairgraph/openminds/controlled_terms/__init__.py +116 -0
  38. fairgraph/openminds/controlled_terms/action_status_type.py +97 -0
  39. fairgraph/openminds/controlled_terms/age_category.py +89 -0
  40. fairgraph/openminds/controlled_terms/analysis_technique.py +108 -0
  41. fairgraph/openminds/controlled_terms/anatomical_axes_orientation.py +89 -0
  42. fairgraph/openminds/controlled_terms/anatomical_identification_type.py +89 -0
  43. fairgraph/openminds/controlled_terms/anatomical_plane.py +89 -0
  44. fairgraph/openminds/controlled_terms/annotation_criteria_type.py +89 -0
  45. fairgraph/openminds/controlled_terms/annotation_type.py +89 -0
  46. fairgraph/openminds/controlled_terms/atlas_type.py +88 -0
  47. fairgraph/openminds/controlled_terms/auditory_stimulus_type.py +127 -0
  48. fairgraph/openminds/controlled_terms/biological_order.py +117 -0
  49. fairgraph/openminds/controlled_terms/biological_process.py +79 -0
  50. fairgraph/openminds/controlled_terms/biological_sex.py +132 -0
  51. fairgraph/openminds/controlled_terms/breeding_type.py +127 -0
  52. fairgraph/openminds/controlled_terms/cell_culture_type.py +117 -0
  53. fairgraph/openminds/controlled_terms/cell_type.py +151 -0
  54. fairgraph/openminds/controlled_terms/chemical_mixture_type.py +89 -0
  55. fairgraph/openminds/controlled_terms/colormap.py +79 -0
  56. fairgraph/openminds/controlled_terms/contribution_type.py +79 -0
  57. fairgraph/openminds/controlled_terms/cranial_window_construction_type.py +89 -0
  58. fairgraph/openminds/controlled_terms/cranial_window_reinforcement_type.py +89 -0
  59. fairgraph/openminds/controlled_terms/criteria_quality_type.py +89 -0
  60. fairgraph/openminds/controlled_terms/data_type.py +89 -0
  61. fairgraph/openminds/controlled_terms/device_type.py +94 -0
  62. fairgraph/openminds/controlled_terms/difference_measure.py +89 -0
  63. fairgraph/openminds/controlled_terms/disease.py +142 -0
  64. fairgraph/openminds/controlled_terms/disease_model.py +142 -0
  65. fairgraph/openminds/controlled_terms/educational_level.py +79 -0
  66. fairgraph/openminds/controlled_terms/electrical_stimulus_type.py +137 -0
  67. fairgraph/openminds/controlled_terms/ethics_assessment.py +79 -0
  68. fairgraph/openminds/controlled_terms/experimental_approach.py +79 -0
  69. fairgraph/openminds/controlled_terms/file_bundle_grouping.py +99 -0
  70. fairgraph/openminds/controlled_terms/file_repository_type.py +89 -0
  71. fairgraph/openminds/controlled_terms/file_usage_role.py +89 -0
  72. fairgraph/openminds/controlled_terms/genetic_strain_type.py +127 -0
  73. fairgraph/openminds/controlled_terms/gustatory_stimulus_type.py +127 -0
  74. fairgraph/openminds/controlled_terms/handedness.py +127 -0
  75. fairgraph/openminds/controlled_terms/language.py +88 -0
  76. fairgraph/openminds/controlled_terms/laterality.py +94 -0
  77. fairgraph/openminds/controlled_terms/learning_resource_type.py +88 -0
  78. fairgraph/openminds/controlled_terms/measured_quantity.py +89 -0
  79. fairgraph/openminds/controlled_terms/measured_signal_type.py +79 -0
  80. fairgraph/openminds/controlled_terms/meta_data_model_type.py +88 -0
  81. fairgraph/openminds/controlled_terms/model_abstraction_level.py +89 -0
  82. fairgraph/openminds/controlled_terms/model_scope.py +89 -0
  83. fairgraph/openminds/controlled_terms/molecular_entity.py +142 -0
  84. fairgraph/openminds/controlled_terms/mri_pulse_sequence.py +98 -0
  85. fairgraph/openminds/controlled_terms/mri_weighting.py +98 -0
  86. fairgraph/openminds/controlled_terms/olfactory_stimulus_type.py +127 -0
  87. fairgraph/openminds/controlled_terms/operating_device.py +79 -0
  88. fairgraph/openminds/controlled_terms/operating_system.py +88 -0
  89. fairgraph/openminds/controlled_terms/optical_stimulus_type.py +127 -0
  90. fairgraph/openminds/controlled_terms/organ.py +161 -0
  91. fairgraph/openminds/controlled_terms/organism_substance.py +151 -0
  92. fairgraph/openminds/controlled_terms/organism_system.py +117 -0
  93. fairgraph/openminds/controlled_terms/patch_clamp_variation.py +89 -0
  94. fairgraph/openminds/controlled_terms/preparation_type.py +98 -0
  95. fairgraph/openminds/controlled_terms/product_accessibility.py +79 -0
  96. fairgraph/openminds/controlled_terms/programming_language.py +88 -0
  97. fairgraph/openminds/controlled_terms/qualitative_overlap.py +79 -0
  98. fairgraph/openminds/controlled_terms/semantic_data_type.py +79 -0
  99. fairgraph/openminds/controlled_terms/service.py +89 -0
  100. fairgraph/openminds/controlled_terms/setup_type.py +89 -0
  101. fairgraph/openminds/controlled_terms/software_application_category.py +79 -0
  102. fairgraph/openminds/controlled_terms/software_feature.py +79 -0
  103. fairgraph/openminds/controlled_terms/species.py +143 -0
  104. fairgraph/openminds/controlled_terms/stimulation_approach.py +98 -0
  105. fairgraph/openminds/controlled_terms/stimulation_technique.py +98 -0
  106. fairgraph/openminds/controlled_terms/subcellular_entity.py +143 -0
  107. fairgraph/openminds/controlled_terms/subject_attribute.py +89 -0
  108. fairgraph/openminds/controlled_terms/tactile_stimulus_type.py +127 -0
  109. fairgraph/openminds/controlled_terms/technique.py +108 -0
  110. fairgraph/openminds/controlled_terms/term_suggestion.py +121 -0
  111. fairgraph/openminds/controlled_terms/terminology.py +89 -0
  112. fairgraph/openminds/controlled_terms/tissue_sample_attribute.py +89 -0
  113. fairgraph/openminds/controlled_terms/tissue_sample_type.py +127 -0
  114. fairgraph/openminds/controlled_terms/type_of_uncertainty.py +89 -0
  115. fairgraph/openminds/controlled_terms/uberon_parcellation.py +153 -0
  116. fairgraph/openminds/controlled_terms/unit_of_measurement.py +108 -0
  117. fairgraph/openminds/controlled_terms/visual_stimulus_type.py +127 -0
  118. fairgraph/openminds/controlledterms.py +6 -0
  119. fairgraph/openminds/core/__init__.py +107 -0
  120. fairgraph/openminds/core/actors/__init__.py +7 -0
  121. fairgraph/openminds/core/actors/account_information.py +44 -0
  122. fairgraph/openminds/core/actors/affiliation.py +30 -0
  123. fairgraph/openminds/core/actors/consortium.py +175 -0
  124. fairgraph/openminds/core/actors/contact_information.py +43 -0
  125. fairgraph/openminds/core/actors/contribution.py +23 -0
  126. fairgraph/openminds/core/actors/organization.py +199 -0
  127. fairgraph/openminds/core/actors/person.py +236 -0
  128. fairgraph/openminds/core/data/__init__.py +13 -0
  129. fairgraph/openminds/core/data/content_type.py +107 -0
  130. fairgraph/openminds/core/data/content_type_pattern.py +53 -0
  131. fairgraph/openminds/core/data/copyright.py +23 -0
  132. fairgraph/openminds/core/data/file.py +275 -0
  133. fairgraph/openminds/core/data/file_archive.py +71 -0
  134. fairgraph/openminds/core/data/file_bundle.py +150 -0
  135. fairgraph/openminds/core/data/file_path_pattern.py +23 -0
  136. fairgraph/openminds/core/data/file_repository.py +99 -0
  137. fairgraph/openminds/core/data/file_repository_structure.py +51 -0
  138. fairgraph/openminds/core/data/hash.py +23 -0
  139. fairgraph/openminds/core/data/license.py +77 -0
  140. fairgraph/openminds/core/data/measurement.py +45 -0
  141. fairgraph/openminds/core/data/service_link.py +49 -0
  142. fairgraph/openminds/core/digital_identifier/__init__.py +11 -0
  143. fairgraph/openminds/core/digital_identifier/doi.py +98 -0
  144. fairgraph/openminds/core/digital_identifier/gridid.py +41 -0
  145. fairgraph/openminds/core/digital_identifier/handle.py +52 -0
  146. fairgraph/openminds/core/digital_identifier/identifiers_dot_org_id.py +41 -0
  147. fairgraph/openminds/core/digital_identifier/isbn.py +88 -0
  148. fairgraph/openminds/core/digital_identifier/issn.py +63 -0
  149. fairgraph/openminds/core/digital_identifier/orcid.py +41 -0
  150. fairgraph/openminds/core/digital_identifier/rorid.py +41 -0
  151. fairgraph/openminds/core/digital_identifier/rrid.py +55 -0
  152. fairgraph/openminds/core/digital_identifier/stock_number.py +23 -0
  153. fairgraph/openminds/core/digital_identifier/swhid.py +48 -0
  154. fairgraph/openminds/core/miscellaneous/__init__.py +7 -0
  155. fairgraph/openminds/core/miscellaneous/comment.py +47 -0
  156. fairgraph/openminds/core/miscellaneous/funding.py +70 -0
  157. fairgraph/openminds/core/miscellaneous/quantitative_value.py +43 -0
  158. fairgraph/openminds/core/miscellaneous/quantitative_value_array.py +49 -0
  159. fairgraph/openminds/core/miscellaneous/quantitative_value_range.py +43 -0
  160. fairgraph/openminds/core/miscellaneous/research_product_group.py +26 -0
  161. fairgraph/openminds/core/miscellaneous/web_resource.py +104 -0
  162. fairgraph/openminds/core/products/__init__.py +12 -0
  163. fairgraph/openminds/core/products/dataset.py +95 -0
  164. fairgraph/openminds/core/products/dataset_version.py +240 -0
  165. fairgraph/openminds/core/products/meta_data_model.py +95 -0
  166. fairgraph/openminds/core/products/meta_data_model_version.py +168 -0
  167. fairgraph/openminds/core/products/model.py +103 -0
  168. fairgraph/openminds/core/products/model_version.py +235 -0
  169. fairgraph/openminds/core/products/project.py +56 -0
  170. fairgraph/openminds/core/products/setup.py +69 -0
  171. fairgraph/openminds/core/products/software.py +95 -0
  172. fairgraph/openminds/core/products/software_version.py +226 -0
  173. fairgraph/openminds/core/products/web_service.py +103 -0
  174. fairgraph/openminds/core/products/web_service_version.py +182 -0
  175. fairgraph/openminds/core/research/__init__.py +17 -0
  176. fairgraph/openminds/core/research/behavioral_protocol.py +69 -0
  177. fairgraph/openminds/core/research/configuration.py +67 -0
  178. fairgraph/openminds/core/research/custom_property_set.py +27 -0
  179. fairgraph/openminds/core/research/numerical_property.py +23 -0
  180. fairgraph/openminds/core/research/property_value_list.py +71 -0
  181. fairgraph/openminds/core/research/protocol.py +67 -0
  182. fairgraph/openminds/core/research/protocol_execution.py +76 -0
  183. fairgraph/openminds/core/research/strain.py +90 -0
  184. fairgraph/openminds/core/research/string_property.py +23 -0
  185. fairgraph/openminds/core/research/subject.py +79 -0
  186. fairgraph/openminds/core/research/subject_group.py +91 -0
  187. fairgraph/openminds/core/research/subject_group_state.py +113 -0
  188. fairgraph/openminds/core/research/subject_state.py +138 -0
  189. fairgraph/openminds/core/research/tissue_sample.py +87 -0
  190. fairgraph/openminds/core/research/tissue_sample_collection.py +99 -0
  191. fairgraph/openminds/core/research/tissue_sample_collection_state.py +109 -0
  192. fairgraph/openminds/core/research/tissue_sample_state.py +127 -0
  193. fairgraph/openminds/ephys/__init__.py +39 -0
  194. fairgraph/openminds/ephys/activity/__init__.py +3 -0
  195. fairgraph/openminds/ephys/activity/cell_patching.py +73 -0
  196. fairgraph/openminds/ephys/activity/electrode_placement.py +67 -0
  197. fairgraph/openminds/ephys/activity/recording_activity.py +67 -0
  198. fairgraph/openminds/ephys/device/__init__.py +6 -0
  199. fairgraph/openminds/ephys/device/electrode.py +81 -0
  200. fairgraph/openminds/ephys/device/electrode_array.py +85 -0
  201. fairgraph/openminds/ephys/device/electrode_array_usage.py +105 -0
  202. fairgraph/openminds/ephys/device/electrode_usage.py +101 -0
  203. fairgraph/openminds/ephys/device/pipette.py +81 -0
  204. fairgraph/openminds/ephys/device/pipette_usage.py +123 -0
  205. fairgraph/openminds/ephys/entity/__init__.py +2 -0
  206. fairgraph/openminds/ephys/entity/channel.py +23 -0
  207. fairgraph/openminds/ephys/entity/recording.py +63 -0
  208. fairgraph/openminds/publications/__init__.py +47 -0
  209. fairgraph/openminds/publications/book.py +106 -0
  210. fairgraph/openminds/publications/chapter.py +100 -0
  211. fairgraph/openminds/publications/learning_resource.py +90 -0
  212. fairgraph/openminds/publications/live_paper.py +95 -0
  213. fairgraph/openminds/publications/live_paper_resource_item.py +58 -0
  214. fairgraph/openminds/publications/live_paper_section.py +57 -0
  215. fairgraph/openminds/publications/live_paper_version.py +177 -0
  216. fairgraph/openminds/publications/periodical.py +53 -0
  217. fairgraph/openminds/publications/publication_issue.py +44 -0
  218. fairgraph/openminds/publications/publication_volume.py +44 -0
  219. fairgraph/openminds/publications/scholarly_article.py +146 -0
  220. fairgraph/openminds/sands/__init__.py +57 -0
  221. fairgraph/openminds/sands/atlas/__init__.py +9 -0
  222. fairgraph/openminds/sands/atlas/atlas_annotation.py +52 -0
  223. fairgraph/openminds/sands/atlas/brain_atlas.py +113 -0
  224. fairgraph/openminds/sands/atlas/brain_atlas_version.py +213 -0
  225. fairgraph/openminds/sands/atlas/common_coordinate_space.py +121 -0
  226. fairgraph/openminds/sands/atlas/common_coordinate_space_version.py +243 -0
  227. fairgraph/openminds/sands/atlas/parcellation_entity.py +133 -0
  228. fairgraph/openminds/sands/atlas/parcellation_entity_version.py +162 -0
  229. fairgraph/openminds/sands/atlas/parcellation_terminology.py +38 -0
  230. fairgraph/openminds/sands/atlas/parcellation_terminology_version.py +38 -0
  231. fairgraph/openminds/sands/mathematical_shapes/__init__.py +3 -0
  232. fairgraph/openminds/sands/mathematical_shapes/circle.py +23 -0
  233. fairgraph/openminds/sands/mathematical_shapes/ellipse.py +27 -0
  234. fairgraph/openminds/sands/mathematical_shapes/rectangle.py +23 -0
  235. fairgraph/openminds/sands/miscellaneous/__init__.py +6 -0
  236. fairgraph/openminds/sands/miscellaneous/anatomical_target_position.py +40 -0
  237. fairgraph/openminds/sands/miscellaneous/coordinate_point.py +23 -0
  238. fairgraph/openminds/sands/miscellaneous/qualitative_relation_assessment.py +34 -0
  239. fairgraph/openminds/sands/miscellaneous/quantitative_relation_assessment.py +38 -0
  240. fairgraph/openminds/sands/miscellaneous/single_color.py +24 -0
  241. fairgraph/openminds/sands/miscellaneous/viewer_specification.py +40 -0
  242. fairgraph/openminds/sands/non_atlas/__init__.py +3 -0
  243. fairgraph/openminds/sands/non_atlas/custom_anatomical_entity.py +110 -0
  244. fairgraph/openminds/sands/non_atlas/custom_annotation.py +54 -0
  245. fairgraph/openminds/sands/non_atlas/custom_coordinate_space.py +67 -0
  246. fairgraph/openminds/specimen_prep/__init__.py +38 -0
  247. fairgraph/openminds/specimen_prep/activity/__init__.py +3 -0
  248. fairgraph/openminds/specimen_prep/activity/cranial_window_preparation.py +69 -0
  249. fairgraph/openminds/specimen_prep/activity/tissue_culture_preparation.py +67 -0
  250. fairgraph/openminds/specimen_prep/activity/tissue_sample_slicing.py +69 -0
  251. fairgraph/openminds/specimen_prep/device/__init__.py +2 -0
  252. fairgraph/openminds/specimen_prep/device/slicing_device.py +73 -0
  253. fairgraph/openminds/specimen_prep/device/slicing_device_usage.py +117 -0
  254. fairgraph/openminds/specimenprep.py +4 -0
  255. fairgraph/openminds/stimulation/__init__.py +38 -0
  256. fairgraph/openminds/stimulation/activity/__init__.py +1 -0
  257. fairgraph/openminds/stimulation/activity/stimulation_activity.py +67 -0
  258. fairgraph/openminds/stimulation/stimulus/__init__.py +1 -0
  259. fairgraph/openminds/stimulation/stimulus/ephys_stimulus.py +63 -0
  260. fairgraph/queries.py +499 -0
  261. fairgraph/registry.py +123 -0
  262. fairgraph/utility.py +607 -0
  263. fairgraph-0.13.0.dist-info/METADATA +222 -0
  264. fairgraph-0.13.0.dist-info/RECORD +267 -0
  265. fairgraph-0.13.0.dist-info/WHEEL +5 -0
  266. fairgraph-0.13.0.dist-info/licenses/LICENSE.txt +177 -0
  267. 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]