Skip to content

Commit

Permalink
Drafting new classes
Browse files Browse the repository at this point in the history
  This commit introduces a rough draft of the classes to be introduced in the
  object/opinionated layer. For now, the subpackage containing them is named 'objects'
  but that may change in the future. For discussion, see #228.

  - Add 'objects' subpackage with init module
  - Add 'objects.py' module with 5 new classes
    - Methods will be implemented in the following commits.
  • Loading branch information
vinisalazar committed Sep 12, 2022
1 parent 5a26812 commit 257de3d
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 0 deletions.
23 changes: 23 additions & 0 deletions erddapy/objects/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
This module contains opinionated, higher-level objects for searching servers and accessing datasets.
It is named 'objects' after object-relational mapping, which is the concept of having an object-oriented
layer between a database (in this case, ERDDAP), and the programming language.
"""


from objects import (
ERDDAPConnection,
ERDDAPDataset,
ERDDAPServer,
GridDataset,
TableDataset,
)

__all__ = [
"ERDDAPDataset",
"ERDDAPConnection",
"ERDDAPServer",
"TableDataset",
"GridDataset",
]
187 changes: 187 additions & 0 deletions erddapy/objects/objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""Main module of the 'objects' subpackage containing most classes."""

from pathlib import Path
from typing import Dict, Union

StrLike = Union[str, bytes]
FilePath = Union[str, Path]


class ERDDAPConnection:
"""
Manages connection that will be used in ERDDAPServer instances.
While most ERDDAP servers allow connections via a bare url, some servers may require authentication
to access data.
"""

def __init__(self, server: str):
"""Initialize instance of ERDDAPConnection."""
self._server = self.to_string(server)

@classmethod
def to_string(cls, value):
"""Convert an instance of ERDDAPConnection to a string."""
if isinstance(value, str):
return value
elif isinstance(value, cls):
return value.server
else:
raise TypeError(
f"Server must be either a string or an instance of ERDDAPConnection. '{value}' was "
f"passed.",
)

def get(self, url_part: str) -> StrLike:
"""
Request data from the server.
Uses requests by default similar to most of the current erddapy data fetching functionality.
Can be overridden to use httpx, and potentially aiohttp or other async functionality, which could
hopefully make anything else async compatible.
"""
pass

def open(self, url_part: str) -> FilePath:
"""Yield file-like object for access for file types that don't enjoy getting passed a string."""
pass

@property
def server(self) -> str:
"""Access the private ._server attribute."""
return self._server

@server.setter
def server(self, value: str):
"""Set private ._server attribute."""
self._server = self.to_string(value)


class ERDDAPDataset:
"""Base class for more focused table or grid datasets."""

def __init__(
self,
dataset_id: str,
connection: str | ERDDAPConnection,
variables,
constraints,
):
"""Initialize instance of ERDDAPDataset."""
self.dataset_id = dataset_id
self._connection = ERDDAPConnection(ERDDAPConnection.to_string(connection))
self._variables = variables
self._constraints = constraints
self._meta = None

@property
def connection(self) -> ERDDAPConnection:
"""Access private ._connection variable."""
return self._connection

@connection.setter
def connection(self, value: str | ERDDAPConnection):
"""Set private ._connection variable."""
self._connection = ERDDAPConnection(ERDDAPConnection.to_string(value))

def get(self, file_type: str) -> StrLike:
"""Request data using underlying connection."""
return self.connection.get(file_type)

def open(self, file_type: str) -> FilePath:
"""Download and open dataset using underlying connection."""
return self.connection.open(file_type)

def get_meta(self):
"""Request dataset metadata from the server."""
self._meta = None

@property
def meta(self):
"""Access private ._meta attribute. Request metadata if ._meta is empty."""
return self.get_meta() if (self._meta is None) else self._meta

@property
def variables(self):
"""Access private ._variables attribute."""
return self._variables

@property
def constraints(self):
"""Access private ._constraints attribute."""
return self._constraints

def url_segment(self, file_type: str) -> str:
"""Return URL segment without the base URL (the portion after 'https://server.com/erddap/')."""
pass

def url(self, file_type: str) -> str:
"""
Return a URL constructed using the underlying ERDDAPConnection.
The URL will contain information regarding the base class server info, the dataset ID,
access method (tabledap/griddap), file type, variables, and constraints.
This allows ERDDAPDataset subclasses to be used as more opinionated URL constructors while still
not tying users to a specific IO method.
Not guaranteed to capture all the specifics of formatting a request, such as if a server requires
specific auth or headers.
"""
pass

def to_dataset(self):
"""Open the dataset as xarray dataset by downloading a subset NetCDF."""
pass

def opendap_dataset(self):
"""Open the full dataset in xarray via OpenDAP."""
pass


class TableDataset(ERDDAPDataset):
"""Subclass of ERDDAPDataset specific to TableDAP datasets."""

def to_dataframe(self):
"""Open the dataset as a Pandas DataFrame."""


class GridDataset(ERDDAPDataset):
"""Subclass of ERDDAPDataset specific to GridDAP datasets."""

pass


class ERDDAPServer:
"""Instance of an ERDDAP server, with support to ERDDAP's native functionalities."""

def __init__(self, connection: str | ERDDAPConnection):
"""Initialize instance of ERDDAPServer."""
self._connection = ERDDAPConnection(ERDDAPConnection.to_string(connection))

@property
def connection(self) -> ERDDAPConnection:
"""Access private ._connection attribute."""
return self._connection

@connection.setter
def connection(self, value: str | ERDDAPConnection):
"""Set private ._connection attribute."""
self._connection = ERDDAPConnection(ERDDAPConnection.to_string(value))

def full_text_search(self, query: str) -> Dict[str, ERDDAPDataset]:
"""Search the server with native ERDDAP full text search capabilities."""
pass

def search(self, query: str) -> Dict[str, ERDDAPDataset]:
"""
Search the server with native ERDDAP full text search capabilities.
Also see ERDDAPServer.full_text_search.
"""
return self.full_text_search(query)

def advanced_search(self, **kwargs) -> Dict[str, ERDDAPDataset]:
"""Search server with ERDDAP advanced search capabilities (may return pre-filtered datasets)."""
pass

0 comments on commit 257de3d

Please sign in to comment.