Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_with_pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Run tests
run: |
source venv/bin/activate
python -m pytest -n=auto --cov=imas --cov-report=term-missing --cov-report=xml:coverage.xml --cov-report=html:htmlcov --junit-xml=junit.xml
python -m pytest -n=auto --memory --ascii --hdf5 --cov=imas --cov-report=term-missing --cov-report=xml:coverage.xml --cov-report=html:htmlcov --junit-xml=junit.xml

- name: Upload coverage report ${{ matrix.python-version }}
uses: actions/upload-artifact@v4
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ coverage.xml
# Translations
*.mo
*.pot

myenv
Comment thread
prasad-sawantdesai marked this conversation as resolved.
Outdated
# Django stuff:
*.log
local_settings.py
Expand Down
40 changes: 2 additions & 38 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import functools
import logging
import os
import sys
from copy import deepcopy
from pathlib import Path

Expand All @@ -22,7 +21,6 @@
import pytest
from packaging.version import Version

from imas.backends.imas_core.imas_interface import has_imas as _has_imas
from imas.backends.imas_core.imas_interface import ll_interface, lowlevel
from imas.dd_zip import dd_etree, dd_xml_versions, latest_dd_version
from imas.ids_defs import (
Expand All @@ -39,17 +37,7 @@

os.environ["IMAS_AL_DISABLE_VALIDATE"] = "1"


try:
import imas # noqa
except ImportError:

class SkipOnIMASAccess:
def __getattr__(self, attr):
pytest.skip("This test requires the `imas` HLI, which is not available.")

# Any test that tries to access an attribute from the `imas` package will be skipped
sys.modules["imas"] = SkipOnIMASAccess()
Comment thread
prasad-sawantdesai marked this conversation as resolved.
import imas # noqa


def pytest_addoption(parser):
Expand All @@ -70,13 +58,6 @@ def pytest_addoption(parser):
"hdf5": HDF5_BACKEND,
"mdsplus": MDSPLUS_BACKEND,
}
try:
from imas.db_entry import DBEntry
from imas_core.exception import ImasCoreBackendException
DBEntry("imas:mdsplus?path=dummy","r")
except ImasCoreBackendException as iex:
if "not available" in str(iex.message):
_BACKENDS.pop("mdsplus")


try:
Expand All @@ -91,28 +72,11 @@ def worker_id():
@pytest.fixture(params=_BACKENDS)
def backend(pytestconfig: pytest.Config, request: pytest.FixtureRequest):
backends_provided = any(map(pytestconfig.getoption, _BACKENDS))
if not _has_imas:
if backends_provided:
raise RuntimeError(
"Explicit backends are provided, but IMAS is not available."
)
pytest.skip("No IMAS available, skip tests using a backend")
Comment thread
prasad-sawantdesai marked this conversation as resolved.
if backends_provided and not pytestconfig.getoption(request.param):
pytest.skip(f"Tests for {request.param} backend are skipped.")
return _BACKENDS[request.param]


@pytest.fixture()
def has_imas():
return _has_imas


@pytest.fixture()
def requires_imas():
if not _has_imas:
pytest.skip("No IMAS available")


def pytest_generate_tests(metafunc):
if "ids_name" in metafunc.fixturenames:
if metafunc.config.getoption("ids"):
Expand Down Expand Up @@ -214,7 +178,7 @@ def wrapper(*args, **kwargs):


@pytest.fixture
def log_lowlevel_calls(monkeypatch, requires_imas):
def log_lowlevel_calls(monkeypatch):
"""Debugging fixture to log calls to the imas lowlevel module."""
for al_function in dir(lowlevel):
if al_function.startswith("ual_") or al_function.startswith("al"):
Expand Down
13 changes: 1 addition & 12 deletions imas/backends/imas_core/db_entry_al.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

from .al_context import ALContext, LazyALContext
from .db_entry_helpers import delete_children, get_children, put_children
from .imas_interface import LLInterfaceError, has_imas, ll_interface
from .imas_interface import LLInterfaceError, ll_interface
from .mdsplus_model import mdsplus_model_dir
from .uda_support import extract_idsdef, get_dd_version_from_idsdef_xml

Expand All @@ -52,14 +52,6 @@
logger = logging.getLogger(__name__)


def require_imas_available():
if not has_imas:
raise RuntimeError(
"The IMAS Core library is not available. Please install 'imas_core', "
"or load a supported IMAS module if you use an HPC environment."
)


class ALDBEntryImpl(DBEntryImpl):
"""DBEntry implementation using imas_core as a backend."""

Expand All @@ -86,7 +78,6 @@ def __init__(self, uri: str, mode: int, factory: IDSFactory):

@classmethod
def from_uri(cls, uri: str, mode: str, factory: IDSFactory) -> "ALDBEntryImpl":
require_imas_available()
if mode not in _OPEN_MODES:
modes = list(_OPEN_MODES)
raise ValueError(f"Unknown mode {mode!r}, was expecting any of {modes}")
Expand All @@ -105,8 +96,6 @@ def from_pulse_run(
options: Any,
factory: IDSFactory,
) -> "ALDBEntryImpl":
# Raise an error if imas is not available
require_imas_available()

# Set defaults
user_name = user_name or getpass.getuser()
Expand Down
31 changes: 9 additions & 22 deletions imas/backends/imas_core/imas_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,17 @@

from packaging.version import Version

logger = logging.getLogger(__name__)
# Import the Access Layer module
# First try to import imas_core, which is available since AL 5.2
from imas_core import _al_lowlevel as lowlevel
from imas_core import imasdef # noqa: F401

logger = logging.getLogger(__name__)

# Import the Access Layer module
has_imas = True
try:
# First try to import imas_core, which is available since AL 5.2
from imas_core import _al_lowlevel as lowlevel
from imas_core import imasdef

# Enable throwing exceptions from the _al_lowlevel interface
enable_exceptions = getattr(lowlevel, "imas_core_config_enable_exceptions", None)
if enable_exceptions:
enable_exceptions()

except ImportError as exc:
imas = None
has_imas = False
imasdef = None
lowlevel = None
logger.warning(
"Could not import 'imas_core': %s. Some functionality is not available.",
exc,
)
# Enable throwing exceptions from the _al_lowlevel interface
enable_exceptions = getattr(lowlevel, "imas_core_config_enable_exceptions", None)
if enable_exceptions:
enable_exceptions()


class LLInterfaceError(RuntimeError):
Expand Down
6 changes: 2 additions & 4 deletions imas/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@


# Expose ALException, which may be thrown by the lowlevel
if _imas_interface.has_imas:
ALException = _imas_interface.lowlevel.ALException
else:
ALException = None

ALException = _imas_interface.lowlevel.ALException


class IDSNameError(ValueError):
Expand Down
114 changes: 37 additions & 77 deletions imas/ids_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,86 +86,46 @@
Identifier for the default serialization protocol.
"""

import functools
import logging

from imas.backends.imas_core.imas_interface import has_imas, imasdef
from imas.backends.imas_core.imas_interface import imasdef

logger = logging.getLogger(__name__)


if has_imas:
ASCII_BACKEND = imasdef.ASCII_BACKEND
CHAR_DATA = imasdef.CHAR_DATA
CLOSE_PULSE = imasdef.CLOSE_PULSE
CLOSEST_INTERP = imasdef.CLOSEST_INTERP
CREATE_PULSE = imasdef.CREATE_PULSE
DOUBLE_DATA = imasdef.DOUBLE_DATA
COMPLEX_DATA = imasdef.COMPLEX_DATA
EMPTY_COMPLEX = imasdef.EMPTY_COMPLEX
EMPTY_FLOAT = imasdef.EMPTY_FLOAT
EMPTY_INT = imasdef.EMPTY_INT
ERASE_PULSE = imasdef.ERASE_PULSE
FORCE_CREATE_PULSE = imasdef.FORCE_CREATE_PULSE
FORCE_OPEN_PULSE = imasdef.FORCE_OPEN_PULSE
HDF5_BACKEND = imasdef.HDF5_BACKEND
IDS_TIME_MODE_HETEROGENEOUS = imasdef.IDS_TIME_MODE_HETEROGENEOUS
IDS_TIME_MODE_HOMOGENEOUS = imasdef.IDS_TIME_MODE_HOMOGENEOUS
IDS_TIME_MODE_INDEPENDENT = imasdef.IDS_TIME_MODE_INDEPENDENT
IDS_TIME_MODE_UNKNOWN = imasdef.IDS_TIME_MODE_UNKNOWN
IDS_TIME_MODES = imasdef.IDS_TIME_MODES
INTEGER_DATA = imasdef.INTEGER_DATA
LINEAR_INTERP = imasdef.LINEAR_INTERP
MDSPLUS_BACKEND = imasdef.MDSPLUS_BACKEND
MEMORY_BACKEND = imasdef.MEMORY_BACKEND
NODE_TYPE_STRUCTURE = imasdef.NODE_TYPE_STRUCTURE
OPEN_PULSE = imasdef.OPEN_PULSE
PREVIOUS_INTERP = imasdef.PREVIOUS_INTERP
READ_OP = imasdef.READ_OP
UDA_BACKEND = imasdef.UDA_BACKEND
UNDEFINED_INTERP = imasdef.UNDEFINED_INTERP
UNDEFINED_TIME = imasdef.UNDEFINED_TIME
WRITE_OP = imasdef.WRITE_OP
ASCII_SERIALIZER_PROTOCOL = getattr(imasdef, "ASCII_SERIALIZER_PROTOCOL", 60)
FLEXBUFFERS_SERIALIZER_PROTOCOL = getattr(
imasdef, "FLEXBUFFERS_SERIALIZER_PROTOCOL", None
)
DEFAULT_SERIALIZER_PROTOCOL = getattr(imasdef, "DEFAULT_SERIALIZER_PROTOCOL", 60)

else:
# Preset some constants which are used elsewhere
# this is a bit ugly, perhaps reuse the list of imports from above?
# it seems no problem to use None, since the use of the values should not
# be allowed, they are only used in operations which use the backend,
# which we (should) gate
ASCII_BACKEND = CHAR_DATA = CLOSE_PULSE = CLOSEST_INTERP = DOUBLE_DATA = None
FORCE_OPEN_PULSE = CREATE_PULSE = ERASE_PULSE = None
COMPLEX_DATA = FORCE_CREATE_PULSE = HDF5_BACKEND = None
INTEGER_DATA = LINEAR_INTERP = MDSPLUS_BACKEND = MEMORY_BACKEND = None
NODE_TYPE_STRUCTURE = OPEN_PULSE = PREVIOUS_INTERP = READ_OP = None
UDA_BACKEND = UNDEFINED_INTERP = UNDEFINED_TIME = WRITE_OP = None
# These constants are also useful when not working with the AL
EMPTY_FLOAT = -9e40
EMPTY_INT = -999_999_999
EMPTY_COMPLEX = complex(EMPTY_FLOAT, EMPTY_FLOAT)
IDS_TIME_MODE_UNKNOWN = EMPTY_INT
IDS_TIME_MODE_HETEROGENEOUS = 0
IDS_TIME_MODE_HOMOGENEOUS = 1
IDS_TIME_MODE_INDEPENDENT = 2
IDS_TIME_MODES = [0, 1, 2]
ASCII_SERIALIZER_PROTOCOL = 60
FLEXBUFFERS_SERIALIZER_PROTOCOL = None
DEFAULT_SERIALIZER_PROTOCOL = 60


def needs_imas(func):
if has_imas:
return func

@functools.wraps(func)
def wrapper(*args, **kwargs):
raise RuntimeError(
f"Function {func.__name__} requires IMAS, but IMAS is not available."
)

return wrapper
ASCII_BACKEND = imasdef.ASCII_BACKEND
CHAR_DATA = imasdef.CHAR_DATA
CLOSE_PULSE = imasdef.CLOSE_PULSE
CLOSEST_INTERP = imasdef.CLOSEST_INTERP
CREATE_PULSE = imasdef.CREATE_PULSE
DOUBLE_DATA = imasdef.DOUBLE_DATA
COMPLEX_DATA = imasdef.COMPLEX_DATA
EMPTY_COMPLEX = imasdef.EMPTY_COMPLEX
EMPTY_FLOAT = imasdef.EMPTY_FLOAT
EMPTY_INT = imasdef.EMPTY_INT
ERASE_PULSE = imasdef.ERASE_PULSE
FORCE_CREATE_PULSE = imasdef.FORCE_CREATE_PULSE
FORCE_OPEN_PULSE = imasdef.FORCE_OPEN_PULSE
HDF5_BACKEND = imasdef.HDF5_BACKEND
IDS_TIME_MODE_HETEROGENEOUS = imasdef.IDS_TIME_MODE_HETEROGENEOUS
IDS_TIME_MODE_HOMOGENEOUS = imasdef.IDS_TIME_MODE_HOMOGENEOUS
IDS_TIME_MODE_INDEPENDENT = imasdef.IDS_TIME_MODE_INDEPENDENT
IDS_TIME_MODE_UNKNOWN = imasdef.IDS_TIME_MODE_UNKNOWN
IDS_TIME_MODES = imasdef.IDS_TIME_MODES
INTEGER_DATA = imasdef.INTEGER_DATA
LINEAR_INTERP = imasdef.LINEAR_INTERP
MDSPLUS_BACKEND = imasdef.MDSPLUS_BACKEND
MEMORY_BACKEND = imasdef.MEMORY_BACKEND
NODE_TYPE_STRUCTURE = imasdef.NODE_TYPE_STRUCTURE
OPEN_PULSE = imasdef.OPEN_PULSE
PREVIOUS_INTERP = imasdef.PREVIOUS_INTERP
READ_OP = imasdef.READ_OP
UDA_BACKEND = imasdef.UDA_BACKEND
UNDEFINED_INTERP = imasdef.UNDEFINED_INTERP
UNDEFINED_TIME = imasdef.UNDEFINED_TIME
WRITE_OP = imasdef.WRITE_OP
ASCII_SERIALIZER_PROTOCOL = getattr(imasdef, "ASCII_SERIALIZER_PROTOCOL", 60)
FLEXBUFFERS_SERIALIZER_PROTOCOL = getattr(
imasdef, "FLEXBUFFERS_SERIALIZER_PROTOCOL", None
)
DEFAULT_SERIALIZER_PROTOCOL = getattr(imasdef, "DEFAULT_SERIALIZER_PROTOCOL", 60)
8 changes: 0 additions & 8 deletions imas/ids_toplevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
IDS_TIME_MODE_INDEPENDENT,
IDS_TIME_MODE_UNKNOWN,
IDS_TIME_MODES,
needs_imas,
)
from imas.ids_metadata import IDSMetadata, IDSType, get_toplevel_metadata
from imas.ids_structure import IDSStructure
Expand Down Expand Up @@ -99,7 +98,6 @@ def default_serializer_protocol():
"""Return the default serializer protocol."""
return DEFAULT_SERIALIZER_PROTOCOL

@needs_imas
def serialize(self, protocol=None) -> bytes:
"""Serialize this IDS to a data buffer.

Expand Down Expand Up @@ -169,7 +167,6 @@ def serialize(self, protocol=None) -> bytes:
return bytes(buffer)
raise ValueError(f"Unrecognized serialization protocol: {protocol}")

@needs_imas
def deserialize(self, data: bytes) -> None:
"""Deserialize the data buffer into this IDS.

Expand Down Expand Up @@ -289,7 +286,6 @@ def _validate(self):
for child in self.iter_nonempty_(accept_lazy=True):
child._validate()

@needs_imas
def get(self, occurrence: int = 0, db_entry: Optional["DBEntry"] = None) -> None:
"""Get data from AL backend storage format.

Expand All @@ -300,7 +296,6 @@ def get(self, occurrence: int = 0, db_entry: Optional["DBEntry"] = None) -> None
raise NotImplementedError()
db_entry.get(self.metadata.name, occurrence, destination=self)

@needs_imas
def getSlice(
self,
time_requested: float,
Expand All @@ -323,7 +318,6 @@ def getSlice(
destination=self,
)

@needs_imas
def putSlice(
self, occurrence: int = 0, db_entry: Optional["DBEntry"] = None
) -> None:
Expand All @@ -336,7 +330,6 @@ def putSlice(
raise NotImplementedError()
db_entry.put_slice(self, occurrence)

@needs_imas
def deleteData(
self, occurrence: int = 0, db_entry: Optional["DBEntry"] = None
) -> None:
Expand All @@ -349,7 +342,6 @@ def deleteData(
raise NotImplementedError()
db_entry.delete_data(self, occurrence)

@needs_imas
def put(self, occurrence: int = 0, db_entry: Optional["DBEntry"] = None) -> None:
"""Put this IDS to the backend.

Expand Down
Loading
Loading