diff --git a/doc/io.rst b/doc/io.rst index 8af59945c71..192890e112a 100644 --- a/doc/io.rst +++ b/doc/io.rst @@ -388,6 +388,38 @@ over the network until we look at particular values: .. image:: _static/opendap-prism-tmax.png +Some servers require authentication before we can access the data. For this +purpose we can explicitly create a :py:class:`~xarray.backends.PydapDataStore` +and pass in a `Requests`__ session object. For example for +HTTP Basic authentication:: + + import xarray as xr + import requests + + session = requests.Session() + session.auth = ('username', 'password') + + store = xr.backends.PydapDataStore.open('http://example.com/data', + session=session) + ds = xr.open_dataset(store) + +`Pydap's cas module`__ has functions that generate custom sessions for +servers that use CAS single sign-on. For example, to connect to servers +that require NASA's URS authentication:: + + import xarray as xr + from pydata.cas.urs import setup_session + + ds_url = 'https://gpm1.gesdisc.eosdis.nasa.gov/opendap/hyrax/example.nc' + + session = setup_session('username', 'password', check_url=ds_url) + store = xr.backends.PydapDataStore.open(ds_url, session=session) + + ds = xr.open_dataset(store) + +__ http://docs.python-requests.org +__ http://pydap.readthedocs.io/en/latest/client.html#authentication + .. _io.rasterio: Rasterio diff --git a/doc/whats-new.rst b/doc/whats-new.rst index cf194bcfa85..99986e0beb8 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -111,6 +111,13 @@ Enhancements By `Joe Hamman `_ and `Gerrit Holl `_. +- Changed :py:class:`~xarray.backends.PydapDataStore` to take a Pydap dataset. + This permits opening Opendap datasets that require authentication, by + instantiating a Pydap dataset with a session object. Also added + :py:meth:`xarray.backends.PydapDataStore.open` which takes a url and session + object (:issue:`1068`). + By `Philip Graae `_. + - Support applying rolling window operations using bottleneck's moving window functions on data stored as dask arrays (:issue:`1279`). By `Joe Hamman `_. diff --git a/xarray/backends/api.py b/xarray/backends/api.py index 59b3a3f69fd..e5a3136f0ca 100644 --- a/xarray/backends/api.py +++ b/xarray/backends/api.py @@ -288,7 +288,7 @@ def maybe_decode_store(store, lock=False): store = backends.ScipyDataStore(filename_or_obj, autoclose=autoclose) elif engine == 'pydap': - store = backends.PydapDataStore(filename_or_obj) + store = backends.PydapDataStore.open(filename_or_obj) elif engine == 'h5netcdf': store = backends.H5NetCDFStore(filename_or_obj, group=group, autoclose=autoclose) diff --git a/xarray/backends/pydap_.py b/xarray/backends/pydap_.py index a4ccbce5bc1..89b7829d2cb 100644 --- a/xarray/backends/pydap_.py +++ b/xarray/backends/pydap_.py @@ -60,9 +60,19 @@ class PydapDataStore(AbstractDataStore): This store provides an alternative way to access OpenDAP datasets that may be useful if the netCDF4 library is not available. """ - def __init__(self, url): + def __init__(self, ds): + """ + Parameters + ---------- + ds : pydap DatasetType + """ + self.ds = ds + + @classmethod + def open(cls, url, session=None): import pydap.client - self.ds = pydap.client.open_url(url) + ds = pydap.client.open_url(url, session=session) + return cls(ds) def open_store_variable(self, var): data = indexing.LazilyIndexedArray(PydapArrayWrapper(var)) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 826aee6a734..a977868c7e6 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -31,6 +31,8 @@ assert_identical) from .test_dataset import create_test_data +from xarray.tests import mock + try: import netCDF4 as nc4 except ImportError: @@ -1509,6 +1511,14 @@ def test_cmp_local_file(self): self.assertDatasetEqual(actual.isel(j=slice(1, 2)), expected.isel(j=slice(1, 2))) + def test_session(self): + from pydap.cas.urs import setup_session + + session = setup_session('XarrayTestUser', 'Xarray2017') + with mock.patch('pydap.client.open_url') as mock_func: + xr.backends.PydapDataStore.open('http://test.url', session=session) + mock_func.assert_called_with('http://test.url', session=session) + @requires_dask def test_dask(self): with self.create_datasets(chunks={'j': 2}) as (actual, expected):