from earthaccess import Auth, DataCollections, DataGranules, Store
auth = Auth()
Auth()¶
earthaccess's Auth class provides 3 different strategies to authenticate ourselves with NASA EDL.
- netrc: Do we have a
.netrcfile with our EDL credentials? if so, we can use it withearthaccess. If we don't have it and want to create one we can, earthaccess allows users to type their credentials and persist them into a.netrcfile. - environment: If we have our EDL credentials as environment variables
- EARTHDATA_USERNAME
- EARTHDATA_PASSWORD
- interactive: We will be asked for our EDL credentials with optional persistence to
.netrc
To persist our credentials to a .netrc file we have to do the following:
auth.login(strategy="interactive", persist=True)
In this notebook we'll use the environment method followed by the netrc strategy. You can of course use the interactive strategy if you don't have a .netrc file.
auth.login(strategy="environment")
# are we authenticated?
if not auth.authenticated:
auth.login(strategy="netrc")
Querying for restricted datasets¶
The DataCollection client can query CMR for any collection (dataset) using all of CMR's Query parameters and has built-in functions to extract useful information from the response.
auth.refresh_tokens()
If we belong to an early adopter group within NASA we can pass the Auth object to the other classes when we instantiate them.
# An anonymous query to CMR
Query = DataCollections().keyword('elevation')
# An authenticated query to CMR
Query = DataCollections(auth).keyword('elevation')
and it's the same with DataGranules
# An anonymous query to CMR
Query = DataGranules().keyword('elevation')
# An authenticated query to CMR
Query = DataGranules(auth).keyword('elevation')
Note: Some collections under an access control list are flagged by CMR and won't count when asking about results with
hits().
# The first step is to create a DataCollections query
Query = DataCollections()
# Use chain methods to customize our query
Query.short_name("ATL06").version("006")
print(f"Collections found: {Query.hits()}")
# filtering what UMM fields to print, to see the full record we omit the fields filters
# meta is always included as
collections = Query.fields(["ShortName", "Version"]).get(5)
# Inspect some results printing just the ShortName and Abstract
collections
Collections found: 1
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/core/formatters.py:770, in PlainTextFormatter.__call__(self, obj) 763 stream = StringIO() 764 printer = pretty.RepresentationPrinter(stream, self.verbose, 765 self.max_width, self.newline, 766 max_seq_length=self.max_seq_length, 767 singleton_pprinters=self.singleton_printers, 768 type_pprinters=self.type_printers, 769 deferred_pprinters=self.deferred_printers) --> 770 printer.pretty(obj) 771 printer.flush() 772 return stream.getvalue() File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:386, in RepresentationPrinter.pretty(self, obj) 383 for cls in _get_mro(obj_class): 384 if cls in self.type_pprinters: 385 # printer registered in self.type_pprinters --> 386 return self.type_pprinters[cls](obj, self, cycle) 387 else: 388 # deferred printer 389 printer = self._in_deferred_types(cls) File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:641, in _seq_pprinter_factory.<locals>.inner(obj, p, cycle) 639 p.text(',') 640 p.breakable() --> 641 p.pretty(x) 642 if len(obj) == 1 and isinstance(obj, tuple): 643 # Special case for 1-item tuples. 644 p.text(',') File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:411, in RepresentationPrinter.pretty(self, obj) 400 return meth(obj, self, cycle) 401 if ( 402 cls is not object 403 # check if cls defines __repr__ (...) 409 and callable(_safe_getattr(cls, "__repr__", None)) 410 ): --> 411 return _repr_pprint(obj, self, cycle) 413 return _default_pprint(obj, self, cycle) 414 finally: File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:786, in _repr_pprint(obj, p, cycle) 784 """A pprint that just redirects to the normal repr function.""" 785 # Find newlines and replace them with p.break_() --> 786 output = repr(obj) 787 lines = output.splitlines() 788 with p.group(): File ~/checkouts/readthedocs.org/user_builds/earthaccess/checkouts/latest/earthaccess/results.py:315, in DataCollection.__repr__(self) 314 def __repr__(self) -> str: --> 315 return json.dumps( 316 self.render_dict, 317 sort_keys=False, 318 indent=2, 319 separators=(",", ": "), 320 ) File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/__init__.py:238, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw) 232 if cls is None: 233 cls = JSONEncoder 234 return cls( 235 skipkeys=skipkeys, ensure_ascii=ensure_ascii, 236 check_circular=check_circular, allow_nan=allow_nan, indent=indent, 237 separators=separators, default=default, sort_keys=sort_keys, --> 238 **kw).encode(obj) File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:202, in JSONEncoder.encode(self, o) 200 chunks = self.iterencode(o, _one_shot=True) 201 if not isinstance(chunks, (list, tuple)): --> 202 chunks = list(chunks) 203 return ''.join(chunks) File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:432, in _make_iterencode.<locals>._iterencode(o, _current_indent_level) 430 yield from _iterencode_list(o, _current_indent_level) 431 elif isinstance(o, dict): --> 432 yield from _iterencode_dict(o, _current_indent_level) 433 else: 434 if markers is not None: File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:406, in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level) 404 else: 405 chunks = _iterencode(value, _current_indent_level) --> 406 yield from chunks 407 if newline_indent is not None: 408 _current_indent_level -= 1 File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:439, in _make_iterencode.<locals>._iterencode(o, _current_indent_level) 437 raise ValueError("Circular reference detected") 438 markers[markerid] = o --> 439 o = _default(o) 440 yield from _iterencode(o, _current_indent_level) 441 if markers is not None: File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:180, in JSONEncoder.default(self, o) 161 def default(self, o): 162 """Implement this method in a subclass such that it returns 163 a serializable object for ``o``, or calls the base implementation 164 (to raise a ``TypeError``). (...) 178 179 """ --> 180 raise TypeError(f'Object of type {o.__class__.__name__} ' 181 f'is not JSON serializable') TypeError: Object of type set is not JSON serializable
if not auth.refresh_tokens():
print("Something went wrong, we may need to regenerate our tokens manually")
/tmp/ipykernel_1665/2137380767.py:1: DeprecationWarning: No replacement, as tokens are now refreshed automatically. if not auth.refresh_tokens():
Query = DataCollections(auth)
# Use chain methods to customize our query
Query.short_name("ATL06").version("006")
# This will say 1, even though we get 2 back.
print(f"Collections found: {Query.hits()}")
collections = Query.fields(["ShortName", "Version"]).get()
# Inspect some results printing just the ShortName and Abstract
collections
Collections found: 1
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/core/formatters.py:770, in PlainTextFormatter.__call__(self, obj) 763 stream = StringIO() 764 printer = pretty.RepresentationPrinter(stream, self.verbose, 765 self.max_width, self.newline, 766 max_seq_length=self.max_seq_length, 767 singleton_pprinters=self.singleton_printers, 768 type_pprinters=self.type_printers, 769 deferred_pprinters=self.deferred_printers) --> 770 printer.pretty(obj) 771 printer.flush() 772 return stream.getvalue() File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:386, in RepresentationPrinter.pretty(self, obj) 383 for cls in _get_mro(obj_class): 384 if cls in self.type_pprinters: 385 # printer registered in self.type_pprinters --> 386 return self.type_pprinters[cls](obj, self, cycle) 387 else: 388 # deferred printer 389 printer = self._in_deferred_types(cls) File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:641, in _seq_pprinter_factory.<locals>.inner(obj, p, cycle) 639 p.text(',') 640 p.breakable() --> 641 p.pretty(x) 642 if len(obj) == 1 and isinstance(obj, tuple): 643 # Special case for 1-item tuples. 644 p.text(',') File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:411, in RepresentationPrinter.pretty(self, obj) 400 return meth(obj, self, cycle) 401 if ( 402 cls is not object 403 # check if cls defines __repr__ (...) 409 and callable(_safe_getattr(cls, "__repr__", None)) 410 ): --> 411 return _repr_pprint(obj, self, cycle) 413 return _default_pprint(obj, self, cycle) 414 finally: File ~/checkouts/readthedocs.org/user_builds/earthaccess/envs/latest/lib/python3.12/site-packages/IPython/lib/pretty.py:786, in _repr_pprint(obj, p, cycle) 784 """A pprint that just redirects to the normal repr function.""" 785 # Find newlines and replace them with p.break_() --> 786 output = repr(obj) 787 lines = output.splitlines() 788 with p.group(): File ~/checkouts/readthedocs.org/user_builds/earthaccess/checkouts/latest/earthaccess/results.py:315, in DataCollection.__repr__(self) 314 def __repr__(self) -> str: --> 315 return json.dumps( 316 self.render_dict, 317 sort_keys=False, 318 indent=2, 319 separators=(",", ": "), 320 ) File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/__init__.py:238, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw) 232 if cls is None: 233 cls = JSONEncoder 234 return cls( 235 skipkeys=skipkeys, ensure_ascii=ensure_ascii, 236 check_circular=check_circular, allow_nan=allow_nan, indent=indent, 237 separators=separators, default=default, sort_keys=sort_keys, --> 238 **kw).encode(obj) File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:202, in JSONEncoder.encode(self, o) 200 chunks = self.iterencode(o, _one_shot=True) 201 if not isinstance(chunks, (list, tuple)): --> 202 chunks = list(chunks) 203 return ''.join(chunks) File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:432, in _make_iterencode.<locals>._iterencode(o, _current_indent_level) 430 yield from _iterencode_list(o, _current_indent_level) 431 elif isinstance(o, dict): --> 432 yield from _iterencode_dict(o, _current_indent_level) 433 else: 434 if markers is not None: File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:406, in _make_iterencode.<locals>._iterencode_dict(dct, _current_indent_level) 404 else: 405 chunks = _iterencode(value, _current_indent_level) --> 406 yield from chunks 407 if newline_indent is not None: 408 _current_indent_level -= 1 File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:439, in _make_iterencode.<locals>._iterencode(o, _current_indent_level) 437 raise ValueError("Circular reference detected") 438 markers[markerid] = o --> 439 o = _default(o) 440 yield from _iterencode(o, _current_indent_level) 441 if markers is not None: File ~/.asdf/installs/python/3.12.12/lib/python3.12/json/encoder.py:180, in JSONEncoder.default(self, o) 161 def default(self, o): 162 """Implement this method in a subclass such that it returns 163 a serializable object for ``o``, or calls the base implementation 164 (to raise a ``TypeError``). (...) 178 179 """ --> 180 raise TypeError(f'Object of type {o.__class__.__name__} ' 181 f'is not JSON serializable') TypeError: Object of type set is not JSON serializable
Oh no! What!? only 1 collection found even though we got 2 results back?!
Interpreting the results¶
The hits() method above will tell you the number of query hits, but only for publicly available data sets.
In this case because cloud hosted ICESat-2 data are not yet publicly available, CMR will return “1” hits, if you filtered DataCollections by provider = NSIDC_CPRD you'll get 0 hits. For now we need an alternative method of seeing how many cloud data sets are available at NSIDC. This is only temporary until cloud-hosted ICESat-2 become publicly available. We can create a collections object (we’re going to want one of these soon anyhow) and print the len() of the collections object to see the true number of hits.
Note: Since we cannot rely on
hits()we need to be aware thatget()may get us too many metadata records depending on the dataset and how broad our query is.
Query = (
DataGranules(auth)
.concept_id("C2153572614-NSIDC_CPRD")
.bounding_box(-134.7, 58.9, -133.9, 59.2)
.temporal("2020-03-01", "2020-03-30")
)
# Unfortunately the hits() methods will behave the same for granule queries
print(f"Granules found with hits(): {Query.hits()}")
cloud_granules = Query.get()
print(f"Actual number found: {len(cloud_granules)}")
Granules found with hits(): 0 Actual number found: 0
store = Store(auth)
files = store.get(cloud_granules, "./data/C2153572614-NSIDC_CPRD/")
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[7], line 2 1 store = Store(auth) ----> 2 files = store.get(cloud_granules, "./data/C2153572614-NSIDC_CPRD/") File ~/checkouts/readthedocs.org/user_builds/earthaccess/checkouts/latest/earthaccess/store.py:686, in Store.get(self, granules, local_path, provider, threads, credentials_endpoint, show_progress, pqdm_kwargs, force) 684 if not granules: 685 msg = "List of URLs or DataGranule instances expected" --> 686 raise ValueError(msg) 688 if local_path is None: 689 today = datetime.datetime.now(tz=datetime.UTC).strftime("%Y-%m-%d") ValueError: List of URLs or DataGranule instances expected
NASA Earthdata API Client 🌍