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.
@@ -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)
@@ -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"]