|
4 | 4 | import collections.abc |
5 | 5 | import dataclasses |
6 | 6 | import functools |
| 7 | +import importlib |
7 | 8 | import logging |
8 | 9 | import os |
9 | 10 | import pathlib |
10 | 11 | import shutil |
| 12 | +import sys |
11 | 13 | import typing |
12 | 14 | import warnings |
13 | 15 | from typing import Sequence |
|
26 | 28 | _Path = Union[str, pathlib.Path] |
27 | 29 |
|
28 | 30 |
|
29 | | -def _gmxapi_missing(*args, exc_info=None, **kwargs): |
30 | | - message = ( |
| 31 | +def _gmxapi_missing(*args, msg: str = "", **kwargs): |
| 32 | + """Placeholder function for missing gmxapi functionality. |
| 33 | +
|
| 34 | + Allows import errors to be deferred until run time to aid in docs builds |
| 35 | + and troubleshooting. Try to provide a useful RuntimeError that includes |
| 36 | + the details of the import error(s). |
| 37 | + """ |
| 38 | + _message = ( |
31 | 39 | "brer requires gmxapi. See https://github.com/kassonlab/brer_md#requirements" |
32 | 40 | ) |
33 | | - if exc_info: |
34 | | - message += f" {exc_info}" |
35 | | - raise RuntimeError(message) |
| 41 | + if msg: |
| 42 | + _message = "\n".join((_message, msg)) |
| 43 | + raise RuntimeError(_message) |
36 | 44 |
|
37 | 45 |
|
38 | | -try: |
39 | | - # noinspection PyPep8Naming,PyUnresolvedReferences |
40 | | - from gmxapi.simulation.context import Context as _context |
41 | | -except (ImportError, ModuleNotFoundError) as e: |
42 | | - try: |
43 | | - # noinspection PyPep8Naming |
44 | | - from gmx.context import Context as _context |
45 | | - except (ImportError, ModuleNotFoundError) as e: |
46 | | - missing = functools.partial(_gmxapi_missing, exception=str(e)) |
47 | | - _context = missing |
48 | | - |
49 | | -try: |
50 | | - # noinspection PyUnresolvedReferences |
51 | | - from gmxapi.simulation.workflow import from_tpr |
52 | | -except (ImportError, ModuleNotFoundError) as e: |
53 | | - try: |
54 | | - # noinspection PyPep8Naming |
55 | | - from gmx.workflow import from_tpr |
56 | | - except (ImportError, ModuleNotFoundError) as e: |
57 | | - missing = functools.partial(_gmxapi_missing, exception=str(e)) |
58 | | - from_tpr = missing |
59 | | - |
60 | | -try: |
61 | | - # noinspection PyUnresolvedReferences |
62 | | - from gmxapi.simulation.workflow import WorkElement |
63 | | -except (ImportError, ModuleNotFoundError) as e: |
64 | | - try: |
65 | | - # noinspection PyPep8Naming |
66 | | - from gmx.workflow import WorkElement |
67 | | - except (ImportError, ModuleNotFoundError) as e: |
68 | | - missing = functools.partial(_gmxapi_missing, exception=str(e)) |
69 | | - WorkElement = missing |
| 46 | +def get_api_callable(attr: str, modules: typing.Iterable[str]): |
| 47 | + """Get a gmxapi callable or placeholder. |
| 48 | +
|
| 49 | + Try to import *attr* from successive *modules*. Return the first callable |
| 50 | + found, else return a placeholder that emits a RuntimeError when called. |
| 51 | + """ |
| 52 | + message = "" |
| 53 | + version = "" |
| 54 | + func = None |
| 55 | + for module in modules: |
| 56 | + try: |
| 57 | + mod = importlib.import_module(module) |
| 58 | + func = getattr(mod, attr) |
| 59 | + base = module.split(".")[0] |
| 60 | + version = sys.modules[base].__version__ |
| 61 | + except ImportError as e: |
| 62 | + message = "\n".join((message, f"Could not import {module}: {str(e)}")) |
| 63 | + else: |
| 64 | + break |
| 65 | + if callable(func): |
| 66 | + qualname = ".".join((func.__module__, func.__name__)) |
| 67 | + else: |
| 68 | + func = functools.partial(_gmxapi_missing, msg=message) |
| 69 | + qualname = ".".join((_gmxapi_missing.__module__, "_gmxapi_missing")) |
| 70 | + |
| 71 | + report = "Using" \ |
| 72 | + + " ".join((qualname, version)) \ |
| 73 | + + " for {attr}." |
| 74 | + logging.info(report) |
| 75 | + return func |
| 76 | + |
| 77 | + |
| 78 | +_context = get_api_callable("Context", ("gmxapi.simulation.context", "gmx.context")) |
| 79 | +from_tpr = get_api_callable("from_tpr", ("gmxapi.simulation.workflow", "gmx.workflow")) |
| 80 | +WorkElement = get_api_callable("WorkElement", ("gmxapi.simulation.workflow", "gmx.workflow")) |
70 | 81 |
|
71 | 82 |
|
72 | 83 | def check_consistency(*, data: PairDataCollection, state: RunData): |
|
0 commit comments