Tutorial de 5 minutos#

La manera mas accesible de usar pydap es como cliente de acceso a datos cientificos en servidores remotos de OPeNDAP.

OPeNDAP - la visión original#

La vision original de OPeNDAP (Cornillion, et al 1993) fue el hacer las equivalencias

\( \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; \boxed{\text{URL} \approx \text{Dataset Remoto} }\)

\( \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; \boxed{\text{URL + Expresión de Restricción} \approx \text{Subregion de un Dataset Remoto}} \)

Esa vision original fue la que conllevo el desarrollo del protocolo DAP2. En la actualidad, tanto OPeNDAP como Unidata implementan el protocolo DAP4, el cual es mas moderno y abarca mas typos de information, y cubre todos los elementos esenciales cubiertos por el protocolo DAP2 (para mas informacion vea DAP4 specification).

La aportación de PyDAP:#

The logica interna de PyDAP permite la construccion de expressiones de restriccion (CEs for su siglas en ingles) para cada url, de una manera interactiva, facilitando el accesso a subconjunto de datos remotos atraves de OPeNDAP. Ademas, como pydap es un “backend engine” del paquete de Python Xarray, usuarios pueden escalar su flujo de trabajo con la combinacion Xarray+PyDAP y el uso de parallelismo que Xarray permite. En general, un dominio basico del uso de expressiones de restriction (CEs) es importante para maximizar los protocolos de OPeNDAP.

Objetivos:#

  • Demonstrar como especificar el protocolo DAP4 al servidor remoto de OPeNDAP.

  • El uso de Xarray y PyDAP para descargar un subcojunto de datos remotos en 2 escenario typicos: a) Un archivo remoto con extension NcML que representa un archivo virtua l de aggregacion , y b) dos archivos remotos de format Netcdf.

  • Demonstrat las distintas maneras en que pueden user las Condiciones de Restriccion (CEs), y como estas se pueden pasar al servidor para que cualquier operacion de extraer subconjuntos sea hecha por el servidor OPeNDAP, de una manera proxima a los archivos remotos, de una manera eficiente.

Requiremientos#

  • Archivos expuestos por un servidor OPeNDAP que implemente el protocolo DAP4. Por ejemplo, el servidor: http://test.opendap.org/opendap/.

  • pydap>=3.5.8

  • xarray>=2025.0

  • numpy>=2.0

Note

The gran mayoria de los servidores OPeNDAP de la NASA implementan el protocolo DAP4.

from pydap.client import open_url, consolidate_metadata, create_session
import xarray as xr
import numpy as np
# create a session to inspect downloads. cache_name must have `debug`
session = create_session(use_cache=True, cache_kwargs={"cache_name":'data/debug_case1'})
session.cache.clear()

1) Accesso a subconjuntos de un archivo NcML file#

El archivo que utilizaremos tiene formate NcML representando una aggregacion virtual de muchos archivos dataset, el cual puede ser encontrado en el servidos prueba con nombre: aggExisting.ncml.

Los servidores de OPeNDAP pueden ser configurados para producir estos archivos de aggregation con formato NcML. La ventaja es que el usuario trabaja con un solo URL para toda la informacion posible, mientras que en el escenario cuando los archivos no has sido aggregados, el usuario tiene entonces que trabajar con multiples (a veces miles) de URLs.

ncml_url = "http://test.opendap.org/opendap/data/ncml/agg/aggExisting.ncml"
dap4_ncml_url = ncml_url.replace("http",  "dap4")
print("=============================================================\n URL DAP4: \n", dap4_ncml_url, "\n=============================================================")
=============================================================
 URL DAP4: 
 dap4://test.opendap.org/opendap/data/ncml/agg/aggExisting.ncml 
=============================================================

Ahora utilizamos Xarray y Pydap para “abrir” el archivo, como si estuviera en tu computadora de trabajo. Para esto ejecte el siguiente bloque de codigo

ds = xr.open_dataset(
    dap4_ncml_url, 
    engine='pydap',
    session = session,
    chunks={},
)
ds
---------------------------------------------------------------------------
ResponseError                             Traceback (most recent call last)
ResponseError: too many 500 error responses

The above exception was the direct cause of the following exception:

MaxRetryError                             Traceback (most recent call last)
File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests/adapters.py:696, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    695 try:
--> 696     resp = conn.urlopen(
    697         method=request.method,
    698         url=url,
    699         body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]
    700         headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072
    701         redirect=False,
    702         assert_same_host=False,
    703         preload_content=False,
    704         decode_content=False,
    705         retries=self.max_retries,
    706         timeout=resolved_timeout,
    707         chunked=chunked,
    708     )
    710 except (ProtocolError, OSError) as err:

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/urllib3/connectionpool.py:955, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    954     log.debug("Retry: %s", url)
--> 955     return self.urlopen(
    956         method,
    957         url,
    958         body,
    959         headers,
    960         retries=retries,
    961         redirect=redirect,
    962         assert_same_host=assert_same_host,
    963         timeout=timeout,
    964         pool_timeout=pool_timeout,
    965         release_conn=release_conn,
    966         chunked=chunked,
    967         body_pos=body_pos,
    968         preload_content=preload_content,
    969         decode_content=decode_content,
    970         **response_kw,
    971     )
    973 return response

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/urllib3/connectionpool.py:955, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    954     log.debug("Retry: %s", url)
--> 955     return self.urlopen(
    956         method,
    957         url,
    958         body,
    959         headers,
    960         retries=retries,
    961         redirect=redirect,
    962         assert_same_host=assert_same_host,
    963         timeout=timeout,
    964         pool_timeout=pool_timeout,
    965         release_conn=release_conn,
    966         chunked=chunked,
    967         body_pos=body_pos,
    968         preload_content=preload_content,
    969         decode_content=decode_content,
    970         **response_kw,
    971     )
    973 return response

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/urllib3/connectionpool.py:945, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    944 try:
--> 945     retries = retries.increment(method, url, response=response, _pool=self)
    946 except MaxRetryError:

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/urllib3/util/retry.py:543, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    542     reason = error or ResponseError(cause)
--> 543     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    545 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)

MaxRetryError: HTTPConnectionPool(host='test.opendap.org', port=80): Max retries exceeded with url: /opendap/data/ncml/agg/aggExisting.ncml.dmr (Caused by ResponseError('too many 500 error responses'))

During handling of the above exception, another exception occurred:

RetryError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 ds = xr.open_dataset(
      2     dap4_ncml_url,
      3     engine='pydap',
      4     session = session,

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/xarray/backends/api.py:607, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
    595 decoders = _resolve_decoders_kwargs(
    596     decode_cf,
    597     open_backend_dataset_parameters=backend.open_dataset_parameters,
   (...)    603     decode_coords=decode_coords,
    604 )
    606 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 607 backend_ds = backend.open_dataset(
    608     filename_or_obj,
    609     drop_variables=drop_variables,
    610     **decoders,
    611     **kwargs,
    612 )
    613 ds = _dataset_from_backend_dataset(
    614     backend_ds,
    615     filename_or_obj,
   (...)    626     **kwargs,
    627 )
    628 return ds

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/xarray/backends/pydap_.py:279, in PydapBackendEntrypoint.open_dataset(self, filename_or_obj, mask_and_scale, decode_times, concat_characters, decode_coords, drop_variables, use_cftime, decode_timedelta, group, application, session, output_grid, timeout, verify, user_charset, checksums)
    257 def open_dataset(
    258     self,
    259     filename_or_obj: (
   (...)    277     checksums=True,
    278 ) -> Dataset:
--> 279     store = PydapDataStore.open(
    280         url=filename_or_obj,
    281         group=group,
    282         application=application,
    283         session=session,
    284         output_grid=output_grid,
    285         timeout=timeout,
    286         verify=verify,
    287         user_charset=user_charset,
    288         checksums=checksums,
    289     )
    290     store_entrypoint = StoreBackendEntrypoint()
    291     with close_on_error(store):

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/xarray/backends/pydap_.py:141, in PydapDataStore.open(cls, url, group, application, session, output_grid, timeout, verify, user_charset, checksums)
    130 kwargs = {
    131     "url": url,
    132     "application": application,
   (...)    137     "user_charset": user_charset,
    138 }
    139 if isinstance(url, str):
    140     # check uit begins with an acceptable scheme
--> 141     dataset = open_url(**kwargs)
    142 elif hasattr(url, "ds"):
    143     # pydap dataset
    144     dataset = url.ds

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/client.py:179, in open_url(url, application, session, output_grid, flat, timeout, verify, checksums, user_charset, protocol, batch, use_cache, session_kwargs, cache_kwargs, get_kwargs)
    172 if not session:
    173     session = create_session(
    174         use_cache=use_cache,
    175         session_kwargs=session_kwargs,
    176         cache_kwargs=cache_kwargs,
    177     )
--> 179 handler = DAPHandler(
    180     url,
    181     application,
    182     session,
    183     output_grid=output_grid,
    184     flat=flat,
    185     timeout=timeout,
    186     verify=verify,
    187     checksums=checksums,
    188     user_charset=user_charset,
    189     protocol=protocol,
    190     get_kwargs=get_kwargs,
    191 )
    192 dataset = handler.dataset
    193 dataset._session = session

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/handlers/dap.py:163, in DAPHandler.__init__(self, url, application, session, output_grid, flat, timeout, verify, checksums, user_charset, protocol, get_kwargs)
    154     arg = (
    155         self.scheme,
    156         self.netloc,
   (...)    160         self.fragment,
    161     )
    162     self.base_url = urlunparse(arg)
--> 163     self.make_dataset()
    164 self.add_proxies()

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/handlers/dap.py:198, in DAPHandler.make_dataset(self)
    194 def make_dataset(
    195     self,
    196 ):
    197     if self.protocol == "dap4":
--> 198         self.dataset_from_dap4()
    199     else:
    200         self.dataset_from_dap2()

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/handlers/dap.py:218, in DAPHandler.dataset_from_dap4(self)
    207     path = self.path
    208 dmr_url = urlunparse(
    209     (
    210         self.scheme,
   (...)    216     )
    217 )
--> 218 r = GET(
    219     dmr_url,
    220     self.application,
    221     self.session,
    222     timeout=self.timeout,
    223     verify=self.verify,
    224     get_kwargs=self.get_kwargs,
    225 )
    226 dmr = safe_charset_text(r, self.user_charset)
    227 self.dataset = dmr_to_dataset(dmr, self.flat)

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/net.py:74, in GET(url, application, session, timeout, verify, use_cache, session_kwargs, cache_kwargs, get_kwargs)
     72     _, _, path, _, query, fragment = urlparse(url)
     73     url = urlunparse(("", "", path, "", _quote(query), fragment))
---> 74 res = create_request(
     75     url,
     76     application=application,
     77     session=session,
     78     timeout=timeout,
     79     verify=verify,
     80     session_kwargs=session_kwargs,
     81     cache_kwargs=cache_kwargs,
     82     get_kwargs=get_kwargs,
     83 )
     84 if isinstance(res, webob_Request):
     85     res = get_response(res, application, verify=verify)

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/net.py:172, in create_request(url, application, timeout, verify, session, session_kwargs, cache_kwargs, get_kwargs)
    170 session_kwargs = session_kwargs or {}
    171 try:
--> 172     req = get_request(
    173         url,
    174         base_session=session,
    175         timeout=timeout,
    176         verify=verify,
    177         get_kwargs=get_kwargs,
    178         backend=cache_kwargs.pop("backend", None),
    179         cache_name=cache_kwargs.pop("http-cache", None),
    180         backend_options=cache_kwargs.pop("backend_options", None),
    181         cache_kwargs=cache_kwargs,
    182         session_kwargs=session_kwargs,
    183     )
    184     req.raise_for_status()
    185     return req

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/pydap/net.py:359, in get_request(url, base_session, timeout, verify, get_kwargs, backend, cache_name, backend_options, cache_kwargs, session_kwargs)
    357 if parsed.scheme == "https":
    358     http_url = urlunparse(parsed._replace(scheme="http"))
--> 359     req = s.get(
    360         http_url,
    361         timeout=timeout,
    362         verify=verify,
    363         allow_redirects=True,
    364         **(get_kwargs or {}),
    365     )
    366 else:
    367     raise e

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests_cache/session.py:133, in CacheMixin.get(self, url, params, **kwargs)
    131 def get(self, url: str, params=None, **kwargs) -> AnyResponse:  # type: ignore
    132     kwargs.setdefault('allow_redirects', True)
--> 133     return self.request('GET', url, params=params, **kwargs)

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests_cache/session.py:189, in CacheMixin.request(self, method, url, headers, expire_after, only_if_cached, refresh, force_refresh, *args, **kwargs)
    187 headers = set_request_headers(headers, expire_after, only_if_cached, refresh, force_refresh)
    188 with patch_form_boundary() if kwargs.get('files') else nullcontext():
--> 189     return super().request(method, url, *args, headers=headers, **kwargs)

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests/sessions.py:651, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    646 send_kwargs = {
    647     "timeout": timeout,
    648     "allow_redirects": allow_redirects,
    649 }
    650 send_kwargs.update(settings)
--> 651 resp = self.send(prep, **send_kwargs)
    653 return resp

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests_cache/session.py:255, in CacheMixin.send(self, request, expire_after, only_if_cached, refresh, force_refresh, **kwargs)
    253     response = self._resend(request, actions, cached_response, **kwargs)  # type: ignore
    254 elif actions.send_request:
--> 255     response = self._send_and_cache(request, actions, cached_response, **kwargs)
    256 else:
    257     response = cached_response  # type: ignore  # Guaranteed to be non-None by this point

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests_cache/session.py:279, in CacheMixin._send_and_cache(self, request, actions, cached_response, **kwargs)
    275 """Send a request and cache the response, unless disabled by settings or headers.
    276 If applicable, also handle conditional requests.
    277 """
    278 request = actions.update_request(request)
--> 279 response = super().send(request, **kwargs)
    280 actions.update_from_response(response)
    282 if not actions.skip_write:

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests/sessions.py:784, in Session.send(self, request, **kwargs)
    781 start = preferred_clock()
    783 # Send the request
--> 784 r = adapter.send(request, **kwargs)
    786 # Total elapsed time of the request (approximately)
    787 elapsed = preferred_clock() - start

File ~/mambaforge/envs/pydap_docs/lib/python3.11/site-packages/requests/adapters.py:720, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    717         raise ConnectTimeout(e, request=request)
    719 if isinstance(e.reason, ResponseError):
--> 720     raise RetryError(e, request=request)
    722 if isinstance(e.reason, _ProxyError):
    723     raise ProxyError(e, request=request)

RetryError: HTTPConnectionPool(host='test.opendap.org', port=80): Max retries exceeded with url: /opendap/data/ncml/agg/aggExisting.ncml.dmr (Caused by ResponseError('too many 500 error responses'))

Note

El archivo sigue estando remoto, pero el programa de Xarray junto con Pydap permiten accesar a la information dentro archivo, como si estuviera accesando un archivo dentro de su computadora.

Como dercargamos un elemento de una variable del archivo remoto?#

Para demostrar como descargar datos, primero inspeccionamos la descripcion de la variable T. Es importante, antes que nada, entender la estructura interna del archivo.

ds['T']

Note

La informacion que describe la variable T implica que toda T esta contenida en un solo chunk. Xarray y OPeNDAP en general transmiten y realizan operaciones dividiento la informacion del archivo en chunks. Y Xarray interpreta a toda variable dentro de un archivo remoto en OPeNDAP, como un solo chunk, incluso cuando el archivo remoto divide la representacion de cada variable en diversos chunks.

# clear the cache to inspect what is being downloaded
session.cache.clear() 
ds['T'].isel(time=1, lon=0, lat=0).load()
print("====================================== \n Solicitud enviada al Servidor OPeNDAP \n ", session.cache.urls()[0].split("?")[-1].split("&dap4.checksum")[0].replace("%5B","[").replace("%5D","]").replace("%3A",":").replace("%2F","/"), "\n====================================== ")

La expression de restriccion (CE) fue contruida por el metodo .isel de Xarray. Este metodo interno de Xarray fue entonces enviada al servidor OPeNDAP, el cual hizo todo el trabajo por nosotros!

2) Accesando a subconjuntos en 2 archivos remotos que pertenecen al mismo proyecto#

En este escenario, los dos archivos remotos describen informacion contigua del mismo proyecto, bajo la suposicion que estos dos archivos pueden aggregarse a lo largo de una dimension. Por ejemplo, un archivo representa los valores de variables geofisicas en la fecha 10/Sept/2025, y el segundo archivo representa valores de las mismas variables geofisicas en la siguiente fecha disponible.

Utilizaremotes para este ejemplo los siguientes archivos: coads_climatology and coads_climatology2. Estos dos archivos abarcan la misma cobertura espacial, y pueden ser aggregados en tiempo.

Note

Es importante verificar siempre que los conjuntos de datos se puedan agregar. PyDAP y Xarray contienen lógica interna que verifica si dos o más conjuntos de datos se pueden concatenar. Sin embargo, estas comprobaciones de seguridad solo consideran dimensiones y coordenadas.

Un paso importante será el uso de Expresiones de Restricción (CE) para garantizar que solo se concatenen las variables de interés.

Warning

Uno de estos archivos tiene variables adicionales que no están presentes en el otro archivo y que descartaremos mediante el uso de CE.

urls = ["http://test.opendap.org/opendap/data/nc/coads_climatology.nc", "http://test.opendap.org/opendap/data/nc/coads_climatology2.nc"]
dap4_urls = [url.replace("http","dap4") for url in urls]

# Expression de Restriccion (CE)
dap4_CE = "?dap4.ce=" + ";".join(["/SST", "/COADSX", "/COADSY", "/TIME"])

# Final list of OPeNDAP URLs
dap4ce_urls =[url+dap4_CE for url in dap4_urls]
print("====================================================\nURLs de OPeNDAP con protocolo DAP4 \n", dap4ce_urls)

Note

Q:¿Por qué usar CEs cuando Xarray tiene un método .drop_variables? Porque Xarray necesita analizar primero todos los metadatos remotos para luego descartar las variables. En algunos archivos, es posible encontrar hasta 1000 variables. Xarray las analizaría todas y luego las descartaría. Con CEs, el servidor envía metadatos restringidos asociados únicamente a las variables deseadas. Asi, Xarray solo procesa las variables de importancia.

Warning

Xarray espera la presencia de dimensiones en los metadatos. Al construir la CE, el usuario debe asegurarse de incluir todas las dimensiones asociadas con las variables de interés. En el ejemplo anterior, COASX, COADSY y TIME son las dimensiones de SST.

Consolidate Metadata acelera el proceso de abrir una serie de archivos.#

consolidate_metadata(dap4ce_urls, session=session, concat_dim="TIME")

Note

consolidate_metadata(dap4_urls, concat_dim='...', session=session) descarga las dimensiones del archivo remoto y las almacena en formato SQLite, para su reuso. Esto significa que el objecto session permite autentificar y actua como un database manager! ¡Esta práctica puede resultar en una mejora del rendimiento en flujos de trabajo entre 10 y 100 veces más rápidos!

Usamos Xarray como herramienta para abrir, descargar, y almacenar la informacion remota.#

Internamente, Xarray utiliza pydap para comunicar con el servidor de OPeNDAP.

ds = xr.open_mfdataset(
    dap4ce_urls, 
    engine='pydap',
    concat_dim='TIME',
    session=session,
    combine="nested",
    parallel=True,
    decode_times=False,
)
ds
ds['SST']

Que pasa si queremos descargar un solo elemento#

session.cache.clear()
%%time
ds['SST'].isel(TIME=0, COADSX=0, COADSY=0).load() # this should download a single point one of the files
print("====================================== \n Solicitud enviada al Servidor OPeNDAP:\n ", session.cache.urls()[0].split("?")[-1].split("&dap4.checksum")[0].replace("%5B","[").replace("%5D","]").replace("%3A",":").replace("%2F","/"), "\n====================================== ")

Toda la variable fue descargada innecessariamente !!#

Lo que queremos, es ver que la solicitud enviada al servidor OPeNDAP contenga la siguiente CE:

dap4.ce=/SST[0:1:0][0:1:0][0:1:0]

xr.open_mfdataset no pasa el argumento de seleccion al servidor, de la misma manera en que xr.open_dataset lo hace. En su lugar, Xarray solicita toda la variable, y ya descargada, Xarray hace la selection localmente, de acuerdo al argumento .isel proporcionado por el usuario.

Como asegurar que la selection is enviada al servidor OPeNDAP?#

La respuesta es proporcional le argumento extra, chunk, cuando abrimos/creamos el dataset con Xarray. Este argumento chunk debe ser igual al tamano de selection que esperamos como resultado final.

Warning

Si el argumento chunk is mas pequeno que el tamano que esperanos de nuestra descarga, Xarray terminara enviando muchas solicitudes de descargas innecesarias al servidor, para luego juntar todos los subconjuntos descargados. Este flujo de trabajo tampoco es idea, pues Xarray termina haciendo trabajo extra. Lo ideal es hacer que el servidor haga todo el trabajo, y Xarray solo proporciona el parallelismo.

A continuacion demostramos lograr que Xarray pase la selection al servidor remoto OPeNDAP, el cual hace la mayoria del trabajo de selection cerca del archivo remoto, y solo envia la informacion requerida.

# consolidate metadata again, since the cached metadata was cleared before
consolidate_metadata(dap4ce_urls, session=session, concat_dim="TIME")
# For a single element in all dimensions, the expected size of the download is:
expected_sizes = {"TIME":1, "COADSX":1, "COADSY":1}
%%time
ds = xr.open_mfdataset(
    dap4ce_urls, 
    engine='pydap',
    concat_dim='TIME',
    session=session,
    combine="nested",
    parallel=True,
    decode_times=False,
    chunks=expected_sizes, # <---------
)
session.cache.clear()
ds['SST'] # inspect chunks before download
%%time
ds['SST'].isel(TIME=0, COADSX=0, COADSY=0).load() # triggers download of an individual chunk
print("====================================== \n Solicitud enviada al Servidor OPeNDAP:\n ", session.cache.urls()[0].split("?")[-1].split("&dap4.checksum")[0].replace("%5B","[").replace("%5D","]").replace("%3A",":").replace("%2F","/"), "\n====================================== ")

Warning: Be cautious about chunking#

¡Ahora solo descargamos exactamente lo que solicitamos! Sin embargo, en algunos casos, el tiempo de descarga puede ser hasta diez veces más lento que cuando solicitamos más datos. La razón de esta lentitud se puede atribuir a la cantidad de fragmentos (chunks) que generó el gráfico de Dask.

Entonces, estos dos son los escenarios que experimentados al tratar de descargar un subconjunto de datos.

  • No chunk definido. Xarray descarga toda la variable.

  • Chunk definido. Xarray descarga solo el elemento deseado del archivo remoto. Pero durante este processo, 388800 chunks fueron creados!

Idealmente, el Chunk manager solo debería activar la descarga de un único fragmento. Sin embargo, se crearon 388800 para garantizar la transferencia del elemento al servidor. Esto, en ocasiones, puede provocar que el cliente (Xarray) tarde mas de lo normal.

En el escenario anterior, llegamos a extremos. Es mejor encontrar un punto medio para los chunks. Lo demostramos a continuación, pero ahora con subconjuntos a lo largo del tiempo.

consolidate_metadata(dap4ce_urls, session=session, concat_dim="TIME")
download_sizes = {"COADSY":1} # note that we will subset across all time
%%time
ds = xr.open_mfdataset(
    dap4ce_urls, 
    engine='pydap',
    concat_dim='TIME',
    session=session,
    combine="nested",
    parallel=True,
    decode_times=False,
    chunks=download_sizes,
)
session.cache.clear()
ds['SST']
%%time
ds['SST'].isel(COADSX=0, COADSY=0).load()
print("====================================== \n Solicitudes enviadas al Servidor OPeNDAP:\n ",  [url.split("?")[-1].split("&dap4.checksum")[0].replace("%5B","[").replace("%5D","]").replace("%3A",":").replace("%2F","/") for url in session.cache.urls()], "\n====================================== ")

Exito! Similar tiempo descarga, pero descarga es mucho menor que antes!#