adss 0.1__py3-none-any.whl → 1.1__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.
- adss/__init__.py +24 -0
- adss/adss_manager.py +53 -0
- adss/auth.py +121 -0
- adss/client.py +671 -0
- adss/endpoints/__init__.py +14 -0
- adss/endpoints/admin.py +433 -0
- adss/endpoints/images.py +898 -0
- adss/endpoints/metadata.py +216 -0
- adss/endpoints/queries.py +498 -0
- adss/endpoints/users.py +311 -0
- adss/exceptions.py +57 -0
- adss/executors/async_query.py +4 -3
- adss/executors/sync_query.py +9 -3
- adss/models/__init__.py +13 -0
- adss/models/metadata.py +138 -0
- adss/models/query.py +134 -0
- adss/models/user.py +123 -0
- adss/table.py +295 -0
- adss/utils/__init__.py +0 -0
- adss/utils/format_table.py +115 -0
- adss/utils.py +107 -0
- adss-1.1.dist-info/LICENSE +11 -0
- {adss-0.1.dist-info → adss-1.1.dist-info}/METADATA +2 -2
- adss-1.1.dist-info/RECORD +30 -0
- {adss-0.1.dist-info → adss-1.1.dist-info}/WHEEL +1 -1
- adss-0.1.dist-info/RECORD +0 -11
- {adss-0.1.dist-info → adss-1.1.dist-info}/top_level.txt +0 -0
adss/endpoints/admin.py
ADDED
@@ -0,0 +1,433 @@
|
|
1
|
+
"""
|
2
|
+
Administrative functionality for the Astronomy TAP Client.
|
3
|
+
"""
|
4
|
+
import requests
|
5
|
+
from typing import Dict, List, Optional, Any
|
6
|
+
|
7
|
+
from ..exceptions import (
|
8
|
+
AuthenticationError, ResourceNotFoundError, PermissionDeniedError,
|
9
|
+
ValidationError
|
10
|
+
)
|
11
|
+
from ..utils import handle_response_errors, format_permission
|
12
|
+
from ..models.user import User, Role, SchemaPermission, TablePermission, RolePermissions
|
13
|
+
|
14
|
+
|
15
|
+
class AdminEndpoint:
|
16
|
+
"""
|
17
|
+
Handles administrative operations (superuser/staff only).
|
18
|
+
"""
|
19
|
+
|
20
|
+
def __init__(self, base_url: str, auth_manager):
|
21
|
+
"""
|
22
|
+
Initialize the Admin endpoint.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
base_url: The base URL of the API server
|
26
|
+
auth_manager: Authentication manager providing auth headers
|
27
|
+
"""
|
28
|
+
self.base_url = base_url.rstrip('/')
|
29
|
+
self.auth_manager = auth_manager
|
30
|
+
|
31
|
+
def create_role(self, name: str, description: Optional[str] = None, **kwargs) -> Role:
|
32
|
+
"""
|
33
|
+
Create a new role (superuser only).
|
34
|
+
|
35
|
+
Args:
|
36
|
+
name: Role name
|
37
|
+
description: Optional role description
|
38
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
The newly created Role object
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
AuthenticationError: If not authenticated
|
45
|
+
PermissionDeniedError: If not a superuser
|
46
|
+
ValidationError: If input validation fails
|
47
|
+
"""
|
48
|
+
data = {
|
49
|
+
"name": name
|
50
|
+
}
|
51
|
+
|
52
|
+
if description:
|
53
|
+
data["description"] = description
|
54
|
+
|
55
|
+
try:
|
56
|
+
response = self.auth_manager.request(
|
57
|
+
method="POST",
|
58
|
+
url="/adss/v1/users/roles",
|
59
|
+
json=data,
|
60
|
+
auth_required=True,
|
61
|
+
**kwargs
|
62
|
+
)
|
63
|
+
handle_response_errors(response)
|
64
|
+
|
65
|
+
role_data = response.json()
|
66
|
+
return Role.from_dict(role_data)
|
67
|
+
|
68
|
+
except Exception as e:
|
69
|
+
if hasattr(e, 'response'):
|
70
|
+
if e.response.status_code == 401:
|
71
|
+
raise AuthenticationError("Authentication required")
|
72
|
+
elif e.response.status_code == 403:
|
73
|
+
raise PermissionDeniedError("Superuser access required")
|
74
|
+
elif e.response.status_code == 400:
|
75
|
+
try:
|
76
|
+
error_data = e.response.json()
|
77
|
+
raise ValidationError(
|
78
|
+
f"Role creation failed: {error_data.get('detail', str(e))}",
|
79
|
+
error_data.get('errors')
|
80
|
+
)
|
81
|
+
except (ValueError, AttributeError):
|
82
|
+
pass
|
83
|
+
raise
|
84
|
+
|
85
|
+
def get_roles(self, skip: int = 0, limit: int = 100, **kwargs) -> List[Role]:
|
86
|
+
"""
|
87
|
+
Get a list of roles (staff only).
|
88
|
+
|
89
|
+
Args:
|
90
|
+
skip: Number of roles to skip (for pagination)
|
91
|
+
limit: Maximum number of roles to return
|
92
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
List of Role objects
|
96
|
+
|
97
|
+
Raises:
|
98
|
+
AuthenticationError: If not authenticated
|
99
|
+
PermissionDeniedError: If not a staff user
|
100
|
+
"""
|
101
|
+
params = {"skip": skip, "limit": limit}
|
102
|
+
|
103
|
+
try:
|
104
|
+
response = self.auth_manager.request(
|
105
|
+
method="GET",
|
106
|
+
url="/adss/v1/users/roles",
|
107
|
+
params=params,
|
108
|
+
auth_required=True,
|
109
|
+
**kwargs
|
110
|
+
)
|
111
|
+
handle_response_errors(response)
|
112
|
+
|
113
|
+
roles_data = response.json()
|
114
|
+
return [Role.from_dict(role_data) for role_data in roles_data]
|
115
|
+
|
116
|
+
except Exception as e:
|
117
|
+
if hasattr(e, 'response'):
|
118
|
+
if e.response.status_code == 401:
|
119
|
+
raise AuthenticationError("Authentication required")
|
120
|
+
elif e.response.status_code == 403:
|
121
|
+
raise PermissionDeniedError("Staff access required")
|
122
|
+
raise
|
123
|
+
|
124
|
+
def add_user_to_role(self, user_id: str, role_id: int, **kwargs) -> bool:
|
125
|
+
"""
|
126
|
+
Add a user to a role (superuser only).
|
127
|
+
|
128
|
+
Args:
|
129
|
+
user_id: ID of the user
|
130
|
+
role_id: ID of the role
|
131
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
True if the user was successfully added to the role
|
135
|
+
|
136
|
+
Raises:
|
137
|
+
AuthenticationError: If not authenticated
|
138
|
+
PermissionDeniedError: If not a superuser
|
139
|
+
ResourceNotFoundError: If the user or role doesn't exist
|
140
|
+
"""
|
141
|
+
try:
|
142
|
+
response = self.auth_manager.request(
|
143
|
+
method="POST",
|
144
|
+
url=f"/adss/v1/users/{user_id}/roles/{role_id}",
|
145
|
+
auth_required=True,
|
146
|
+
**kwargs
|
147
|
+
)
|
148
|
+
handle_response_errors(response)
|
149
|
+
|
150
|
+
return True
|
151
|
+
|
152
|
+
except Exception as e:
|
153
|
+
if hasattr(e, 'response'):
|
154
|
+
if e.response.status_code == 401:
|
155
|
+
raise AuthenticationError("Authentication required")
|
156
|
+
elif e.response.status_code == 403:
|
157
|
+
raise PermissionDeniedError("Superuser access required")
|
158
|
+
elif e.response.status_code == 404:
|
159
|
+
raise ResourceNotFoundError("User or role not found")
|
160
|
+
raise
|
161
|
+
|
162
|
+
def remove_user_from_role(self, user_id: str, role_id: int, **kwargs) -> bool:
|
163
|
+
"""
|
164
|
+
Remove a user from a role (superuser only).
|
165
|
+
|
166
|
+
Args:
|
167
|
+
user_id: ID of the user
|
168
|
+
role_id: ID of the role
|
169
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
True if the user was successfully removed from the role
|
173
|
+
|
174
|
+
Raises:
|
175
|
+
AuthenticationError: If not authenticated
|
176
|
+
PermissionDeniedError: If not a superuser
|
177
|
+
ResourceNotFoundError: If the user or role doesn't exist
|
178
|
+
"""
|
179
|
+
try:
|
180
|
+
response = self.auth_manager.request(
|
181
|
+
method="DELETE",
|
182
|
+
url=f"/adss/v1/users/{user_id}/roles/{role_id}",
|
183
|
+
auth_required=True,
|
184
|
+
**kwargs
|
185
|
+
)
|
186
|
+
handle_response_errors(response)
|
187
|
+
|
188
|
+
return True
|
189
|
+
|
190
|
+
except Exception as e:
|
191
|
+
if hasattr(e, 'response'):
|
192
|
+
if e.response.status_code == 401:
|
193
|
+
raise AuthenticationError("Authentication required")
|
194
|
+
elif e.response.status_code == 403:
|
195
|
+
raise PermissionDeniedError("Superuser access required")
|
196
|
+
elif e.response.status_code == 404:
|
197
|
+
raise ResourceNotFoundError("User or role not found")
|
198
|
+
raise
|
199
|
+
|
200
|
+
def add_schema_permission(self, role_id: int, schema_name: str, permission: str, **kwargs) -> bool:
|
201
|
+
"""
|
202
|
+
Add a schema permission to a role (superuser only).
|
203
|
+
|
204
|
+
Args:
|
205
|
+
role_id: ID of the role
|
206
|
+
schema_name: Name of the schema
|
207
|
+
permission: Permission type ('read', 'write', or 'all')
|
208
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
True if the permission was successfully added
|
212
|
+
|
213
|
+
Raises:
|
214
|
+
AuthenticationError: If not authenticated
|
215
|
+
PermissionDeniedError: If not a superuser
|
216
|
+
ResourceNotFoundError: If the role doesn't exist
|
217
|
+
ValidationError: If the permission type is invalid
|
218
|
+
"""
|
219
|
+
# Validate and format permission
|
220
|
+
permission = format_permission(permission)
|
221
|
+
|
222
|
+
data = {
|
223
|
+
"schema_name": schema_name,
|
224
|
+
"permission": permission
|
225
|
+
}
|
226
|
+
|
227
|
+
try:
|
228
|
+
response = self.auth_manager.request(
|
229
|
+
method="POST",
|
230
|
+
url=f"/adss/v1/users/roles/{role_id}/schema-permissions",
|
231
|
+
json=data,
|
232
|
+
auth_required=True,
|
233
|
+
**kwargs
|
234
|
+
)
|
235
|
+
handle_response_errors(response)
|
236
|
+
|
237
|
+
return True
|
238
|
+
|
239
|
+
except Exception as e:
|
240
|
+
if hasattr(e, 'response'):
|
241
|
+
if e.response.status_code == 401:
|
242
|
+
raise AuthenticationError("Authentication required")
|
243
|
+
elif e.response.status_code == 403:
|
244
|
+
raise PermissionDeniedError("Superuser access required")
|
245
|
+
elif e.response.status_code == 404:
|
246
|
+
raise ResourceNotFoundError("Role not found")
|
247
|
+
elif e.response.status_code == 400:
|
248
|
+
try:
|
249
|
+
error_data = e.response.json()
|
250
|
+
raise ValidationError(
|
251
|
+
f"Adding schema permission failed: {error_data.get('detail', str(e))}",
|
252
|
+
error_data.get('errors')
|
253
|
+
)
|
254
|
+
except (ValueError, AttributeError):
|
255
|
+
pass
|
256
|
+
raise
|
257
|
+
|
258
|
+
def add_table_permission(self, role_id: int, schema_name: str, table_name: str, permission: str, **kwargs) -> bool:
|
259
|
+
"""
|
260
|
+
Add a table permission to a role (superuser only).
|
261
|
+
|
262
|
+
Args:
|
263
|
+
role_id: ID of the role
|
264
|
+
schema_name: Name of the schema
|
265
|
+
table_name: Name of the table
|
266
|
+
permission: Permission type ('read', 'write', or 'all')
|
267
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
True if the permission was successfully added
|
271
|
+
|
272
|
+
Raises:
|
273
|
+
AuthenticationError: If not authenticated
|
274
|
+
PermissionDeniedError: If not a superuser
|
275
|
+
ResourceNotFoundError: If the role doesn't exist
|
276
|
+
ValidationError: If the permission type is invalid
|
277
|
+
"""
|
278
|
+
# Validate and format permission
|
279
|
+
permission = format_permission(permission)
|
280
|
+
|
281
|
+
data = {
|
282
|
+
"schema_name": schema_name,
|
283
|
+
"table_name": table_name,
|
284
|
+
"permission": permission
|
285
|
+
}
|
286
|
+
|
287
|
+
try:
|
288
|
+
response = self.auth_manager.request(
|
289
|
+
method="POST",
|
290
|
+
url=f"/adss/v1/users/roles/{role_id}/table-permissions",
|
291
|
+
json=data,
|
292
|
+
auth_required=True,
|
293
|
+
**kwargs
|
294
|
+
)
|
295
|
+
handle_response_errors(response)
|
296
|
+
|
297
|
+
return True
|
298
|
+
|
299
|
+
except Exception as e:
|
300
|
+
if hasattr(e, 'response'):
|
301
|
+
if e.response.status_code == 401:
|
302
|
+
raise AuthenticationError("Authentication required")
|
303
|
+
elif e.response.status_code == 403:
|
304
|
+
raise PermissionDeniedError("Superuser access required")
|
305
|
+
elif e.response.status_code == 404:
|
306
|
+
raise ResourceNotFoundError("Role not found")
|
307
|
+
elif e.response.status_code == 400:
|
308
|
+
try:
|
309
|
+
error_data = e.response.json()
|
310
|
+
raise ValidationError(
|
311
|
+
f"Adding table permission failed: {error_data.get('detail', str(e))}",
|
312
|
+
error_data.get('errors')
|
313
|
+
)
|
314
|
+
except (ValueError, AttributeError):
|
315
|
+
pass
|
316
|
+
raise
|
317
|
+
|
318
|
+
def remove_schema_permission(self, role_id: int, schema_name: str, **kwargs) -> bool:
|
319
|
+
"""
|
320
|
+
Remove a schema permission from a role (superuser only).
|
321
|
+
|
322
|
+
Args:
|
323
|
+
role_id: ID of the role
|
324
|
+
schema_name: Name of the schema
|
325
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
326
|
+
|
327
|
+
Returns:
|
328
|
+
True if the permission was successfully removed
|
329
|
+
|
330
|
+
Raises:
|
331
|
+
AuthenticationError: If not authenticated
|
332
|
+
PermissionDeniedError: If not a superuser
|
333
|
+
ResourceNotFoundError: If the role doesn't exist
|
334
|
+
"""
|
335
|
+
try:
|
336
|
+
response = self.auth_manager.request(
|
337
|
+
method="DELETE",
|
338
|
+
url=f"/adss/v1/users/roles/{role_id}/schema-permissions/{schema_name}",
|
339
|
+
auth_required=True,
|
340
|
+
**kwargs
|
341
|
+
)
|
342
|
+
handle_response_errors(response)
|
343
|
+
|
344
|
+
return True
|
345
|
+
|
346
|
+
except Exception as e:
|
347
|
+
if hasattr(e, 'response'):
|
348
|
+
if e.response.status_code == 401:
|
349
|
+
raise AuthenticationError("Authentication required")
|
350
|
+
elif e.response.status_code == 403:
|
351
|
+
raise PermissionDeniedError("Superuser access required")
|
352
|
+
elif e.response.status_code == 404:
|
353
|
+
raise ResourceNotFoundError("Role or schema permission not found")
|
354
|
+
raise
|
355
|
+
|
356
|
+
def remove_table_permission(self, role_id: int, schema_name: str, table_name: str, **kwargs) -> bool:
|
357
|
+
"""
|
358
|
+
Remove a table permission from a role (superuser only).
|
359
|
+
|
360
|
+
Args:
|
361
|
+
role_id: ID of the role
|
362
|
+
schema_name: Name of the schema
|
363
|
+
table_name: Name of the table
|
364
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
True if the permission was successfully removed
|
368
|
+
|
369
|
+
Raises:
|
370
|
+
AuthenticationError: If not authenticated
|
371
|
+
PermissionDeniedError: If not a superuser
|
372
|
+
ResourceNotFoundError: If the role doesn't exist
|
373
|
+
"""
|
374
|
+
try:
|
375
|
+
response = self.auth_manager.request(
|
376
|
+
method="DELETE",
|
377
|
+
url=f"/adss/v1/users/roles/{role_id}/table-permissions/{schema_name}/{table_name}",
|
378
|
+
auth_required=True,
|
379
|
+
**kwargs
|
380
|
+
)
|
381
|
+
handle_response_errors(response)
|
382
|
+
|
383
|
+
return True
|
384
|
+
|
385
|
+
except Exception as e:
|
386
|
+
if hasattr(e, 'response'):
|
387
|
+
if e.response.status_code == 401:
|
388
|
+
raise AuthenticationError("Authentication required")
|
389
|
+
elif e.response.status_code == 403:
|
390
|
+
raise PermissionDeniedError("Superuser access required")
|
391
|
+
elif e.response.status_code == 404:
|
392
|
+
raise ResourceNotFoundError("Role or table permission not found")
|
393
|
+
raise
|
394
|
+
|
395
|
+
def get_role_permissions(self, role_id: int, **kwargs) -> RolePermissions:
|
396
|
+
"""
|
397
|
+
Get permissions for a role (staff only).
|
398
|
+
|
399
|
+
Args:
|
400
|
+
role_id: ID of the role
|
401
|
+
**kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
|
402
|
+
|
403
|
+
Returns:
|
404
|
+
RolePermissions object containing schema and table permissions
|
405
|
+
|
406
|
+
Raises:
|
407
|
+
AuthenticationError: If not authenticated
|
408
|
+
PermissionDeniedError: If not a staff user
|
409
|
+
ResourceNotFoundError: If the role doesn't exist
|
410
|
+
"""
|
411
|
+
try:
|
412
|
+
response = self.auth_manager.request(
|
413
|
+
method="GET",
|
414
|
+
url=f"/adss/v1/users/roles/{role_id}/permissions",
|
415
|
+
auth_required=True,
|
416
|
+
**kwargs
|
417
|
+
)
|
418
|
+
handle_response_errors(response)
|
419
|
+
|
420
|
+
role_data = response.json()
|
421
|
+
permissions_data = role_data.get("permissions", {})
|
422
|
+
|
423
|
+
return RolePermissions.from_dict(permissions_data)
|
424
|
+
|
425
|
+
except Exception as e:
|
426
|
+
if hasattr(e, 'response'):
|
427
|
+
if e.response.status_code == 401:
|
428
|
+
raise AuthenticationError("Authentication required")
|
429
|
+
elif e.response.status_code == 403:
|
430
|
+
raise PermissionDeniedError("Staff access required")
|
431
|
+
elif e.response.status_code == 404:
|
432
|
+
raise ResourceNotFoundError("Role not found")
|
433
|
+
raise
|