disqover-api 1.23.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 (166) hide show
  1. disqover/__init__.py +156 -0
  2. disqover/_hooks.py +118 -0
  3. disqover/application_info/__init__.py +1 -0
  4. disqover/application_info/api.py +143 -0
  5. disqover/authenticator.py +34 -0
  6. disqover/background_jobs/__init__.py +0 -0
  7. disqover/background_jobs/models.py +50 -0
  8. disqover/common/__init__.py +18 -0
  9. disqover/common/attribute.py +54 -0
  10. disqover/common/exceptions.py +14 -0
  11. disqover/common/identifiable.py +13 -0
  12. disqover/common/jsonapi.py +73 -0
  13. disqover/common/object.py +529 -0
  14. disqover/compatibility.py +76 -0
  15. disqover/constants.py +58 -0
  16. disqover/credentials_provider.py +192 -0
  17. disqover/data_ingestion/__init__.py +37 -0
  18. disqover/data_ingestion/api.py +16 -0
  19. disqover/data_ingestion/models/__init__.py +0 -0
  20. disqover/data_ingestion/models/actions.py +152 -0
  21. disqover/data_ingestion/models/canonical_type_component.py +217 -0
  22. disqover/data_ingestion/models/component.py +168 -0
  23. disqover/data_ingestion/models/component_result.py +105 -0
  24. disqover/data_ingestion/models/component_type.py +155 -0
  25. disqover/data_ingestion/models/datasource.py +61 -0
  26. disqover/data_ingestion/models/file_type.py +33 -0
  27. disqover/data_ingestion/models/pipeline.py +482 -0
  28. disqover/data_ingestion/models/pipeline_run.py +289 -0
  29. disqover/data_ingestion/models/run_options.py +162 -0
  30. disqover/data_ingestion/models/run_pipeline_job.py +120 -0
  31. disqover/data_ingestion/models/segment.py +71 -0
  32. disqover/data_ingestion/models/source_data.py +380 -0
  33. disqover/data_ingestion/models/uri_templates.py +55 -0
  34. disqover/data_ingestion/models/verification.py +279 -0
  35. disqover/data_query/__init__.py +60 -0
  36. disqover/data_query/api.py +390 -0
  37. disqover/data_query/facet_parameters.py +118 -0
  38. disqover/data_query/json_to_query.py +220 -0
  39. disqover/data_query/models/__init__.py +0 -0
  40. disqover/data_query/models/attribute.py +111 -0
  41. disqover/data_query/models/canonical_type.py +162 -0
  42. disqover/data_query/models/configuration.py +330 -0
  43. disqover/data_query/models/datasource.py +145 -0
  44. disqover/data_query/models/facet.py +261 -0
  45. disqover/data_query/models/facet_result.py +79 -0
  46. disqover/data_query/models/facet_value.py +77 -0
  47. disqover/data_query/models/federation.py +21 -0
  48. disqover/data_query/models/hierarchy.py +42 -0
  49. disqover/data_query/models/instance.py +140 -0
  50. disqover/data_query/models/instance_facet_values.py +134 -0
  51. disqover/data_query/models/links.py +24 -0
  52. disqover/data_query/models/nested_facets.py +150 -0
  53. disqover/data_query/models/ontology.py +56 -0
  54. disqover/data_query/models/property.py +256 -0
  55. disqover/data_query/models/property_value.py +175 -0
  56. disqover/data_query/models/query_instances.py +493 -0
  57. disqover/data_query/models/relation.py +196 -0
  58. disqover/data_query/models/sub_type.py +43 -0
  59. disqover/data_query/models/synonym.py +61 -0
  60. disqover/data_query/models/tabular.py +62 -0
  61. disqover/data_query/models/tools.py +5 -0
  62. disqover/data_query/queries.py +1581 -0
  63. disqover/data_query/query_to_json.py +218 -0
  64. disqover/disqover_session.py +286 -0
  65. disqover/events/__init__.py +6 -0
  66. disqover/events/api.py +16 -0
  67. disqover/events/models.py +333 -0
  68. disqover/http_basics.py +61 -0
  69. disqover/instance_collections/__init__.py +4 -0
  70. disqover/instance_collections/api.py +10 -0
  71. disqover/instance_collections/models.py +273 -0
  72. disqover/internal_authenticator.py +35 -0
  73. disqover/label_cache.py +147 -0
  74. disqover/m2m_authenticator.py +35 -0
  75. disqover/managed_files/__init__.py +3 -0
  76. disqover/managed_files/api.py +14 -0
  77. disqover/managed_files/models.py +51 -0
  78. disqover/oidc_authenticator.py +324 -0
  79. disqover/paging.py +55 -0
  80. disqover/plugins/__init__.py +6 -0
  81. disqover/plugins/api.py +9 -0
  82. disqover/plugins/examples/exchange_rates/plugin/__init__.py +1 -0
  83. disqover/plugins/examples/exchange_rates/plugin/callbacks.py +177 -0
  84. disqover/plugins/examples/exchange_rates/plugin/fetch.py +23 -0
  85. disqover/plugins/examples/exchange_rates/plugin/requirements.txt +1 -0
  86. disqover/plugins/examples/identifier_search/README.md +17 -0
  87. disqover/plugins/examples/identifier_search/identifier_search_widget/iframe.html +41 -0
  88. disqover/plugins/examples/identifier_search/plugin/__init__.py +0 -0
  89. disqover/plugins/examples/identifier_search/plugin/callbacks.py +70 -0
  90. disqover/plugins/examples/inverter/plugin/__init__.py +1 -0
  91. disqover/plugins/examples/inverter/plugin/callbacks.py +81 -0
  92. disqover/plugins/examples/inverter/plugin/inverter.py +5 -0
  93. disqover/plugins/examples/mock_instance_search/plugin/__init__.py +0 -0
  94. disqover/plugins/examples/mock_instance_search/plugin/callbacks.py +42 -0
  95. disqover/plugins/examples/numerical_aggregator/plugin/__init__.py +1 -0
  96. disqover/plugins/examples/numerical_aggregator/plugin/arrays.py +59 -0
  97. disqover/plugins/examples/numerical_aggregator/plugin/callbacks.py +294 -0
  98. disqover/plugins/examples/numerical_aggregator/plugin/evaluate.py +35 -0
  99. disqover/plugins/examples/numerical_aggregator/plugin/operations.py +48 -0
  100. disqover/plugins/examples/numerical_aggregator/plugin/parse.py +48 -0
  101. disqover/plugins/examples/numerical_aggregator/plugin/requirements.txt +4 -0
  102. disqover/plugins/examples/numerical_aggregator/plugin/store.py +28 -0
  103. disqover/plugins/examples/numerical_aggregator/plugin/type_hints.py +16 -0
  104. disqover/plugins/examples/property_exporter_stream/plugin/__init__.py +0 -0
  105. disqover/plugins/examples/property_exporter_stream/plugin/callbacks.py +171 -0
  106. disqover/plugins/examples/run_pipeline/plugin/__init__.py +1 -0
  107. disqover/plugins/examples/run_pipeline/plugin/callbacks.py +42 -0
  108. disqover/plugins/examples/s3_exporter/plugin/__init__.py +1 -0
  109. disqover/plugins/examples/s3_exporter/plugin/callbacks.py +56 -0
  110. disqover/plugins/examples/s3_exporter/plugin/requirements.txt +1 -0
  111. disqover/plugins/examples/syncer/plugin/__init__.py +1 -0
  112. disqover/plugins/examples/syncer/plugin/callbacks.py +80 -0
  113. disqover/plugins/get_examples.py +14 -0
  114. disqover/plugins/models.py +106 -0
  115. disqover/plugins/validate.py +763 -0
  116. disqover/plugins/validation/__init__.py +0 -0
  117. disqover/plugins/validation/helpers.py +136 -0
  118. disqover/plugins/validation/options.py +535 -0
  119. disqover/rds/__init__.py +9 -0
  120. disqover/rds/api.py +12 -0
  121. disqover/rds/models/__init__.py +0 -0
  122. disqover/rds/models/dataset.py +23 -0
  123. disqover/rds/models/published_dataset.py +65 -0
  124. disqover/rds/models/schedule.py +153 -0
  125. disqover/rds/models/schedule_run_job.py +69 -0
  126. disqover/rds/models/subscription.py +272 -0
  127. disqover/resources/__init__.py +3 -0
  128. disqover/resources/api.py +10 -0
  129. disqover/resources/models.py +119 -0
  130. disqover/stored_data/__init__.py +11 -0
  131. disqover/stored_data/api.py +75 -0
  132. disqover/stored_data/models/__init__.py +9 -0
  133. disqover/stored_data/models/alias.py +138 -0
  134. disqover/stored_data/models/data_and_parameters.py +99 -0
  135. disqover/stored_data/models/filters.py +285 -0
  136. disqover/stored_data/models/permissions.py +123 -0
  137. disqover/stored_data/models/resource.py +598 -0
  138. disqover/stored_data/models/resource_type.py +234 -0
  139. disqover/stored_data/models/scope.py +7 -0
  140. disqover/stored_data/models/user_spec.py +82 -0
  141. disqover/system/__init__.py +3 -0
  142. disqover/system/api.py +16 -0
  143. disqover/system/models.py +68 -0
  144. disqover/tasks/__init__.py +5 -0
  145. disqover/tasks/api.py +12 -0
  146. disqover/tasks/models.py +393 -0
  147. disqover/type_hints.py +149 -0
  148. disqover/user_management/__init__.py +19 -0
  149. disqover/user_management/api.py +45 -0
  150. disqover/user_management/models/__init__.py +17 -0
  151. disqover/user_management/models/external.py +86 -0
  152. disqover/user_management/models/license.py +85 -0
  153. disqover/user_management/models/m2m.py +108 -0
  154. disqover/user_management/models/permission.py +92 -0
  155. disqover/user_management/models/security_event.py +44 -0
  156. disqover/user_management/models/user.py +273 -0
  157. disqover/user_management/models/user_group.py +221 -0
  158. disqover/user_views/__init__.py +4 -0
  159. disqover/user_views/api.py +13 -0
  160. disqover/user_views/models.py +96 -0
  161. disqover/utilities.py +344 -0
  162. disqover_api-1.23.0.dist-info/METADATA +1818 -0
  163. disqover_api-1.23.0.dist-info/RECORD +166 -0
  164. disqover_api-1.23.0.dist-info/WHEEL +4 -0
  165. disqover_api-1.23.0.dist-info/entry_points.txt +3 -0
  166. disqover_api-1.23.0.dist-info/licenses/LICENSE +20 -0
disqover/__init__.py ADDED
@@ -0,0 +1,156 @@
1
+ from .application_info import ApplicationInfoAPI
2
+ from .common import DisqoverAPIError
3
+ from .credentials_provider import BearerTokenCredentialsProvider
4
+ from .credentials_provider import CommandLineCredentialsProvider
5
+ from .credentials_provider import InlineCredentialsProvider
6
+ from .data_ingestion import Component
7
+ from .data_ingestion import ComponentCounter
8
+ from .data_ingestion import ComponentResult
9
+ from .data_ingestion import ComponentStatus
10
+ from .data_ingestion import ComponentType
11
+ from .data_ingestion import ComponentTypeOption
12
+ from .data_ingestion import ComponentTypes
13
+ from .data_ingestion import ComponentTypeSubOptions
14
+ from .data_ingestion import ComponentWarning
15
+ from .data_ingestion import DataIngestionAPI
16
+ from .data_ingestion import FacetDataType
17
+ from .data_ingestion import FacetViewType
18
+ from .data_ingestion import FileType
19
+ from .data_ingestion import IncrementalInfo
20
+ from .data_ingestion import Pipeline
21
+ from .data_ingestion import PipelineRun
22
+ from .data_ingestion import PipelineRunInfo
23
+ from .data_ingestion import PipelineRuns
24
+ from .data_ingestion import Pipelines
25
+ from .data_ingestion import PipelineStatus
26
+ from .data_ingestion import PropertyDataType
27
+ from .data_ingestion import PropertyRenderer
28
+ from .data_ingestion import RunOptions
29
+ from .data_ingestion import RunPipelineJob
30
+ from .data_ingestion import ScanResult
31
+ from .data_ingestion import ScanResultBase
32
+ from .data_ingestion import Segment
33
+ from .data_ingestion import SourceContents
34
+ from .data_ingestion import SourceData
35
+ from .data_ingestion import SourceDirectories
36
+ from .data_ingestion import SourceDirectory
37
+ from .data_ingestion import SourceFile
38
+ from .data_ingestion import SourceFiles
39
+ from .data_ingestion import SourceItem
40
+ from .data_ingestion import UnresolvedScanResult
41
+ from .data_ingestion import UriTemplates
42
+ from .data_ingestion import actions
43
+ from .data_query import AtLocation
44
+ from .data_query import BelongsToCanonicalType
45
+ from .data_query import CanonicalType
46
+ from .data_query import CategoricalFacetParameters
47
+ from .data_query import CombinationQuery
48
+ from .data_query import Concept
49
+ from .data_query import DataConfiguration
50
+ from .data_query import DataOntology
51
+ from .data_query import DataQueryAPI
52
+ from .data_query import DataSource
53
+ from .data_query import FacetEqual
54
+ from .data_query import FacetMissing
55
+ from .data_query import FacetPresent
56
+ from .data_query import FacetResult
57
+ from .data_query import FacetType
58
+ from .data_query import FederationLocation
59
+ from .data_query import FromDataSource
60
+ from .data_query import FromLocalIngestion
61
+ from .data_query import FromRDSServer
62
+ from .data_query import HierarchicalFacetParameters
63
+ from .data_query import InCollection
64
+ from .data_query import InProximity
65
+ from .data_query import InRange
66
+ from .data_query import Instance
67
+ from .data_query import InstancesFacetValuesResult
68
+ from .data_query import InstanceSlice
69
+ from .data_query import InstanceURIs
70
+ from .data_query import InTree
71
+ from .data_query import LinkedTo
72
+ from .data_query import LinkedToCanonicalType
73
+ from .data_query import LinksResult
74
+ from .data_query import LinksTo
75
+ from .data_query import LinksToCanonicalType
76
+ from .data_query import LocationEqual
77
+ from .data_query import MatchPropertyText
78
+ from .data_query import MatchText
79
+ from .data_query import NestedFacetQuantifier
80
+ from .data_query import NestedFacetsQuantification
81
+ from .data_query import NestedFacetsResult
82
+ from .data_query import NumericalFacetParameters
83
+ from .data_query import NumericalFacetResult
84
+ from .data_query import PluginSearch
85
+ from .data_query import Query
86
+ from .data_query import RawQuery
87
+ from .data_query import RelatedTo
88
+ from .data_query import RelatesTo
89
+ from .data_query import Relation
90
+ from .data_query import RelationType
91
+ from .data_query import SimpleText
92
+ from .data_query import SubInstancesOf
93
+ from .data_query import SubType
94
+ from .data_query import TabularColumn
95
+ from .data_query import TabularOptions
96
+ from .data_query import parse_query
97
+ from .disqover_session import DisqoverSession
98
+ from .instance_collections import CollectionInstance
99
+ from .instance_collections import InstanceCollection
100
+ from .instance_collections import InstanceCollections
101
+ from .instance_collections import InstanceCollectionsAPI
102
+ from .m2m_authenticator import M2MAuthenticator
103
+ from .managed_files import ManagedFile
104
+ from .managed_files import ManagedFiles
105
+ from .managed_files import ManagedFilesAPI
106
+ from .oidc_authenticator import DeviceFlowAuthenticator
107
+ from .plugins import PluginsAPI
108
+ from .rds import DataSet
109
+ from .rds import PublishedDataSets
110
+ from .rds import RemoteDataSubscriptionAPI
111
+ from .rds import Schedule
112
+ from .rds import ScheduleRun
113
+ from .rds import ScheduleRunJob
114
+ from .rds import Subscription
115
+ from .rds import Subscriptions
116
+ from .resources import Resource
117
+ from .resources import Resources
118
+ from .resources import ResourcesAPI
119
+ from .stored_data import ResourcePermissionsRule
120
+ from .stored_data import StoredDataAPI
121
+ from .stored_data import StoredDataResource
122
+ from .stored_data import StoredDataResourceData
123
+ from .stored_data import StoredDataResources
124
+ from .stored_data import StoredDataResourceType
125
+ from .stored_data import StoredDataResourceTypes
126
+ from .stored_data import SubResourceParameters
127
+ from .stored_data import UserSpecification
128
+ from .stored_data import UserSpecificationType
129
+ from .stored_data import migrate_resources
130
+ from .system import SystemAPI
131
+ from .system import SystemSetting
132
+ from .system import SystemSettings
133
+ from .tasks import Task
134
+ from .tasks import Tasks
135
+ from .tasks import TasksAPI
136
+ from .user_management import CriticalityLevel
137
+ from .user_management import ExternalGroup
138
+ from .user_management import ExternalGroups
139
+ from .user_management import LicenseAgreement
140
+ from .user_management import LicenseAgreements
141
+ from .user_management import LicenseManagementAPI
142
+ from .user_management import M2MUser
143
+ from .user_management import M2MUsers
144
+ from .user_management import Operation
145
+ from .user_management import SecurityEvent
146
+ from .user_management import SecurityEvents
147
+ from .user_management import User
148
+ from .user_management import UserAccess
149
+ from .user_management import UserGroup
150
+ from .user_management import UserGroups
151
+ from .user_management import UserManagementAPI
152
+ from .user_management import Users
153
+ from .user_views import UserView
154
+ from .user_views import UserViews
155
+ from .user_views import UserViewsAPI
156
+ from .user_views import UserViewsApi
disqover/_hooks.py ADDED
@@ -0,0 +1,118 @@
1
+ import abc
2
+ import json
3
+ import logging
4
+ import time
5
+ from contextlib import contextmanager
6
+ from typing import Callable
7
+ from typing import Optional
8
+
9
+ import requests
10
+
11
+ from disqover.http_basics import UNAUTHENTICATED
12
+ from disqover.http_basics import RequestOptions
13
+ from disqover.http_basics import describe_error
14
+ from disqover.http_basics import is_error
15
+
16
+ ResponseHook = Callable[..., Optional[requests.Response]] # parameters are (requests.Response, *args, **kwargs)
17
+
18
+
19
+ class PredefinedResponseHook(abc.ABC):
20
+ def __init__(self, session: "DisqoverSession"):
21
+ self._session = session
22
+ self._current_request: Optional[RequestOptions] = None
23
+
24
+ @abc.abstractmethod
25
+ def __call__(self, response: requests.Response, *args, **kwargs): ...
26
+
27
+ def _log_debug(self, func, *args, **kwargs) -> None:
28
+ self._log(logging.DEBUG, func, *args, **kwargs)
29
+
30
+ def _log_info(self, func, *args, **kwargs) -> None:
31
+ self._log(logging.INFO, func, *args, **kwargs)
32
+
33
+ def _log_warning(self, func, *args, **kwargs) -> None:
34
+ self._log(logging.WARNING, func, *args, **kwargs)
35
+
36
+ def _log_error(self, func, *args, **kwargs) -> None:
37
+ self._log(logging.ERROR, func, *args, **kwargs)
38
+
39
+ def _log(self, level, func, *args, **kwargs) -> None:
40
+ """lazy logging"""
41
+ if self._session._logger.isEnabledFor(level):
42
+ try:
43
+ msg = func()
44
+ except TypeError:
45
+ msg = func
46
+ self._session._logger.log(level, msg, *args, **kwargs)
47
+
48
+ @contextmanager
49
+ def set_current_request(self, request: RequestOptions):
50
+ self._current_request = request
51
+ yield
52
+ self._current_request = None
53
+
54
+
55
+ class ReAuthenticateAndReplay(PredefinedResponseHook):
56
+ def __call__(self, response: requests.Response, *args, **kwargs):
57
+ if response.status_code == UNAUTHENTICATED:
58
+ if self._session._authenticator.can_reauthenticate:
59
+ self._log_debug("Authenticated session has expired, re-authenticating ...")
60
+ self._session._reset_session()
61
+ self._session._authenticator.authenticate(self._session._http_session, self._session._logger)
62
+ return self._session._get_response(self._current_request)
63
+ else:
64
+ raise ValueError("Invalid user credentials")
65
+
66
+
67
+ class ResponseLogger(PredefinedResponseHook):
68
+ def __init__(self, session: "DisqoverSession", max_content_length: int = 250):
69
+ super().__init__(session)
70
+ self._max_content_length = max_content_length
71
+ self._ignore_error_status: set[int] = set()
72
+
73
+ def __call__(self, response: requests.Response, *args, **kwargs):
74
+ self._handle_default(response)
75
+ if is_error(response.status_code) and response.status_code not in self._ignore_error_status:
76
+ self._handle_error(response)
77
+
78
+ def _handle_default(self, response: requests.Response) -> None:
79
+ def format_msg():
80
+ try:
81
+ content_str = response.text
82
+ except:
83
+ content_str = "<binary data>"
84
+ if len(content_str) > self._max_content_length:
85
+ content_str = f"{content_str[:self._max_content_length]} ... ({len(content_str)} characters)"
86
+ else:
87
+ content_str = response.text
88
+ return f"Response: {response.status_code} with HEADERS: {response.headers} and CONTENT: {content_str}"
89
+
90
+ self._log_debug(format_msg)
91
+
92
+ def _handle_error(self, response: requests.Response) -> None:
93
+ self._log_error(f"!! A {describe_error(response)} has occurred")
94
+ self._log_error(f"!! REQUEST:")
95
+ self._log_error(f"!! - {self._current_request.verb.upper()} {self._current_request.path}")
96
+ if self._current_request.params:
97
+ self._log_error(f"!! - parameters:")
98
+ self._log_json(self._current_request.params)
99
+ if self._current_request.headers:
100
+ self._log_error(f"!! - headers:")
101
+ self._log_dict(self._current_request.headers)
102
+ if self._current_request.payload:
103
+ self._log_error(f"!! - payload:")
104
+ self._log_json(self._current_request.payload)
105
+ self._log_error("!!")
106
+ self._log_error(f"!! RESPONSE: (status={response.status_code})")
107
+ self._log_error(f"!! - headers:")
108
+ self._log_dict(response.headers)
109
+ self._log_error(f"!! - data:")
110
+ self._log_error(f"!! {response.content}")
111
+
112
+ def _log_dict(self, dct: dict):
113
+ for key, val in dct.items():
114
+ self._log_error(f"!! {key.upper()} = {val}")
115
+
116
+ def _log_json(self, json_data):
117
+ for line in json.dumps(json_data, indent=2).splitlines():
118
+ self._log_error(f"!! {line}")
@@ -0,0 +1 @@
1
+ from .api import ApplicationInfoAPI
@@ -0,0 +1,143 @@
1
+ import dataclasses
2
+ import datetime
3
+ import enum
4
+ from typing import Optional
5
+
6
+ from disqover.common import API
7
+ from disqover.disqover_session import DisqoverSession
8
+ from disqover.type_hints import JSONDict
9
+ from disqover.utilities import parse_datetime
10
+
11
+
12
+ class DataQueryVersion(enum.Enum):
13
+ v1_0 = "v1.0"
14
+ v1_1 = "v1.1"
15
+
16
+
17
+ def get_query_api_prefix(version: Optional[DataQueryVersion]) -> str:
18
+ if version is None:
19
+ return "api"
20
+ else:
21
+ return f"api/query/{version.value}"
22
+
23
+
24
+ @dataclasses.dataclass(frozen=True)
25
+ class DisqoverInfo:
26
+
27
+ version: str
28
+ datasets_last_modified: datetime.datetime
29
+ federated: bool
30
+ federated_and_responds: bool
31
+ license_nr_actions: int
32
+ allow_sharing: bool
33
+ session_time_minutes: int
34
+ enable_full_purls: bool
35
+ maps_mapbox_access_token: str
36
+ maps_mapbox_style_url: str
37
+ default_query_api_version: DataQueryVersion
38
+ disqover7: bool = True # For backwards compatibility, should be removed eventually
39
+
40
+ def _get_version_part(self, part: int) -> Optional[int]:
41
+ try:
42
+ return int(self.version.split(".")[part])
43
+ except:
44
+ return None # e.g. 'dev'
45
+
46
+ def has_official_version(self) -> bool:
47
+ # 'dev' -> False
48
+ # '6.11.1' -> True
49
+ return (
50
+ self._get_version_part(0) is not None
51
+ and self._get_version_part(1) is not None
52
+ and self._get_version_part(2) is not None
53
+ )
54
+
55
+ @property
56
+ def major_version(self) -> Optional[int]:
57
+ # e.g. '5.20.1' -> 5
58
+ return self._get_version_part(0)
59
+
60
+ @property
61
+ def minor_version(self) -> Optional[int]:
62
+ # e.g. '5.20.1' -> 2
63
+ return self._get_version_part(1)
64
+
65
+ @property
66
+ def patch_version(self) -> Optional[int]:
67
+ # e.g. '5.20.1' -> 1
68
+ return self._get_version_part(2)
69
+
70
+
71
+ @dataclasses.dataclass(frozen=True)
72
+ class SanityInfo:
73
+
74
+ name: str
75
+ description: str
76
+ status: str
77
+
78
+ def is_service_up(self) -> bool:
79
+ return self.status == "OK"
80
+
81
+
82
+ class SearchPluginInfo:
83
+ def __init__(self, json_data: JSONDict):
84
+ self.name = json_data.get("name")
85
+ self.description = json_data.get("description")
86
+ self.config = json_data.get("config")
87
+ self.type = json_data.get("type")
88
+ self.visible_for_groups = json_data.get("visible_for_groups")
89
+
90
+
91
+ class ApplicationInfoAPI(API):
92
+ def __init__(self, session: DisqoverSession):
93
+ super().__init__(session)
94
+
95
+ def get_info(self) -> DisqoverInfo:
96
+ result = self.session.get("/api/info")
97
+ version = result.get("version")
98
+ datasets_last_modified = (
99
+ parse_datetime(result["datasets_last_modified"]) if result.get("datasets_last_modified") else None
100
+ )
101
+ federated = result.get("federated")
102
+ federated_and_responds = result.get("federated_and_responds")
103
+ license_nr_actions = result.get("license_nr_actions")
104
+ allow_sharing = result.get("allow_sharing")
105
+ session_time_minutes = result.get("session_time_minutes")
106
+ enable_full_purls = result.get("enable_full_purls")
107
+ maps_mapbox_access_token = result.get("maps_mapbox_access_token")
108
+ maps_mapbox_style_url = result.get("maps_mapbox_style_url")
109
+ query_api_version_str = result.get("query_api_version", "1.0")
110
+ query_api_version = DataQueryVersion(f"v{query_api_version_str}")
111
+ return DisqoverInfo(
112
+ version,
113
+ datasets_last_modified,
114
+ federated,
115
+ federated_and_responds,
116
+ license_nr_actions,
117
+ allow_sharing,
118
+ session_time_minutes,
119
+ enable_full_purls,
120
+ maps_mapbox_access_token,
121
+ maps_mapbox_style_url,
122
+ query_api_version,
123
+ )
124
+
125
+ def sanity_check(self) -> list[SanityInfo]:
126
+ response = self.session.get("/api/info/sanity_check")
127
+ services = []
128
+ for info_data in response:
129
+ name = info_data.get("name")
130
+ description = info_data.get("description")
131
+ status = info_data.get("status")
132
+ services.append(SanityInfo(name, description, status))
133
+ return services
134
+
135
+ @property
136
+ def auth_type(self):
137
+ response = self.session.get("/api/info/auth_type")
138
+ return response
139
+
140
+ @property
141
+ def search_plugin_list(self) -> list[SearchPluginInfo]:
142
+ response = self.session.get("/api/plugins")
143
+ return [SearchPluginInfo(info) for info in response]
@@ -0,0 +1,34 @@
1
+ import posixpath
2
+ import urllib.parse
3
+
4
+ from .type_hints import URL
5
+
6
+
7
+ class Authenticator:
8
+ """Encapsulates the data needed to create a session on a DISQOVER server.
9
+
10
+ Abstract class.
11
+ """
12
+
13
+ can_reauthenticate = True
14
+
15
+ @property
16
+ def url(self) -> str:
17
+ """The URL of the server, including the protocol and without trailing path.
18
+ E.g. https://federation.disqover.com
19
+ """
20
+ raise NotImplementedError()
21
+
22
+ def authenticate(self, http_session, logger):
23
+ raise NotImplementedError()
24
+
25
+ def authenticate_request(self, path: str, headers, logger) -> URL:
26
+ raise NotImplementedError()
27
+
28
+ def reset_cookies(self):
29
+ pass
30
+
31
+ def _join_path(self, path) -> URL:
32
+ if urllib.parse.urlparse(path).scheme:
33
+ return path # absolute URL path
34
+ return urllib.parse.urljoin(self.url, posixpath.normpath(path))
File without changes
@@ -0,0 +1,50 @@
1
+ from disqover.common import APIObjectBase
2
+ from disqover.disqover_session import DisqoverSession
3
+
4
+ DEFAULT_PAGE_SIZE = 50
5
+
6
+
7
+ class JobError(Exception):
8
+ pass
9
+
10
+
11
+ class JobExecutionError(JobError):
12
+ pass
13
+
14
+
15
+ class AlreadyRunning(JobError):
16
+ pass
17
+
18
+
19
+ class UnknownStatus(JobError):
20
+ pass
21
+
22
+
23
+ class NotRunning(JobError):
24
+ pass
25
+
26
+
27
+ class JobNotFound(JobError):
28
+ pass
29
+
30
+
31
+ class Job(APIObjectBase):
32
+ def __init__(self, session: DisqoverSession):
33
+ super().__init__(session)
34
+ self.errors = []
35
+
36
+ def start(self) -> None:
37
+ raise NotImplementedError("This kind of job can not be started") # can be implemented in derived class, though
38
+
39
+ def stop(self) -> None:
40
+ raise NotImplementedError("This kind of job can not be stopped") # can be implemented in derived class, though
41
+
42
+ def get_status(self):
43
+ raise NotImplementedError("Must be implemented in a derived class")
44
+
45
+ def wait(self, interval: int = 50) -> None:
46
+ raise NotImplementedError("Must be implemented in a derived class")
47
+
48
+ def stop_and_wait(self) -> None: # to make it clear that the regular 'stop' method is non-blocking!
49
+ self.stop()
50
+ self.wait()
@@ -0,0 +1,18 @@
1
+ from .attribute import APIAttribute
2
+ from .attribute import ReadOnlyAPIAttribute
3
+ from .attribute import ReadWriteAPIAttribute
4
+ from .exceptions import DisqoverAPIError
5
+ from .exceptions import DisqoverError
6
+ from .identifiable import Identifiable
7
+ from .object import APIObject
8
+ from .object import APIObjectBase
9
+ from .object import APIObjectCollection
10
+ from .object import APIObjectCollectionWithGetAll
11
+ from .object import ReadOnlyAPIObject
12
+
13
+
14
+ class API(APIObjectBase):
15
+
16
+ # A high-level DISQOVER API class that bundles related functionality
17
+
18
+ pass
@@ -0,0 +1,54 @@
1
+ from typing import Any
2
+ from typing import Optional
3
+
4
+ AttributeKey = str
5
+ AttributeValue = Any
6
+
7
+
8
+ class APIAttribute:
9
+ """
10
+ An attribute is defined by key and used as descriptor on an 'APIObject' class. It attempts to
11
+ retrieve the object itself when the attribute value is requested.
12
+ """
13
+
14
+ # TODO: take 2 arguments when defining an `APIAttribute`: one for the type of the value (now often indicated
15
+ # ad hoc with a comment) and one for a user-friendly description of the attribute
16
+
17
+ @property
18
+ def key(self) -> AttributeKey:
19
+ return self._key
20
+
21
+ def __set_name__(self, owner, key: AttributeKey) -> None:
22
+ self._key = key
23
+
24
+ def __get__(self, obj: Optional["APIObject"], objtype=None):
25
+ if obj is None:
26
+ return type("APIAttributeHelp", (), {"__doc__": f"Attribute '{self._key}'"})
27
+ obj._retrieve()
28
+ return obj._get(self._key)
29
+
30
+
31
+ class ReadOnlyAPIAttribute(APIAttribute):
32
+ """
33
+ A read-only attribute can never be set.
34
+ """
35
+
36
+ def __set__(self, obj: "APIObject", value: AttributeValue):
37
+ raise AttributeError(f"Attribute {self._key} of {obj.__class__.__name__} is read-only.")
38
+
39
+
40
+ class ReadWriteAPIAttribute(APIAttribute):
41
+ """
42
+ An attribute of this class can be 'get' and 'set'. Upon setting a new value, this value is compared to the original
43
+ one. The object is saved if the value has changed.
44
+ """
45
+
46
+ def __set__(self, obj: "APIObject", value: AttributeValue):
47
+ obj._retrieve()
48
+ old_value = obj._get(self._key)
49
+ if old_value != value:
50
+ obj._set(self._key, value)
51
+ self.save_attribute(obj)
52
+
53
+ def save_attribute(self, obj: "APIObject"):
54
+ obj._save(changed_attribute=self._key)
@@ -0,0 +1,14 @@
1
+ class DisqoverError(Exception):
2
+ pass
3
+
4
+
5
+ class DisqoverAPIError(DisqoverError):
6
+ pass
7
+
8
+
9
+ class APIObjectNotFoundError(DisqoverAPIError):
10
+ pass
11
+
12
+
13
+ class ProtectedDataError(DisqoverAPIError):
14
+ pass
@@ -0,0 +1,13 @@
1
+ from typing import Optional
2
+
3
+ from disqover.type_hints import URI
4
+
5
+
6
+ class Identifiable:
7
+ def __init__(self, uri: URI, label: Optional[str] = None, description: Optional[str] = None):
8
+ self.uri: URI = uri
9
+ self.label: str = label
10
+ self.description: Optional[str] = description
11
+
12
+ def __repr__(self):
13
+ return f'{self.__class__.__name__}("{self.uri}")'
@@ -0,0 +1,73 @@
1
+ import json
2
+ from typing import Optional
3
+ from typing import Union
4
+ from urllib.parse import quote
5
+
6
+ from ..paging import PageParameters
7
+ from ..type_hints import JSONDict
8
+
9
+ APIResourceID = Union[str, int]
10
+
11
+
12
+ def create_jsonapi_object(
13
+ type_id: str, resource_id: Optional[APIResourceID] = None, attributes: Optional[JSONDict] = None
14
+ ) -> JSONDict:
15
+ data = {"type": type_id}
16
+ if resource_id:
17
+ data["id"] = resource_id
18
+ if attributes:
19
+ data["attributes"] = attributes
20
+ return {"data": data}
21
+
22
+
23
+ def create_jsonapi_objects(type_id: str, resources: list[JSONDict]) -> JSONDict:
24
+ data = []
25
+ for resource in resources:
26
+ id = resource.pop("id", None)
27
+ resource_json = {"type": type_id, "attributes": resource}
28
+ if id is not None:
29
+ resource_json["id"] = id
30
+ data.append(resource_json)
31
+ return {"data": data}
32
+
33
+
34
+ def parse_jsonapi_object(data: JSONDict, check_type: Optional[str] = None) -> tuple[APIResourceID, JSONDict]:
35
+ if check_type:
36
+ assert data["data"]["type"] == check_type
37
+ return data["data"].get("id"), data["data"].get("attributes", {})
38
+
39
+
40
+ def parse_jsonapi_objects(data: JSONDict) -> dict[APIResourceID, JSONDict]:
41
+ result = {}
42
+ resources = data["data"]
43
+ for resource in resources:
44
+ result[resource["id"]] = resource["attributes"]
45
+ return result
46
+
47
+
48
+ def get_slicing_url_part(
49
+ resource: str,
50
+ paging: PageParameters,
51
+ order_by: Optional[str],
52
+ direction: Optional[str],
53
+ filters: Optional[JSONDict] = None,
54
+ ):
55
+ url = ""
56
+
57
+ # Pagination
58
+ if paging:
59
+ url += f"&page[page]={paging.index+1}&page[size]={paging.size}"
60
+
61
+ # Sorting
62
+ if order_by:
63
+ is_ascending = direction is None or direction == "asc"
64
+ direction_prefix = "" if is_ascending else "-"
65
+ url += f"&sort={direction_prefix}{order_by}"
66
+
67
+ # Filtering
68
+ if filters:
69
+ for key, value in filters.items():
70
+ value = json.dumps(value) if isinstance(value, dict) else value
71
+ url += f"&filter[{resource}.{key}]={quote(str(value))}"
72
+
73
+ return f"?{url[1:]}"