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/node.py ADDED
@@ -0,0 +1,488 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Optional, Dict, List, Union, Any
3
+ from copy import copy
4
+ from itertools import chain
5
+ import logging
6
+ from warnings import warn
7
+
8
+ from openminds.base import value_to_jsonld, Link
9
+ from openminds.properties import Property
10
+
11
+ from .registry import Node
12
+ from .base import Resolvable, ErrorHandling, RepresentsSingleObject
13
+ from .kgproxy import KGProxy
14
+ from .kgquery import KGQuery
15
+ from .queries import QueryProperty, get_query_properties, get_query_filter_property, get_filter_value
16
+ from .errors import ResolutionFailure, CannotBuildExistenceQuery
17
+ from .utility import (
18
+ as_list, # temporary for backwards compatibility (a lot of code imports it from here)
19
+ expand_uri,
20
+ invert_dict,
21
+ normalize_data,
22
+ types_match,
23
+ )
24
+
25
+ if TYPE_CHECKING:
26
+ from .client import KGClient
27
+ from .utility import ActivityLog
28
+
29
+ logger = logging.getLogger("fairgraph")
30
+
31
+ JSONdict = Dict[str, Any] # see https://github.com/python/typing/issues/182 for some possible improvements
32
+
33
+
34
+ class ContainsMetadata(Resolvable, metaclass=Node): # KGObject and EmbeddedMetadata
35
+ properties: List[Property]
36
+ reverse_properties: List[Property] = []
37
+ context: Dict[str, str]
38
+ type_: str
39
+ release_status: Optional[str]
40
+ space: Union[str, None]
41
+ default_space: Union[str, None]
42
+ remote_data: Optional[JSONdict]
43
+ aliases: Dict[str, str] = {}
44
+ error_handling: ErrorHandling = ErrorHandling.log
45
+
46
+ def __init__(self, data: Optional[Dict] = None, **properties):
47
+ properties_copy = copy(properties)
48
+ for prop in self.__class__.all_properties:
49
+ try:
50
+ val = properties[prop.name]
51
+ except KeyError:
52
+ if prop.required:
53
+ msg = "Property '{}' is required.".format(prop.name)
54
+ ErrorHandling.handle_violation(self.error_handling, msg)
55
+ val = None
56
+ else:
57
+ properties_copy.pop(prop.name)
58
+ if isinstance(val, (list, tuple)) and len(val) == 0: # empty list
59
+ val = None
60
+ setattr(self, prop.name, val)
61
+ for name_, alias_ in self.aliases.items():
62
+ # the trailing underscores are because 'name' and 'alias' can be keys in 'properties'
63
+ if name_ in properties_copy:
64
+ val = properties_copy.pop(name_)
65
+ if val is not None:
66
+ if properties.get(alias_, None):
67
+ raise ValueError(f"'{name_}' is an alias for '{alias_}', you cannot specify both")
68
+ setattr(self, alias_, val)
69
+ if len(properties_copy) > 0:
70
+ if len(properties_copy) == 1:
71
+ raise NameError(
72
+ f'{self.__class__.__name__} does not have a property named "{list(properties_copy)[0]}".'
73
+ )
74
+ else:
75
+ raise NameError(
76
+ f"""{self.__class__.__name__} does not have properties named "{'", "'.join(properties_copy)}"."""
77
+ )
78
+
79
+ def __getattribute__(self, name):
80
+ try:
81
+ return object.__getattribute__(self, name)
82
+ except AttributeError:
83
+ if name in self.aliases:
84
+ return object.__getattribute__(self, self.aliases[name])
85
+ else:
86
+ raise
87
+
88
+ def __setattr__(self, name, value):
89
+ try:
90
+ prop = self._property_lookup[name]
91
+ except KeyError:
92
+ if name in self.aliases:
93
+ setattr(self, self.aliases[name], value)
94
+ else:
95
+ super().__setattr__(name, value)
96
+ else:
97
+ if not isinstance(value, KGQuery):
98
+ failures = prop.validate(value)
99
+ if failures:
100
+ errmsg = str(failures) # todo: create a nicer error message
101
+ ErrorHandling.handle_violation(self.error_handling, errmsg)
102
+ super().__setattr__(name, value)
103
+
104
+ @classmethod
105
+ def get_property(cls, name):
106
+ return cls._property_lookup[name]
107
+
108
+ @classmethod
109
+ def from_jsonld(cls, data: JSONdict, release_status: Optional[str] = None) -> ContainsMetadata:
110
+ """
111
+ Create an instance of the class from a JSON-LD document.
112
+ """
113
+ raise NotImplementedError("This should be implemented by subclasses")
114
+
115
+ def save(
116
+ self,
117
+ client: KGClient,
118
+ space: Optional[str] = None,
119
+ recursive: bool = True,
120
+ activity_log: Optional[ActivityLog] = None,
121
+ replace: bool = False,
122
+ ignore_auth_errors: bool = False,
123
+ ignore_duplicates: bool = False,
124
+ ):
125
+ raise NotImplementedError("This should be implemented by subclasses")
126
+
127
+ @classmethod
128
+ def set_error_handling(cls, value: Union[ErrorHandling, None]):
129
+ """
130
+ Control validation for this class.
131
+
132
+ Args:
133
+ value (str): action to follow when there is a validation failure.
134
+ (e.g. if a required property is not provided).
135
+ Possible values: "error", "warning", "log", None
136
+ """
137
+ if value is None:
138
+ value = ErrorHandling.none
139
+ else:
140
+ value = ErrorHandling(value)
141
+ cls.error_handling = value
142
+ # set the same action for all embedded types
143
+ for prop in cls.properties:
144
+ for type_ in prop.types:
145
+ if issubclass(type_, ContainsMetadata) and not issubclass(type_, RepresentsSingleObject):
146
+ # i.e., is EmbeddedMetadata
147
+ type_.set_error_handling(value)
148
+
149
+ @classmethod
150
+ def normalize_filter(cls, filter_dict: Dict[str, Any], check_validity: bool = True) -> Dict[str, Any]:
151
+ """
152
+ Normalize a dict containing filter key:value pairs so that it can be used
153
+ in a call to the KG query API.
154
+
155
+ Example:
156
+ >>> import fairgraph.openminds.core as omcore
157
+ >>> person = omcore.Person.from_uuid("045f846f-f010-4db8-97b9-b95b20970bf2", kg_client)
158
+ >>> filter_dict = {"custodians": person, "name": "Virtual"}
159
+ >>> omcore.Dataset.normalize_filter(filter_dict)
160
+ {'name': 'Virtual',
161
+ 'custodians': 'https://kg.ebrains.eu/api/instances/045f846f-f010-4db8-97b9-b95b20970bf2'}
162
+ """
163
+ normalized = {}
164
+ filter_dict_copy = filter_dict.copy()
165
+
166
+ # handle aliases
167
+ for name_, alias_ in cls.aliases.items():
168
+ if name_ in filter_dict_copy:
169
+ filter_dict_copy[alias_] = filter_dict_copy.pop(name_)
170
+
171
+ def _check_validity(filters, property_names):
172
+ invalid_filters = set(filters).difference(property_names)
173
+ if invalid_filters:
174
+ if len(invalid_filters) == 1:
175
+ msg = f"Invalid filter: "
176
+ else:
177
+ msg = f"Invalid filters: "
178
+ raise ValueError(msg + ", ".join(sorted(invalid_filters)))
179
+
180
+ if check_validity:
181
+ _check_validity(filter_dict_copy, cls.all_property_names + list(cls.aliases.keys()))
182
+
183
+ for prop in cls.all_properties:
184
+ if prop.name in filter_dict_copy:
185
+ value = filter_dict_copy[prop.name]
186
+ if isinstance(value, dict):
187
+ _check_validity(
188
+ value,
189
+ set(
190
+ chain(
191
+ *(
192
+ child_cls.all_property_names + list(child_cls.aliases.keys())
193
+ for child_cls in prop.types
194
+ )
195
+ )
196
+ ),
197
+ )
198
+ normalized[prop.name] = {}
199
+ for child_cls in prop.types:
200
+ normalized[prop.name].update(child_cls.normalize_filter(value, check_validity=False))
201
+ else:
202
+ normalized[prop.name] = get_filter_value(prop, value)
203
+
204
+ return normalized
205
+
206
+ @classmethod
207
+ def generate_query_properties(
208
+ cls, follow_links: Optional[Dict[str, Any]] = None, with_reverse_properties: Optional[bool] = False
209
+ ):
210
+ """
211
+ Generate a list of QueryProperty instances for this class
212
+ for use in constructing a KG query definition.
213
+
214
+ Args:
215
+ follow_links (dict): The links in the graph to follow when constructing the query.
216
+ Defaults to None.
217
+ with_reverse_properties (bool): Whether to include reverse properties.
218
+ Defaults to False.`
219
+ """
220
+ if issubclass(cls, RepresentsSingleObject): # KGObject
221
+ query_properties = [
222
+ QueryProperty("https://core.kg.ebrains.eu/vocab/meta/space", name="query:space"),
223
+ QueryProperty("@type"),
224
+ ]
225
+ else: # EmbeddedMetadata
226
+ query_properties = [QueryProperty("@type")]
227
+ reverse_aliases = invert_dict(cls.aliases)
228
+
229
+ if with_reverse_properties:
230
+ included_properties = cls.all_properties
231
+ else:
232
+ included_properties = cls.properties[:] # need to make a copy because we may be appending to it
233
+ if follow_links:
234
+ for prop_name in follow_links:
235
+ try:
236
+ prop = cls.get_property(prop_name)
237
+ # where a property can be of multiple types,
238
+ # not all types may be relevant to follow_links
239
+ except KeyError:
240
+ pass
241
+ else:
242
+ if prop.reverse:
243
+ included_properties.append(prop)
244
+
245
+ for prop in included_properties:
246
+ if prop.is_link and follow_links:
247
+ if prop.name in follow_links:
248
+ query_properties.extend(
249
+ get_query_properties(prop, cls.context, follow_links[prop.name], with_reverse_properties)
250
+ )
251
+ elif reverse_aliases.get(prop.name, None) in follow_links:
252
+ query_properties.extend(
253
+ get_query_properties(
254
+ prop, cls.context, follow_links[reverse_aliases[prop.name]], with_reverse_properties
255
+ )
256
+ )
257
+ else:
258
+ query_properties.extend(
259
+ get_query_properties(prop, cls.context, with_reverse_properties=with_reverse_properties)
260
+ )
261
+ else:
262
+ query_properties.extend(
263
+ get_query_properties(prop, cls.context, with_reverse_properties=with_reverse_properties)
264
+ )
265
+ return query_properties
266
+
267
+ @classmethod
268
+ def generate_query_filter_properties(
269
+ cls,
270
+ filters: Optional[Dict[str, Any]] = None,
271
+ ):
272
+ """
273
+
274
+ Args:
275
+ filters (dict, optional): A dict containing search parameters for the query.
276
+ """
277
+ if filters is None:
278
+ filters = {}
279
+ properties = []
280
+ for prop in cls.all_properties:
281
+ if prop.name in filters:
282
+ properties.append(get_query_filter_property(prop, cls.context, filters[prop.name]))
283
+ return properties
284
+
285
+ @classmethod
286
+ def _deserialize_data(cls, data: JSONdict, include_id: bool = False):
287
+
288
+ def _get_type_from_data(data_item):
289
+ # KG returns a list of types, openMINDS expects only a single string
290
+ if isinstance(data_item, dict) and "@type" in data_item:
291
+ if isinstance(data_item["@type"], (list, tuple)):
292
+ assert len(data_item["@type"]) == 1
293
+ return data_item["@type"][0]
294
+ else:
295
+ return data_item["@type"]
296
+ else:
297
+ return None
298
+
299
+ def _normalize_type(data_item):
300
+ # replace @type as list with @type as string
301
+ if isinstance(data_item, (list, tuple)):
302
+ data_item = [_normalize_type(part) for part in data_item]
303
+ else:
304
+ type_from_data_item = _get_type_from_data(data_item)
305
+ if type_from_data_item:
306
+ data_item = data_item.copy()
307
+ data_item["@type"] = type_from_data_item
308
+ return data_item
309
+
310
+ type_from_data = _get_type_from_data(data)
311
+ # check types match
312
+ if not types_match(cls.type_, type_from_data):
313
+ raise TypeError("type mismatch {} - {}".format(cls.type_, type_from_data))
314
+
315
+ # normalize data by expanding keys
316
+ D = {"@type": type_from_data}
317
+ if "@context" in data:
318
+ context = data["@context"]
319
+ else:
320
+ context = cls.context
321
+ if include_id and "@id" in data:
322
+ D["@id"] = data["@id"]
323
+ for key, value in data.items():
324
+ if "__" in key:
325
+ key, type_filter = key.split("__")
326
+ normalised_key = expand_uri(key, context)
327
+ value = [item for item in as_list(value) if _get_type_from_data(item).endswith(type_filter)]
328
+ if normalised_key in D:
329
+ if isinstance(D[normalised_key], list):
330
+ D[normalised_key].extend(value)
331
+ else:
332
+ D[normalised_key] = [D[normalised_key]] + value
333
+ else:
334
+ D[normalised_key] = value
335
+ elif key.startswith("Q"): # for 'Q' properties in data from queries
336
+ D[key] = value
337
+ elif key[0] != "@":
338
+ normalised_key = expand_uri(key, context)
339
+ D[normalised_key] = value
340
+
341
+ deserialized_data = {}
342
+ for prop in cls.all_properties:
343
+ expanded_path = expand_uri(prop.path, cls.context)
344
+ data_item = _normalize_type(D.get(expanded_path))
345
+
346
+ if data_item is not None and prop.reverse:
347
+ # for reverse properties, more than one property can have the same path
348
+ # so we extract only those sub-items whose types match
349
+ try:
350
+ data_item = [
351
+ part
352
+ for part in as_list(data_item)
353
+ if _get_type_from_data(part) in [t.type_ for t in prop.types]
354
+ ]
355
+ except AttributeError:
356
+ # problem when a forward and reverse path both given the same expanded path
357
+ # e.g. for Configuration
358
+ data_item = None
359
+
360
+ # sometimes queries put single items in a list, this removes the enclosing list
361
+ if (not prop.multiple) and isinstance(data_item, (list, tuple)) and len(data_item) == 1:
362
+ data_item = data_item[0]
363
+
364
+ if data_item is None:
365
+ if prop.reverse and "@id" in data:
366
+ if isinstance(prop.reverse, list):
367
+ # todo: handle all possible reverses
368
+ # for now, we just take the first
369
+ deserialized_data[prop.name] = KGQuery(prop.types, {prop.reverse[0]: data["@id"]})
370
+ else:
371
+ deserialized_data[prop.name] = KGQuery(prop.types, {prop.reverse: data["@id"]})
372
+ else:
373
+ deserialized_data[prop.name] = None
374
+ else:
375
+ try:
376
+ value = prop.deserialize(data_item)
377
+ except ValueError as err:
378
+ ErrorHandling.handle_violation(cls.error_handling, str(err))
379
+ else:
380
+ if isinstance(value, Link) and value.allowed_types is not None:
381
+ value = KGProxy(value.allowed_types, value.identifier)
382
+ elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], Link):
383
+ # here we assume that if the first item is a Link, they all are
384
+ value = [KGProxy(item.allowed_types, item.identifier) for item in value]
385
+ deserialized_data[prop.name] = value
386
+
387
+ return deserialized_data
388
+
389
+ def resolve(
390
+ self,
391
+ client: KGClient,
392
+ release_status: Optional[str] = None,
393
+ use_cache: bool = True,
394
+ follow_links: Optional[Dict[str, Any]] = None,
395
+ ):
396
+ """
397
+ Resolve properties that are represented by KGProxy objects.
398
+
399
+ Args:
400
+ client: KGClient object that handles the communication with the KG.
401
+ release_status (str): The scope of instances to include in the response.
402
+ Valid values are 'released', 'in progress', 'any'.
403
+ use_cache (bool): whether to use cached data if they exist. Defaults to True.
404
+ follow_links (dict): The links in the graph to follow. Defaults to None.
405
+
406
+ Note: a real (non-proxy) object resolves to itself.
407
+ """
408
+ use_release_status = release_status or self.release_status or "released"
409
+ if follow_links:
410
+ reverse_aliases = invert_dict(self.__class__.aliases)
411
+ for prop in self.__class__.all_properties:
412
+ if prop.is_link:
413
+ follow_name = None
414
+ if prop.name in follow_links:
415
+ follow_name = prop.name
416
+ elif reverse_aliases.get(prop.name, None) in follow_links:
417
+ follow_name = reverse_aliases[prop.name]
418
+
419
+ if follow_name:
420
+ if issubclass(prop.types[0], ContainsMetadata):
421
+ values = getattr(self, prop.name)
422
+ resolved_values: List[Any] = []
423
+ for value in as_list(values):
424
+ if isinstance(value, Resolvable):
425
+ if isinstance(value, ContainsMetadata) and isinstance(
426
+ value, RepresentsSingleObject
427
+ ):
428
+ # i.e. isinstance(value, KGObject) - already resolved
429
+ resolved_values.append(value)
430
+ else:
431
+ try:
432
+ resolved_value = value.resolve(
433
+ client,
434
+ release_status=use_release_status,
435
+ use_cache=use_cache,
436
+ follow_links=follow_links[follow_name],
437
+ )
438
+ except ResolutionFailure as err:
439
+ warn(str(err))
440
+ resolved_values.append(value)
441
+ else:
442
+ resolved_values.append(resolved_value)
443
+ if isinstance(values, RepresentsSingleObject):
444
+ assert len(resolved_values) == 1
445
+ setattr(self, prop.name, resolved_values[0])
446
+ elif values is None:
447
+ assert len(resolved_values) == 0
448
+ setattr(self, prop.name, None)
449
+ else:
450
+ setattr(self, prop.name, resolved_values)
451
+ return self
452
+
453
+ def _build_existence_query(self) -> Union[None, Dict[str, Any]]:
454
+ """
455
+ Generate a KG query definition (as a JSON-LD document) that can be used to
456
+ check whether a locally-defined object (with no ID) already exists in the KG.
457
+ """
458
+ if self.existence_query_properties is None:
459
+ return None
460
+
461
+ query_properties = []
462
+ for property_name in self.existence_query_properties:
463
+ for property in self.__class__.all_properties:
464
+ if property.name == property_name:
465
+ query_properties.append(property)
466
+ break
467
+ if len(query_properties) < 1:
468
+ raise CannotBuildExistenceQuery("Empty existence query for class {}".format(self.__class__.__name__))
469
+ query = {}
470
+ for property in query_properties:
471
+ query_property_name = property.name
472
+ value = getattr(self, property.name)
473
+ if isinstance(value, ContainsMetadata):
474
+ if hasattr(value, "id") and value.id:
475
+ query[query_property_name] = value.id
476
+ else:
477
+ sub_query = value._build_existence_query()
478
+ query.update({f"{query_property_name}__{key}": val for key, val in sub_query.items()})
479
+ elif isinstance(value, (list, tuple)):
480
+ raise CannotBuildExistenceQuery("not implemented yet")
481
+ elif value is None:
482
+ raise CannotBuildExistenceQuery(f"Required value for '{query_property_name}' is missing")
483
+ else:
484
+ query_val = value_to_jsonld(value, include_empty_properties=False, embed_linked_nodes=False)
485
+ if query_val is None:
486
+ raise CannotBuildExistenceQuery(f"Required value for '{query_property_name}' is missing")
487
+ query[query_property_name] = query_val
488
+ return query
@@ -0,0 +1 @@
1
+ from . import chemicals, computation, controlled_terms, core, ephys, publications, sands, specimen_prep, stimulation
@@ -0,0 +1,40 @@
1
+ import sys
2
+ import inspect
3
+ from fairgraph.kgobject import KGObject
4
+ from fairgraph.embedded import EmbeddedMetadata
5
+
6
+ from .amount_of_chemical import AmountOfChemical
7
+ from .chemical_mixture import ChemicalMixture
8
+ from .chemical_substance import ChemicalSubstance
9
+ from .product_source import ProductSource
10
+
11
+
12
+ def list_kg_classes():
13
+ """List all KG classes defined in this module"""
14
+ return [
15
+ obj
16
+ for name, obj in inspect.getmembers(sys.modules[__name__])
17
+ if inspect.isclass(obj) and issubclass(obj, KGObject) and obj.__module__.startswith(__name__)
18
+ ]
19
+
20
+
21
+ def list_embedded_metadata_classes():
22
+ """List all embedded metadata classes defined in this module"""
23
+ return [
24
+ obj
25
+ for name, obj in inspect.getmembers(sys.modules[__name__])
26
+ if inspect.isclass(obj) and issubclass(obj, EmbeddedMetadata) and obj.__module__.startswith(__name__)
27
+ ]
28
+
29
+
30
+ def set_error_handling(value):
31
+ """
32
+ Control validation for all classes in this module.
33
+
34
+ Args:
35
+ value (str): action to follow when there is a validation failure.
36
+ (e.g. if a required property is not provided).
37
+ Possible values: "error", "warning", "log", None
38
+ """
39
+ for cls in list_kg_classes() + list_embedded_metadata_classes():
40
+ cls.set_error_handling(value)
@@ -0,0 +1,23 @@
1
+ """
2
+ Structured information about the amount of a given chemical that was used.
3
+ """
4
+
5
+ # this file was auto-generated
6
+
7
+ from openminds.properties import Property
8
+ from openminds.v4.chemicals import AmountOfChemical as OMAmountOfChemical
9
+ from fairgraph import EmbeddedMetadata
10
+
11
+
12
+ class AmountOfChemical(EmbeddedMetadata, OMAmountOfChemical):
13
+ """
14
+ Structured information about the amount of a given chemical that was used.
15
+ """
16
+
17
+ type_ = "https://openminds.om-i.org/types/AmountOfChemical"
18
+ # forward properties are defined in the parent class (in openMINDS-Python)
19
+ reverse_properties = []
20
+ existence_query_properties = ("chemical_product", "amount")
21
+
22
+ def __init__(self, amount=None, chemical_product=None, id=None, data=None, space=None, release_status=None):
23
+ return EmbeddedMetadata.__init__(self, data=data, amount=amount, chemical_product=chemical_product)
@@ -0,0 +1,72 @@
1
+ """
2
+ Structured information about a mixture of chemical substances.
3
+ """
4
+
5
+ # this file was auto-generated
6
+
7
+ from openminds.properties import Property
8
+ from openminds.v4.chemicals import ChemicalMixture as OMChemicalMixture
9
+ from fairgraph import KGObject
10
+
11
+
12
+ class ChemicalMixture(KGObject, OMChemicalMixture):
13
+ """
14
+ Structured information about a mixture of chemical substances.
15
+ """
16
+
17
+ type_ = "https://openminds.om-i.org/types/ChemicalMixture"
18
+ default_space = "in-depth"
19
+ # forward properties are defined in the parent class (in openMINDS-Python)
20
+ reverse_properties = [
21
+ Property(
22
+ "composes",
23
+ ["openminds.v4.ephys.Electrode", "openminds.v4.ephys.ElectrodeArray", "openminds.v4.ephys.Pipette"],
24
+ ["insulatorMaterial", "material"],
25
+ reverse=["insulator_material", "material"],
26
+ multiple=True,
27
+ description="reverse of insulator_material, material",
28
+ ),
29
+ Property(
30
+ "used_in",
31
+ [
32
+ "openminds.v4.ephys.CellPatching",
33
+ "openminds.v4.ephys.PipetteUsage",
34
+ "openminds.v4.specimen_prep.TissueCulturePreparation",
35
+ "openminds.v4.specimen_prep.TissueSampleSlicing",
36
+ ],
37
+ ["cultureMedium", "pipetteSolution", "tissueBathSolution"],
38
+ reverse=["culture_medium", "pipette_solution", "tissue_bath_solution"],
39
+ multiple=True,
40
+ description="reverse of culture_medium, pipette_solution, tissue_bath_solution",
41
+ ),
42
+ ]
43
+ existence_query_properties = ("has_parts", "type")
44
+
45
+ def __init__(
46
+ self,
47
+ name=None,
48
+ additional_remarks=None,
49
+ composes=None,
50
+ has_parts=None,
51
+ product_source=None,
52
+ type=None,
53
+ used_in=None,
54
+ id=None,
55
+ data=None,
56
+ space=None,
57
+ release_status=None,
58
+ ):
59
+ return KGObject.__init__(
60
+ self,
61
+ id=id,
62
+ space=space,
63
+ release_status=release_status,
64
+ data=data,
65
+ name=name,
66
+ additional_remarks=additional_remarks,
67
+ composes=composes,
68
+ has_parts=has_parts,
69
+ product_source=product_source,
70
+ type=type,
71
+ used_in=used_in,
72
+ )