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/__init__.py
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
"""
|
2
|
+
Astronomy TAP Client - A Python client for interacting with the Astronomy TAP Service API.
|
3
|
+
|
4
|
+
This package provides a comprehensive client for working with the Astronomy TAP Service,
|
5
|
+
including authentication, query execution, user management, and administrative functions.
|
6
|
+
"""
|
7
|
+
|
8
|
+
__version__ = "0.1.0"
|
9
|
+
|
10
|
+
from .client import ADSSClient
|
11
|
+
from .exceptions import (
|
12
|
+
ADSSClientError, AuthenticationError, PermissionDeniedError,
|
13
|
+
ResourceNotFoundError, QueryExecutionError
|
14
|
+
)
|
15
|
+
from .models.user import User, Role
|
16
|
+
from .models.query import Query, QueryResult
|
17
|
+
from .models.metadata import Schema, Table, Column
|
18
|
+
|
19
|
+
__all__ = [
|
20
|
+
'ADSSClient',
|
21
|
+
'ADSSClientError', 'AuthenticationError', 'PermissionDeniedError',
|
22
|
+
'ResourceNotFoundError', 'QueryExecutionError',
|
23
|
+
'User', 'Role', 'Query', 'QueryResult', 'Schema', 'Table', 'Column'
|
24
|
+
]
|
adss/adss_manager.py
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
import requests
|
2
|
+
import pprint
|
3
|
+
import os
|
4
|
+
|
5
|
+
import xml.etree.ElementTree as ET
|
6
|
+
|
7
|
+
from adss.table import Table
|
8
|
+
from adss.variables import BASEURL
|
9
|
+
|
10
|
+
class ADSSManager:
|
11
|
+
def __init__(self):
|
12
|
+
self.tables = {}
|
13
|
+
|
14
|
+
def load_tables(self):
|
15
|
+
res = requests.get(os.path.join(BASEURL, 'tables'))
|
16
|
+
|
17
|
+
# Parse the XML
|
18
|
+
root = ET.fromstring(res.content)
|
19
|
+
|
20
|
+
# In this XML, the root element is in the "vosi" namespace but the child elements (schema, table, etc.)
|
21
|
+
# are unqualified (i.e. have no prefix). Thus, we can search for them without a namespace.
|
22
|
+
tables = []
|
23
|
+
for schema_elem in root.findall('schema'):
|
24
|
+
schema_name = schema_elem.find('name').text if schema_elem.find('name') is not None else None
|
25
|
+
|
26
|
+
for table_elem in schema_elem.findall('table'):
|
27
|
+
table_name = table_elem.find('name').text if table_elem.find('name') is not None else None
|
28
|
+
columns = []
|
29
|
+
for col_elem in table_elem.findall('column'):
|
30
|
+
col_name = col_elem.find('name').text if col_elem.find('name') is not None else None
|
31
|
+
#dataType_elem = col_elem.find('dataType')
|
32
|
+
#data_type = dataType_elem.text if dataType_elem is not None else None
|
33
|
+
# The xsi:type attribute is in the XML namespace for xsi
|
34
|
+
#xsi_type = dataType_elem.get('{http://www.w3.org/2001/XMLSchema-instance}type') if dataType_elem is not None else None
|
35
|
+
columns.append(col_name)
|
36
|
+
|
37
|
+
if schema_name == "public":
|
38
|
+
name = table_name
|
39
|
+
else:
|
40
|
+
name = f"{schema_name}.{table_name}"
|
41
|
+
|
42
|
+
tables.append(Table(name, columns))
|
43
|
+
|
44
|
+
self.tables = tables
|
45
|
+
|
46
|
+
def print_tables(self):
|
47
|
+
pprint.pprint(self.tables)
|
48
|
+
|
49
|
+
def get_table(self, name):
|
50
|
+
for table in self.tables:
|
51
|
+
if name in table.name:
|
52
|
+
return table
|
53
|
+
return None
|
adss/auth.py
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
import requests
|
2
|
+
from typing import Dict, Optional, Tuple
|
3
|
+
|
4
|
+
from .exceptions import AuthenticationError
|
5
|
+
from .utils import handle_response_errors
|
6
|
+
from .models.user import User
|
7
|
+
|
8
|
+
|
9
|
+
class Auth:
|
10
|
+
"""
|
11
|
+
Handles authentication, token management, and HTTP requests for the TAP client.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, base_url: str, verify_ssl: bool = True):
|
15
|
+
self.base_url = base_url.rstrip('/')
|
16
|
+
self.token: Optional[str] = None
|
17
|
+
self.current_user: Optional[User] = None
|
18
|
+
self.verify_ssl = verify_ssl
|
19
|
+
|
20
|
+
def login(self, username: str, password: str, **kwargs) -> Tuple[str, User]:
|
21
|
+
"""
|
22
|
+
Log in with username and password, obtaining an authentication token.
|
23
|
+
"""
|
24
|
+
login_url = f"{self.base_url}/adss/v1/auth/login"
|
25
|
+
data = {"username": username, "password": password}
|
26
|
+
|
27
|
+
try:
|
28
|
+
# Use our own request() method here
|
29
|
+
response = self.request(
|
30
|
+
method="POST",
|
31
|
+
url=login_url,
|
32
|
+
auth_required=False,
|
33
|
+
data=data,
|
34
|
+
**kwargs
|
35
|
+
)
|
36
|
+
handle_response_errors(response)
|
37
|
+
|
38
|
+
token_data = response.json()
|
39
|
+
self.token = token_data.get("access_token")
|
40
|
+
if not self.token:
|
41
|
+
raise AuthenticationError("Login succeeded but no token returned")
|
42
|
+
|
43
|
+
# Now fetch user info (this will use auth_required=True internally)
|
44
|
+
self.current_user = self._get_current_user(**kwargs)
|
45
|
+
return self.token, self.current_user
|
46
|
+
|
47
|
+
except requests.RequestException as e:
|
48
|
+
raise AuthenticationError(f"Login failed: {e}")
|
49
|
+
|
50
|
+
def logout(self) -> None:
|
51
|
+
self.token = None
|
52
|
+
self.current_user = None
|
53
|
+
|
54
|
+
def is_authenticated(self) -> bool:
|
55
|
+
return self.token is not None
|
56
|
+
|
57
|
+
def _get_current_user(self, **kwargs) -> User:
|
58
|
+
"""
|
59
|
+
Fetch the current user's information using the stored token.
|
60
|
+
"""
|
61
|
+
if not self.token:
|
62
|
+
raise AuthenticationError("Not authenticated")
|
63
|
+
|
64
|
+
me_url = f"{self.base_url}/adss/v1/users/me"
|
65
|
+
auth_headers = self._get_auth_headers()
|
66
|
+
|
67
|
+
try:
|
68
|
+
# Again, use request() so SSL and auth headers are applied consistently
|
69
|
+
response = self.request(
|
70
|
+
method="GET",
|
71
|
+
url=me_url,
|
72
|
+
headers=auth_headers,
|
73
|
+
auth_required=True,
|
74
|
+
**kwargs
|
75
|
+
)
|
76
|
+
handle_response_errors(response)
|
77
|
+
|
78
|
+
user_data = response.json()
|
79
|
+
return User.from_dict(user_data)
|
80
|
+
|
81
|
+
except requests.RequestException as e:
|
82
|
+
raise AuthenticationError(f"Failed to get user info: {e}")
|
83
|
+
|
84
|
+
def _get_auth_headers(self) -> Dict[str, str]:
|
85
|
+
headers = {"Accept": "application/json"}
|
86
|
+
if self.token:
|
87
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
88
|
+
return headers
|
89
|
+
|
90
|
+
def request(
|
91
|
+
self,
|
92
|
+
method: str,
|
93
|
+
url: str,
|
94
|
+
headers: Optional[Dict[str, str]] = None,
|
95
|
+
auth_required: bool = False,
|
96
|
+
**kwargs
|
97
|
+
) -> requests.Response:
|
98
|
+
"""
|
99
|
+
Make an HTTP request with automatic base_url prefix, SSL config, and auth headers.
|
100
|
+
"""
|
101
|
+
if auth_required and not self.is_authenticated():
|
102
|
+
raise AuthenticationError("Authentication required for this request")
|
103
|
+
|
104
|
+
# Prepend base_url if needed
|
105
|
+
if not url.startswith(('http://', 'https://')):
|
106
|
+
url = f"{self.base_url}/{url.lstrip('/')}"
|
107
|
+
|
108
|
+
# Merge headers
|
109
|
+
final_headers = self._get_auth_headers()
|
110
|
+
if headers:
|
111
|
+
final_headers.update(headers)
|
112
|
+
|
113
|
+
# Apply verify_ssl unless overridden
|
114
|
+
if 'verify' not in kwargs:
|
115
|
+
kwargs['verify'] = self.verify_ssl
|
116
|
+
|
117
|
+
return requests.request(method, url, headers=final_headers, **kwargs)
|
118
|
+
|
119
|
+
def refresh_user_info(self, **kwargs) -> User:
|
120
|
+
self.current_user = self._get_current_user(**kwargs)
|
121
|
+
return self.current_user
|