Skip to content

Commit

Permalink
Added ability to input a path to geo files (#83)
Browse files Browse the repository at this point in the history
A file path to a geo spec file or compressed geo archive can now be passed in wherever a named geo from the library could be passed previously. Also fixes an issue where error messaging for an exception caused by a missing api key was unclear.
  • Loading branch information
TJohnsonAZ authored Feb 2, 2024
1 parent 58d1b76 commit ea4c4d8
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 24 deletions.
32 changes: 24 additions & 8 deletions epymorph/cli/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"""
import os
from argparse import _SubParsersAction
from pathlib import Path

from epymorph.data import geo_library, geo_library_dynamic
from epymorph.geo import cache
from epymorph.geo.static import StaticGeoFileOps as F

Expand All @@ -28,13 +30,13 @@ def define_argparser(command_parser: _SubParsersAction):
fetch_command.add_argument(
'geo',
type=str,
help='the name of a geo from the library')
help='the name of the geo to fetch; must include a geo path if not already in the library')
fetch_command.add_argument(
'-f', '--force',
action='store_true',
help='(optional) include this flag to force an override of previously cached data')
fetch_command.set_defaults(handler=lambda args: fetch(
geo_name=args.geo,
geo_name_or_path=args.geo,
force=args.force
))

Expand Down Expand Up @@ -66,17 +68,31 @@ def define_argparser(command_parser: _SubParsersAction):
# - 2 empty cache


def fetch(geo_name: str, force: bool) -> int:
def fetch(geo_name_or_path: str, force: bool) -> int:
"""CLI command handler: cache dynamic geo data."""
# cache specified geo
filepath = cache.CACHE_PATH / F.to_archive_filename(geo_name)

# split geo name and path
if geo_name_or_path in geo_library_dynamic:
geo_name = geo_name_or_path
geo_path = None
elif os.path.exists(Path(geo_name_or_path).expanduser()):
geo_path = Path(geo_name_or_path).expanduser()
geo_name = geo_path.stem
else:
raise cache.GeoCacheException("Specified geo not found.")

# cache geo according to information passed
file_path = cache.CACHE_PATH / F.to_archive_filename(geo_name)
if geo_path is not None and geo_name in geo_library:
msg = f"A geo named {geo_name} is already present in the library. Please use the existing geo or change the file name."
raise cache.GeoCacheException(msg)
choice = 'y'
if os.path.exists(filepath) and not force:
if os.path.exists(file_path) and not force:
choice = input(f'{geo_name} is already cached, overwrite? [y/n] ')
if force or choice == 'y':
try:
cache.fetch(geo_name)
print('geo sucessfully cached.')
cache.fetch(geo_name_or_path)
print("geo sucessfully cached.")
except cache.GeoCacheException as e:
print(e)
return 1 # exit code: geo not found
Expand Down
30 changes: 25 additions & 5 deletions epymorph/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
from epymorph.data import Library, geo_library, ipm_library, mm_library
from epymorph.engine.standard_sim import Output, StandardSimulation
from epymorph.error import UnknownModel
from epymorph.geo.adrio import adrio_maker_library
from epymorph.geo.cache import load_from_cache
from epymorph.geo.dynamic import DynamicGeoFileOps
from epymorph.geo.geo import Geo
from epymorph.geo.static import StaticGeoFileOps
from epymorph.initializer import initializer_library, normalize_init_params
from epymorph.movement.parser import MovementSpec, parse_movement_spec
from epymorph.simulation import (TimeFrame, default_rng, enable_logging,
Expand Down Expand Up @@ -48,6 +51,7 @@ def define_argparser(command_parser: _SubParsersAction):
help='(optional) include this flag to run in profiling mode')
p.add_argument(
'-i', '--ignore_cache',
action='store_true',
help='(optional) include this flag to run the simulation without utilizing the Geo cache.'
)
p.set_defaults(handler=lambda args: run(
Expand Down Expand Up @@ -232,17 +236,33 @@ def decorator(*args: P.args, **kwargs: P.kwargs) -> ModelT:


@load_messaging("GEO")
def load_model_geo(name: str, ignore_cache: bool) -> Geo:
def load_model_geo(name_or_path: str, ignore_cache: bool) -> Geo:
"""Loads a geo by name."""
if not ignore_cache:
cached = load_from_cache(name)
cached = load_from_cache(name_or_path)
if cached is not None:
return cached

if name not in geo_library:
raise UnknownModel('GEO', name)
if name_or_path in geo_library:
return geo_library[name_or_path]()

path = Path(name_or_path).expanduser()
if not path.exists():
raise UnknownModel('GEO', name_or_path)

# check for non-library geo cached
if not ignore_cache:
cached = load_from_cache(path.stem)
if cached is not None:
return cached

return geo_library[name]()
if path.suffix == ".geo":
return DynamicGeoFileOps.load_from_spec(path, adrio_maker_library)
elif path.suffix == ".tar":
return StaticGeoFileOps.load_from_archive(path)
else:
msg = "Invalid file type. A geo can only be loaded from a .geo or .geo.tar file."
raise Exception(msg)


@load_messaging("IPM")
Expand Down
35 changes: 25 additions & 10 deletions epymorph/geo/cache.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Logic for saving to, loading from, and managing a cache of geos on the user's hard disk."""
import os
from pathlib import Path

from platformdirs import user_cache_path

from epymorph.data import geo_library_dynamic
from epymorph.data import adrio_maker_library, geo_library_dynamic
from epymorph.geo.dynamic import DynamicGeo
from epymorph.geo.dynamic import DynamicGeoFileOps as DF
from epymorph.geo.geo import Geo
from epymorph.geo.static import StaticGeo
from epymorph.geo.static import StaticGeoFileOps as F
Expand All @@ -17,19 +19,32 @@ class GeoCacheException(Exception):
"""An exception raised when a geo cache operation fails."""


def fetch(geo_name: str) -> None:
def fetch(geo_name_or_path: str) -> None:
"""
Caches all attribute data for a dynamic geo from the library.
Caches all attribute data for a dynamic geo from the library or spec file at a given path.
Raises GeoCacheException if spec not found.
"""
file_path = CACHE_PATH / F.to_archive_filename(geo_name)
geo_load = geo_library_dynamic.get(geo_name)
if geo_load is not None:
geo = geo_load()
static_geo = convert_to_static_geo(geo)
F.save_as_archive(static_geo, file_path)

# checks for geo in the library (name passed)
if geo_name_or_path in geo_library_dynamic:
file_path = CACHE_PATH / F.to_archive_filename(geo_name_or_path)
geo_load = geo_library_dynamic.get(geo_name_or_path)
if geo_load is not None:
geo = geo_load()
static_geo = convert_to_static_geo(geo)
static_geo.save(file_path)

# checks for geo spec at given path (path passed)
else:
raise GeoCacheException(f'spec file for {geo_name} not found.')
geo_path = Path(geo_name_or_path).expanduser()
if os.path.exists(geo_path):
geo_name = geo_path.stem
file_path = CACHE_PATH / F.to_archive_filename(geo_name)
geo = DF.load_from_spec(geo_path, adrio_maker_library)
static_geo = convert_to_static_geo(geo)
static_geo.save(file_path)
else:
raise GeoCacheException(f'spec file at {geo_name_or_path} not found.')


def remove(geo_name: str) -> None:
Expand Down
3 changes: 2 additions & 1 deletion epymorph/geo/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ def load_from_spec(file: os.PathLike, adrio_maker_library: ADRIOMakerLibrary) ->
spec = DynamicGeoSpec.deserialize(spec_json)
return DynamicGeo.from_library(spec, adrio_maker_library)
except Exception as e:
raise GeoValidationException(f"Unable to load '{file}' as a geo.") from e
msg = f"Unable to load '{file}' as a geo: {e}"
raise GeoValidationException(msg) from e

0 comments on commit ea4c4d8

Please sign in to comment.