Skip to content

Commit

Permalink
Merge pull request #13 from khaeru/issue/5
Browse files Browse the repository at this point in the history
Remove ixmp dependency in tests
  • Loading branch information
khaeru committed Feb 4, 2021
2 parents 60e34a9 + 8a766a0 commit b8fbde6
Show file tree
Hide file tree
Showing 29 changed files with 732 additions and 650 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ jobs:
- name: Check typing with mypy
run: |
pip install mypy pytest xarray
# Also install packages that provide type hints
pip install mypy ixmp pytest xarray
mypy .
- name: Test package build
Expand Down
5 changes: 2 additions & 3 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html


# -- Project information ---------------------------------------------------------------

project = "genno"
Expand Down Expand Up @@ -42,8 +41,8 @@
# Add any paths that contain custom static files (such as style sheets) here, relative
# to this directory. They are copied after the builtin static files, so a file named
# "default.css" will overwrite the builtin "default.css".
# html_static_path = ["_static"]
html_static_path = []
html_static_path = ["_static"]


# -- Options for sphinx.ext.extlinks ---------------------------------------------------

Expand Down
4 changes: 0 additions & 4 deletions genno/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
from .compat.ixmp.reporter import Reporter
from .core import configure
from .core.computer import Computer
from .core.exceptions import ComputationError, KeyExistsError, MissingKeyError
from .core.key import Key
from .core.quantity import Quantity
from .util import RENAME_DIMS

__all__ = [
"RENAME_DIMS",
"ComputationError",
"Computer",
"Key",
"KeyExistsError",
"MissingKeyError",
"Quantity",
"Reporter",
"configure",
]
30 changes: 27 additions & 3 deletions genno/compat/ixmp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
from .reporter import Reporter, keys_for_quantity
HAS_IXMP = True

__all__ = ["Reporter", "keys_for_quantity"]

HAS_IXMP = True
def configure(path=None, **config):
"""Configure :mod:`genno` globally.
Modifies global variables that affect the behaviour of *all* Reporters and
computations, namely :obj:`.RENAME_DIMS`.
Valid configuration keys—passed as *config* keyword arguments—include:
Other Parameters
----------------
rename_dims : mapping of str -> str
Update :obj:`.RENAME_DIMS`.
Warns
-----
UserWarning
If *config* contains unrecognized keys.
"""
from genno import core

from .util import RENAME_DIMS

core.configure(path, **config)

# Dimensions to be renamed
RENAME_DIMS.update(config.get("rename_dims", {}))
7 changes: 4 additions & 3 deletions genno/compat/ixmp/computations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import pandas as pd
import pint

from genno.compat.ixmp.util import RENAME_DIMS, dims_for_qty, get_reversed_rename_dims
from genno.core.quantity import Quantity
from genno.util import RENAME_DIMS, dims_for_qty, get_reversed_rename_dims, parse_units
from genno.util import parse_units

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -59,7 +60,7 @@ def data_for_quantity(ix_type, name, column, scenario, config):
data = getattr(scenario, ix_type)(name, filters)

# ixmp/GAMS scalar is not returned as pd.DataFrame
if isinstance(data, dict):
if isinstance(data, dict): # pragma: no cover
data = pd.DataFrame.from_records([data])

# Warn if no values are returned.
Expand All @@ -86,7 +87,7 @@ def data_for_quantity(ix_type, name, column, scenario, config):
# Remove the unit from the DataFrame
try:
attrs = {"_unit": parse_units(data.pop("unit"))}
except KeyError:
except KeyError: # pragma: no cover
# 'equ' are returned without units
attrs = {}
except ValueError as e:
Expand Down
4 changes: 2 additions & 2 deletions genno/compat/ixmp/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@

from genno.core.computer import Computer
from genno.core.key import Key
from genno.util import RENAME_DIMS, dims_for_qty

from . import computations as ixmp_computations
from .util import RENAME_DIMS, dims_for_qty

log = logging.getLogger(__name__)

Expand All @@ -57,7 +57,7 @@ def from_scenario(cls, scenario, **kwargs):
Returns
-------
:class:`Reporter <genno.Reporter>`
:class:`Reporter <genno.compat.ixmp.Reporter>`
A Reporter instance containing:
- A 'scenario' key referring to the *scenario* object.
Expand Down
38 changes: 38 additions & 0 deletions genno/compat/ixmp/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from functools import lru_cache
from typing import Dict

import pandas as pd

#: Dimensions to rename when extracting raw data from Scenario objects.
#: Mapping from Scenario dimension name -> preferred dimension name.
RENAME_DIMS: Dict[str, str] = {}


def dims_for_qty(data):
"""Return the list of dimensions for *data*.
If *data* is a :class:`pandas.DataFrame`, its columns are processed;
otherwise it must be a list.
genno.RENAME_DIMS is used to rename dimensions.
"""
if isinstance(data, pd.DataFrame):
# List of the dimensions
dims = data.columns.tolist()
else:
dims = list(data)

# Remove columns containing values or units; dimensions are the remainder
for col in "value", "lvl", "mrg", "unit":
try:
dims.remove(col)
except ValueError:
continue

# Rename dimensions
return [RENAME_DIMS.get(d, d) for d in dims]


@lru_cache(1)
def get_reversed_rename_dims():
return {v: k for k, v in RENAME_DIMS.items()}
12 changes: 6 additions & 6 deletions genno/computations.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Elementary computations for reporting."""
"""Elementary computations for genno."""
# Notes:
# - To avoid ambiguity, computations should not have default arguments. Define
# default values for the corresponding methods on the Reporter class.
# default values for the corresponding methods on the Computer class.
import logging
from collections.abc import Mapping
from pathlib import Path
Expand Down Expand Up @@ -136,7 +136,7 @@ def aggregate(quantity, groups, keep):
quantity.sel({dim: members}).sum(dim=dim).assign_coords(**{dim: group})
)
if Quantity.CLASS == "AttrSeries":
# .transpose() is necesary for AttrSeries
# .transpose() is necessary for AttrSeries
agg = agg.transpose(*quantity.dims)
else:
# Restore fill_value=NaN for compatibility
Expand Down Expand Up @@ -182,7 +182,7 @@ def concat(*objs, **kwargs):
Any strings included amongst *args* are discarded, with a logged warning;
these usually indicate that a quantity is referenced which is not in the
Reporter.
Computer.
"""
objs = filter_concat_args(objs)
if Quantity.CLASS == "AttrSeries":
Expand Down Expand Up @@ -295,7 +295,7 @@ def load_file(path, dims={}, units=None, name=None):
"""Read the file at *path* and return its contents as a :class:`.Quantity`.
Some file formats are automatically converted into objects for direct use
in reporting code:
in genno computations:
:file:`.csv`:
Converted to :class:`.Quantity`. CSV files must have a 'value' column;
Expand All @@ -315,7 +315,7 @@ def load_file(path, dims={}, units=None, name=None):
name : str
Name for the loaded Quantity.
"""
# TODO optionally cache: if the same Reporter is used repeatedly, then the file will
# TODO optionally cache: if the same Computer is used repeatedly, then the file will
# be read each time; instead cache the contents in memory.
if path.suffix == ".csv":
data = pd.read_csv(path, comment="#")
Expand Down
12 changes: 3 additions & 9 deletions genno/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import pint
import yaml

from genno.util import RENAME_DIMS, REPLACE_UNITS
from genno.util import REPLACE_UNITS

log = logging.getLogger(__name__)


def configure(path=None, **config):
"""Configure reporting globally.
"""Configure :mod:`genno` globally.
Modifies global variables that affect the behaviour of *all* Computers and
computations, namely :obj:`.RENAME_DIMS` and :obj:`.REPLACE_UNITS`.
computations, namely :obj:`.REPLACE_UNITS`.
Valid configuration keys—passed as *config* keyword arguments—include:
Expand All @@ -28,9 +28,6 @@ def configure(path=None, **config):
:mod:`pint` application registry so that units are recognized. See
the pint :ref:`documentation on defining units <pint:defining>`.
rename_dims : mapping of str -> str
Update :obj:`.RENAME_DIMS`.
Warns
-----
UserWarning
Expand All @@ -54,9 +51,6 @@ def configure(path=None, **config):
for old, new in units.get("replace", {}).items():
REPLACE_UNITS[old] = new

# Dimensions to be renamed
RENAME_DIMS.update(config.get("rename_dims", {}))


def _config_args(path=None, keys={}):
"""Handle configuration arguments."""
Expand Down
16 changes: 8 additions & 8 deletions genno/core/computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Computer:
#: A dask-format :doc:`graph <graphs>`.
graph: Dict[str, Union[str, dict]] = {"config": {}}

#: The default reporting key.
#: The default key to compute for :meth:`.get` with no argument.
default_key = None

# An index of key names -> full keys
Expand All @@ -87,7 +87,7 @@ def configure(self, path=None, **config):
Valid configuration keys include:
- *default*: the default reporting key; sets :attr:`default_key`.
- *default*: the default key; sets :attr:`default_key`.
- *filters*: a :class:`dict`, passed to :meth:`set_filters`.
- *files*: a :class:`list` where every element is a :class:`dict`
of keyword arguments to :meth:`add_file`.
Expand All @@ -109,7 +109,7 @@ def configure(self, path=None, **config):

# Read sections

# Default report
# Default key
try:
self.default_key = config["default"]
except KeyError:
Expand Down Expand Up @@ -424,8 +424,8 @@ def full_key(self, name_or_key):
An quantity 'foo' with dimensions (a, c, n, q, x) is available in the Computer
as ``'foo:a-c-n-q-x'``. This :class:`.Key` can be retrieved with::
rep.full_key("foo")
rep.full_key("foo:c")
c.full_key("foo")
c.full_key("foo:c")
# etc.
"""
name = str(Key.from_str_or_key(name_or_key, drop=True)).rstrip(":")
Expand Down Expand Up @@ -578,7 +578,7 @@ def disaggregate(self, qty, new_dim, method="shares", args=[]):
def add_file(self, path, key=None, **kwargs):
"""Add exogenous quantities from *path*.
Reporting the `key` or using it in other computations causes `path` to
Computing the `key` or using it in other computations causes `path` to
be loaded and converted to :class:`.Quantity`.
Parameters
Expand Down Expand Up @@ -636,15 +636,15 @@ def describe(self, key=None, quiet=True):
return result

def visualize(self, filename, **kwargs):
"""Generate an image describing the reporting structure.
"""Generate an image describing the Computer structure.
This is a shorthand for :meth:`dask.visualize`. Requires
`graphviz <https://pypi.org/project/graphviz/>`__.
"""
return dask.visualize(self.graph, filename=filename, **kwargs)

def write(self, key, path):
"""Write the report *key* to the file *path*."""
"""Write the result of `key` to the file `path`."""
# Call the method directly without adding it to the graph
key = self.check_keys(key)[0]
self._get_comp("write_report")(self.get(key), path)
Expand Down
2 changes: 1 addition & 1 deletion genno/core/describe.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


def describe_recursive(graph, comp, depth=0, seen=None):
"""Recursive helper for :meth:`genno.Reporter.describe`.
"""Recursive helper for :meth:`.describe`.
Parameters
----------
Expand Down
16 changes: 8 additions & 8 deletions genno/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@


class ComputationError(Exception):
"""Wrapper to print intelligible exception information for Reporter.get().
"""Wrapper to print intelligible exception information for Computer.get().
In order to aid in debugging, this helper:
- Omits the parts of the stack trace that are internal to dask, and
- Gives the key in the Reporter.graph and the computation task that
- Gives the key in the Computer.graph and the computation task that
caused the exception.
"""

Expand All @@ -21,7 +21,7 @@ def __str__(self):
"""String representation.
Most exception handling (Python, IPython, Jupyter) will print the
traceback that led to *self* (i.e. the call to :meth:`.Reporter.get`),
traceback that led to *self* (i.e. the call to :meth:`.Computer.get`),
followed by the string returned by this method.
"""
try:
Expand All @@ -40,11 +40,11 @@ def _format(self):
# Assemble the exception printout
return "".join(
chain(
# Reporter information for debugging
# Computer information for debugging
[
f"computing {key} using:\n\n" if key else "",
f"{task}\n\n" if task else "",
"Use Reporter.describe(...) to trace the computation.\n\n",
"Use Computer.describe(...) to trace the computation.\n\n",
"Computation traceback:\n",
],
# Traceback; omitting a few dask internal calls below execute_task
Expand All @@ -66,12 +66,12 @@ def __str__(self):


def process_dask_tb(exc):
"""Process *exc* arising from :meth:`.Reporter.get`.
"""Process *exc* arising from :meth:`.Computer.get`.
Returns a tuple with 3 elements:
- The key of the reporting computation.
- The info key of the reporting computation.
- The key of the computation.
- The info key of the computation.
- A list of traceback.FrameSummary objects, without locals, for *only*
frames that are not internal to dask.
"""
Expand Down
2 changes: 1 addition & 1 deletion genno/core/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def from_str_or_key(cls, value, drop=[], append=[], tag=None):
# Parse a string
name, *dims = value.split(":")
_tag = dims[1] if len(dims) == 2 else None
dims = dims[0].split("-") if len(dims) else []
dims = dims[0].split("-") if len(dims) and dims != [""] else []
base = cls(name, dims, _tag)

# Drop and append dimensions; add tag
Expand Down

0 comments on commit b8fbde6

Please sign in to comment.