sparclclient 1.2.1.dev7__py2.py3-none-any.whl → 1.2.2__py2.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.
sparcl/Results.py CHANGED
@@ -21,6 +21,15 @@ class Results(UserList):
21
21
  self.fields = client.fields
22
22
  self.to_science_fields()
23
23
 
24
+ # HACK 12/14/2023 -sp- to fix UUID problem presumably
25
+ # produced on stack version upgrade (to Django 4.2, postgres 13+)
26
+ # Done per AB for expediency since real solution will be easier
27
+ # after field-renaming is removed.
28
+ for rec in self.recs:
29
+ if "sparcl_id" in rec:
30
+ rec["sparcl_id"] = str(rec["sparcl_id"])
31
+ # END __init__()
32
+
24
33
  # https://docs.python.org/3/library/collections.html#collections.deque.clear
25
34
  def clear(self):
26
35
  """Delete the contents of this collection."""
@@ -132,9 +141,9 @@ class Results(UserList):
132
141
  # Transform science fields to internal fields
133
142
  new_recs = self.science_to_internal_fields()
134
143
  # Get the ids or specids from retrieved records
135
- if type(ids_og[0]) == str:
144
+ if type(ids_og[0]) is str:
136
145
  ids_re = [f["sparcl_id"] for f in new_recs]
137
- elif type(ids_og[0]) == int:
146
+ elif type(ids_og[0]) is int:
138
147
  ids_re = [f["specid"] for f in new_recs]
139
148
  # Enumerate the original ids
140
149
  dict_og = {x: i for i, x in enumerate(ids_og)}
sparcl/__init__.py CHANGED
@@ -2,7 +2,6 @@
2
2
  A client for getting spectra and meta-data from NOIRLab.
3
3
  """
4
4
 
5
-
6
5
  # List of packages to import when "from sparcl import *" is used
7
6
  __all__ = ["client", "align_records"]
8
7
 
@@ -26,16 +25,10 @@ __all__ = ["client", "align_records"]
26
25
 
27
26
  # must mach: [N!]N(.N)*[{a|b|rc}N][.postN][.devN]
28
27
  # Example of a correct version string: '0.4.0a3.dev35'
29
- # __version__ = '0.4.0b1.dev8'
30
- # __version__ = '0.4.0b1.dev10'
31
- # __version__ = '1.0.0'
32
- # __version__ = '1.0.0b1.dev7'
33
- # __version__ = '1.0.0b1.dev8'
34
- # __version__ = '1.0.0b1.dev9'
35
- # __version__ = '1.0.1b2.dev1'
36
- # __version__ = '1.1rc1'
37
- # __version__ = '1.1rc2'
38
28
  # __version__ = '1.1'
39
29
  # __version__ = '1.2.0b4'
40
30
  # __version__ = '1.2.0' # Release
41
- __version__ = "1.2.1.dev7"
31
+ # __version__ = "1.2.1b3"
32
+ # __version__ = "1.2.1"
33
+ # FIRST uncommented value will be used! (so only leave one uncommented)
34
+ __version__ = "1.2.2"
sparcl/client.py CHANGED
@@ -1,7 +1,16 @@
1
1
  """Client module for SPARCL.
2
2
  This module interfaces to the SPARC-Server to get spectra data.
3
3
  """
4
+
4
5
  # python -m unittest tests.tests_api
6
+
7
+ # ### Run tests against DEV
8
+ # serverurl=http://localhost:8050 python -m unittest tests.tests_api
9
+ #
10
+ # ### Run tests Against PAT Server.
11
+ # export serverurl=https://sparc1.datalab.noirlab.edu/
12
+ # python -m unittest tests.tests_api
13
+
5
14
  #
6
15
  # Doctest example:
7
16
  # cd ~/sandbox/sparclclient
@@ -14,6 +23,7 @@ This module interfaces to the SPARC-Server to get spectra data.
14
23
  from urllib.parse import urlencode, urlparse
15
24
  from warnings import warn
16
25
  import pickle
26
+ import getpass
17
27
 
18
28
  #!from pathlib import Path
19
29
  import tempfile
@@ -22,6 +32,9 @@ import tempfile
22
32
  # External Packages
23
33
  import requests
24
34
 
35
+ #!from requests.auth import HTTPBasicAuth
36
+ from requests.auth import AuthBase
37
+
25
38
  ############################################
26
39
  # Local Packages
27
40
  from sparcl.fields import Fields
@@ -102,6 +115,19 @@ RESERVED = set([DEFAULT, ALL])
102
115
  #! return set(lists[0]).intersection(*lists[1:])
103
116
 
104
117
 
118
+ class TokenAuth(AuthBase):
119
+ """Attaches HTTP Token Authentication to the given Request object."""
120
+
121
+ def __init__(self, token):
122
+ # setup any auth-related data here
123
+ self.token = token
124
+
125
+ def __call__(self, request):
126
+ # modify and return the request
127
+ request.headers["Authorization"] = self.token
128
+ return request
129
+
130
+
105
131
  ###########################
106
132
  # ## The Client class
107
133
 
@@ -113,7 +139,7 @@ class SparclClient: # was SparclApi()
113
139
  about the Client and Server that is usefule to Developers.
114
140
 
115
141
  Args:
116
- url (:obj:`str`, optional): Base URL of SPARC Server. Defaults
142
+ url (:obj:`str`, optional): Base URL of SPARCL Server. Defaults
117
143
  to 'https://astrosparcl.datalab.noirlab.edu'.
118
144
 
119
145
  verbose (:obj:`bool`, optional): Default verbosity is set to
@@ -137,13 +163,11 @@ class SparclClient: # was SparclApi()
137
163
 
138
164
  """
139
165
 
140
- KNOWN_GOOD_API_VERSION = 11.0 # @@@ Change when Server version incremented
166
+ KNOWN_GOOD_API_VERSION = 12.0 # @@@ Change when Server version incremented
141
167
 
142
168
  def __init__(
143
169
  self,
144
170
  *,
145
- email=None,
146
- password=None,
147
171
  url=_PROD,
148
172
  verbose=False,
149
173
  show_curl=False,
@@ -153,11 +177,11 @@ class SparclClient: # was SparclApi()
153
177
  """Create client instance."""
154
178
  session = requests.Session()
155
179
  self.session = session
156
-
157
- self.session.auth = (email, password) if email and password else None
180
+ self.session.auth = None
158
181
  self.rooturl = url.rstrip("/") # eg. "http://localhost:8050"
159
182
  self.apiurl = f"{self.rooturl}/sparc"
160
183
  self.apiversion = None
184
+ self.token = None
161
185
  self.verbose = verbose
162
186
  self.show_curl = show_curl # Show CURL equivalent of client method
163
187
  #!self.internal_names = internal_names
@@ -199,7 +223,6 @@ class SparclClient: # was SparclApi()
199
223
  f"at {self.apiurl}."
200
224
  )
201
225
  raise Exception(msg)
202
- # self.session = requests.Session() #@@@
203
226
 
204
227
  self.clientversion = client_version
205
228
  self.fields = Fields(self.apiurl)
@@ -214,11 +237,106 @@ class SparclClient: # was SparclApi()
214
237
  f"(sparclclient:{self.clientversion},"
215
238
  f" api:{self.apiversion},"
216
239
  f" {self.apiurl},"
240
+ f" client_hash={ut.githash()},"
217
241
  f" verbose={self.verbose},"
218
242
  f" connect_timeout={self.c_timeout},"
219
243
  f" read_timeout={self.r_timeout})"
220
244
  )
221
245
 
246
+ def login(self, email, password=None):
247
+ """Login to the SPARCL service.
248
+
249
+ Args:
250
+ email (:obj:`str`): User login email.
251
+
252
+ password (:obj:`str`, optional): User SSO password.
253
+ If not given, the output will prompt the user
254
+ to enter in their SSO password.
255
+
256
+ Returns:
257
+ None.
258
+
259
+ Example:
260
+ >>>
261
+ >> client = SparclClient()
262
+ >> client.login('test_user@noirlab.edu', 'testpw')
263
+ Logged in successfully with email='test_user@noirlab.edu'
264
+ """
265
+
266
+ if email is None: # "logout"
267
+ old_email = self.session.auth[0] if self.session.auth else None
268
+ self.session.auth = None
269
+ self.token = None
270
+ print(
271
+ f"Logged-out successfully. "
272
+ f" Previously logged-in with email {old_email}."
273
+ )
274
+ return None
275
+ if password is None:
276
+ password = getpass.getpass(prompt="SSO Password: ")
277
+ url = f"{self.apiurl}/get_token/"
278
+ # print(f'login: get_token {url=}')
279
+ res = requests.post(
280
+ url,
281
+ json=dict(email=email, password=password),
282
+ timeout=self.timeout,
283
+ )
284
+ try:
285
+ res.raise_for_status()
286
+ #!print(f"DBG: {res.content=}")
287
+ self.token = res.json()
288
+ self.session.auth = (email, password)
289
+ except Exception:
290
+ self.session.auth = None
291
+ self.token = None
292
+ msg = (
293
+ "Could not login with given credentials."
294
+ ' Reverted to "Anonymous" user.'
295
+ )
296
+ return msg
297
+
298
+ print(f"Logged in successfully with {email=}")
299
+ return None
300
+
301
+ def logout(self):
302
+ """Logout of the SPARCL service.
303
+
304
+ Args:
305
+ None.
306
+
307
+ Returns:
308
+ None.
309
+
310
+ Example:
311
+ >>> client = SparclClient()
312
+ >>> client.logout()
313
+ Logged-out successfully. Previously logged-in with email None.
314
+ """
315
+
316
+ return self.login(None)
317
+
318
+ @property
319
+ def authorized(self):
320
+ auth = TokenAuth(self.token) if self.token else None
321
+ response = requests.get(
322
+ f"{self.apiurl}/auth_status/", auth=auth, timeout=self.timeout
323
+ )
324
+ auth_status = response.json()
325
+ #! print(f"DBG authorized: {auth_status=}")
326
+
327
+ username = auth_status.get("Loggedin_User")
328
+ #! all_private_drs = set(auth_status.get("All_Private_Datasets"))
329
+ all_public_drs = set(auth_status.get("All_Public_Datasets"))
330
+ auth_drs = set(auth_status.get("Authorized_Private_Datasets"))
331
+ res = dict(
332
+ Loggedin_As=username, # email
333
+ Authorized_Datasets=auth_drs | all_public_drs,
334
+ #! Unauthorized_Datasets=all_private_drs - auth_drs,
335
+ #! All_Private_Datasets=all_private_drs,
336
+ #! All_Datasets=all_drs,
337
+ )
338
+ return res
339
+
222
340
  @property
223
341
  def all_datasets(self):
224
342
  """Set of all DataSets available from Server"""
@@ -233,7 +351,7 @@ class SparclClient: # was SparclApi()
233
351
  dataset_list (:obj:`list`, optional): List of data sets from
234
352
  which to get the default fields. Defaults to None, which
235
353
  will return the intersection of default fields in all
236
- data sets hosted on the SPARC database.
354
+ data sets hosted on the SPARCL database.
237
355
 
238
356
  Returns:
239
357
  List of fields tagged as 'default' from DATASET_LIST.
@@ -264,7 +382,7 @@ class SparclClient: # was SparclApi()
264
382
  dataset_list (:obj:`list`, optional): List of data sets from
265
383
  which to get all fields. Defaults to None, which
266
384
  will return the intersection of all fields in all
267
- data sets hosted on the SPARC database.
385
+ data sets hosted on the SPARCL database.
268
386
 
269
387
  Returns:
270
388
  List of fields tagged as 'all' from DATASET_LIST.
@@ -322,7 +440,7 @@ class SparclClient: # was SparclApi()
322
440
  dataset_list (:obj:`list`, optional): List of data sets from
323
441
  which to get available fields. Defaults to None, which
324
442
  will return the intersection of all available fields in
325
- all data sets hosted on the SPARC database.
443
+ all data sets hosted on the SPARCL database.
326
444
 
327
445
  Returns:
328
446
  Set of fields available from data sets in DATASET_LIST.
@@ -330,7 +448,8 @@ class SparclClient: # was SparclApi()
330
448
  Example:
331
449
  >>> client = SparclClient()
332
450
  >>> sorted(client.get_available_fields())
333
- ['data_release', 'datasetgroup', 'dateobs', 'dateobs_center', 'dec', 'dirpath', 'exptime', 'extra_files', 'filename', 'filesize', 'flux', 'instrument', 'ivar', 'mask', 'model', 'ra', 'redshift', 'redshift_err', 'redshift_warning', 'site', 'sparcl_id', 'specid', 'specprimary', 'spectype', 'survey', 'targetid', 'telescope', 'updated', 'wave_sigma', 'wavelength', 'wavemax', 'wavemin']
451
+ ['data_release', 'datasetgroup', 'dateobs', 'dateobs_center', 'dec', 'exptime', 'extra_files', 'file', 'flux', 'instrument', 'ivar', 'mask', 'model', 'ra', 'redshift', 'redshift_err', 'redshift_warning', 'site', 'sparcl_id', 'specid', 'specprimary', 'spectype', 'survey', 'targetid', 'telescope', 'updated', 'wave_sigma', 'wavelength', 'wavemax', 'wavemin']
452
+
334
453
  """ # noqa: E501
335
454
 
336
455
  drs = self.fields.all_drs if dataset_list is None else dataset_list
@@ -349,7 +468,7 @@ class SparclClient: # was SparclApi()
349
468
  Example:
350
469
  >>> client = SparclClient()
351
470
  >>> client.version
352
- 9.0
471
+ 12.0
353
472
  """
354
473
 
355
474
  if self.apiversion is None:
@@ -364,12 +483,14 @@ class SparclClient: # was SparclApi()
364
483
  outfields=None,
365
484
  *,
366
485
  constraints={}, # dict(fname) = [op, param, ...]
367
- # dataset_list=None,
486
+ #! exclude_unauth = True, # Not implemented yet
368
487
  limit=500,
369
488
  sort=None,
489
+ # count=False,
490
+ # dataset_list=None,
370
491
  verbose=None,
371
492
  ):
372
- """Find records in the SPARC database.
493
+ """Find records in the SPARCL database.
373
494
 
374
495
  Args:
375
496
  outfields (:obj:`list`, optional): List of fields to return.
@@ -429,6 +550,7 @@ class SparclClient: # was SparclApi()
429
550
  }
430
551
  uparams = dict(
431
552
  limit=limit,
553
+ #! count='Y' if count else 'N'
432
554
  )
433
555
  if sort is not None:
434
556
  uparams["sort"] = sort
@@ -445,7 +567,8 @@ class SparclClient: # was SparclApi()
445
567
  cmd = ut.curl_find_str(sspec, self.rooturl, qstr=qstr)
446
568
  print(cmd)
447
569
 
448
- res = requests.post(url, json=sspec, timeout=self.timeout)
570
+ auth = TokenAuth(self.token) if self.token else None
571
+ res = requests.post(url, json=sspec, auth=auth, timeout=self.timeout)
449
572
 
450
573
  if res.status_code != 200:
451
574
  if verbose and ("traceback" in res.json()):
@@ -461,14 +584,14 @@ class SparclClient: # was SparclApi()
461
584
  self, uuid_list, *, dataset_list=None, countOnly=False, verbose=False
462
585
  ):
463
586
  """Return the subset of sparcl_ids in the given uuid_list that are
464
- NOT stored in the SPARC database.
587
+ NOT stored in the SPARCL database.
465
588
 
466
589
  Args:
467
590
  uuid_list (:obj:`list`): List of sparcl_ids.
468
591
 
469
592
  dataset_list (:obj:`list`, optional): List of data sets from
470
593
  which to find missing sparcl_ids. Defaults to None, meaning
471
- all data sets hosted on the SPARC database.
594
+ all data sets hosted on the SPARCL database.
472
595
 
473
596
  countOnly (:obj:`bool`, optional): Set to True to return only
474
597
  a count of the missing sparcl_ids from the uuid_list.
@@ -479,7 +602,7 @@ class SparclClient: # was SparclApi()
479
602
 
480
603
  Returns:
481
604
  A list of the subset of sparcl_ids in the given uuid_list that
482
- are NOT stored in the SPARC database.
605
+ are NOT stored in the SPARCL database.
483
606
 
484
607
  Example:
485
608
  >>> client = SparclClient()
@@ -514,14 +637,14 @@ class SparclClient: # was SparclApi()
514
637
  self, specid_list, *, dataset_list=None, countOnly=False, verbose=False
515
638
  ):
516
639
  """Return the subset of specids in the given specid_list that are
517
- NOT stored in the SPARC database.
640
+ NOT stored in the SPARCL database.
518
641
 
519
642
  Args:
520
643
  specid_list (:obj:`list`): List of specids.
521
644
 
522
645
  dataset_list (:obj:`list`, optional): List of data sets from
523
646
  which to find missing specids. Defaults to None, meaning
524
- all data sets hosted on the SPARC database.
647
+ all data sets hosted on the SPARCL database.
525
648
 
526
649
  countOnly (:obj:`bool`, optional): Set to True to return only
527
650
  a count of the missing specids from the specid_list.
@@ -532,11 +655,12 @@ class SparclClient: # was SparclApi()
532
655
 
533
656
  Returns:
534
657
  A list of the subset of specids in the given specid_list that
535
- are NOT stored in the SPARC database.
658
+ are NOT stored in the SPARCL database.
536
659
 
537
660
  Example:
538
- >>> client = SparclClient(url=_PAT)
539
- >>> specids = ['7972592460248666112', '3663710814482833408']
661
+ >>> client = SparclClient()
662
+ >>> found = client.find(outfields=['specid'], limit=2)
663
+ >>> specids = [f.specid for f in found.records]
540
664
  >>> client.missing_specids(specids + ['bad_id'])
541
665
  ['bad_id']
542
666
  """
@@ -598,7 +722,7 @@ class SparclClient: # was SparclApi()
598
722
  limit=500,
599
723
  verbose=None,
600
724
  ):
601
- """Retrieve spectra records from the SPARC database by list of
725
+ """Retrieve spectra records from the SPARCL database by list of
602
726
  sparcl_ids.
603
727
 
604
728
  Args:
@@ -610,7 +734,7 @@ class SparclClient: # was SparclApi()
610
734
 
611
735
  dataset_list (:obj:`list`, optional): List of data sets from
612
736
  which to retrieve spectra data. Defaults to None, meaning all
613
- data sets hosted on the SPARC database.
737
+ data sets hosted on the SPARCL database.
614
738
 
615
739
  limit (:obj:`int`, optional): Maximum number of records to
616
740
  return. Defaults to 500. Maximum allowed is 24,000.
@@ -623,7 +747,7 @@ class SparclClient: # was SparclApi()
623
747
 
624
748
  Example:
625
749
  >>> client = SparclClient()
626
- >>> ids = ['00000f0b-07db-4234-892a-6e347db79c89',]
750
+ >>> ids = client.find(limit=1).ids
627
751
  >>> inc = ['sparcl_id', 'flux', 'wavelength', 'model']
628
752
  >>> ret = client.retrieve(uuid_list=ids, include=inc)
629
753
  >>> type(ret.records[0].wavelength)
@@ -646,6 +770,7 @@ class SparclClient: # was SparclApi()
646
770
  format = "pkl" # 'json',
647
771
  chunk = 500
648
772
 
773
+ orig_dataset_list = dataset_list
649
774
  if dataset_list is None:
650
775
  dataset_list = self.fields.all_drs
651
776
  assert isinstance(
@@ -687,8 +812,13 @@ class SparclClient: # was SparclApi()
687
812
  #! "chunk_len": chunk,
688
813
  "format": format,
689
814
  #! "1thread": "yes", # @@@ 7.3.2023
690
- "dataset_list": ",".join(dataset_list),
815
+ #!"dataset_list": ",".join(dataset_list),
691
816
  }
817
+ # Do not put dataset_list in server call if it wasn't in client call.
818
+ # Else will interfer with defaulting behavior of AUTH (DLS-504).
819
+ if orig_dataset_list is not None:
820
+ uparams["dataset_list"] = ",".join(dataset_list)
821
+
692
822
  qstr = urlencode(uparams)
693
823
 
694
824
  #!url = f'{self.apiurl}/retrieve/?{qstr}'
@@ -703,9 +833,8 @@ class SparclClient: # was SparclApi()
703
833
  print(cmd)
704
834
 
705
835
  try:
706
- res = requests.post(
707
- url, json=ids, auth=self.session.auth, timeout=self.timeout
708
- )
836
+ auth = TokenAuth(self.token) if self.token else None
837
+ res = requests.post(url, json=ids, auth=auth, timeout=self.timeout)
709
838
  except requests.exceptions.ConnectTimeout as reCT:
710
839
  raise ex.UnknownSparcl(f"ConnectTimeout: {reCT}")
711
840
  except requests.exceptions.ReadTimeout as reRT:
@@ -782,7 +911,8 @@ class SparclClient: # was SparclApi()
782
911
  limit=500,
783
912
  verbose=False,
784
913
  ):
785
- """Retrieve spectra records from the SPARC database by list of specids.
914
+ """Retrieve spectra records from the SPARCL database by list of
915
+ specids.
786
916
 
787
917
  Args:
788
918
  specid_list (:obj:`list`): List of specids.
@@ -793,7 +923,7 @@ class SparclClient: # was SparclApi()
793
923
 
794
924
  dataset_list (:obj:`list`, optional): List of data sets from
795
925
  which to retrieve spectra data. Defaults to None, meaning all
796
- data sets hosted on the SPARC database.
926
+ data sets hosted on the SPARCL database.
797
927
 
798
928
  limit (:obj:`int`, optional): Maximum number of records to
799
929
  return. Defaults to 500. Maximum allowed is 24,000.
sparcl/exceptions.py CHANGED
@@ -21,9 +21,13 @@ def genSparclException(response, verbose=False):
21
21
  return UnknownField(status.get("errorMessage"))
22
22
  elif status.get("errorCode") == "BADCONST":
23
23
  return BadSearchConstraint(status.get("errorMessage"))
24
+ elif status.get("errorCode") == "NODRACCESS":
25
+ return AccessNotAllowed(status.get("errorMessage"))
24
26
  else:
25
27
  return UnknownServerError(
26
- f"{status.get('errorMessage')} " f"[{status.get('errorCode')}]"
28
+ f"{status.get('errorMessage')} "
29
+ f"[{status.get('errorCode')}] "
30
+ f"{status.get('traceback')}"
27
31
  )
28
32
 
29
33
 
@@ -32,7 +36,7 @@ class BaseSparclException(Exception):
32
36
 
33
37
  error_code = "UNKNOWN"
34
38
  error_message = "<NA>"
35
- traceback = None
39
+ traceback = True
36
40
 
37
41
  def get_subclass_name(self):
38
42
  return self.__class__.__name__
@@ -138,4 +142,8 @@ class NoIDs(BaseSparclException):
138
142
  error_code = "NOIDS"
139
143
 
140
144
 
145
+ class AccessNotAllowed(BaseSparclException):
146
+ error_code = "DSDENIED"
147
+
148
+
141
149
  # error_code values should be no bigger than 8 characters 12345678
sparcl/fields.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Get Field names associated with various SPARCL conditions.
2
2
  """
3
+
3
4
  # Python Standard Library
4
5
  from collections import defaultdict
5
6
 
sparcl/gather_2d.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Align or resample spectra related fields across multiple records."""
2
+
2
3
  # See client.py for Doctest example
3
4
  #
4
5
  # For info about problems with floating point,
@@ -194,7 +195,7 @@ def align_records(records, fields=["flux", "wavelength"], precision=7):
194
195
  >>> got = client.retrieve(found.ids, include=specflds)
195
196
  >>> ar_dict, grid = align_records(got.records, fields=specflds)
196
197
  >>> ar_dict['model'].shape
197
- (21, 4666)
198
+ (21, 4669)
198
199
 
199
200
  """
200
201
  # Report Garbage In