adss 1.0__py3-none-any.whl → 1.2__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/models/user.py ADDED
@@ -0,0 +1,123 @@
1
+ """
2
+ User-related data models for the Astronomy TAP Client.
3
+ """
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Dict, Optional, Any
6
+ from datetime import datetime
7
+
8
+ from adss.utils import parse_datetime
9
+
10
+
11
+ @dataclass
12
+ class SchemaPermission:
13
+ """Schema-level permission."""
14
+ schema_name: str
15
+ permission: str # 'read', 'write', or 'all'
16
+
17
+
18
+ @dataclass
19
+ class TablePermission:
20
+ """Table-level permission."""
21
+ schema_name: str
22
+ table_name: str
23
+ permission: str # 'read', 'write', or 'all'
24
+
25
+
26
+ @dataclass
27
+ class RolePermissions:
28
+ """Permissions associated with a role."""
29
+ schema_permissions: List[SchemaPermission] = field(default_factory=list)
30
+ table_permissions: List[TablePermission] = field(default_factory=list)
31
+
32
+ @classmethod
33
+ def from_dict(cls, data: Dict[str, Any]) -> 'RolePermissions':
34
+ """Create a RolePermissions object from a dictionary."""
35
+ schema_perms = [
36
+ SchemaPermission(**p)
37
+ for p in data.get('schema_permissions', [])
38
+ ]
39
+
40
+ table_perms = [
41
+ TablePermission(**p)
42
+ for p in data.get('table_permissions', [])
43
+ ]
44
+
45
+ return cls(
46
+ schema_permissions=schema_perms,
47
+ table_permissions=table_perms
48
+ )
49
+
50
+
51
+ @dataclass
52
+ class Role:
53
+ """User role with associated permissions."""
54
+ id: int
55
+ name: str
56
+ description: Optional[str] = None
57
+ permissions: Optional[RolePermissions] = None
58
+
59
+ @classmethod
60
+ def from_dict(cls, data: Dict[str, Any]) -> 'Role':
61
+ """Create a Role object from a dictionary."""
62
+ role_id = data.get('id')
63
+ name = data.get('name')
64
+ description = data.get('description')
65
+
66
+ permissions_data = data.get('permissions')
67
+ permissions = None
68
+ if permissions_data:
69
+ permissions = RolePermissions.from_dict(permissions_data)
70
+
71
+ return cls(
72
+ id=role_id,
73
+ name=name,
74
+ description=description,
75
+ permissions=permissions
76
+ )
77
+
78
+
79
+ @dataclass
80
+ class User:
81
+ """User model with authentication and role information."""
82
+ id: str
83
+ username: str
84
+ email: str
85
+ full_name: Optional[str] = None
86
+ is_active: bool = True
87
+ is_staff: bool = False
88
+ is_superuser: bool = False
89
+ created_at: Optional[datetime] = None
90
+ last_login: Optional[datetime] = None
91
+ roles: List[Role] = field(default_factory=list)
92
+
93
+ @classmethod
94
+ def from_dict(cls, data: Dict[str, Any]) -> 'User':
95
+ """Create a User object from a dictionary."""
96
+ user_id = data.get('id')
97
+ username = data.get('username')
98
+ email = data.get('email')
99
+ full_name = data.get('full_name')
100
+ is_active = data.get('is_active', True)
101
+ is_staff = data.get('is_staff', False)
102
+ is_superuser = data.get('is_superuser', False)
103
+
104
+ created_at = parse_datetime(data.get('created_at'))
105
+ last_login = parse_datetime(data.get('last_login'))
106
+
107
+ roles = [
108
+ Role.from_dict(role_data)
109
+ for role_data in data.get('roles', [])
110
+ ]
111
+
112
+ return cls(
113
+ id=user_id,
114
+ username=username,
115
+ email=email,
116
+ full_name=full_name,
117
+ is_active=is_active,
118
+ is_staff=is_staff,
119
+ is_superuser=is_superuser,
120
+ created_at=created_at,
121
+ last_login=last_login,
122
+ roles=roles
123
+ )
adss/utils.py ADDED
@@ -0,0 +1,107 @@
1
+ """
2
+ Utility functions for the Astronomy TAP Client.
3
+ """
4
+ import json
5
+ import pandas as pd
6
+ import pyarrow.parquet as pq
7
+ import io
8
+ from datetime import datetime
9
+ from typing import Dict, Any, Optional, Union, List
10
+
11
+ from adss.exceptions import (
12
+ ADSSClientError, AuthenticationError, PermissionDeniedError,
13
+ ResourceNotFoundError, QueryExecutionError, ServerError
14
+ )
15
+
16
+
17
+ def handle_response_errors(response):
18
+ """Handles HTTP response errors and raises appropriate exceptions."""
19
+ if 200 <= response.status_code < 300:
20
+ return response
21
+
22
+ try:
23
+ error_data = response.json()
24
+ error_message = error_data.get('detail', str(error_data))
25
+ except Exception:
26
+ error_message = response.text or f"HTTP Error {response.status_code}"
27
+
28
+ # For 401 errors, check if it's an anonymous query attempt
29
+ if response.status_code == 401:
30
+ # If the error mentions "authentication required for protected schemas"
31
+ # or similar, it's likely an anonymous query trying to access restricted data
32
+ if "protected" in error_message.lower() or "requires authentication" in error_message.lower():
33
+ raise PermissionDeniedError(f"This query requires authentication: {error_message}", response)
34
+ else:
35
+ raise AuthenticationError(error_message, response)
36
+ elif response.status_code == 403:
37
+ raise PermissionDeniedError(error_message, response)
38
+ elif response.status_code == 404:
39
+ raise ResourceNotFoundError(error_message, response)
40
+ elif response.status_code >= 500:
41
+ raise ServerError(f"Server error: {error_message}", response)
42
+ else:
43
+ raise ADSSClientError(error_message, response)
44
+
45
+ def parse_datetime(dt_str: Optional[str]) -> Optional[datetime]:
46
+ """
47
+ Parses a datetime string into a datetime object.
48
+ """
49
+ if not dt_str:
50
+ return None
51
+
52
+ try:
53
+ return datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
54
+ except (ValueError, TypeError):
55
+ return None
56
+
57
+
58
+ def parquet_to_dataframe(parquet_data: bytes) -> pd.DataFrame:
59
+ """
60
+ Converts Parquet bytes to a pandas DataFrame.
61
+ """
62
+ try:
63
+ buffer = io.BytesIO(parquet_data)
64
+ table = pq.read_table(buffer)
65
+ return table.to_pandas()
66
+ except Exception as e:
67
+ raise ADSSClientError(f"Failed to convert Parquet data to DataFrame: {str(e)}")
68
+
69
+
70
+ def format_table_name(schema: str, table: str) -> str:
71
+ """
72
+ Formats a schema and table name into a fully qualified table name.
73
+ """
74
+ return f"{schema}.{table}"
75
+
76
+
77
+ def prepare_query_params(params: Dict[str, Any]) -> Dict[str, str]:
78
+ """
79
+ Prepares query parameters for API requests, handling different types.
80
+ """
81
+ processed_params = {}
82
+
83
+ for key, value in params.items():
84
+ if value is None:
85
+ continue
86
+ elif isinstance(value, bool):
87
+ processed_params[key] = str(value).lower()
88
+ elif isinstance(value, (list, dict)):
89
+ processed_params[key] = json.dumps(value)
90
+ else:
91
+ processed_params[key] = str(value)
92
+
93
+ return processed_params
94
+
95
+
96
+ def format_permission(permission_type: str) -> str:
97
+ """
98
+ Validates and formats a permission type (read, write, all).
99
+ """
100
+ valid_permissions = {'read', 'write', 'all'}
101
+ permission = permission_type.lower()
102
+
103
+ if permission not in valid_permissions:
104
+ raise ValueError(f"Invalid permission type: {permission_type}. "
105
+ f"Must be one of: {', '.join(valid_permissions)}")
106
+
107
+ return permission
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: adss
3
- Version: 1.0
3
+ Version: 1.2
4
4
  Summary: Astronomical Data Smart System
5
5
  Home-page: https://github.com/schwarzam/adss
6
6
  Author: Gustavo Schwarz
@@ -0,0 +1,30 @@
1
+ adss/__init__.py,sha256=TqNjMZ7PuNXc-FnOlVKSNHSAp9ZqcLMdHDuRYt47_Y8,834
2
+ adss/adss_manager.py,sha256=vaS6y1IycVW8AjpAQeG58VwjCDLyMBUylJzYSeg6D9o,2068
3
+ adss/auth.py,sha256=M8GAN8pdfmJfsEOqv_EW2ru0uP0H1JtK7IfBDrIZMyg,4025
4
+ adss/client.py,sha256=Ojc6jqZtCBMwRoTGuBiF0SEDkdWs_-xbGwyk03nHkr8,29613
5
+ adss/exceptions.py,sha256=YeN-xRHvlSmwyS8ni2jOEhhgZK9J1jsG11pOedy3Gfg,1482
6
+ adss/table.py,sha256=Ua663njPk2sg8BtQPo1wZ-V09YvnjrEyIb_SmBhdOYY,13383
7
+ adss/utils.py,sha256=0RISndgXnwVy8cLMFa4Mm7CfGqwGdX-X-HZ0NmPDVD0,3558
8
+ adss/variables.py,sha256=kmbwxJBDC97yKakrnBvONRh1FVvSXU4YKqnjExAU2ZA,51
9
+ adss/endpoints/__init__.py,sha256=Pr29901fT8ClCS2GasTjTiBNyn7DfVfxILpYDFsMvPA,488
10
+ adss/endpoints/admin.py,sha256=S6ZrkeA_Lh_LCpF1NHyfMKqjbIiylYXUSV65H_WKg1U,16391
11
+ adss/endpoints/images.py,sha256=ItAiBss_jQvWQWRUvy0c9Cjn1r9lDR8eOPauqOcPcZ8,35777
12
+ adss/endpoints/metadata.py,sha256=RPrRP6Uz6-uPMIcntMgfss9vAd5iN7JXjZbF8SW0EYg,8238
13
+ adss/endpoints/queries.py,sha256=5BONw_IcGORMPNe-5J6BpoFY6z7lKcktEVhqZ9j17_8,17286
14
+ adss/endpoints/users.py,sha256=6Abkl3c3_YKdMYR_JWI-uL9HTHxcjlIOnE29GyN5_QE,10811
15
+ adss/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ adss/executors/async_query.py,sha256=qmSm-Ex8ZJSjn8lmm_F5BufVpDV-w6vOqXie7krCp7k,3977
17
+ adss/executors/sync_query.py,sha256=3e-ALG3GnA906o_oefci5XHNcdnoPWuc67ml-YATMKE,1243
18
+ adss/models/__init__.py,sha256=ADWVaGy4dkpEMH3iS_6EnRSBlEgoM5Vy9zORQr-UG6w,404
19
+ adss/models/metadata.py,sha256=6fdH_0BenVRmeXkkKbsG2B68O-N2FXTTRgxsEhAHRoU,4058
20
+ adss/models/query.py,sha256=Af-iojZb-nO6qj-yMT_PlNM7Hip6EwBfNeaQPMJPNM0,4293
21
+ adss/models/user.py,sha256=5qVT5qOktokmVLkGszPGCTZWv0wC-7aBMvJ8EeBOqdw,3493
22
+ adss/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ adss/operations/cone_search.py,sha256=qfdFA2TGqnzuggz4nep21_y4LgmHP4ZMpVupxn87dB0,706
24
+ adss/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ adss/utils/format_table.py,sha256=UYCQ6Xum3dPHrh0cAh_KCj6vHShAvdHlV0rtIv7J09Q,3695
26
+ adss-1.2.dist-info/LICENSE,sha256=1aYqcyqjrdNXY9hqgZkCWprcoA112oKvdrfPyvMYPTc,1468
27
+ adss-1.2.dist-info/METADATA,sha256=l8AVLDMarCFGUu3oHE3IWCLCBn88wFPxVioKYiIiIoQ,378
28
+ adss-1.2.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
29
+ adss-1.2.dist-info/top_level.txt,sha256=ebD44L3R0PEvEFoRCJ-RjTIsQ9Yjpo2aAYC1BMtueLg,5
30
+ adss-1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (71.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
adss-1.0.dist-info/RECORD DELETED
@@ -1,16 +0,0 @@
1
- adss/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- adss/adss_manager.py,sha256=vaS6y1IycVW8AjpAQeG58VwjCDLyMBUylJzYSeg6D9o,2068
3
- adss/table.py,sha256=Ua663njPk2sg8BtQPo1wZ-V09YvnjrEyIb_SmBhdOYY,13383
4
- adss/variables.py,sha256=kmbwxJBDC97yKakrnBvONRh1FVvSXU4YKqnjExAU2ZA,51
5
- adss/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- adss/executors/async_query.py,sha256=qmSm-Ex8ZJSjn8lmm_F5BufVpDV-w6vOqXie7krCp7k,3977
7
- adss/executors/sync_query.py,sha256=3e-ALG3GnA906o_oefci5XHNcdnoPWuc67ml-YATMKE,1243
8
- adss/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- adss/operations/cone_search.py,sha256=qfdFA2TGqnzuggz4nep21_y4LgmHP4ZMpVupxn87dB0,706
10
- adss/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- adss/utils/format_table.py,sha256=UYCQ6Xum3dPHrh0cAh_KCj6vHShAvdHlV0rtIv7J09Q,3695
12
- adss-1.0.dist-info/LICENSE,sha256=1aYqcyqjrdNXY9hqgZkCWprcoA112oKvdrfPyvMYPTc,1468
13
- adss-1.0.dist-info/METADATA,sha256=mXmdv0NUDgCapF58GJOkJj5_bCU5rjHgCjYI1cCDY7Q,378
14
- adss-1.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
15
- adss-1.0.dist-info/top_level.txt,sha256=ebD44L3R0PEvEFoRCJ-RjTIsQ9Yjpo2aAYC1BMtueLg,5
16
- adss-1.0.dist-info/RECORD,,
File without changes
File without changes