Source code for bmi_topography.api_key

import os
import warnings
from functools import partial
from pathlib import Path

from .errors import BadApiKeySource, BadKeyError, MissingKeyError


[docs] class ApiKey: """Store an API key to use when fetching topography data from OpenTopography. Parameters ---------- api_key : str An API key as a (non-empty) string. source : str, optional A string that indicates where the key came from. Possible values are: *user*, *env*, *file*, and *demo*. Raises ------ BadKeyError The provided API key is invalid. MissingKeyError A key could not be found in the usual places. BadApiKeySource An invalid source was provided. Examples -------- >>> from bmi_topography.api_key import ApiKey >>> api_key = ApiKey("foobar") >>> api_key ApiKey('foobar', source='user') """ DEMO_API_KEY = "demoapikeyot2022" API_KEY_FILES = (".opentopography.txt", "~/.opentopography.txt") API_KEY_ENV_VAR = "OPENTOPOGRAPHY_API_KEY" def __init__(self, api_key, source="user"): if not isinstance(api_key, str) or len(api_key) == 0: raise BadKeyError("invalid API key") self._api_key = api_key self._source = ApiKey._validate_source(source) if self.is_demo_key(): warnings.warn( "You are using a demo key to fetch data from OpenTopography, functionality " "will be limited. See https://bmi-topography.readthedocs.io/en/latest/#api-key " "for more information." ) @staticmethod def _validate_source(source): valid_sources = ["user", "env", "file", "demo"] if isinstance(source, str) and source.split(":")[0] in valid_sources: return source else: raise BadApiKeySource( f"{source}: Invalid source (not one of {', '.join(valid_sources)}" )
[docs] @classmethod def from_env(cls): """Get the key from and environment variables.""" try: api_key = os.environ[ApiKey.API_KEY_ENV_VAR] except KeyError: raise MissingKeyError(f"unable to find key ({ApiKey.API_KEY_ENV_VAR})") else: return cls(api_key, source="env")
[docs] @classmethod def from_file(cls): """Read the key from a file.""" if filepath := _find_first_of(ApiKey.API_KEY_FILES): with open(filepath, "r") as fp: api_key = fp.read().strip() else: raise MissingKeyError( f"unable to find key ({', '.join(ApiKey.API_KEY_FILES)})" ) return cls(api_key, source=f"file:{filepath}")
[docs] @classmethod def from_sources(cls, api_key=None): """Get a key from the first of a series of sources. Look for a key from the following sources, returning the first found: (1) provided by a user through the *api_key* keyword, (2) provided by an environment variable, (3) provided in a text file, and (4) use a demo key """ for from_source in [partial(cls, api_key), cls.from_env, cls.from_file]: try: return from_source() except (BadKeyError, MissingKeyError): pass return cls(ApiKey.DEMO_API_KEY, source="demo")
@property def api_key(self): """The API key.""" return self._api_key @property def source(self): """Where the API key came from.""" return self._source
[docs] def is_demo_key(self): """Check if the key is a demo key.""" return self._api_key == ApiKey.DEMO_API_KEY
def __repr__(self): return f"ApiKey({self.api_key!r}, source={self.source!r})" def __str__(self): return self.api_key def __len__(self): return len(self.api_key) def __eq__(self, val): try: return self.api_key == val.api_key except AttributeError: return str(self) == val
def _find_first_of(files): """Find the first existing file from a list of files.""" found = None for path in (Path(name) for name in files): if path.expanduser().is_file(): found = path.expanduser() break return found