adss 1.31__py3-none-any.whl → 1.33__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/auth.py CHANGED
@@ -1,11 +1,58 @@
1
- import requests
1
+ import os
2
+ import time
2
3
  from typing import Dict, Optional, Tuple
3
4
 
5
+ import requests # kept for compatibility (exceptions/type expectations)
6
+ import httpx
7
+
4
8
  from adss.exceptions import AuthenticationError
5
9
  from adss.utils import handle_response_errors
6
10
  from adss.models.user import User
7
11
 
8
- import os
12
+
13
+ # --- internal defaults (safe timeouts; env-overridable) ---
14
+ _CONNECT_TIMEOUT = float(os.getenv("ADSS_CONNECT_TIMEOUT", "5"))
15
+ _READ_TIMEOUT = float(os.getenv("ADSS_READ_TIMEOUT", "600"))
16
+ _DEFAULT_TIMEOUT = (_CONNECT_TIMEOUT, _READ_TIMEOUT)
17
+
18
+ _TOTAL_RETRIES = int(os.getenv("ADSS_RETRY_TOTAL", "3"))
19
+ _BACKOFF_FACTOR = float(os.getenv("ADSS_RETRY_BACKOFF", "0.5"))
20
+ _TRUST_ENV = os.getenv("ADSS_TRUST_ENV", "1").lower() not in ("0", "false", "no")
21
+ _FORCE_CLOSE_STREAMS = os.getenv("ADSS_FORCE_CLOSE_STREAMS", "0").lower() in ("1", "true", "yes")
22
+
23
+
24
+ def _to_httpx_timeout(t):
25
+ """Map (connect, read) tuple or scalar into httpx.Timeout."""
26
+ if isinstance(t, tuple) and len(t) == 2:
27
+ connect, read = t
28
+ return httpx.Timeout(connect=connect, read=read, write=read, pool=connect)
29
+ if isinstance(t, (int, float)):
30
+ return httpx.Timeout(t)
31
+ return httpx.Timeout(connect=_CONNECT_TIMEOUT, read=_READ_TIMEOUT, write=_READ_TIMEOUT, pool=_CONNECT_TIMEOUT)
32
+
33
+
34
+ def _attach_requests_compat(resp: httpx.Response) -> httpx.Response:
35
+ """
36
+ Attach requests-like helpers so existing code doesn't break:
37
+ - iter_content(chunk_size) -> yields bytes
38
+ - raw.read([n]) -> bytes (returns remaining body)
39
+ """
40
+ if not hasattr(resp, "iter_content"):
41
+ def iter_content(chunk_size: int = 1024 * 1024):
42
+ return resp.iter_bytes(chunk_size=chunk_size)
43
+ setattr(resp, "iter_content", iter_content)
44
+
45
+ if not hasattr(resp, "raw"):
46
+ class _RawAdapter:
47
+ def __init__(self, r: httpx.Response):
48
+ self._r = r
49
+ def read(self, amt: Optional[int] = None) -> bytes:
50
+ # httpx sync API doesn't expose partial read; return remaining.
51
+ return self._r.read()
52
+ setattr(resp, "raw", _RawAdapter(resp))
53
+
54
+ return resp
55
+
9
56
 
10
57
  class Auth:
11
58
  """
@@ -18,6 +65,9 @@ class Auth:
18
65
  self.current_user: Optional[User] = None
19
66
  self.verify_ssl = verify_ssl
20
67
 
68
+ # Single keep-alive client; set verify at construction.
69
+ self._client = httpx.Client(trust_env=_TRUST_ENV, verify=self.verify_ssl)
70
+
21
71
  def login(self, username: str, password: str, **kwargs) -> Tuple[str, User]:
22
72
  """
23
73
  Log in with username and password, obtaining an authentication token.
@@ -26,7 +76,6 @@ class Auth:
26
76
  data = {"username": username, "password": password}
27
77
 
28
78
  try:
29
- # Use our own request() method here
30
79
  response = self.request(
31
80
  method="POST",
32
81
  url=login_url,
@@ -41,12 +90,12 @@ class Auth:
41
90
  if not self.token:
42
91
  raise AuthenticationError("Login succeeded but no token returned")
43
92
 
44
- # Now fetch user info (this will use auth_required=True internally)
45
93
  self.current_user = self._get_current_user(**kwargs)
46
94
  return self.token, self.current_user
47
95
 
48
- except requests.RequestException as e:
49
- raise AuthenticationError(f"Login failed: {e}")
96
+ except httpx.RequestError as e:
97
+ # preserve existing caller except-blocks that catch requests.RequestException
98
+ raise requests.RequestException(str(e)) # noqa: B904
50
99
 
51
100
  def logout(self) -> None:
52
101
  self.token = None
@@ -66,7 +115,6 @@ class Auth:
66
115
  auth_headers = self._get_auth_headers()
67
116
 
68
117
  try:
69
- # Again, use request() so SSL and auth headers are applied consistently
70
118
  response = self.request(
71
119
  method="GET",
72
120
  url=me_url,
@@ -79,8 +127,8 @@ class Auth:
79
127
  user_data = response.json()
80
128
  return User.from_dict(user_data)
81
129
 
82
- except requests.RequestException as e:
83
- raise AuthenticationError(f"Failed to get user info: {e}")
130
+ except httpx.RequestError as e:
131
+ raise requests.RequestException(str(e)) # noqa: B904
84
132
 
85
133
  def _get_auth_headers(self) -> Dict[str, str]:
86
134
  headers = {"Accept": "application/json"}
@@ -88,6 +136,99 @@ class Auth:
88
136
  headers["Authorization"] = f"Bearer {self.token}"
89
137
  return headers
90
138
 
139
+ # ---------------- core helpers ---------------- #
140
+
141
+ def _full_url(self, url: str) -> str:
142
+ return url if url.startswith(('http://', 'https://')) else f"{self.base_url}/{url.lstrip('/')}"
143
+
144
+ def _request_with_retries_nonstream(
145
+ self,
146
+ *,
147
+ method: str,
148
+ url: str,
149
+ headers: Dict[str, str],
150
+ params=None,
151
+ data=None,
152
+ json=None,
153
+ files=None,
154
+ timeout: httpx.Timeout,
155
+ follow_redirects: bool,
156
+ ) -> httpx.Response:
157
+ last_exc = None
158
+ for attempt in range(_TOTAL_RETRIES + 1):
159
+ try:
160
+ return self._client.request(
161
+ method=method.upper(),
162
+ url=url,
163
+ headers=headers,
164
+ params=params,
165
+ data=data,
166
+ json=json,
167
+ files=files,
168
+ follow_redirects=follow_redirects,
169
+ timeout=timeout,
170
+ )
171
+ except httpx.RequestError as e:
172
+ last_exc = e
173
+ if attempt >= _TOTAL_RETRIES:
174
+ break
175
+ time.sleep(_BACKOFF_FACTOR * (2 ** attempt))
176
+ raise requests.RequestException(str(last_exc)) # noqa: B904
177
+
178
+ def _request_with_retries_stream(
179
+ self,
180
+ *,
181
+ method: str,
182
+ url: str,
183
+ headers: Dict[str, str],
184
+ params=None,
185
+ data=None,
186
+ json=None,
187
+ files=None,
188
+ timeout: httpx.Timeout,
189
+ follow_redirects: bool,
190
+ ) -> httpx.Response:
191
+ """
192
+ Use client.stream(...) but keep the stream open for the caller.
193
+ We manually __enter__ the context manager and override resp.close()
194
+ to ensure resources are cleaned when caller closes the response.
195
+ """
196
+ last_exc = None
197
+ for attempt in range(_TOTAL_RETRIES + 1):
198
+ try:
199
+ cm = self._client.stream(
200
+ method=method.upper(),
201
+ url=url,
202
+ headers=headers,
203
+ params=params,
204
+ data=data,
205
+ json=json,
206
+ files=files,
207
+ follow_redirects=follow_redirects,
208
+ timeout=timeout,
209
+ )
210
+ resp = cm.__enter__() # don't exit: let caller iterate/close
211
+ # Make close() also exit the context manager safely
212
+ _orig_close = resp.close
213
+ def _close():
214
+ try:
215
+ _orig_close()
216
+ finally:
217
+ try:
218
+ cm.__exit__(None, None, None)
219
+ except Exception:
220
+ pass
221
+ resp.close = _close # type: ignore[attr-defined]
222
+ return resp
223
+ except httpx.RequestError as e:
224
+ last_exc = e
225
+ if attempt >= _TOTAL_RETRIES:
226
+ break
227
+ time.sleep(_BACKOFF_FACTOR * (2 ** attempt))
228
+ raise requests.RequestException(str(last_exc)) # noqa: B904
229
+
230
+ # ---------------- public API (unchanged signatures) ------------------- #
231
+
91
232
  def request(
92
233
  self,
93
234
  method: str,
@@ -102,25 +243,57 @@ class Auth:
102
243
  if auth_required and not self.is_authenticated():
103
244
  raise AuthenticationError("Authentication required for this request")
104
245
 
105
- # Prepend base_url if needed
106
- if not url.startswith(('http://', 'https://')):
107
- url = f"{self.base_url}/{url.lstrip('/')}"
246
+ url = self._full_url(url)
108
247
 
109
248
  # Merge headers
110
249
  final_headers = self._get_auth_headers()
111
250
  if headers:
112
251
  final_headers.update(headers)
113
252
 
114
- # Apply verify_ssl unless overridden
115
- if 'verify' not in kwargs:
116
- kwargs['verify'] = self.verify_ssl
253
+ # Map requests-style kwargs to httpx
254
+ timeout = _to_httpx_timeout(kwargs.pop('timeout', _DEFAULT_TIMEOUT))
255
+ follow_redirects = kwargs.pop('allow_redirects', True)
256
+ stream_flag = bool(kwargs.pop('stream', False))
257
+
258
+ # (verify is fixed per-client at __init__; ignore/strip any incoming 'verify' kw)
259
+ kwargs.pop('verify', None)
117
260
 
118
- return requests.request(method, url, headers=final_headers, **kwargs)
261
+ # Build payload pieces compatibly
262
+ params = kwargs.pop('params', None)
263
+ data = kwargs.pop('data', None)
264
+ json_ = kwargs.pop('json', None)
265
+ files = kwargs.pop('files', None)
266
+
267
+ if stream_flag:
268
+ resp = self._request_with_retries_stream(
269
+ method=method,
270
+ url=url,
271
+ headers=final_headers,
272
+ params=params,
273
+ data=data,
274
+ json=json_,
275
+ files=files,
276
+ timeout=timeout,
277
+ follow_redirects=follow_redirects,
278
+ )
279
+ else:
280
+ resp = self._request_with_retries_nonstream(
281
+ method=method,
282
+ url=url,
283
+ headers=final_headers,
284
+ params=params,
285
+ data=data,
286
+ json=json_,
287
+ files=files,
288
+ timeout=timeout,
289
+ follow_redirects=follow_redirects,
290
+ )
291
+ return _attach_requests_compat(resp)
119
292
 
120
293
  def refresh_user_info(self, **kwargs) -> User:
121
294
  self.current_user = self._get_current_user(**kwargs)
122
295
  return self.current_user
123
-
296
+
124
297
  def download(
125
298
  self,
126
299
  method: str,
@@ -140,23 +313,34 @@ class Auth:
140
313
  if auth_required and not self.is_authenticated():
141
314
  raise AuthenticationError("Authentication required for this request")
142
315
 
143
- # Prepend base_url if needed
144
- if not url.startswith(('http://', 'https://')):
145
- url = f"{self.base_url}/{url.lstrip('/')}"
316
+ url = self._full_url(url)
146
317
 
147
318
  # Merge headers
148
319
  final_headers = self._get_auth_headers()
149
320
  if headers:
150
321
  final_headers.update(headers)
322
+ if _FORCE_CLOSE_STREAMS:
323
+ final_headers.setdefault("Connection", "close")
151
324
 
152
- # Apply verify_ssl unless overridden
153
- if 'verify' not in kwargs:
154
- kwargs['verify'] = self.verify_ssl
325
+ timeout = _to_httpx_timeout(kwargs.pop('timeout', _DEFAULT_TIMEOUT))
326
+ follow_redirects = kwargs.pop('allow_redirects', True)
327
+ kwargs.pop('verify', None) # verify is fixed on client
155
328
 
156
- # Force streaming
157
- kwargs['stream'] = True
329
+ params = kwargs.pop('params', None)
330
+ data = kwargs.pop('data', None)
331
+ json_ = kwargs.pop('json', None)
332
+ files = kwargs.pop('files', None)
158
333
 
159
- resp = requests.request(method, url, headers=final_headers, **kwargs)
334
+ resp = self._request_with_retries_stream(
335
+ method=method,
336
+ url=url,
337
+ headers=final_headers,
338
+ params=params,
339
+ data=data,
340
+ json=json_,
341
+ files=files,
342
+ timeout=timeout,
343
+ follow_redirects=follow_redirects,
344
+ )
160
345
  handle_response_errors(resp) # fail fast on HTTP errors
161
-
162
- return resp
346
+ return _attach_requests_compat(resp)
adss/client.py CHANGED
@@ -516,7 +516,7 @@ class ADSSClient:
516
516
  """
517
517
  return self.images.cone_search(collection_id, ra, dec, radius, filter_name, limit, **kwargs)
518
518
 
519
- def download_image(self, file_id: int, output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
519
+ def download_file(self, file_id: int, output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
520
520
  """
521
521
  Download an image file.
522
522
 
adss/endpoints/images.py CHANGED
@@ -160,7 +160,7 @@ class ImagesEndpoint:
160
160
  for chunk in resp.iter_content(8192):
161
161
  f.write(chunk)
162
162
  return output_path
163
- return resp.content
163
+ return resp.read()
164
164
  except Exception as e:
165
165
  raise ResourceNotFoundError(f"Failed to download image file {file_id}: {e}")
166
166
 
@@ -220,9 +220,9 @@ class LuptonImagesEndpoint:
220
220
  output_path = os.path.join(output_path, filename)
221
221
  if output_path:
222
222
  with open(output_path, 'wb') as f:
223
- f.write(resp.content)
224
- return resp.content
225
- return resp.content
223
+ f.write(resp.read())
224
+ return resp.read()
225
+ return resp.read()
226
226
 
227
227
  except Exception as e:
228
228
  raise ResourceNotFoundError(f"Failed to create RGB image: {e}")
@@ -293,9 +293,9 @@ class LuptonImagesEndpoint:
293
293
  output_path = os.path.join(output_path, filename)
294
294
  if output_path:
295
295
  with open(output_path, 'wb') as f:
296
- f.write(resp.content)
297
- return resp.content
298
- return resp.content
296
+ f.write(resp.read())
297
+ return resp.read()
298
+ return resp.read()
299
299
 
300
300
  except Exception as e:
301
301
  raise ResourceNotFoundError(f"Failed to create RGB image by filenames: {e}")
@@ -340,9 +340,9 @@ class LuptonImagesEndpoint:
340
340
  output_path = os.path.join(output_path, filename)
341
341
  if output_path:
342
342
  with open(output_path, 'wb') as f:
343
- f.write(resp.content)
344
- return resp.content
345
- return resp.content
343
+ f.write(resp.read())
344
+ return resp.read()
345
+ return resp.read()
346
346
 
347
347
  except Exception as e:
348
348
  raise ResourceNotFoundError(f"Failed to create RGB image by coordinates: {e}")
@@ -395,9 +395,9 @@ class LuptonImagesEndpoint:
395
395
  output_path = os.path.join(output_path, filename)
396
396
  if output_path:
397
397
  with open(output_path, 'wb') as f:
398
- f.write(resp.content)
399
- return resp.content
400
- return resp.content
398
+ f.write(resp.read())
399
+ return resp.read()
400
+ return resp.read()
401
401
 
402
402
  except Exception as e:
403
403
  raise ResourceNotFoundError(f"Failed to create RGB image by object: {e}")
@@ -451,9 +451,9 @@ class StampImagesEndpoint:
451
451
  output_path = os.path.join(output_path, filename)
452
452
  if output_path:
453
453
  with open(output_path, 'wb') as f:
454
- f.write(resp.content)
455
- return resp.content
456
- return resp.content
454
+ f.write(resp.read())
455
+ return resp.read()
456
+ return resp.read()
457
457
 
458
458
  except Exception as e:
459
459
  raise ResourceNotFoundError(f"Failed to create stamp from file {file_id}: {e}")
@@ -516,9 +516,9 @@ class StampImagesEndpoint:
516
516
  output_path = os.path.join(output_path, filename)
517
517
  if output_path:
518
518
  with open(output_path, 'wb') as f:
519
- f.write(resp.content)
520
- return resp.content
521
- return resp.content
519
+ f.write(resp.read())
520
+ return resp.read()
521
+ return resp.read()
522
522
 
523
523
  except Exception as e:
524
524
  raise ResourceNotFoundError(f"Failed to create stamp from file {filename}: {e}")
@@ -566,9 +566,9 @@ class StampImagesEndpoint:
566
566
  output_path = os.path.join(output_path, filename)
567
567
  if output_path:
568
568
  with open(output_path, 'wb') as f:
569
- f.write(resp.content)
570
- return resp.content
571
- return resp.content
569
+ f.write(resp.read())
570
+ return resp.read()
571
+ return resp.read()
572
572
 
573
573
  except Exception as e:
574
574
  raise ResourceNotFoundError(f"Failed to create stamp by coordinates: {e}")
@@ -618,9 +618,9 @@ class StampImagesEndpoint:
618
618
  output_path = os.path.join(output_path, filename)
619
619
  if output_path:
620
620
  with open(output_path, 'wb') as f:
621
- f.write(resp.content)
622
- return resp.content
623
- return resp.content
621
+ f.write(resp.read())
622
+ return resp.read()
623
+ return resp.read()
624
624
 
625
625
  except Exception as e:
626
626
  raise ResourceNotFoundError(f"Failed to create stamp by object: {e}")
@@ -682,9 +682,9 @@ class TrilogyImagesEndpoint:
682
682
  output_path = os.path.join(output_path, filename)
683
683
  if output_path:
684
684
  with open(output_path, 'wb') as f:
685
- f.write(resp.content)
686
- return resp.content
687
- return resp.content
685
+ f.write(resp.read())
686
+ return resp.read()
687
+ return resp.read()
688
688
 
689
689
  except Exception as e:
690
690
  raise ResourceNotFoundError(f"Failed to create Trilogy RGB image: {e}")
@@ -731,9 +731,9 @@ class TrilogyImagesEndpoint:
731
731
  output_path = os.path.join(output_path, filename)
732
732
  if output_path:
733
733
  with open(output_path, 'wb') as f:
734
- f.write(resp.content)
734
+ f.write(resp.read())
735
735
  return output_path
736
- return resp.content
736
+ return resp.read()
737
737
 
738
738
  except Exception as e:
739
739
  raise ResourceNotFoundError(f"Failed to create Trilogy RGB image by coordinates: {e}")
@@ -787,9 +787,9 @@ class TrilogyImagesEndpoint:
787
787
  output_path = os.path.join(output_path, filename)
788
788
  if output_path:
789
789
  with open(output_path, 'wb') as f:
790
- f.write(resp.content)
791
- return resp.content
792
- return resp.content
790
+ f.write(resp.read())
791
+ return resp.read()
792
+ return resp.read()
793
793
 
794
794
  except Exception as e:
795
795
  raise ResourceNotFoundError(f"Failed to create Trilogy RGB image by object: {e}")
adss/endpoints/queries.py CHANGED
@@ -116,7 +116,7 @@ class QueriesEndpoint:
116
116
  )
117
117
 
118
118
  # Parse Parquet data
119
- df = parquet_to_dataframe(response.content)
119
+ df = parquet_to_dataframe(response.read())
120
120
 
121
121
  return QueryResult(
122
122
  query=query_obj,
@@ -280,7 +280,7 @@ class QueriesEndpoint:
280
280
  handle_response_errors(response)
281
281
 
282
282
  # Parse Parquet data
283
- df = parquet_to_dataframe(response.content)
283
+ df = parquet_to_dataframe(response.read())
284
284
 
285
285
  # Extract metadata
286
286
  expires_at = response.headers.get('X-Expires-At')
adss/utils.py CHANGED
@@ -19,7 +19,7 @@ def handle_response_errors(response):
19
19
  return response
20
20
 
21
21
  try:
22
- error_data = response.json()
22
+ error_data = response.read()
23
23
  error_message = error_data.get('detail', str(error_data))
24
24
  except Exception:
25
25
  error_message = response.text or f"HTTP Error {response.status_code}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adss
3
- Version: 1.31
3
+ Version: 1.33
4
4
  Summary: Astronomical Data Smart System
5
5
  Author-email: Gustavo Schwarz <gustavo.b.schwarz@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/schwarzam/adss
@@ -9,6 +9,7 @@ Requires-Python: >=3.8
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: pyarrow
12
+ Requires-Dist: httpx
12
13
  Requires-Dist: requests
13
14
  Requires-Dist: astropy
14
15
  Dynamic: license-file
@@ -178,3 +179,147 @@ cl.get_image_collections()
178
179
  ]
179
180
  ```
180
181
 
182
+ And then to list the files in a collection:
183
+
184
+ ```python
185
+ cl.list_files(1) ## pass the collection ID
186
+ ```
187
+
188
+ ```
189
+ [
190
+ {
191
+ 'filename': 'SPLUS-s17s23_F515_swpweight.fz',
192
+ 'full_path': '/dados/splus/SPLUS-s17s23 SPLUS-s17s23_F515_swpweight.fz',
193
+ 'file_type': 'fz',
194
+ 'ra_center': 316.45153076969416,
195
+ 'dec_center': -21.580560694390957,
196
+ 'width': 11000,
197
+ 'height': 11000,
198
+ 'pixel_scale': 0.55000000000008,
199
+ 'hdus': 2,
200
+ 'data_hdu': 1,
201
+ 'object_name': 'SPLUS-s17s23',
202
+ 'filter': 'F515',
203
+ 'instrument': 'T80Cam',
204
+ 'telescope': 'T80',
205
+ 'date_obs': None,
206
+ 'file_size': 51353280,
207
+ 'id': 28,
208
+ 'collection_id': 1,
209
+ 'created_at': '2025-04-22T15:35:05.487208',
210
+ 'updated_at': '2025-05-08T19:53:09.541437'},
211
+ },...]
212
+ ```
213
+
214
+ You can then download a file by its filename:
215
+
216
+ ```python
217
+ file_bytes = cl.download_file(
218
+ file_id=28,
219
+ output_path=None
220
+ )
221
+ ```
222
+
223
+ Then handle the bytes. Example:
224
+
225
+ ```python
226
+ # if a fits you may open like
227
+ import io
228
+ from astropy.io import fits
229
+
230
+ hdul = fits.open(io.BytesIO(file_bytes))
231
+
232
+ # or a image
233
+ from PIL import Image
234
+ import matplotlib.pyplot as plt
235
+
236
+ image = Image.open(io.BytesIO(file_bytes))
237
+ plt.imshow(image)
238
+ ```
239
+
240
+ ### Image Tools
241
+
242
+ Now notice that (**if**) the image collection has some wcs parameters as `ra_center`, `dec_center`, `pixel_scale`. This allows us to do some image cutouts and colored images in real time. Example:
243
+
244
+ ```python
245
+ cutout_bytes = cl.create_stamp_by_coordinates(
246
+ collection_id = 1,
247
+ ra = 0.1,
248
+ dec = 0.1,
249
+ size = 300,
250
+ filter = "R",
251
+ size_unit="pixels",
252
+ format = "fits",
253
+ pattern="swp."
254
+ )
255
+
256
+ hdul = fits.open(BytesIO(cutout_bytes))
257
+ ```
258
+
259
+ or if the image collection has object_name info you may filter by it, forcing the cutout from that object:
260
+
261
+ ```python
262
+ cutout_bytes = cl.stamp_images.create_stamp_by_object(
263
+ collection_id=1,
264
+ object_name="STRIPE82-0002",
265
+ size=300,
266
+ ra=0.1,
267
+ dec=0.1,
268
+ filter_name="R",
269
+ size_unit="pixels",
270
+ format="fits"
271
+ )
272
+ cutout = fits.open(BytesIO(cutout_bytes))
273
+ ```
274
+
275
+ or just by file_id, this will force the cutout from that specific file:
276
+
277
+ ```python
278
+ cl.stamp_images.create_stamp(
279
+ file_id=28,
280
+ size=300,
281
+ ra=0.1,
282
+ dec=0.1,
283
+ size_unit="pixels",
284
+ format="fits"
285
+ )
286
+ ```
287
+
288
+ ### Colored images
289
+
290
+ Colored images API is very similar to the cutouts. You just need to provide a list of filters and the output format (png or jpg). Example with lupton et al. (2004) algorithm:
291
+
292
+ ```python
293
+ im_bytes = cl.create_rgb_image_by_coordinates(
294
+ collection_id=1,
295
+ ra=0.1,
296
+ dec=0.1,
297
+ size=300,
298
+ size_unit="pixels",
299
+ r_filter="I",
300
+ g_filter="R",
301
+ b_filter="G",
302
+ )
303
+
304
+ im = Image.open(BytesIO(im_bytes))
305
+ im.show()
306
+ ```
307
+
308
+ Or trilogy algorithm:
309
+
310
+ ```python
311
+ im_bytes = cl.trilogy_images.create_trilogy_rgb_by_coordinates(
312
+ collection_id=1,
313
+ ra=0.1,
314
+ dec=0.1,
315
+ size=300,
316
+ size_unit="pixels",
317
+ r_filters=["I", "R", "Z", "F861", "G"],
318
+ g_filters=["F660"],
319
+ b_filters=["U", "F378", "F395", "F410", "F430", "F515"],
320
+ satpercent=0.15,
321
+ )
322
+
323
+ im = Image.open(BytesIO(im_bytes))
324
+ im.show()
325
+ ```
@@ -1,21 +1,21 @@
1
1
  adss/__init__.py,sha256=3FpHFL3Pk5BvETwd70P2QqYvDq799Cu2AGxGxudGAAE,1020
2
- adss/auth.py,sha256=4eZ5VjqyzF-gXNywjGdjjphd5im3wOFLf5tihKNfbdw,5320
3
- adss/client.py,sha256=TIGXsYos9Tt8zSiSwYDdHoPDjiurHoQvH1RVUPeuCK8,29896
2
+ adss/auth.py,sha256=_csq8YJMyNTSgxNjtJW1YxdDeVx0R-Hs9-hcBoExQCs,11977
3
+ adss/client.py,sha256=oJ0O4ZUdmycprpKXBXEpPWiu4T4Jf79uPsXzOpuEG-w,29895
4
4
  adss/exceptions.py,sha256=YeN-xRHvlSmwyS8ni2jOEhhgZK9J1jsG11pOedy3Gfg,1482
5
- adss/utils.py,sha256=KeQUtTCcye3W07oHpBnwS7g3gG-RqwWMlaE7UgDWwsU,3557
5
+ adss/utils.py,sha256=hBfE6FJD-R6OTWcIf4ChtHTS07EHFGM6Oh1OE_xOjOE,3557
6
6
  adss/endpoints/__init__.py,sha256=Pr29901fT8ClCS2GasTjTiBNyn7DfVfxILpYDFsMvPA,488
7
7
  adss/endpoints/admin.py,sha256=S6ZrkeA_Lh_LCpF1NHyfMKqjbIiylYXUSV65H_WKg1U,16391
8
- adss/endpoints/images.py,sha256=jNKC-zjbVZ89PaRazeh7-30uvPz7-L1mX5w6JXeXl_E,31909
8
+ adss/endpoints/images.py,sha256=gsZzh1cfhULz-SUUV4ImiAIS2lv70eJWI1Sk6MJdShw,31876
9
9
  adss/endpoints/metadata.py,sha256=RPrRP6Uz6-uPMIcntMgfss9vAd5iN7JXjZbF8SW0EYg,8238
10
- adss/endpoints/queries.py,sha256=du4C_K8870ffyZkaLnMD08jMAWeVBygdk_bjgnEEMWM,17633
10
+ adss/endpoints/queries.py,sha256=qpJ0mdJK8DDhznkHX_DOEWkvbWKFyfemewcKyLFLUP4,17631
11
11
  adss/endpoints/users.py,sha256=6Abkl3c3_YKdMYR_JWI-uL9HTHxcjlIOnE29GyN5_QE,10811
12
12
  adss/models/__init__.py,sha256=ADWVaGy4dkpEMH3iS_6EnRSBlEgoM5Vy9zORQr-UG6w,404
13
13
  adss/models/metadata.py,sha256=6fdH_0BenVRmeXkkKbsG2B68O-N2FXTTRgxsEhAHRoU,4058
14
14
  adss/models/query.py,sha256=V1H9UAv9wORAr85aajeY7H1zaxyfNtKuEoBtBU66DbM,4820
15
15
  adss/models/user.py,sha256=5qVT5qOktokmVLkGszPGCTZWv0wC-7aBMvJ8EeBOqdw,3493
16
- adss-1.31.dist-info/licenses/LICENSE,sha256=yPw116pnd1J4TuMPnvm6I_irZUyC30EoBZ4BtWFAL7I,1557
16
+ adss-1.33.dist-info/licenses/LICENSE,sha256=yPw116pnd1J4TuMPnvm6I_irZUyC30EoBZ4BtWFAL7I,1557
17
17
  dev/fetch_idr6.py,sha256=b6FrHPr-ZLaDup_wLOaQWP2fK254Sr3YNHbTxuUt088,12788
18
- adss-1.31.dist-info/METADATA,sha256=h0AEToEPxgL_4FKFjq1Tcf9d_LVhVfWwD1fhF4_DwAA,5656
19
- adss-1.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- adss-1.31.dist-info/top_level.txt,sha256=NT2zObOOiTWXc0yowpEjT6BiiI1e7WXlXd0ZoK7T5hk,9
21
- adss-1.31.dist-info/RECORD,,
18
+ adss-1.33.dist-info/METADATA,sha256=KDrZgw4R_Xo9CQyy-i2pZZqfZteiiFf5-aZ_BSV48Is,8759
19
+ adss-1.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ adss-1.33.dist-info/top_level.txt,sha256=NT2zObOOiTWXc0yowpEjT6BiiI1e7WXlXd0ZoK7T5hk,9
21
+ adss-1.33.dist-info/RECORD,,
File without changes