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
fairgraph/utility.py ADDED
@@ -0,0 +1,607 @@
1
+ """
2
+
3
+
4
+ """
5
+
6
+ # Copyright 2019-2020 CNRS
7
+
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ from __future__ import annotations
21
+ from copy import deepcopy
22
+ import hashlib
23
+ import logging
24
+ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, TYPE_CHECKING
25
+ import warnings
26
+
27
+ from openminds.registry import lookup_type
28
+
29
+ from .base import OPENMINDS_VERSION
30
+
31
+ if TYPE_CHECKING:
32
+ from .client import KGClient
33
+ from .kgobject import KGObject
34
+
35
+ logger = logging.getLogger("fairgraph")
36
+
37
+ JSONdict = Dict[str, Any] # see https://github.com/python/typing/issues/182 for some possible improvements
38
+ ATTACHMENT_SIZE_LIMIT = 1024 * 1024 # 1 MB
39
+
40
+
41
+ def as_list(obj: Union[None, KGObject, dict, str, list, tuple]) -> list:
42
+ """
43
+ Converts the input obj into a list.
44
+
45
+ Args:
46
+ obj: The input object to be converted to a list.
47
+
48
+ Returns:
49
+ list: A list - see Notes below.
50
+
51
+ Raises:
52
+ TypeError: If the input obj cannot be converted to a list.
53
+
54
+ Notes:
55
+ - If obj is None, it returns an empty list.
56
+ - If obj is a dict or a str, it returns a list containing obj.
57
+ - If obj is a list or a tuple, it returns a list with the same elements as obj.
58
+ - If obj is not any of the above, it tries to convert obj into a list. If it fails due to a TypeError, it raises a TypeError with an appropriate error message.
59
+ """
60
+ if obj is None:
61
+ return []
62
+ elif isinstance(obj, (dict, str)):
63
+ return [obj]
64
+ try:
65
+ L = list(obj)
66
+ except TypeError:
67
+ L = [obj]
68
+ return L
69
+
70
+
71
+ def invert_dict(D):
72
+ newD = {}
73
+ for key, value in D.items():
74
+ newD[value] = key
75
+ return newD
76
+
77
+
78
+ def expand_uri(uri_list: Union[str, List[str]], context: Dict[str, Any]) -> Union[str, Tuple[str, ...]]:
79
+ """
80
+ Expands a URI or a list of URIs using a given context.
81
+
82
+ Args:
83
+ uri_list (Union[str, List[str]]): A URI or a list of URIs to be expanded.
84
+ context (Dict[str, Any]): A dictionary containing a mapping of prefixes to base URLs.
85
+
86
+ Returns:
87
+ Union[str, Tuple[str, ...]]: An expanded URI or a tuple of expanded URIs.
88
+
89
+ Raises:
90
+ ValueError: If a prefix in the URI is not found in the context.
91
+
92
+ Examples:
93
+ >>> context = {'foaf': 'http://xmlns.com/foaf/0.1/'}
94
+ >>> uri_list = 'foaf:Person'
95
+ >>> expand_uri(uri_list, context)
96
+ 'http://xmlns.com/foaf/0.1/Person'
97
+
98
+ """
99
+ expanded_uris = []
100
+ for uri in as_list(uri_list):
101
+ if uri.startswith("http") or uri.startswith("@"):
102
+ expanded_uris.append(uri)
103
+ else:
104
+ parts = uri.split(":")
105
+ if len(parts) == 1:
106
+ prefix = "@vocab"
107
+ identifier = uri
108
+ else:
109
+ prefix, identifier = parts
110
+ if prefix not in context:
111
+ raise ValueError(f"prefix {prefix} not found in context")
112
+ base_url = context[prefix]
113
+ if not base_url.endswith("/"):
114
+ base_url += "/"
115
+ expanded_uris.append(f"{base_url}{identifier}")
116
+ if isinstance(uri_list, str):
117
+ return expanded_uris[0]
118
+ else:
119
+ return tuple(expanded_uris)
120
+
121
+
122
+ def compact_uri(
123
+ uri_list: Union[str, List[str]], context: Dict[str, Any], strict: bool = False
124
+ ) -> Union[str, Tuple[str, ...]]:
125
+ """
126
+ Compacts a URI or a list of URIs using a given context.
127
+
128
+ Args:
129
+ uri_list (Union[str, List[str]]): A URI or a list of URIs to be compacted.
130
+ context (Dict[str, Any]): A dictionary containing a mapping of prefixes to base URLs.
131
+ strict (bool, optional): Whether to raise an error if a URI cannot be compacted. Defaults to False.
132
+
133
+ Returns:
134
+ Union[str, Tuple[str, ...]]: A compacted URI or a tuple of compacted URIs.
135
+
136
+ Raises:
137
+ ValueError: If strict is True and a URI cannot be compacted.
138
+
139
+ Examples:
140
+ >>> context = {'foaf': 'http://xmlns.com/foaf/0.1/'}
141
+ >>> uri_list = 'http://xmlns.com/foaf/0.1/Person'
142
+ >>> compact_uri(uri_list, context)
143
+ 'foaf:Person'
144
+ """
145
+ compacted_uris = []
146
+ for uri in as_list(uri_list):
147
+ if uri.startswith("http"):
148
+ found = False
149
+ for prefix, base_url in context.items():
150
+ if uri.startswith(base_url):
151
+ start = len(base_url)
152
+ identifier = uri[start:].strip("/")
153
+ if prefix == "@vocab":
154
+ compacted_uris.append(identifier)
155
+ else:
156
+ compacted_uris.append(f"{prefix}:{identifier}")
157
+ found = True
158
+ break
159
+ if not found:
160
+ if strict:
161
+ raise ValueError(f"Unable to compact {uri} with the provided context")
162
+ else:
163
+ compacted_uris.append(uri)
164
+ else:
165
+ compacted_uris.append(uri)
166
+ if isinstance(uri_list, str):
167
+ return compacted_uris[0]
168
+ else:
169
+ return tuple(compacted_uris)
170
+
171
+
172
+ def normalize_data(data: Union[None, JSONdict], context: Dict[str, Any]) -> Union[None, JSONdict]:
173
+ """
174
+ Normalizes JSON-LD data using a given context.
175
+
176
+ Args:
177
+ data (Union[None, JSONdict]): A JSON-LD data dict to be normalized.
178
+ context (Dict[str, Any]): A dictionary containing a mapping of prefixes to base URLs.
179
+
180
+ Returns:
181
+ Union[None, JSONdict]: A normalized JSON-LD data dict.
182
+
183
+ Examples:
184
+ >>> context = {'foaf': 'http://xmlns.com/foaf/0.1/'}
185
+ >>> data = {
186
+ ... "foaf:name": "John Smith",
187
+ ... "foaf:age": 35,
188
+ ... "foaf:knows": {
189
+ ... "foaf:name": "Jane Doe",
190
+ ... "foaf:age": 25
191
+ ... }
192
+ ... }
193
+ >>> normalize_data(data, context)
194
+ {
195
+ "http://xmlns.com/foaf/0.1/name": "John Smith",
196
+ "http://xmlns.com/foaf/0.1/age": 35,
197
+ "http://xmlns.com/foaf/0.1/knows": {
198
+ "http://xmlns.com/foaf/0.1/name": "Jane Doe",
199
+ "http://xmlns.com/foaf/0.1/age": 25
200
+ }
201
+ }
202
+
203
+ """
204
+ if data is None:
205
+ return data
206
+ normalized: JSONdict = {}
207
+ for key, value in data.items():
208
+ assert isinstance(key, str)
209
+ if key == "@context":
210
+ continue
211
+ elif key.startswith("Q"):
212
+ expanded_key = key
213
+ else:
214
+ result = expand_uri(key, context)
215
+ assert isinstance(result, str) # for type checking
216
+ expanded_key = result
217
+ assert expanded_key.startswith("http") or expanded_key.startswith("@") or expanded_key.startswith("Q")
218
+
219
+ if hasattr(value, "__len__") and len(value) == 0:
220
+ pass
221
+ elif expanded_key == "@id":
222
+ if value.startswith("http"):
223
+ # do not take local ids, e.g., those starting with "_"
224
+ normalized[expanded_key] = value
225
+ elif expanded_key == "@type":
226
+ normalized[expanded_key] = value
227
+ elif isinstance(value, (list, tuple)):
228
+ normalized[expanded_key] = []
229
+ for item in value:
230
+ if isinstance(item, dict):
231
+ normalized[expanded_key].append(normalize_data(item, context))
232
+ else:
233
+ normalized[expanded_key].append(item)
234
+ elif isinstance(value, dict):
235
+ normalized[expanded_key] = normalize_data(value, context)
236
+ else:
237
+ normalized[expanded_key] = value
238
+ return normalized
239
+
240
+
241
+ def in_notebook() -> bool:
242
+ try:
243
+ shell = get_ipython().__class__.__name__ # type: ignore
244
+ if shell == "ZMQInteractiveShell":
245
+ return True
246
+ elif shell == "TerminalInteractiveShell":
247
+ return False
248
+ else:
249
+ return False
250
+ except NameError:
251
+ return False
252
+
253
+
254
+ def expand_filter(filter_dict: Dict[str, Any]):
255
+ """
256
+ Expand single-level filter specification (provided by user) into
257
+ a multi-level dict as required by the query-generation machinery.
258
+
259
+ Example:
260
+ >>> filter = {
261
+ ... "developers__affiliations__member_of__alias": "CNRS",
262
+ ... "digital_identifier__identifier": "https://doi.org/some-doi"
263
+ ... }
264
+ >>> expand_filter(filter)
265
+ {
266
+ "developers": {
267
+ "affiliations": {
268
+ "member_of": {
269
+ "alias": "CNRS
270
+ }
271
+ }
272
+ },
273
+ "digital_identifier": {
274
+ "identifier": "https://doi.org/some-doi"
275
+ }
276
+ }
277
+ """
278
+ expanded = {}
279
+ for key, value in filter_dict.items():
280
+ if hasattr(value, "items"):
281
+ raise TypeError("Filter specifications should be a single-level dict, without nesting")
282
+ local_path = expanded
283
+ parts = key.split("__")
284
+ for part in parts[:-1]:
285
+ local_path[part] = {}
286
+ local_path = local_path[part]
287
+ local_path[parts[-1]] = value
288
+ return expanded
289
+
290
+
291
+ def sha1sum(filename):
292
+ BUFFER_SIZE = 128 * 1024
293
+ h = hashlib.sha1()
294
+ with open(filename, "rb") as fp:
295
+ while True:
296
+ data = fp.read(BUFFER_SIZE)
297
+ if not data:
298
+ break
299
+ h.update(data)
300
+ return h.hexdigest()
301
+
302
+
303
+ class LogEntry:
304
+ """
305
+ Represents an entry in an activity log.
306
+
307
+ Attributes:
308
+ cls (str): The name of the class of the Knowledge Grapg object.
309
+ id (Optional[str]): The identifer of the object being logged.
310
+ delta (Optional[JSONdict]): A dictionary containing the changes made to the object.
311
+ space (Optional[str]): The Knowledge Graph space containing the object.
312
+ type_ (str): The type of the log entry.
313
+ """
314
+
315
+ def __init__(
316
+ self,
317
+ cls: str,
318
+ id: Optional[str],
319
+ delta: Optional[JSONdict],
320
+ space: Optional[str],
321
+ type_: str,
322
+ ):
323
+ self.cls = cls
324
+ self.id = id
325
+ self.delta = delta
326
+ self.space = space
327
+ self.type = type_
328
+
329
+ def __repr__(self):
330
+ return f"{self.type}: {self.cls}({self.id}) in '{self.space}'"
331
+
332
+ def as_dict(self):
333
+ return {
334
+ "cls": self.cls,
335
+ "id": self.id,
336
+ "delta": self.delta,
337
+ "space": self.space,
338
+ "type_": self.type
339
+ }
340
+
341
+
342
+ class ActivityLog:
343
+ """
344
+ Represents a log of activities performed on Knowledge Graph objects.
345
+
346
+ Attributes:
347
+ entries (List[LogEntry]): A list of LogEntry objects representing the activities performed.
348
+ """
349
+
350
+ def __init__(self):
351
+ self.entries = []
352
+
353
+ def update(self, item: KGObject, delta: Optional[JSONdict], space: Optional[str], entry_type: str):
354
+ """
355
+ Adds a new log entry to the activity log.
356
+
357
+ Args:
358
+ item (KGObject): The object being logged.
359
+ delta (Optional[JSONdict]): A dictionary containing the changes made to the object.
360
+ space (Optional[str]): The Knowledge Graph space containing the object.
361
+ entry_type (str): The type of the log entry.
362
+ """
363
+ self.entries.append(LogEntry(item.__class__.__name__, item.uuid, delta, space, entry_type))
364
+
365
+ def __repr__(self):
366
+ return "\n".join((str(entry) for entry in self.entries))
367
+
368
+
369
+ TERMS_OF_USE = """
370
+ # EBRAINS Knowledge Graph Data Platform Citation Requirements
371
+
372
+ This text is provided to describe the requirements for citing datasets,
373
+ models and software found via EBRAINS Knowledge Graph Data Platform (KG):
374
+ https://kg.ebrains.eu/search.
375
+ It is meant to provide a more human-readable form of key parts of the
376
+ KG Terms of Service, but in the event of disagreement between the KG Terms of
377
+ Service and these Citation Requirements, the former is to be taken as authoritative.
378
+
379
+ ## Dataset, model and software licensing
380
+
381
+ Datasets, models and software in the KG have explicit licensing conditions attached.
382
+ The license is typically one of the Creative Commons licenses.
383
+ You must follow the licensing conditions attached to the dataset, model or software,
384
+ including all restrictions on commercial use, requirements for attribution or
385
+ requirements to share-alike.
386
+
387
+ ## EBRAINS Knowledge Graph citation policy
388
+
389
+ If you use content or services from the EBRAINS Knowledge Graph (Search or API)
390
+ to advance a scientific publication you must follow the following citation policy:
391
+
392
+ 1. For a dataset or model which is released under a Creative Commons license
393
+ which includes "Attribution":
394
+
395
+ 1. Cite the dataset / model as defined in the provided citation instructions
396
+ ("Cite dataset / model") and - if available - also cite the primary publication listed
397
+
398
+ or
399
+
400
+ 2. in cases where neither citation instructions nor a primary publication are provided,
401
+ and only in such cases, the names of the contributors should be cited
402
+ (Data / model provided by Contributor 1, Contributor 2, …, and Contributor N) .
403
+
404
+ 2. For software, please cite as defined in the software's respective citation policy.
405
+ If you can't identify a clear citation policy for the software in question,
406
+ use the open source repository as the citation link.
407
+
408
+ 3. For EBRAINS services which were key in attaining your results, please consider
409
+ citing the corresponding software which the service relies on,
410
+ including but not limited to:
411
+
412
+ EBRAINS Knowledge Graph, "https://kg.ebrains.eu"
413
+
414
+ Failure to cite datasets, models, or software used in another publication or
415
+ presentation would constitute scientific misconduct.
416
+ Failure to cite datasets, models, or software used in a scientific publication
417
+ must be corrected by an Erratum and correction of the given article if it was
418
+ discovered post-publication.
419
+
420
+ ## Final thoughts
421
+
422
+ Citations are essential for encouraging researchers to release their datasets,
423
+ models and software through the KG or other scientific sharing platforms.
424
+ Your citation may help them to get their next job or next grant and will
425
+ ultimately encourage researchers to produce and release more useful open data
426
+ and open source. Make science more reproducible and more efficient.
427
+ """
428
+
429
+
430
+ def accepted_terms_of_use(client: KGClient, accept_terms_of_use: bool = False) -> bool:
431
+ if accept_terms_of_use or client.accepted_terms_of_use:
432
+ return True
433
+ else:
434
+ if in_notebook():
435
+ from IPython.display import display, Markdown # type: ignore
436
+
437
+ display(Markdown(TERMS_OF_USE))
438
+ else:
439
+ print(TERMS_OF_USE)
440
+ user_response = input("Do you accept the EBRAINS KG Terms of Service? ")
441
+ if user_response in ("y", "Y", "yes", "YES"):
442
+ client.accepted_terms_of_use = True
443
+ return True
444
+ else:
445
+ warnings.warn("Please accept the terms of use before downloading the dataset")
446
+ return False
447
+
448
+
449
+ def types_match(a, b):
450
+ # temporarily, during the openMINDS transition v3-v4, we allow different namespaces for the types
451
+ assert isinstance(a, str), a
452
+ assert isinstance(b, str), b
453
+ if a == b:
454
+ return True
455
+ elif a.split("/")[-1] == b.split("/")[-1]:
456
+ logger.warning(f"Assuming {a} matches {b} in types_match()")
457
+ return True
458
+ else:
459
+ return False
460
+
461
+
462
+ def _adapt_namespaces(data, adapt_keys, adapt_type, adapt_instance_uri):
463
+ if isinstance(data, list):
464
+ for item in data:
465
+ _adapt_namespaces(item, adapt_keys, adapt_type, adapt_instance_uri)
466
+ elif isinstance(data, dict):
467
+ # adapt property URIs
468
+ old_keys = tuple(data.keys())
469
+ new_keys = adapt_keys(old_keys)
470
+ for old_key, new_key in zip(old_keys, new_keys):
471
+ data[new_key] = data.pop(old_key)
472
+ for key, value in data.items():
473
+ if key == "@id":
474
+ data[key] = adapt_instance_uri(value)
475
+ elif isinstance(value, (list, dict)):
476
+ _adapt_namespaces(value, adapt_keys, adapt_type, adapt_instance_uri)
477
+ # adapt @type URIs
478
+ if "@type" in data:
479
+ data["@type"] = adapt_type(data["@type"])
480
+ else:
481
+ pass
482
+
483
+
484
+ def adapt_namespaces_3to4(data):
485
+
486
+ def adapt_keys_3to4(uri_list):
487
+ replacement = ("openminds.ebrains.eu/vocab", "openminds.om-i.org/props")
488
+ return (uri.replace(*replacement) for uri in uri_list)
489
+
490
+ def adapt_type_3to4(uri):
491
+ if isinstance(uri, list):
492
+ assert len(uri) == 1
493
+ uri = uri[0]
494
+ return f"https://openminds.om-i.org/types/{uri.split('/')[-1]}"
495
+
496
+ def adapt_instance_uri_3to4(uri):
497
+ if uri.startswith("https://openminds"):
498
+ return uri.replace("ebrains.eu", "om-i.org")
499
+ else:
500
+ return uri
501
+
502
+ return _adapt_namespaces(data, adapt_keys_3to4, adapt_type_3to4, adapt_instance_uri_3to4)
503
+
504
+
505
+ def adapt_type_4to3(uri):
506
+ if isinstance(uri, list):
507
+ assert len(uri) == 1
508
+ uri = uri[0]
509
+ cls = lookup_type(uri, OPENMINDS_VERSION)
510
+
511
+ if cls.__module__ == "test.test_client":
512
+ return cls.type_
513
+
514
+ module_name = cls.__module__.split(".")[2] # e.g., 'fairgraph.openminds.core.actors.person' -> "core"
515
+ module_name = {"controlled_terms": "controlledTerms", "specimen_prep": "specimenPrep"}.get(
516
+ module_name, module_name
517
+ )
518
+ return f"https://openminds.ebrains.eu/{module_name}/{cls.__name__}"
519
+
520
+
521
+ def adapt_namespaces_4to3(data):
522
+
523
+ def adapt_keys_4to3(uri_list):
524
+ replacement = ("openminds.om-i.org/props", "openminds.ebrains.eu/vocab")
525
+ return (uri.replace(*replacement) for uri in uri_list)
526
+
527
+ def adapt_instance_uri_4to3(uri):
528
+ if uri.startswith("https://openminds"):
529
+ return uri.replace("om-i.org", "ebrains.eu")
530
+ else:
531
+ return uri
532
+
533
+ return _adapt_namespaces(data, adapt_keys_4to3, adapt_type_4to3, adapt_instance_uri_4to3)
534
+
535
+
536
+ def adapt_namespaces_for_query(query):
537
+ """Map from v4+ to v3 openMINDS namespace"""
538
+
539
+ def adapt_path(item_path, replacement):
540
+ if isinstance(item_path, str):
541
+ return item_path.replace(*replacement)
542
+ elif isinstance(item_path, list):
543
+ return [adapt_path(part, replacement) for part in item_path]
544
+ else:
545
+ assert isinstance(item_path, dict)
546
+ new_item_path = item_path.copy()
547
+ new_item_path["@id"] = item_path["@id"].replace(*replacement)
548
+ if "typeFilter" in item_path:
549
+ if isinstance(item_path["typeFilter"], list):
550
+ new_item_path["typeFilter"] = [
551
+ {"@id": adapt_type_4to3(subitem["@id"])} for subitem in item_path["typeFilter"]
552
+ ]
553
+ else:
554
+ new_item_path["typeFilter"]["@id"] = adapt_type_4to3(item_path["typeFilter"]["@id"])
555
+ return new_item_path
556
+
557
+ def adapt_structure(structure, replacement):
558
+ for item in structure:
559
+ item["path"] = adapt_path(item["path"], replacement)
560
+ if "structure" in item:
561
+ adapt_structure(item["structure"], replacement)
562
+
563
+ def adapt_filters(structure, replacement):
564
+ for item in structure:
565
+ if "filter" in item and "value" in item["filter"]:
566
+ item["filter"]["value"] = item["filter"]["value"].replace(*replacement)
567
+ if "structure" in item:
568
+ adapt_filters(item["structure"], replacement)
569
+
570
+ migrated_query = deepcopy(query)
571
+ migrated_query["meta"]["type"] = adapt_type_4to3(migrated_query["meta"]["type"])
572
+ adapt_structure(migrated_query["structure"], ("openminds.om-i.org/props", "openminds.ebrains.eu/vocab"))
573
+ adapt_filters(migrated_query["structure"], ("openminds.om-i.org/instances", "openminds.ebrains.eu/instances"))
574
+ return migrated_query
575
+
576
+
577
+ def initialise_instances(class_list):
578
+ """Cast openMINDS instances to their fairgraph subclass"""
579
+ for cls in class_list:
580
+ cls.set_error_handling(None)
581
+ # find parent openMINDS class
582
+ for parent_cls in cls.__mro__[1:]:
583
+ if parent_cls.__name__ == cls.__name__:
584
+ # could also do this by looking for issubclass(parent_cls, openminds.Node)
585
+ break
586
+ for key, value in parent_cls.__dict__.items():
587
+ if isinstance(value, parent_cls):
588
+ fg_instance = cls.from_jsonld(value.to_jsonld())
589
+ fg_instance._space = cls.default_space
590
+ setattr(cls, key, fg_instance)
591
+ cls.set_error_handling("log")
592
+
593
+
594
+ def handle_scope_keyword(scope, release_status):
595
+ """
596
+ The keyword 'scope' has been renamed 'release_status',
597
+ use of 'scope' is deprecated but still accepted.
598
+ """
599
+ if scope in ("released", "in progress", "any"):
600
+ warnings.warn(
601
+ "The keyword 'scope' is deprecated, and will be removed in version 1.0; it has been renamed to 'release_status'",
602
+ DeprecationWarning,
603
+ stacklevel=2,
604
+ )
605
+ return scope
606
+ else:
607
+ return release_status