dataspace-sdk 0.1.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.
- dataspace_sdk/__init__.py +18 -0
- dataspace_sdk/auth.py +139 -0
- dataspace_sdk/base.py +148 -0
- dataspace_sdk/client.py +133 -0
- dataspace_sdk/exceptions.py +29 -0
- dataspace_sdk/resources/__init__.py +7 -0
- dataspace_sdk/resources/aimodels.py +246 -0
- dataspace_sdk/resources/datasets.py +228 -0
- dataspace_sdk/resources/usecases.py +243 -0
- dataspace_sdk-0.1.0.dist-info/METADATA +472 -0
- dataspace_sdk-0.1.0.dist-info/RECORD +13 -0
- dataspace_sdk-0.1.0.dist-info/WHEEL +5 -0
- dataspace_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""DataSpace Python SDK for programmatic access to DataSpace resources."""
|
|
2
|
+
|
|
3
|
+
from dataspace_sdk.client import DataSpaceClient
|
|
4
|
+
from dataspace_sdk.exceptions import (
|
|
5
|
+
DataSpaceAPIError,
|
|
6
|
+
DataSpaceAuthError,
|
|
7
|
+
DataSpaceNotFoundError,
|
|
8
|
+
DataSpaceValidationError,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DataSpaceClient",
|
|
14
|
+
"DataSpaceAPIError",
|
|
15
|
+
"DataSpaceAuthError",
|
|
16
|
+
"DataSpaceNotFoundError",
|
|
17
|
+
"DataSpaceValidationError",
|
|
18
|
+
]
|
dataspace_sdk/auth.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Authentication module for DataSpace SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from dataspace_sdk.exceptions import DataSpaceAuthError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AuthClient:
|
|
11
|
+
"""Handles authentication with DataSpace API."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, base_url: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the authentication client.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
base_url: Base URL of the DataSpace API
|
|
19
|
+
"""
|
|
20
|
+
self.base_url = base_url.rstrip("/")
|
|
21
|
+
self.access_token: Optional[str] = None
|
|
22
|
+
self.refresh_token: Optional[str] = None
|
|
23
|
+
self.user_info: Optional[Dict] = None
|
|
24
|
+
|
|
25
|
+
def login_with_keycloak(self, keycloak_token: str) -> Dict:
|
|
26
|
+
"""
|
|
27
|
+
Login using a Keycloak token.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
keycloak_token: Valid Keycloak access token
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dictionary containing user info and tokens
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
DataSpaceAuthError: If authentication fails
|
|
37
|
+
"""
|
|
38
|
+
url = f"{self.base_url}/api/auth/keycloak/login/"
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = requests.post(
|
|
42
|
+
url,
|
|
43
|
+
json={"token": keycloak_token},
|
|
44
|
+
headers={"Content-Type": "application/json"},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if response.status_code == 200:
|
|
48
|
+
data = response.json()
|
|
49
|
+
self.access_token = data.get("access")
|
|
50
|
+
self.refresh_token = data.get("refresh")
|
|
51
|
+
self.user_info = data.get("user")
|
|
52
|
+
return data
|
|
53
|
+
else:
|
|
54
|
+
error_msg = response.json().get("error", "Authentication failed")
|
|
55
|
+
raise DataSpaceAuthError(
|
|
56
|
+
error_msg,
|
|
57
|
+
status_code=response.status_code,
|
|
58
|
+
response=response.json(),
|
|
59
|
+
)
|
|
60
|
+
except requests.RequestException as e:
|
|
61
|
+
raise DataSpaceAuthError(f"Network error during authentication: {str(e)}")
|
|
62
|
+
|
|
63
|
+
def refresh_access_token(self) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Refresh the access token using the refresh token.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
New access token
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
DataSpaceAuthError: If token refresh fails
|
|
72
|
+
"""
|
|
73
|
+
if not self.refresh_token:
|
|
74
|
+
raise DataSpaceAuthError("No refresh token available")
|
|
75
|
+
|
|
76
|
+
url = f"{self.base_url}/api/auth/token/refresh/"
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
response = requests.post(
|
|
80
|
+
url,
|
|
81
|
+
json={"refresh": self.refresh_token},
|
|
82
|
+
headers={"Content-Type": "application/json"},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if response.status_code == 200:
|
|
86
|
+
data = response.json()
|
|
87
|
+
self.access_token = data.get("access")
|
|
88
|
+
return self.access_token
|
|
89
|
+
else:
|
|
90
|
+
raise DataSpaceAuthError(
|
|
91
|
+
"Token refresh failed",
|
|
92
|
+
status_code=response.status_code,
|
|
93
|
+
response=response.json(),
|
|
94
|
+
)
|
|
95
|
+
except requests.RequestException as e:
|
|
96
|
+
raise DataSpaceAuthError(f"Network error during token refresh: {str(e)}")
|
|
97
|
+
|
|
98
|
+
def get_user_info(self) -> Dict:
|
|
99
|
+
"""
|
|
100
|
+
Get current user information.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dictionary containing user information
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
DataSpaceAuthError: If request fails
|
|
107
|
+
"""
|
|
108
|
+
if not self.access_token:
|
|
109
|
+
raise DataSpaceAuthError("Not authenticated. Please login first.")
|
|
110
|
+
|
|
111
|
+
url = f"{self.base_url}/api/auth/user/info/"
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
response = requests.get(
|
|
115
|
+
url,
|
|
116
|
+
headers=self._get_auth_headers(),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if response.status_code == 200:
|
|
120
|
+
self.user_info = response.json()
|
|
121
|
+
return self.user_info
|
|
122
|
+
else:
|
|
123
|
+
raise DataSpaceAuthError(
|
|
124
|
+
"Failed to get user info",
|
|
125
|
+
status_code=response.status_code,
|
|
126
|
+
response=response.json(),
|
|
127
|
+
)
|
|
128
|
+
except requests.RequestException as e:
|
|
129
|
+
raise DataSpaceAuthError(f"Network error getting user info: {str(e)}")
|
|
130
|
+
|
|
131
|
+
def _get_auth_headers(self) -> Dict[str, str]:
|
|
132
|
+
"""Get headers with authentication token."""
|
|
133
|
+
if not self.access_token:
|
|
134
|
+
return {}
|
|
135
|
+
return {"Authorization": f"Bearer {self.access_token}"}
|
|
136
|
+
|
|
137
|
+
def is_authenticated(self) -> bool:
|
|
138
|
+
"""Check if the client is authenticated."""
|
|
139
|
+
return self.access_token is not None
|
dataspace_sdk/base.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Base client for making API requests."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from dataspace_sdk.exceptions import (
|
|
8
|
+
DataSpaceAPIError,
|
|
9
|
+
DataSpaceAuthError,
|
|
10
|
+
DataSpaceNotFoundError,
|
|
11
|
+
DataSpaceValidationError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseAPIClient:
|
|
16
|
+
"""Base client for making API requests to DataSpace."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, base_url: str, auth_client=None):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the base API client.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
base_url: Base URL of the DataSpace API
|
|
24
|
+
auth_client: Authentication client instance
|
|
25
|
+
"""
|
|
26
|
+
self.base_url = base_url.rstrip("/")
|
|
27
|
+
self.auth_client = auth_client
|
|
28
|
+
|
|
29
|
+
def _get_headers(self, additional_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
30
|
+
"""
|
|
31
|
+
Get request headers including authentication.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
additional_headers: Additional headers to include
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dictionary of headers
|
|
38
|
+
"""
|
|
39
|
+
headers = {"Content-Type": "application/json"}
|
|
40
|
+
|
|
41
|
+
if self.auth_client and self.auth_client.is_authenticated():
|
|
42
|
+
headers["Authorization"] = f"Bearer {self.auth_client.access_token}"
|
|
43
|
+
|
|
44
|
+
if additional_headers:
|
|
45
|
+
headers.update(additional_headers)
|
|
46
|
+
|
|
47
|
+
return headers
|
|
48
|
+
|
|
49
|
+
def _make_request(
|
|
50
|
+
self,
|
|
51
|
+
method: str,
|
|
52
|
+
endpoint: str,
|
|
53
|
+
params: Optional[Dict[str, Any]] = None,
|
|
54
|
+
data: Optional[Dict[str, Any]] = None,
|
|
55
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
56
|
+
headers: Optional[Dict[str, str]] = None,
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Make an HTTP request to the API.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
method: HTTP method (GET, POST, etc.)
|
|
63
|
+
endpoint: API endpoint
|
|
64
|
+
params: Query parameters
|
|
65
|
+
data: Form data
|
|
66
|
+
json_data: JSON data
|
|
67
|
+
headers: Additional headers
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Response data as dictionary
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
DataSpaceAPIError: For API errors
|
|
74
|
+
DataSpaceAuthError: For authentication errors
|
|
75
|
+
DataSpaceNotFoundError: For 404 errors
|
|
76
|
+
DataSpaceValidationError: For validation errors
|
|
77
|
+
"""
|
|
78
|
+
url = f"{self.base_url}{endpoint}"
|
|
79
|
+
request_headers = self._get_headers(headers)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
response = requests.request(
|
|
83
|
+
method=method,
|
|
84
|
+
url=url,
|
|
85
|
+
params=params,
|
|
86
|
+
data=data,
|
|
87
|
+
json=json_data,
|
|
88
|
+
headers=request_headers,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Handle different status codes
|
|
92
|
+
if response.status_code == 200 or response.status_code == 201:
|
|
93
|
+
return response.json() if response.content else {}
|
|
94
|
+
elif response.status_code == 204:
|
|
95
|
+
return {}
|
|
96
|
+
elif response.status_code == 401:
|
|
97
|
+
raise DataSpaceAuthError(
|
|
98
|
+
"Authentication required or token expired",
|
|
99
|
+
status_code=response.status_code,
|
|
100
|
+
response=response.json() if response.content else {},
|
|
101
|
+
)
|
|
102
|
+
elif response.status_code == 404:
|
|
103
|
+
raise DataSpaceNotFoundError(
|
|
104
|
+
"Resource not found",
|
|
105
|
+
status_code=response.status_code,
|
|
106
|
+
response=response.json() if response.content else {},
|
|
107
|
+
)
|
|
108
|
+
elif response.status_code == 400:
|
|
109
|
+
raise DataSpaceValidationError(
|
|
110
|
+
"Validation error",
|
|
111
|
+
status_code=response.status_code,
|
|
112
|
+
response=response.json() if response.content else {},
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
raise DataSpaceAPIError(
|
|
116
|
+
f"API request failed with status {response.status_code}",
|
|
117
|
+
status_code=response.status_code,
|
|
118
|
+
response=response.json() if response.content else {},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
except requests.RequestException as e:
|
|
122
|
+
raise DataSpaceAPIError(f"Network error: {str(e)}")
|
|
123
|
+
|
|
124
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
125
|
+
"""Make a GET request."""
|
|
126
|
+
return self._make_request("GET", endpoint, params=params)
|
|
127
|
+
|
|
128
|
+
def post(
|
|
129
|
+
self,
|
|
130
|
+
endpoint: str,
|
|
131
|
+
data: Optional[Dict[str, Any]] = None,
|
|
132
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""Make a POST request."""
|
|
135
|
+
return self._make_request("POST", endpoint, data=data, json_data=json_data)
|
|
136
|
+
|
|
137
|
+
def put(
|
|
138
|
+
self,
|
|
139
|
+
endpoint: str,
|
|
140
|
+
data: Optional[Dict[str, Any]] = None,
|
|
141
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
"""Make a PUT request."""
|
|
144
|
+
return self._make_request("PUT", endpoint, data=data, json_data=json_data)
|
|
145
|
+
|
|
146
|
+
def delete(self, endpoint: str) -> Dict[str, Any]:
|
|
147
|
+
"""Make a DELETE request."""
|
|
148
|
+
return self._make_request("DELETE", endpoint)
|
dataspace_sdk/client.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Main DataSpace SDK client."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from dataspace_sdk.auth import AuthClient
|
|
6
|
+
from dataspace_sdk.resources.aimodels import AIModelClient
|
|
7
|
+
from dataspace_sdk.resources.datasets import DatasetClient
|
|
8
|
+
from dataspace_sdk.resources.usecases import UseCaseClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DataSpaceClient:
|
|
12
|
+
"""
|
|
13
|
+
Main client for interacting with DataSpace API.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> from dataspace_sdk import DataSpaceClient
|
|
17
|
+
>>>
|
|
18
|
+
>>> # Initialize client
|
|
19
|
+
>>> client = DataSpaceClient(base_url="https://api.dataspace.example.com")
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Login with Keycloak token
|
|
22
|
+
>>> client.login(keycloak_token="your_keycloak_token")
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Search for datasets
|
|
25
|
+
>>> datasets = client.datasets.search(query="health", tags=["public-health"])
|
|
26
|
+
>>>
|
|
27
|
+
>>> # Get a specific dataset
|
|
28
|
+
>>> dataset = client.datasets.get_by_id("dataset-uuid")
|
|
29
|
+
>>>
|
|
30
|
+
>>> # Get organization's resources
|
|
31
|
+
>>> org_datasets = client.datasets.get_organization_datasets("org-uuid")
|
|
32
|
+
>>> org_models = client.aimodels.get_organization_models("org-uuid")
|
|
33
|
+
>>> org_usecases = client.usecases.get_organization_usecases("org-uuid")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, base_url: str):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the DataSpace client.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
base_url: Base URL of the DataSpace API (e.g., "https://api.dataspace.example.com")
|
|
42
|
+
"""
|
|
43
|
+
self.base_url = base_url.rstrip("/")
|
|
44
|
+
self._auth = AuthClient(self.base_url)
|
|
45
|
+
|
|
46
|
+
# Initialize resource clients
|
|
47
|
+
self.datasets = DatasetClient(self.base_url, self._auth)
|
|
48
|
+
self.aimodels = AIModelClient(self.base_url, self._auth)
|
|
49
|
+
self.usecases = UseCaseClient(self.base_url, self._auth)
|
|
50
|
+
|
|
51
|
+
def login(self, keycloak_token: str) -> dict:
|
|
52
|
+
"""
|
|
53
|
+
Login using a Keycloak token.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
keycloak_token: Valid Keycloak access token
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary containing user info and tokens
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
DataSpaceAuthError: If authentication fails
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> client = DataSpaceClient(base_url="https://api.dataspace.example.com")
|
|
66
|
+
>>> user_info = client.login(keycloak_token="your_token")
|
|
67
|
+
>>> print(user_info["user"]["username"])
|
|
68
|
+
"""
|
|
69
|
+
return self._auth.login_with_keycloak(keycloak_token)
|
|
70
|
+
|
|
71
|
+
def refresh_token(self) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Refresh the access token using the refresh token.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
New access token
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
DataSpaceAuthError: If token refresh fails
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> client.refresh_token()
|
|
83
|
+
"""
|
|
84
|
+
return self._auth.refresh_access_token()
|
|
85
|
+
|
|
86
|
+
def get_user_info(self) -> dict:
|
|
87
|
+
"""
|
|
88
|
+
Get current user information.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary containing user information including organizations
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
DataSpaceAuthError: If not authenticated or request fails
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
>>> user_info = client.get_user_info()
|
|
98
|
+
>>> print(user_info["organizations"])
|
|
99
|
+
"""
|
|
100
|
+
return self._auth.get_user_info()
|
|
101
|
+
|
|
102
|
+
def is_authenticated(self) -> bool:
|
|
103
|
+
"""
|
|
104
|
+
Check if the client is authenticated.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
True if authenticated, False otherwise
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> if client.is_authenticated():
|
|
111
|
+
... datasets = client.datasets.search()
|
|
112
|
+
"""
|
|
113
|
+
return self._auth.is_authenticated()
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def user(self) -> Optional[dict]:
|
|
117
|
+
"""
|
|
118
|
+
Get cached user information.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
User information dictionary or None if not authenticated
|
|
122
|
+
"""
|
|
123
|
+
return self._auth.user_info
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def access_token(self) -> Optional[str]:
|
|
127
|
+
"""
|
|
128
|
+
Get the current access token.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Access token string or None if not authenticated
|
|
132
|
+
"""
|
|
133
|
+
return self._auth.access_token
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Custom exceptions for DataSpace SDK."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DataSpaceAPIError(Exception):
|
|
5
|
+
"""Base exception for DataSpace API errors."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, message: str, status_code: int = None, response: dict = None):
|
|
8
|
+
self.message = message
|
|
9
|
+
self.status_code = status_code
|
|
10
|
+
self.response = response
|
|
11
|
+
super().__init__(self.message)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataSpaceAuthError(DataSpaceAPIError):
|
|
15
|
+
"""Exception raised for authentication errors."""
|
|
16
|
+
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DataSpaceNotFoundError(DataSpaceAPIError):
|
|
21
|
+
"""Exception raised when a resource is not found."""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DataSpaceValidationError(DataSpaceAPIError):
|
|
27
|
+
"""Exception raised for validation errors."""
|
|
28
|
+
|
|
29
|
+
pass
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Resource clients for DataSpace SDK."""
|
|
2
|
+
|
|
3
|
+
from dataspace_sdk.resources.aimodels import AIModelClient
|
|
4
|
+
from dataspace_sdk.resources.datasets import DatasetClient
|
|
5
|
+
from dataspace_sdk.resources.usecases import UseCaseClient
|
|
6
|
+
|
|
7
|
+
__all__ = ["DatasetClient", "AIModelClient", "UseCaseClient"]
|