-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
5a26812
commit 257de3d
Showing
2 changed files
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |