Skip to content

Commit

Permalink
Fully document pystow and pass darglint checks (#33)
Browse files Browse the repository at this point in the history
* Begin full application of darglint

* More documentation

* Finish documenting API

* Bandage it

* Update utils.py
  • Loading branch information
cthoyt committed Jan 23, 2022
1 parent b696c2e commit 7ba8453
Show file tree
Hide file tree
Showing 9 changed files with 521 additions and 69 deletions.
8 changes: 6 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pandas =
pandas
aws =
boto3
tests =
coverage
pytest
docs =
sphinx
sphinx-rtd-theme
Expand Down Expand Up @@ -114,5 +117,6 @@ exclude_lines =
# Darglint Configuration #
##########################
[darglint]
docstring_style=sphinx
strictness=short
docstring_style = sphinx
strictness = full
# enable = DAR104
124 changes: 119 additions & 5 deletions src/pystow/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,31 @@ def ensure_open_zip(
mode: str = "r",
open_kwargs: Optional[Mapping[str, Any]] = None,
):
"""Ensure a file is downloaded then open it with :mod:`zipfile`."""
"""Ensure a file is downloaded then open it with :mod:`zipfile`.
:param key:
The name of the module. No funny characters. The envvar
`<key>_HOME` where key is uppercased is checked first before using
the default home directory.
:param subkeys:
A sequence of additional strings to join. If none are given,
returns the directory for this module.
:param url:
The URL to download.
:param inner_path:
The relative path to the file inside the archive
:param name:
Overrides the name of the file at the end of the URL, if given. Also
useful for URLs that don't have proper filenames with extensions.
:param force:
Should the download be done again, even if the path already exists?
Defaults to false.
:param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
:param mode: The read mode, passed to :func:`zipfile.open`
:param open_kwargs: Additional keyword arguments passed to :func:`zipfile.open`
:yields: An open file object
"""
_module = Module.from_key(key, ensure_exists=True)
yield _module.ensure_open_zip(
*subkeys,
Expand Down Expand Up @@ -284,6 +308,8 @@ def ensure_open_tarfile(
name: Optional[str] = None,
force: bool = False,
download_kwargs: Optional[Mapping[str, Any]] = None,
mode: str = "r",
open_kwargs: Optional[Mapping[str, Any]] = None,
):
"""Ensure a tar file is downloaded and open a file inside it.
Expand All @@ -305,6 +331,8 @@ def ensure_open_tarfile(
Should the download be done again, even if the path already exists?
Defaults to false.
:param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
:param mode: The read mode, passed to :func:`tarfile.open`
:param open_kwargs: Additional keyword arguments passed to :func:`tarfile.open`
:yields: An open file object
"""
Expand All @@ -316,6 +344,8 @@ def ensure_open_tarfile(
name=name,
force=force,
download_kwargs=download_kwargs,
mode=mode,
open_kwargs=open_kwargs,
)


Expand Down Expand Up @@ -504,7 +534,28 @@ def ensure_tar_df(
download_kwargs: Optional[Mapping[str, Any]] = None,
read_csv_kwargs: Optional[Mapping[str, Any]] = None,
):
"""Download a tar file and open an inner file as a dataframe with :mod:`pandas`."""
"""Download a tar file and open an inner file as a dataframe with :mod:`pandas`.
:param key: The module name
:param subkeys:
A sequence of additional strings to join. If none are given,
returns the directory for this module.
:param url:
The URL to download.
:param inner_path:
The relative path to the file inside the archive
:param name:
Overrides the name of the file at the end of the URL, if given. Also
useful for URLs that don't have proper filenames with extensions.
:param force:
Should the download be done again, even if the path already exists?
Defaults to false.
:param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
:param read_csv_kwargs: Keyword arguments to pass through to :func:`pandas.read_csv`.
:returns: A dataframe
.. warning:: If you have lots of files to read in the same archive, it's better just to unzip first.
"""
_module = Module.from_key(key, ensure_exists=True)
return _module.ensure_tar_df(
*subkeys,
Expand All @@ -527,7 +578,28 @@ def ensure_tar_xml(
download_kwargs: Optional[Mapping[str, Any]] = None,
parse_kwargs: Optional[Mapping[str, Any]] = None,
):
"""Download a tar file and open an inner XML file with :mod:`lxml`."""
"""Download a tar file and open an inner file as an XML with :mod:`lxml`.
:param key: The module name
:param subkeys:
A sequence of additional strings to join. If none are given,
returns the directory for this module.
:param url:
The URL to download.
:param inner_path:
The relative path to the file inside the archive
:param name:
Overrides the name of the file at the end of the URL, if given. Also
useful for URLs that don't have proper filenames with extensions.
:param force:
Should the download be done again, even if the path already exists?
Defaults to false.
:param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
:param parse_kwargs: Keyword arguments to pass through to :func:`lxml.etree.parse`.
:returns: An ElementTree object
.. warning:: If you have lots of files to read in the same archive, it's better just to unzip first.
"""
_module = Module.from_key(key, ensure_exists=True)
return _module.ensure_tar_xml(
*subkeys,
Expand All @@ -550,7 +622,27 @@ def ensure_zip_df(
download_kwargs: Optional[Mapping[str, Any]] = None,
read_csv_kwargs: Optional[Mapping[str, Any]] = None,
):
"""Download a zip file and open an inner file as a dataframe with :mod:`pandas`."""
"""Download a zip file and open an inner file as a dataframe with :mod:`pandas`.
:param key: The module name
:param subkeys:
A sequence of additional strings to join. If none are given,
returns the directory for this module.
:param url:
The URL to download.
:param inner_path:
The relative path to the file inside the archive
:param name:
Overrides the name of the file at the end of the URL, if given. Also
useful for URLs that don't have proper filenames with extensions.
:param force:
Should the download be done again, even if the path already exists?
Defaults to false.
:param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
:param read_csv_kwargs: Keyword arguments to pass through to :func:`pandas.read_csv`.
:return: A pandas DataFrame
:rtype: pandas.DataFrame
"""
_module = Module.from_key(key, ensure_exists=True)
return _module.ensure_zip_df(
*subkeys,
Expand All @@ -573,7 +665,29 @@ def ensure_zip_np(
download_kwargs: Optional[Mapping[str, Any]] = None,
load_kwargs: Optional[Mapping[str, Any]] = None,
):
"""Download a zip file and open an inner file as an array with :mod:`numpy`."""
"""Download a zip file and open an inner file as an array-like with :mod:`numpy`.
:param key: The module name
:param subkeys:
A sequence of additional strings to join. If none are given,
returns the directory for this module.
:param url:
The URL to download.
:param inner_path:
The relative path to the file inside the archive
:param name:
Overrides the name of the file at the end of the URL, if given. Also
useful for URLs that don't have proper filenames with extensions.
:param force:
Should the download be done again, even if the path already exists?
Defaults to false.
:param download_kwargs:
Keyword arguments to pass through to :func:`pystow.utils.download`.
:param load_kwargs:
Additional keyword arguments that are passed through to :func:`read_zip_np`
and transitively to :func:`numpy.load`.
:returns: An array-like object
"""
_module = Module.from_key(key, ensure_exists=True)
return _module.ensure_zip_np(
*subkeys,
Expand Down
60 changes: 46 additions & 14 deletions src/pystow/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import logging
import os
from abc import ABC, abstractmethod
from pathlib import Path
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -50,7 +51,7 @@
Getter = Callable[[], X]


class Cached(Generic[X]):
class Cached(Generic[X], ABC):
"""Caching decorator."""

def __init__(
Expand All @@ -67,7 +68,11 @@ def __init__(
self.force = force

def __call__(self, func: Getter[X]) -> Getter[X]:
"""Apply this instance as a decorator."""
"""Apply this instance as a decorator.
:param func: The function to wrap
:return: A wrapped function
"""

@functools.wraps(func)
def _wrapped() -> X:
Expand All @@ -81,25 +86,34 @@ def _wrapped() -> X:

return _wrapped

@abstractmethod
def load(self) -> X:
"""Load data from the cache (typically by opening a file at the given path)."""
raise NotImplementedError

@abstractmethod
def dump(self, rv: X) -> None:
"""Dump data to the cache (typically by opening a file at the given path)."""
raise NotImplementedError
"""Dump data to the cache (typically by opening a file at the given path).
:param rv: The data to dump
"""


class CachedJSON(Cached[JSONType]):
"""Make a function lazily cache its return value as JSON."""

def load(self) -> JSONType:
"""Load data from the cache as JSON."""
"""Load data from the cache as JSON.
:returns: A python object with JSON-like data from the cache
"""
with open(self.path) as file:
return json.load(file)

def dump(self, rv: JSONType) -> None:
"""Dump data to the cache as JSON."""
"""Dump data to the cache as JSON.
:param rv: The JSON data to dump
"""
with open(self.path, "w") as file:
json.dump(rv, file, indent=2)

Expand All @@ -108,12 +122,18 @@ class CachedPickle(Cached[Any]):
"""Make a function lazily cache its return value as a pickle."""

def load(self) -> Any:
"""Load data from the cache as a pickle."""
"""Load data from the cache as a pickle.
:returns: A python object loaded from the cache
"""
with open(self.path, "rb") as file:
return pickle.load(file)

def dump(self, rv: Any) -> None:
"""Dump data to the cache as a pickle."""
"""Dump data to the cache as a pickle.
:param rv: The arbitrary python object to dump
"""
with open(self.path, "wb") as file:
pickle.dump(rv, file, protocol=pickle.HIGHEST_PROTOCOL)

Expand All @@ -122,12 +142,18 @@ class CachedCollection(Cached[List[str]]):
"""Make a function lazily cache its return value as file."""

def load(self) -> List[str]:
"""Load data from the cache as a list of strings."""
"""Load data from the cache as a list of strings.
:returns: A list of strings loaded from the cache
"""
with open(self.path) as file:
return [line.strip() for line in file]

def dump(self, rv: Any) -> None:
"""Dump data to the cache as a list of strings."""
def dump(self, rv: List[str]) -> None:
"""Dump data to the cache as a list of strings.
:param rv: The list of strings to dump
"""
with open(self.path, "w") as file:
for line in rv:
print(line, file=file) # noqa:T001
Expand Down Expand Up @@ -168,7 +194,10 @@ def __init__(
self.read_csv_kwargs.setdefault("keep_default_na", False)

def load(self) -> "pd.DataFrame":
"""Load data from the cache as a dataframe."""
"""Load data from the cache as a dataframe.
:returns: A dataframe loaded from the cache.
"""
import pandas as pd

return pd.read_csv(
Expand All @@ -178,5 +207,8 @@ def load(self) -> "pd.DataFrame":
)

def dump(self, rv: "pd.DataFrame") -> None:
"""Dump data to the cache as a dataframe."""
"""Dump data to the cache as a dataframe.
:param rv: The dataframe to dump
"""
rv.to_csv(self.path, sep=self.sep, index=False)
1 change: 1 addition & 0 deletions src/pystow/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa

"""Command line interface for PyStow."""

Expand Down
24 changes: 21 additions & 3 deletions src/pystow/config_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,25 @@


def get_name() -> str:
"""Get the config home directory name."""
"""Get the config home directory name.
:returns: The name of the pystow home directory, either loaded from
the :data:`CONFIG_NAME_ENVVAR`` environment variable or given by the default
value :data:`CONFIG_NAME_DEFAULT`.
"""
return os.getenv(CONFIG_NAME_ENVVAR, default=CONFIG_NAME_DEFAULT)


def get_home(ensure_exists: bool = True) -> Path:
"""Get the config home directory."""
"""Get the config home directory.
:param ensure_exists: If true, ensures the directory is created
:returns: A path object representing the pystow home directory, as one of:
1. :data:`CONFIG_HOME_ENVVAR` environment variable or
3. The default directory constructed in the user's home directory plus what's
returned by :func:`get_name`.
"""
default = Path.home() / get_name()
return getenv_path(CONFIG_HOME_ENVVAR, default, ensure_exists=ensure_exists)

Expand Down Expand Up @@ -109,7 +122,12 @@ def _cast(rv, dtype):


def write_config(module: str, key: str, value: str) -> None:
"""Write a configuration value."""
"""Write a configuration value.
:param module: The name of the app (e.g., ``indra``)
:param key: The key of the configuration in the app
:param value: The value of the configuration in the app
"""
_get_cfp.cache_clear()
cfp = ConfigParser()
path = get_home() / f"{module}.ini"
Expand Down
Loading

0 comments on commit 7ba8453

Please sign in to comment.