cdasws 1.7.47__py3-none-any.whl → 1.8.3__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.
cdasws/__init__.py CHANGED
@@ -41,10 +41,17 @@ the United States under Title 17, U.S.Code. All Other Rights Reserved.
41
41
 
42
42
  Notes
43
43
  -----
44
- Due to rate limiting implemented by the CDAS web services, an attempt
45
- to make simultaneous requests from many threads is likely to actually
46
- reduce performance. At this time, it is best to make calls from
47
- five or fewer threads.
44
+ <ul>
45
+ <li>Due to rate limiting implemented by the CDAS web services, an
46
+ attempt to make simultaneous requests from many threads is likely
47
+ to actually reduce performance. At this time, it is best to make
48
+ calls from five or fewer threads.</li>
49
+ <li>Since CDAS data has datetime values with a UTC timezone, all
50
+ client provided datetime values should have a timezone of UTC.
51
+ If a given value's timezone is not UTC, the value is adjusted to
52
+ UTC. If a given value has no timezone (is naive), a UTC timezone
53
+ is set.</li>
54
+ </ul>
48
55
  """
49
56
 
50
57
 
@@ -53,12 +60,14 @@ import os
53
60
  import platform
54
61
  import logging
55
62
  import re
63
+ from importlib.util import find_spec
56
64
  import urllib.parse
57
65
  from urllib.parse import urlparse
58
66
  import json
59
67
  from operator import itemgetter
60
68
  import time
61
69
  from datetime import datetime, timedelta, timezone
70
+ import xml.etree.ElementTree as ET
62
71
  from tempfile import mkstemp
63
72
  from typing import Any, Callable, Dict, List, Tuple, Union
64
73
 
@@ -72,11 +81,19 @@ from cdasws.datarequest import ImageFormat, GraphOptions, GraphRequest
72
81
  from cdasws.datarequest import TextFormat, TextRequest, ThumbnailRequest
73
82
  from cdasws.timeinterval import TimeInterval
74
83
 
75
- try:
84
+
85
+ # requires python >= 3.4
86
+ if find_spec('spacepy.datamodel') is not None:
76
87
  import spacepy.datamodel as spdm # type: ignore
77
88
  SPDM_AVAILABLE = True
78
- except ImportError:
89
+ else:
79
90
  SPDM_AVAILABLE = False
91
+ # python < 3.4
92
+ #try:
93
+ # import spacepy.datamodel as spdm # type: ignore
94
+ # SPDM_AVAILABLE = True
95
+ #except ImportError:
96
+ # SPDM_AVAILABLE = False
80
97
 
81
98
  try:
82
99
  from cdflib.xarray import cdf_to_xarray
@@ -120,7 +137,7 @@ except ImportError:
120
137
  CDF_XARRAY_AVAILABLE = False
121
138
 
122
139
 
123
- __version__ = "1.7.47"
140
+ __version__ = "1.8.3"
124
141
 
125
142
 
126
143
  #
@@ -129,6 +146,22 @@ __version__ = "1.7.47"
129
146
  #
130
147
  RETRY_LIMIT = 100
131
148
 
149
+ #
150
+ # XML schema namespace
151
+ #
152
+ NS = 'http://cdaweb.gsfc.nasa.gov/schema'
153
+ #
154
+ # XHTML schema namespace
155
+ #
156
+ XHTML_NS = 'http://www.w3.org/1999/xhtml'
157
+ #
158
+ # All namespaces found in responses.
159
+ #
160
+ NAMESPACES = {
161
+ 'cdas': NS,
162
+ 'xhtml': XHTML_NS
163
+ }
164
+
132
165
 
133
166
  def _get_data_progress(
134
167
  progress: float,
@@ -250,7 +283,7 @@ class CdasWs:
250
283
 
251
284
  self._request_headers = {
252
285
  'Content-Type' : 'application/json',
253
- 'Accept' : 'application/json',
286
+ 'Accept' : 'application/xml',
254
287
  'User-Agent' : self._user_agent,
255
288
  #'Accept-Encoding' : 'gzip' # only beneficial for icdfml responses
256
289
  }
@@ -332,17 +365,28 @@ class CdasWs:
332
365
  self.logger.info('response.text: %s', response.text)
333
366
  return []
334
367
 
335
- observatory_groups = response.json()
336
-
337
368
  if self.logger.level <= logging.DEBUG:
338
- self.logger.debug('observatory_groups: %s',
339
- json.dumps(observatory_groups,
340
- indent=4, sort_keys=True))
369
+ self.logger.debug('response.text = %s', response.text)
341
370
 
342
- if not observatory_groups:
343
- return []
371
+ observatory_response = ET.fromstring(response.text)
372
+
373
+ observatory_group_descriptions = []
374
+ for description in observatory_response.findall(\
375
+ 'cdas:ObservatoryGroupDescription', namespaces=NAMESPACES):
376
+
377
+ observatory_ids = []
378
+ for observatory_id in description.findall(\
379
+ 'cdas:ObservatoryId', namespaces=NAMESPACES):
380
+
381
+ observatory_ids.append(observatory_id.text)
344
382
 
345
- return observatory_groups['ObservatoryGroupDescription']
383
+ observatory_group_descriptions.append({
384
+ 'Name': description.find(\
385
+ 'cdas:Name', namespaces=NAMESPACES).text,
386
+ 'ObservatoryId': observatory_ids
387
+ })
388
+
389
+ return observatory_group_descriptions
346
390
 
347
391
 
348
392
  def get_instrument_types(
@@ -392,17 +436,24 @@ class CdasWs:
392
436
  self.logger.info('response.text: %s', response.text)
393
437
  return []
394
438
 
395
- instrument_types = response.json()
439
+ if self.logger.level <= logging.DEBUG:
440
+ self.logger.debug('response.text = %s', response.text)
441
+
442
+ instrument_response = ET.fromstring(response.text)
396
443
 
397
444
  if self.logger.level <= logging.DEBUG:
398
- self.logger.debug('instrument_types = %s',
399
- json.dumps(instrument_types, indent=4,
400
- sort_keys=True))
445
+ self.logger.debug('instrument_response = %s',
446
+ ET.tostring(instrument_response))
401
447
 
402
- if not instrument_types:
403
- return []
448
+ instrument_types = []
449
+ for description in instrument_response.findall(\
450
+ 'cdas:InstrumentTypeDescription', namespaces=NAMESPACES):
404
451
 
405
- return instrument_types['InstrumentTypeDescription']
452
+ instrument_types.append({
453
+ 'Name': description.find('cdas:Name',
454
+ namespaces=NAMESPACES).text
455
+ })
456
+ return instrument_types
406
457
 
407
458
 
408
459
  def get_instruments(
@@ -452,17 +503,29 @@ class CdasWs:
452
503
  self.logger.info('response.text: %s', response.text)
453
504
  return []
454
505
 
455
- instruments = response.json()
506
+ if self.logger.level <= logging.DEBUG:
507
+ self.logger.debug('response.text = %s', response.text)
508
+
509
+ instruments_response = ET.fromstring(response.text)
456
510
 
457
511
  if self.logger.level <= logging.DEBUG:
458
- self.logger.debug('instruments = %s',
459
- json.dumps(instruments, indent=4,
460
- sort_keys=True))
512
+ self.logger.debug('instruments = %s', response.text)
513
+ #ET.indent(instruments_response, space=' '))
461
514
 
462
- if not instruments:
463
- return []
515
+ instruments = []
516
+ for instrument_description in instruments_response.findall(\
517
+ 'cdas:InstrumentDescription', namespaces=NAMESPACES):
464
518
 
465
- return instruments['InstrumentDescription']
519
+ instruments.append({
520
+ 'Name': instrument_description.find(\
521
+ 'cdas:Name', namespaces=NAMESPACES).text,
522
+ 'ShortDescription': instrument_description.find(\
523
+ 'cdas:ShortDescription', namespaces=NAMESPACES).text,
524
+ 'LongDescription': instrument_description.find(\
525
+ 'cdas:LongDescription', namespaces=NAMESPACES).text
526
+ })
527
+
528
+ return instruments
466
529
 
467
530
 
468
531
  def get_observatories(
@@ -512,17 +575,28 @@ class CdasWs:
512
575
  self.logger.info('response.text: %s', response.text)
513
576
  return []
514
577
 
515
- observatories = response.json()
578
+ if self.logger.level <= logging.DEBUG:
579
+ self.logger.debug('response.text = %s', response.text)
580
+
581
+ observatory_response = ET.fromstring(response.text)
516
582
 
517
583
  if self.logger.level <= logging.DEBUG:
518
- self.logger.debug('observatories = %s',
519
- json.dumps(observatories, indent=4,
520
- sort_keys=True))
584
+ self.logger.debug('observatories = %s', response.text)
521
585
 
522
- if not observatories:
523
- return []
586
+ observatories = []
587
+
588
+ for observatory in observatory_response.findall(\
589
+ 'cdas:ObservatoryDescription', namespaces=NAMESPACES):
590
+ observatories.append({
591
+ 'Name': observatory.find(\
592
+ 'cdas:Name', namespaces=NAMESPACES).text,
593
+ 'ShortDescription': observatory.find(\
594
+ 'cdas:ShortDescription', namespaces=NAMESPACES).text,
595
+ 'LongDescription': observatory.find(\
596
+ 'cdas:LongDescription', namespaces=NAMESPACES).text
597
+ })
524
598
 
525
- return observatories['ObservatoryDescription']
599
+ return observatories
526
600
 
527
601
 
528
602
  def get_observatory_groups_and_instruments(
@@ -567,17 +641,55 @@ class CdasWs:
567
641
  self.logger.info('response.text: %s', response.text)
568
642
  return []
569
643
 
570
- observatories = response.json()
571
-
572
644
  if self.logger.level <= logging.DEBUG:
573
- self.logger.debug('observatories = %s',
574
- json.dumps(observatories, indent=4,
575
- sort_keys=True))
645
+ self.logger.debug('response.text = %s', response.text)
576
646
 
577
- if not observatories:
578
- return []
647
+ observatories_response = ET.fromstring(response.text)
579
648
 
580
- return observatories['ObservatoryGroupInstrumentDescription']
649
+ if self.logger.level <= logging.DEBUG:
650
+ self.logger.debug('observatories = %s', response.text)
651
+
652
+ o_g_i_ds = []
653
+
654
+ for o_g_i_d in observatories_response.findall(\
655
+ 'cdas:ObservatoryGroupInstrumentDescription',\
656
+ namespaces=NAMESPACES):
657
+
658
+ o_g_i_d_name = o_g_i_d.find('cdas:Name',
659
+ namespaces=NAMESPACES).text
660
+ o_is = []
661
+ for o_i in o_g_i_d.findall('cdas:ObservatoryInstruments',
662
+ namespaces=NAMESPACES):
663
+
664
+ o_i_name = o_i.find('cdas:Name',
665
+ namespaces=NAMESPACES).text
666
+ i_ds = []
667
+ for i_d in o_i.findall('cdas:InstrumentDescription',
668
+ namespaces=NAMESPACES):
669
+ i_d_name = i_d.find('cdas:Name',
670
+ namespaces=NAMESPACES).text
671
+ i_d_short_description = \
672
+ i_d.find('cdas:ShortDescription',
673
+ namespaces=NAMESPACES).text
674
+ i_d_long_description = \
675
+ i_d.find('cdas:LongDescription',
676
+ namespaces=NAMESPACES).text
677
+ i_ds.append({
678
+ 'Name': i_d_name,
679
+ 'ShortDescription': i_d_short_description,
680
+ 'LongDescription': i_d_long_description
681
+ })
682
+ o_is.append({
683
+ 'Name': o_i_name,
684
+ 'InstrumentDescription': i_ds
685
+ })
686
+
687
+ o_g_i_ds.append({
688
+ 'Name': o_g_i_d_name,
689
+ 'ObservatoryInstruments': o_is
690
+ })
691
+
692
+ return o_g_i_ds
581
693
 
582
694
 
583
695
  # pylint: disable=too-many-branches
@@ -610,11 +722,13 @@ class CdasWs:
610
722
  that no datasets are eliminated based upon their instrument
611
723
  value.<br>
612
724
  <b>startDate</b> - a datetime specifying the start of a time
613
- interval. If this parameter is ommited, the time interval
614
- will begin infinitely in the past.<br>
725
+ interval. See module note about timezone value. If this
726
+ parameter is ommited, the time interval will begin infinitely
727
+ in the past.<br>
615
728
  <b>stopDate</b> - a datetime specifying the end of a time
616
- interval. If this parameter is omitted, the time interval
617
- will end infinitely in the future.<br>
729
+ interval. See module note about timezone value. If this
730
+ parameter is omitted, the time interval will end infinitely
731
+ in the future.<br>
618
732
  <b>idPattern</b> - a java.util.regex compatible regular
619
733
  expression that must match the dataset's identifier value.
620
734
  Omitting this parameter is equivalent to `.*`.<br>
@@ -702,16 +816,97 @@ class CdasWs:
702
816
  self.logger.info('response.text: %s', response.text)
703
817
  return []
704
818
 
705
- datasets = response.json()
706
-
707
819
  if self.logger.level <= logging.DEBUG:
708
- self.logger.debug('datasets = %s',
709
- json.dumps(datasets, indent=4, sort_keys=True))
820
+ self.logger.debug('response.text = %s', response.text)
710
821
 
711
- if not datasets:
712
- return []
822
+ dss = ET.fromstring(response.text)
713
823
 
714
- return sorted(datasets['DatasetDescription'], key=itemgetter('Id'))
824
+ if self.logger.level <= logging.DEBUG:
825
+ self.logger.debug('datasets = %s', response.text)
826
+
827
+ datasets = []
828
+ for ds in dss.findall('cdas:DatasetDescription',
829
+ namespaces=NAMESPACES):
830
+
831
+ observatory_groups = []
832
+ for o_g in ds.findall('cdas:ObservatoryGroup',
833
+ namespaces=NAMESPACES):
834
+ observatory_groups.append(o_g.text)
835
+
836
+ instrument_types = []
837
+ for i_t in ds.findall('cdas:InstrumentType',
838
+ namespaces=NAMESPACES):
839
+ instrument_types.append(i_t.text)
840
+
841
+ dataset_links = []
842
+ for d_l in ds.findall('cdas:DatasetLink',
843
+ namespaces=NAMESPACES):
844
+ dataset_links.append({
845
+ 'Title': d_l.find('cdas:Title',
846
+ namespaces=NAMESPACES).text,
847
+ 'Text': d_l.find('cdas:Text',
848
+ namespaces=NAMESPACES).text,
849
+ 'Url': d_l.find('cdas:Url',
850
+ namespaces=NAMESPACES).text,
851
+ })
852
+
853
+ observatories = []
854
+ for obs_elem in ds.findall('cdas:Observatory',
855
+ namespaces=NAMESPACES):
856
+ observatories.append(obs_elem.text)
857
+
858
+ instruments = []
859
+ for instr_elem in ds.findall('cdas:Instrument',
860
+ namespaces=NAMESPACES):
861
+ instruments.append(instr_elem.text)
862
+
863
+ dataset = {
864
+ 'Id': ds.find('cdas:Id', namespaces=NAMESPACES).text,
865
+ 'Observatory': observatories,
866
+ 'Instrument': instruments,
867
+ 'ObservatoryGroup': observatory_groups,
868
+ 'InstrumentType': instrument_types,
869
+ 'Label': ds.find('cdas:Label',
870
+ namespaces=NAMESPACES).text,
871
+ 'TimeInterval': {
872
+ 'Start': ds.find('cdas:TimeInterval/cdas:Start',
873
+ namespaces=NAMESPACES).text,
874
+ 'End': ds.find('cdas:TimeInterval/cdas:End',
875
+ namespaces=NAMESPACES).text
876
+ },
877
+ 'PiName': ds.find('cdas:PiName',
878
+ namespaces=NAMESPACES).text,
879
+ 'PiAffiliation': ds.find('cdas:PiAffiliation',
880
+ namespaces=NAMESPACES).text,
881
+ 'Notes': ds.find('cdas:Notes',
882
+ namespaces=NAMESPACES).text,
883
+ 'DatasetLink': dataset_links
884
+ }
885
+ doi = ds.find('cdas:Doi', namespaces=NAMESPACES)
886
+ if doi is not None:
887
+ dataset['Doi'] = doi.text
888
+
889
+ spase_resource_id = ds.find('cdas:SpaseResourceId',
890
+ namespaces=NAMESPACES)
891
+ if spase_resource_id is not None:
892
+ dataset['SpaseResourceId'] = spase_resource_id.text
893
+
894
+ additional_metadata = []
895
+ for add_meta in ds.findall('cdas:AdditionalMetadata',
896
+ namespaces=NAMESPACES):
897
+ meta_type = add_meta.attrib['Type']
898
+ value = add_meta.text
899
+ additional_metadata.append({
900
+ 'Type': meta_type,
901
+ 'value': value
902
+ })
903
+
904
+ if len(additional_metadata) > 0:
905
+ dataset['AdditionalMetadata'] = additional_metadata
906
+
907
+ datasets.append(dataset)
908
+
909
+ return sorted(datasets, key=itemgetter('Id'))
715
910
  # pylint: enable=too-many-branches
716
911
 
717
912
 
@@ -782,24 +977,23 @@ class CdasWs:
782
977
  self.logger.info('response.text: %s', response.text)
783
978
  return []
784
979
 
785
- inventory = response.json()
786
-
787
980
  if self.logger.level <= logging.DEBUG:
788
- self.logger.debug('inventory = %s',
789
- json.dumps(inventory, indent=4, sort_keys=True))
981
+ self.logger.debug('response.text = %s', response.text)
790
982
 
983
+ inventory = ET.fromstring(response.text)
791
984
  intervals = []
792
-
793
- data_intervals = inventory['InventoryDescription'][0]
794
-
795
- if 'TimeInterval' in data_intervals:
796
-
797
- for time_interval in data_intervals['TimeInterval']:
798
-
985
+ for inventory_desc in inventory.findall(\
986
+ 'cdas:InventoryDescription',
987
+ namespaces=NAMESPACES):
988
+ for time_interval in inventory_desc.findall(\
989
+ 'cdas:TimeInterval',
990
+ namespaces=NAMESPACES):
799
991
  intervals.append(
800
992
  TimeInterval(
801
- time_interval['Start'],
802
- time_interval['End']
993
+ time_interval.find('cdas:Start',
994
+ namespaces=NAMESPACES).text,
995
+ time_interval.find('cdas:End',
996
+ namespaces=NAMESPACES).text
803
997
  )
804
998
  )
805
999
 
@@ -872,12 +1066,36 @@ class CdasWs:
872
1066
  self.logger.info('response.text: %s', response.text)
873
1067
  return []
874
1068
 
875
- variables = response.json()
1069
+ if self.logger.level <= logging.DEBUG:
1070
+ self.logger.debug('response.text = %s', response.text)
876
1071
 
877
- if not variables:
878
- return []
1072
+ var_descriptions = ET.fromstring(response.text)
1073
+
1074
+ variables = []
1075
+ for var_description in var_descriptions.findall(\
1076
+ 'cdas:VariableDescription',
1077
+ namespaces=NAMESPACES):
1078
+ name = var_description.find('cdas:Name',
1079
+ namespaces=NAMESPACES).text
1080
+ short_description = var_description.find(\
1081
+ 'cdas:ShortDescription',
1082
+ namespaces=NAMESPACES).text
1083
+ if short_description is None:
1084
+ short_description = ''
1085
+
1086
+ long_description = var_description.find(\
1087
+ 'cdas:LongDescription',
1088
+ namespaces=NAMESPACES).text
1089
+ if long_description is None:
1090
+ long_description = ''
879
1091
 
880
- return variables['VariableDescription']
1092
+ variables.append({
1093
+ 'Name': name,
1094
+ 'ShortDescription': short_description,
1095
+ 'LongDescription': long_description
1096
+ })
1097
+
1098
+ return variables
881
1099
 
882
1100
 
883
1101
  def get_variable_names(
@@ -906,6 +1124,143 @@ class CdasWs:
906
1124
  return variable_names
907
1125
 
908
1126
 
1127
+ @staticmethod
1128
+ def _get_thumbnail_description_dict(
1129
+ file_description_elem: ET.Element
1130
+ ) -> Dict:
1131
+ """
1132
+ Gets ThumbnailDescription dictionary representation from the
1133
+ given FileDescription element.
1134
+
1135
+ Parameters
1136
+ ----------
1137
+ file_description_elem
1138
+ a FileDescription Element.
1139
+ Returns
1140
+ -------
1141
+ Dict
1142
+ a Dictionary representation of the ThumbnailDescription
1143
+ contained in the given FileDescription element.
1144
+ """
1145
+ thumbnail_desc = file_description_elem.find(\
1146
+ 'cdas:ThumbnailDescription',
1147
+ namespaces=NAMESPACES)
1148
+ if thumbnail_desc is not None:
1149
+ time_interval = thumbnail_desc.find('cdas:TimeInterval',
1150
+ namespaces=NAMESPACES)
1151
+ start = time_interval.find('cdas:Start',
1152
+ namespaces=NAMESPACES).text
1153
+ end = time_interval.find('cdas:End',
1154
+ namespaces=NAMESPACES).text
1155
+ return {
1156
+ 'Name': thumbnail_desc.find('cdas:Name',
1157
+ namespaces=NAMESPACES).text,
1158
+ 'Dataset': thumbnail_desc.find('cdas:Dataset',
1159
+ namespaces=NAMESPACES).text,
1160
+ 'TimeInterval': {
1161
+ 'Start': start,
1162
+ 'End': end
1163
+ },
1164
+ 'VarName': thumbnail_desc.find('cdas:VarName',
1165
+ namespaces=NAMESPACES).text,
1166
+ 'Options': int(thumbnail_desc.find(\
1167
+ 'cdas:Options',
1168
+ namespaces=NAMESPACES).text),
1169
+ 'NumFrames': int(thumbnail_desc.find(\
1170
+ 'cdas:NumFrames',
1171
+ namespaces=NAMESPACES).text),
1172
+ 'NumRows': int(thumbnail_desc.find(\
1173
+ 'cdas:NumRows',
1174
+ namespaces=NAMESPACES).text),
1175
+ 'NumCols': int(thumbnail_desc.find(\
1176
+ 'cdas:NumCols',
1177
+ namespaces=NAMESPACES).text),
1178
+ 'TitleHeight': int(thumbnail_desc.find(\
1179
+ 'cdas:TitleHeight',
1180
+ namespaces=NAMESPACES).text),
1181
+ 'ThumbnailHeight': int(thumbnail_desc.find(\
1182
+ 'cdas:ThumbnailHeight',
1183
+ namespaces=NAMESPACES).text),
1184
+ 'ThumbnailWidth': int(thumbnail_desc.find(\
1185
+ 'cdas:ThumbnailWidth',
1186
+ namespaces=NAMESPACES).text),
1187
+ 'StartRecord': int(thumbnail_desc.find(\
1188
+ 'cdas:StartRecord',
1189
+ namespaces=NAMESPACES).text),
1190
+ 'MyScale': float(thumbnail_desc.find(\
1191
+ 'cdas:MyScale',
1192
+ namespaces=NAMESPACES).text),
1193
+ 'XyStep': float(thumbnail_desc.find(\
1194
+ 'cdas:XyStep',
1195
+ namespaces=NAMESPACES).text)
1196
+ }
1197
+ return None
1198
+
1199
+
1200
+ @staticmethod
1201
+ def _get_data_result_dict(
1202
+ xml_data_result: str
1203
+ ) -> Dict:
1204
+ """
1205
+ Gets DataResult dictionary representation from the
1206
+ given XML DataResult element.
1207
+
1208
+ Parameters
1209
+ ----------
1210
+ xml_data_result
1211
+ XML representation of a DataResult.
1212
+ Returns
1213
+ -------
1214
+ Dict
1215
+ a Dictionary representation of the given XML representation
1216
+ of a DataResult.
1217
+ """
1218
+ data_result = ET.fromstring(xml_data_result)
1219
+ file_descriptions = []
1220
+ for file_description in data_result.findall(\
1221
+ 'cdas:FileDescription', namespaces=NAMESPACES):
1222
+
1223
+ dict_file_description = {
1224
+ 'Name': file_description.find('cdas:Name',
1225
+ namespaces=NAMESPACES).text,
1226
+ 'MimeType': file_description.find(\
1227
+ 'cdas:MimeType',
1228
+ namespaces=NAMESPACES).text,
1229
+ 'StartTime': file_description.find(\
1230
+ 'cdas:StartTime',
1231
+ namespaces=NAMESPACES).text,
1232
+ 'EndTime': file_description.find(\
1233
+ 'cdas:EndTime',
1234
+ namespaces=NAMESPACES).text,
1235
+ 'Length': int(file_description.find(\
1236
+ 'cdas:Length',
1237
+ namespaces=NAMESPACES).text),
1238
+ 'LastModified': file_description.find(\
1239
+ 'cdas:LastModified',
1240
+ namespaces=NAMESPACES).text
1241
+ }
1242
+ thumbnail_dict = CdasWs._get_thumbnail_description_dict(\
1243
+ file_description)
1244
+ if thumbnail_dict is not None:
1245
+ dict_file_description['ThumbnailDescription'] = \
1246
+ thumbnail_dict
1247
+
1248
+ thumbnail_id_elem = file_description.find(\
1249
+ 'cdas:ThumbnailId',
1250
+ namespaces=NAMESPACES)
1251
+ if thumbnail_id_elem is not None:
1252
+ dict_file_description['ThumbnailId'] = \
1253
+ thumbnail_id_elem.text
1254
+
1255
+ file_descriptions.append(dict_file_description)
1256
+
1257
+ if len(file_descriptions) > 0:
1258
+ return {
1259
+ 'FileDescription': file_descriptions
1260
+ }
1261
+ return None
1262
+
1263
+
909
1264
  def get_data_result(
910
1265
  self,
911
1266
  data_request: DataRequest,
@@ -953,18 +1308,10 @@ class CdasWs:
953
1308
  response = self._session.post(url, data=data_request.json(),
954
1309
  timeout=self._timeout)
955
1310
 
956
- try:
957
- data_result = response.json()
958
- except ValueError:
959
- # for example, a 503 from apache will not be json
960
- self.logger.debug('Non-JSON response: %s', response.text)
961
-
962
- if self.logger.level <= logging.DEBUG:
963
- self.logger.debug('data_result = %s',
964
- json.dumps(data_result, indent=4,
965
- sort_keys=True))
966
1311
  if response.status_code == 200:
967
1312
 
1313
+ data_result = CdasWs._get_data_result_dict(response.text)
1314
+
968
1315
  if not data_result:
969
1316
  return (response.status_code, None)
970
1317
 
@@ -1025,9 +1372,9 @@ class CdasWs:
1025
1372
  variables
1026
1373
  array containing names of variables to get.
1027
1374
  start
1028
- start time of data to get.
1375
+ start time of data to get. See module note about timezone.
1029
1376
  end
1030
- end time of data to get.
1377
+ end time of data to get. See module note about timezone.
1031
1378
  keywords
1032
1379
  optional keyword parameters as follows:<br>
1033
1380
  <b>binData</b> - indicates that uniformly spaced values should
@@ -1065,7 +1412,7 @@ class CdasWs:
1065
1412
  --------
1066
1413
  CdasWs.get_data : In addition to what get_data_file does,
1067
1414
  get_data also downloads and reads the data file into memory
1068
- (SpaceData object).
1415
+ (SpaceData or xarray.Dataset object).
1069
1416
  """
1070
1417
  # pylint: disable=too-many-locals
1071
1418
  # pylint: disable=too-many-return-statements
@@ -1263,10 +1610,11 @@ class CdasWs:
1263
1610
  ALL-VARIABLES may be used instead of specifying all the
1264
1611
  individual variable names.
1265
1612
  time0
1266
- TimeInterval(s) or start time of data to get.
1613
+ TimeInterval(s) or start time of data to get. See module
1614
+ note about timezone.
1267
1615
  time1
1268
1616
  when time0 is not one or more TimeInterval(s), the end time
1269
- of data to get.
1617
+ of data to get. See module note about timezone.
1270
1618
  keywords
1271
1619
  optional keyword parameters as follows:<br>
1272
1620
  <b>binData</b> - indicates that uniformly spaced values should
@@ -1298,7 +1646,7 @@ class CdasWs:
1298
1646
  Tuple
1299
1647
  [0] contains a dictionary of HTTP and CDAS status information.
1300
1648
  When successful, ['http']['status_code'] will be 200.<br>
1301
- [1] contains the requested data (SpaceData or xarray.Dateset
1649
+ [1] contains the requested data (SpaceData or xarray.Dataset
1302
1650
  object) or None.
1303
1651
  Raises
1304
1652
  ------
@@ -1449,9 +1797,9 @@ class CdasWs:
1449
1797
  variables
1450
1798
  array containing names of variables to get.
1451
1799
  start
1452
- start time of data to get.
1800
+ start time of data to get. See module note about timezone.
1453
1801
  end
1454
- end time of data to get.
1802
+ end time of data to get. See module note about timezone.
1455
1803
  options
1456
1804
  graph options.
1457
1805
  image_format
@@ -1552,9 +1900,9 @@ class CdasWs:
1552
1900
  variables
1553
1901
  array containing names of variables to get.
1554
1902
  start
1555
- start time of data to get.
1903
+ start time of data to get. See module note about timezone.
1556
1904
  end
1557
- end time of data to get.
1905
+ end time of data to get. See module note about timezone.
1558
1906
  identifier
1559
1907
  thumbnail identifier (returned in a previous get_graph
1560
1908
  result).
@@ -1651,9 +1999,9 @@ class CdasWs:
1651
1999
  variables
1652
2000
  array containing names of variables to get.
1653
2001
  start
1654
- start time of data to get.
2002
+ start time of data to get. See module note about timezone.
1655
2003
  end
1656
- end time of data to get.
2004
+ end time of data to get. See module note about timezone.
1657
2005
  compression
1658
2006
  file compression.
1659
2007
  text_format
@@ -1753,9 +2101,9 @@ class CdasWs:
1753
2101
  variables
1754
2102
  array containing names of variables to get.
1755
2103
  start
1756
- start time of data to get.
2104
+ start time of data to get. See module note about timezone.
1757
2105
  end
1758
- end time of data to get.
2106
+ end time of data to get. See module note about timezone.
1759
2107
  keywords
1760
2108
  optional keyword parameters as follows:<br>
1761
2109
  <b>binData</b> - indicates that uniformly spaced values should
@@ -1848,9 +2196,9 @@ class CdasWs:
1848
2196
  dataset
1849
2197
  dataset identifier of data to get.
1850
2198
  start
1851
- start time of data to get.
2199
+ start time of data to get. See module note about timezone.
1852
2200
  end
1853
- end time of data to get.
2201
+ end time of data to get. See module note about timezone.
1854
2202
  keywords
1855
2203
  optional keyword parameters as follows:<br>
1856
2204
  <b>progressCallback</b> - is a
@@ -1962,14 +2310,20 @@ class CdasWs:
1962
2310
  self.logger.info('response.text: %s', response.text)
1963
2311
  return (response.status_code, None)
1964
2312
 
1965
- results = response.json()
1966
-
1967
2313
  if self.logger.level <= logging.DEBUG:
1968
- self.logger.debug('results: %s',
1969
- json.dumps(results,
1970
- indent=4, sort_keys=True))
2314
+ self.logger.debug('response.text = %s', response.text)
1971
2315
 
1972
- if not results:
1973
- return (response.status_code, None)
2316
+ results = ET.fromstring(response.text)
2317
+
2318
+ ssc_id = []
2319
+ for ssc_id_elem in results.findall('SscId'):
2320
+ ssc_id.append(ssc_id_elem.text)
2321
+
2322
+ if len(ssc_id) == 0:
2323
+ result = None
2324
+ elif len(ssc_id) == 1:
2325
+ result = ssc_id[0]
2326
+ else:
2327
+ result = ssc_id
1974
2328
 
1975
- return (response.status_code, results.get('SscId', None))
2329
+ return (response.status_code, result)
cdasws/datarequest.py CHANGED
@@ -24,7 +24,7 @@
24
24
  #
25
25
  # NOSA HEADER END
26
26
  #
27
- # Copyright (c) 2019-2023 United States Government as represented by
27
+ # Copyright (c) 2019-2024 United States Government as represented by
28
28
  # the National Aeronautics and Space Administration. No copyright is
29
29
  # claimed in the United States under Title 17, U.S.Code. All Other
30
30
  # Rights Reserved.
@@ -36,7 +36,7 @@ Package defining classes to represent the DataRequestEntity and its
36
36
  sub-classes from
37
37
  <https://cdaweb.gsfc.nasa.gov/WebServices/REST/CDAS.xsd>.<br>
38
38
 
39
- Copyright &copy; 2019-2023 United States Government as represented by the
39
+ Copyright &copy; 2019-2024 United States Government as represented by the
40
40
  National Aeronautics and Space Administration. No copyright is claimed in
41
41
  the United States under Title 17, U.S.Code. All Other Rights Reserved.
42
42
  """
@@ -378,6 +378,7 @@ class GraphOptions:
378
378
  related variables from different datasets will result in the
379
379
  same graphs being produced as when the option is not included.
380
380
  """
381
+ # pylint: disable=too-many-arguments
381
382
  def __init__(self,
382
383
  coarse_noise_filter: bool = False,
383
384
  x_axis_width_factor: int = 3,
@@ -391,6 +392,7 @@ class GraphOptions:
391
392
  self._y_axis_height_factor = y_axis_height_factor
392
393
  self._combine = combine
393
394
  self._overplot = overplot
395
+ # pylint: enable=too-many-arguments
394
396
 
395
397
 
396
398
  @property
cdasws/timeinterval.py CHANGED
@@ -24,7 +24,7 @@
24
24
  #
25
25
  # NOSA HEADER END
26
26
  #
27
- # Copyright (c) 2018-2021 United States Government as represented by
27
+ # Copyright (c) 2018-2024 United States Government as represented by
28
28
  # the National Aeronautics and Space Administration. No copyright is
29
29
  # claimed in the United States under Title 17, U.S.Code. All Other
30
30
  # Rights Reserved.
@@ -35,7 +35,7 @@
35
35
  Package defining a class to represent the TimeInterval class from
36
36
  <https://cdaweb.gsfc.nasa.gov/WebServices/REST/CDAS.xsd>.<br>
37
37
 
38
- Copyright &copy; 2018-2021 United States Government as represented by the
38
+ Copyright &copy; 2018-2024 United States Government as represented by the
39
39
  National Aeronautics and Space Administration. No copyright is claimed in
40
40
  the United States under Title 17, U.S.Code. All Other Rights Reserved.
41
41
  """
@@ -53,6 +53,14 @@ class TimeInterval:
53
53
  A class representing a TimeInterval class from
54
54
  <https://cdaweb.gsfc.nasa.gov/WebServices/REST/CDAS.xsd>.
55
55
 
56
+ Notes
57
+ -----
58
+ Since CDAS data uses datetime values with a UTC timezone,
59
+ the resulting start and end properties have a UTC timezone. If
60
+ the given values' timezone is not UTC, the start/end values are
61
+ adjusted to UTC. If the given value has no timezone (is naive) a
62
+ UTC value is set.
63
+
56
64
  Parameters
57
65
  ----------
58
66
  start
@@ -135,7 +143,11 @@ class TimeInterval:
135
143
  @staticmethod
136
144
  def get_datetime(value: Union[datetime, str]) -> datetime:
137
145
  """
138
- Produces a datetime representation of the given value.
146
+ Produces a datetime representation of the given value. The
147
+ returned datetime always has a timezone of timezone.utc. If
148
+ the given value's timezone is not utc, the return value is
149
+ adjusted to utc. If the given value has no timezone (is naive)
150
+ a utc value is set.
139
151
 
140
152
  Parameters
141
153
  ----------
@@ -144,7 +156,8 @@ class TimeInterval:
144
156
  Returns
145
157
  -------
146
158
  datetime
147
- datetime representation of the given value.
159
+ datetime representation of the given value in the UTC
160
+ timezone.
148
161
  Raises
149
162
  ------
150
163
  ValueError
@@ -157,9 +170,11 @@ class TimeInterval:
157
170
  else:
158
171
  raise ValueError('unrecognized datetime value')
159
172
 
160
- datetime_value.astimezone(timezone.utc)
173
+ if datetime_value.tzinfo is None or \
174
+ datetime_value.tzinfo.utcoffset(None) is None:
175
+ return datetime_value.replace(tzinfo=timezone.utc)
161
176
 
162
- return datetime_value
177
+ return datetime_value.astimezone(timezone.utc)
163
178
 
164
179
 
165
180
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cdasws
3
- Version: 1.7.47
3
+ Version: 1.8.3
4
4
  Summary: NASA's Coordinated Data Analysis System Web Service Client Library
5
5
  Home-page: https://cdaweb.gsfc.nasa.gov/WebServices/REST
6
6
  Author: Bernie Harris
@@ -0,0 +1,13 @@
1
+ cdasws/__init__.py,sha256=fAJWF-p8WAD92OoHpnTbicLhCCWM_Cbp9WvcCMXJ7Qs,90144
2
+ cdasws/__main__.py,sha256=sgIqwgEaTto8mJggMAq3UppD6vus5DNQ4fWS3mvxvtY,12411
3
+ cdasws/cdasws.py,sha256=755mLWlMFXknNP3f8g_W9An6niAmksqcb1NdgiAybj0,29552
4
+ cdasws/datarepresentation.py,sha256=kKgAKCA4uzGoBQ8r8jbXstBg8SCg_EQtsoGhO8kIpco,2164
5
+ cdasws/datarequest.py,sha256=RT6lZH2ArDB_N_cTu3fzIxhVuTWlr5dzLGmv086IQmY,21713
6
+ cdasws/timeinterval.py,sha256=677SULpzclE4XqFuU4_SjZLVCZKlQKa2ekvswnmDVkU,6392
7
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ tests/test_cdasws.py,sha256=DsKesU19_9zUIO1a7oyWWtywBGN7gjdQHFLMGUZ4X8c,24395
9
+ cdasws-1.8.3.dist-info/LICENSE,sha256=og42scUY42lPLGBg0wHt6JtrbeInVEr5xojyrguPqrQ,12583
10
+ cdasws-1.8.3.dist-info/METADATA,sha256=zpbX0jD6vHaI1YKNIwI_OlTyw2by5-VHJRES2zSjHgI,5744
11
+ cdasws-1.8.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
12
+ cdasws-1.8.3.dist-info/top_level.txt,sha256=GyIvHk5F6ysnTdDfnQsjZbTBt_EYrKyC0oeiIt9l-AE,7
13
+ cdasws-1.8.3.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- cdasws/__init__.py,sha256=8UpPbGwbzIBxeAN7uWmwXbZMTiO1pJk565syQ7ZGtf0,74559
2
- cdasws/__main__.py,sha256=sgIqwgEaTto8mJggMAq3UppD6vus5DNQ4fWS3mvxvtY,12411
3
- cdasws/cdasws.py,sha256=755mLWlMFXknNP3f8g_W9An6niAmksqcb1NdgiAybj0,29552
4
- cdasws/datarepresentation.py,sha256=kKgAKCA4uzGoBQ8r8jbXstBg8SCg_EQtsoGhO8kIpco,2164
5
- cdasws/datarequest.py,sha256=DutrESTuL9BVoMRymtVuxN4koXeaRxe9cT-jEoDVK8I,21632
6
- cdasws/timeinterval.py,sha256=d4i2Q8CtbAX0JdTLTCpyw40r16CycrUQACxhUVogu8E,5664
7
- tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- tests/test_cdasws.py,sha256=DsKesU19_9zUIO1a7oyWWtywBGN7gjdQHFLMGUZ4X8c,24395
9
- cdasws-1.7.47.dist-info/LICENSE,sha256=og42scUY42lPLGBg0wHt6JtrbeInVEr5xojyrguPqrQ,12583
10
- cdasws-1.7.47.dist-info/METADATA,sha256=f5qIZPV2tQArAIG4zPi62caRsfZ-967EP83IpVxQKLk,5745
11
- cdasws-1.7.47.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
12
- cdasws-1.7.47.dist-info/top_level.txt,sha256=GyIvHk5F6ysnTdDfnQsjZbTBt_EYrKyC0oeiIt9l-AE,7
13
- cdasws-1.7.47.dist-info/RECORD,,