Skip to content

Commit

Permalink
Merge pull request #377 from ungarj/testing_tools
Browse files Browse the repository at this point in the history
add testing module
  • Loading branch information
ungarj authored Nov 3, 2021
2 parents 4490a18 + b2377a5 commit 9284ab1
Show file tree
Hide file tree
Showing 20 changed files with 617 additions and 534 deletions.
2 changes: 1 addition & 1 deletion mapchete/commands/_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


def convert(
tiledir: str,
tiledir: Union[str, dict],
output: str,
zoom: Union[int, List[int]] = None,
area: Union[BaseGeometry, str, dict] = None,
Expand Down
137 changes: 75 additions & 62 deletions mapchete/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,61 +478,17 @@ def input(self):
]
)

initalized_inputs = OrderedDict()

if self._init_inputs:
for k, v in raw_inputs.items():
# for files and tile directories
if isinstance(v, str):
logger.debug("load input reader for simple input %s", v)
try:
reader = load_input_reader(
dict(
path=absolute_path(path=v, base_dir=self.config_dir),
pyramid=self.process_pyramid,
pixelbuffer=self.process_pyramid.pixelbuffer,
delimiters=self._delimiters,
),
readonly=self.mode == "readonly",
)
except Exception as e:
logger.exception(e)
raise MapcheteDriverError(
"error when loading input %s: %s" % (v, e)
)
logger.debug("input reader for simple input %s is %s", v, reader)

# for abstract inputs
elif isinstance(v, dict):
logger.debug("load input reader for abstract input %s", v)
try:
reader = load_input_reader(
dict(
abstract=deepcopy(v),
pyramid=self.process_pyramid,
pixelbuffer=self.process_pyramid.pixelbuffer,
delimiters=self._delimiters,
conf_dir=self.config_dir,
),
readonly=self.mode == "readonly",
)
except Exception as e:
logger.exception(e)
raise MapcheteDriverError(
"error when loading input %s: %s" % (v, e)
)
logger.debug("input reader for abstract input %s is %s", v, reader)
else:
raise MapcheteConfigError("invalid input type %s", type(v))
# trigger bbox creation
reader.bbox(out_crs=self.process_pyramid.crs)
initalized_inputs[k] = reader
return initialize_inputs(
raw_inputs,
config_dir=self.config_dir,
pyramid=self.process_pyramid,
delimiters=self._delimiters,
readonly=self.mode == "readonly",
)

else:
for k in raw_inputs.keys():
initalized_inputs[k] = None

return initalized_inputs
return OrderedDict([(k, None) for k in raw_inputs.keys()])

@cached_property
def baselevels(self):
Expand Down Expand Up @@ -604,16 +560,9 @@ def get_process_func_params(self, zoom):
def get_inputs_for_tile(self, tile):
"""Get and open all inputs for given tile."""

def _open_inputs(i):
for k, v in i.items():
if v is None:
continue
elif isinstance(v, dict):
yield (k, list(_open_inputs(v)))
else:
yield (k, v.open(tile))

return OrderedDict(list(_open_inputs(self.params_at_zoom(tile.zoom)["input"])))
return OrderedDict(
list(open_inputs(self.params_at_zoom(tile.zoom)["input"], tile))
)

def params_at_zoom(self, zoom):
"""
Expand Down Expand Up @@ -1052,6 +1001,60 @@ def get_process_func(process=None, config_dir=None, run_compile=False):
raise MapcheteProcessImportError(e)


def initialize_inputs(
raw_inputs,
config_dir=None,
pyramid=None,
delimiters=None,
readonly=False,
):
initalized_inputs = OrderedDict()
for k, v in raw_inputs.items():
# for files and tile directories
if isinstance(v, str):
logger.debug("load input reader for simple input %s", v)
try:
reader = load_input_reader(
dict(
path=absolute_path(path=v, base_dir=config_dir),
pyramid=pyramid,
pixelbuffer=pyramid.pixelbuffer,
delimiters=delimiters,
),
readonly=readonly,
)
except Exception as e:
logger.exception(e)
raise MapcheteDriverError("error when loading input %s: %s" % (v, e))
logger.debug("input reader for simple input %s is %s", v, reader)

# for abstract inputs
elif isinstance(v, dict):
logger.debug("load input reader for abstract input %s", v)
try:
reader = load_input_reader(
dict(
abstract=deepcopy(v),
pyramid=pyramid,
pixelbuffer=pyramid.pixelbuffer,
delimiters=delimiters,
conf_dir=config_dir,
),
readonly=readonly,
)
except Exception as e:
logger.exception(e)
raise MapcheteDriverError("error when loading input %s: %s" % (v, e))
logger.debug("input reader for abstract input %s is %s", v, reader)
else:
raise MapcheteConfigError("invalid input type %s", type(v))
# trigger bbox creation
reader.bbox(out_crs=pyramid.crs)
initalized_inputs[k] = reader

return initalized_inputs


def _load_process_module(process=None, config_dir=None, run_compile=False):
tmpfile = None
try:
Expand Down Expand Up @@ -1095,6 +1098,16 @@ def _load_process_module(process=None, config_dir=None, run_compile=False):
return module


def open_inputs(inputs, tile):
for k, v in inputs.items():
if v is None:
continue
elif isinstance(v, dict):
yield (k, list(open_inputs(v, tile)))
else:
yield (k, v.open(tile))


def _config_to_dict(input_config):
if isinstance(input_config, dict):
if "config_dir" not in input_config:
Expand Down
123 changes: 123 additions & 0 deletions mapchete/testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
Useful tools to facilitate testing.
"""
from collections import OrderedDict
import logging
import os
import oyaml as yaml
from shapely.ops import unary_union
import uuid

import mapchete
from mapchete.config import initialize_inputs, open_inputs
from mapchete.io import fs_from_path
from mapchete.tile import BufferedTile, BufferedTilePyramid


logger = logging.getLogger(__name__)


# helper functions
def dict_from_mapchete(path):
"""
Read mapchete configuration from file and return as dictionary.
"""
with open(path) as src:
return dict(yaml.safe_load(src.read()), config_dir=os.path.dirname(path))


class ProcessFixture:
def __init__(self, path=None, output_tempdir=None, output_suffix=""):
self.path = path
self.dict = None
if output_tempdir:
self._output_tempdir = (
os.path.join(output_tempdir, uuid.uuid4().hex) + output_suffix
)
else:
self._output_tempdir = None
self._out_fs = None
self._mp = None

def __enter__(self, *args):
self.dict = dict_from_mapchete(self.path)
if self._output_tempdir:
# set output directory
self.dict["output"]["path"] = self._output_tempdir

return self

def __exit__(self, *args):
# properly close mapchete
if self._mp:
self._mp.__exit__(*args)
self.clear_output()

def clear_output(self):
# delete written output if any
if self._output_tempdir:
out_dir = self._output_tempdir
else:
out_dir = os.path.join(self.dict["config_dir"], self.dict["output"]["path"])
try:
fs_from_path(out_dir).rm(out_dir, recursive=True)
except FileNotFoundError:
pass

def process_mp(self, tile=None, tile_zoom=None):
"""
Return MapcheteProcess object used to test processes.
"""
if tile:
tile = self.mp().config.process_pyramid.tile(*tile)
else:
# just use first process tile from lowest zoom level
tile = self.first_process_tile(zoom=tile_zoom)
return mapchete.MapcheteProcess(
tile=tile,
params=self.mp().config.params_at_zoom(tile.zoom),
input=self.mp().config.get_inputs_for_tile(tile),
)

def mp(self):
"""
Return Mapchete object from mapchete.open().
"""
if not self._mp:
self._mp = mapchete.open(self.dict)
return self._mp

def first_process_tile(self, zoom=None):
zoom = zoom or max(self.mp().config.zoom_levels)
return next(self.mp().get_process_tiles(zoom))


def get_process_mp(tile=None, zoom=0, input=None, params=None, metatiling=1, **kwargs):
if tile:
pyramid = BufferedTilePyramid("geodetic", metatiling=metatiling)
tile = pyramid.tile(*tile)
else:
pyramid = BufferedTilePyramid("geodetic", metatiling=metatiling)
initialized_inputs = initialize_inputs(
input,
config_dir=None,
pyramid=pyramid,
delimiters=None,
readonly=False,
)
if tile is None:
if zoom is None: # pragma: no cover
raise ValueError("either tile or tile_zoom have to be provided")
tile = next(
pyramid.tiles_from_geom(
unary_union(
[v.bbox(out_crs=pyramid.crs) for v in initialized_inputs.values()]
),
zoom,
)
)
logger.debug(f"tile is {tile}")
inputs = OrderedDict(open_inputs(initialized_inputs, tile))
return mapchete.MapcheteProcess(
tile=tile, input=inputs, params=params or {}, **kwargs
)
2 changes: 1 addition & 1 deletion test/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Run tests

.. code-block:: shell
py.test test_*.py
pytest -v --cov mapchete
Under certain environments, curl-based tests can fail. In this case, try:
Expand Down
Loading

0 comments on commit 9284ab1

Please sign in to comment.