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.
- disqover/__init__.py +156 -0
- disqover/_hooks.py +118 -0
- disqover/application_info/__init__.py +1 -0
- disqover/application_info/api.py +143 -0
- disqover/authenticator.py +34 -0
- disqover/background_jobs/__init__.py +0 -0
- disqover/background_jobs/models.py +50 -0
- disqover/common/__init__.py +18 -0
- disqover/common/attribute.py +54 -0
- disqover/common/exceptions.py +14 -0
- disqover/common/identifiable.py +13 -0
- disqover/common/jsonapi.py +73 -0
- disqover/common/object.py +529 -0
- disqover/compatibility.py +76 -0
- disqover/constants.py +58 -0
- disqover/credentials_provider.py +192 -0
- disqover/data_ingestion/__init__.py +37 -0
- disqover/data_ingestion/api.py +16 -0
- disqover/data_ingestion/models/__init__.py +0 -0
- disqover/data_ingestion/models/actions.py +152 -0
- disqover/data_ingestion/models/canonical_type_component.py +217 -0
- disqover/data_ingestion/models/component.py +168 -0
- disqover/data_ingestion/models/component_result.py +105 -0
- disqover/data_ingestion/models/component_type.py +155 -0
- disqover/data_ingestion/models/datasource.py +61 -0
- disqover/data_ingestion/models/file_type.py +33 -0
- disqover/data_ingestion/models/pipeline.py +482 -0
- disqover/data_ingestion/models/pipeline_run.py +289 -0
- disqover/data_ingestion/models/run_options.py +162 -0
- disqover/data_ingestion/models/run_pipeline_job.py +120 -0
- disqover/data_ingestion/models/segment.py +71 -0
- disqover/data_ingestion/models/source_data.py +380 -0
- disqover/data_ingestion/models/uri_templates.py +55 -0
- disqover/data_ingestion/models/verification.py +279 -0
- disqover/data_query/__init__.py +60 -0
- disqover/data_query/api.py +390 -0
- disqover/data_query/facet_parameters.py +118 -0
- disqover/data_query/json_to_query.py +220 -0
- disqover/data_query/models/__init__.py +0 -0
- disqover/data_query/models/attribute.py +111 -0
- disqover/data_query/models/canonical_type.py +162 -0
- disqover/data_query/models/configuration.py +330 -0
- disqover/data_query/models/datasource.py +145 -0
- disqover/data_query/models/facet.py +261 -0
- disqover/data_query/models/facet_result.py +79 -0
- disqover/data_query/models/facet_value.py +77 -0
- disqover/data_query/models/federation.py +21 -0
- disqover/data_query/models/hierarchy.py +42 -0
- disqover/data_query/models/instance.py +140 -0
- disqover/data_query/models/instance_facet_values.py +134 -0
- disqover/data_query/models/links.py +24 -0
- disqover/data_query/models/nested_facets.py +150 -0
- disqover/data_query/models/ontology.py +56 -0
- disqover/data_query/models/property.py +256 -0
- disqover/data_query/models/property_value.py +175 -0
- disqover/data_query/models/query_instances.py +493 -0
- disqover/data_query/models/relation.py +196 -0
- disqover/data_query/models/sub_type.py +43 -0
- disqover/data_query/models/synonym.py +61 -0
- disqover/data_query/models/tabular.py +62 -0
- disqover/data_query/models/tools.py +5 -0
- disqover/data_query/queries.py +1581 -0
- disqover/data_query/query_to_json.py +218 -0
- disqover/disqover_session.py +286 -0
- disqover/events/__init__.py +6 -0
- disqover/events/api.py +16 -0
- disqover/events/models.py +333 -0
- disqover/http_basics.py +61 -0
- disqover/instance_collections/__init__.py +4 -0
- disqover/instance_collections/api.py +10 -0
- disqover/instance_collections/models.py +273 -0
- disqover/internal_authenticator.py +35 -0
- disqover/label_cache.py +147 -0
- disqover/m2m_authenticator.py +35 -0
- disqover/managed_files/__init__.py +3 -0
- disqover/managed_files/api.py +14 -0
- disqover/managed_files/models.py +51 -0
- disqover/oidc_authenticator.py +324 -0
- disqover/paging.py +55 -0
- disqover/plugins/__init__.py +6 -0
- disqover/plugins/api.py +9 -0
- disqover/plugins/examples/exchange_rates/plugin/__init__.py +1 -0
- disqover/plugins/examples/exchange_rates/plugin/callbacks.py +177 -0
- disqover/plugins/examples/exchange_rates/plugin/fetch.py +23 -0
- disqover/plugins/examples/exchange_rates/plugin/requirements.txt +1 -0
- disqover/plugins/examples/identifier_search/README.md +17 -0
- disqover/plugins/examples/identifier_search/identifier_search_widget/iframe.html +41 -0
- disqover/plugins/examples/identifier_search/plugin/__init__.py +0 -0
- disqover/plugins/examples/identifier_search/plugin/callbacks.py +70 -0
- disqover/plugins/examples/inverter/plugin/__init__.py +1 -0
- disqover/plugins/examples/inverter/plugin/callbacks.py +81 -0
- disqover/plugins/examples/inverter/plugin/inverter.py +5 -0
- disqover/plugins/examples/mock_instance_search/plugin/__init__.py +0 -0
- disqover/plugins/examples/mock_instance_search/plugin/callbacks.py +42 -0
- disqover/plugins/examples/numerical_aggregator/plugin/__init__.py +1 -0
- disqover/plugins/examples/numerical_aggregator/plugin/arrays.py +59 -0
- disqover/plugins/examples/numerical_aggregator/plugin/callbacks.py +294 -0
- disqover/plugins/examples/numerical_aggregator/plugin/evaluate.py +35 -0
- disqover/plugins/examples/numerical_aggregator/plugin/operations.py +48 -0
- disqover/plugins/examples/numerical_aggregator/plugin/parse.py +48 -0
- disqover/plugins/examples/numerical_aggregator/plugin/requirements.txt +4 -0
- disqover/plugins/examples/numerical_aggregator/plugin/store.py +28 -0
- disqover/plugins/examples/numerical_aggregator/plugin/type_hints.py +16 -0
- disqover/plugins/examples/property_exporter_stream/plugin/__init__.py +0 -0
- disqover/plugins/examples/property_exporter_stream/plugin/callbacks.py +171 -0
- disqover/plugins/examples/run_pipeline/plugin/__init__.py +1 -0
- disqover/plugins/examples/run_pipeline/plugin/callbacks.py +42 -0
- disqover/plugins/examples/s3_exporter/plugin/__init__.py +1 -0
- disqover/plugins/examples/s3_exporter/plugin/callbacks.py +56 -0
- disqover/plugins/examples/s3_exporter/plugin/requirements.txt +1 -0
- disqover/plugins/examples/syncer/plugin/__init__.py +1 -0
- disqover/plugins/examples/syncer/plugin/callbacks.py +80 -0
- disqover/plugins/get_examples.py +14 -0
- disqover/plugins/models.py +106 -0
- disqover/plugins/validate.py +763 -0
- disqover/plugins/validation/__init__.py +0 -0
- disqover/plugins/validation/helpers.py +136 -0
- disqover/plugins/validation/options.py +535 -0
- disqover/rds/__init__.py +9 -0
- disqover/rds/api.py +12 -0
- disqover/rds/models/__init__.py +0 -0
- disqover/rds/models/dataset.py +23 -0
- disqover/rds/models/published_dataset.py +65 -0
- disqover/rds/models/schedule.py +153 -0
- disqover/rds/models/schedule_run_job.py +69 -0
- disqover/rds/models/subscription.py +272 -0
- disqover/resources/__init__.py +3 -0
- disqover/resources/api.py +10 -0
- disqover/resources/models.py +119 -0
- disqover/stored_data/__init__.py +11 -0
- disqover/stored_data/api.py +75 -0
- disqover/stored_data/models/__init__.py +9 -0
- disqover/stored_data/models/alias.py +138 -0
- disqover/stored_data/models/data_and_parameters.py +99 -0
- disqover/stored_data/models/filters.py +285 -0
- disqover/stored_data/models/permissions.py +123 -0
- disqover/stored_data/models/resource.py +598 -0
- disqover/stored_data/models/resource_type.py +234 -0
- disqover/stored_data/models/scope.py +7 -0
- disqover/stored_data/models/user_spec.py +82 -0
- disqover/system/__init__.py +3 -0
- disqover/system/api.py +16 -0
- disqover/system/models.py +68 -0
- disqover/tasks/__init__.py +5 -0
- disqover/tasks/api.py +12 -0
- disqover/tasks/models.py +393 -0
- disqover/type_hints.py +149 -0
- disqover/user_management/__init__.py +19 -0
- disqover/user_management/api.py +45 -0
- disqover/user_management/models/__init__.py +17 -0
- disqover/user_management/models/external.py +86 -0
- disqover/user_management/models/license.py +85 -0
- disqover/user_management/models/m2m.py +108 -0
- disqover/user_management/models/permission.py +92 -0
- disqover/user_management/models/security_event.py +44 -0
- disqover/user_management/models/user.py +273 -0
- disqover/user_management/models/user_group.py +221 -0
- disqover/user_views/__init__.py +4 -0
- disqover/user_views/api.py +13 -0
- disqover/user_views/models.py +96 -0
- disqover/utilities.py +344 -0
- disqover_api-1.23.0.dist-info/METADATA +1818 -0
- disqover_api-1.23.0.dist-info/RECORD +166 -0
- disqover_api-1.23.0.dist-info/WHEEL +4 -0
- disqover_api-1.23.0.dist-info/entry_points.txt +3 -0
- 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,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:]}"
|