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 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