adss 1.0__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/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