Skip to content

Commit

Permalink
fix: Don't define terse SCSV schema parser unles Python >= 3.12
Browse files Browse the repository at this point in the history
It requires `itertools.batched` which was added in Python 3.12.
Also removes the import of `pydrex.io` in `logging`, to avoid cyclical
import errors.
  • Loading branch information
adigitoleo committed Jun 6, 2024
1 parent ade34f3 commit 964caee
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 36 deletions.
23 changes: 23 additions & 0 deletions src/pydrex/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"""

import contextlib as cl
import collections as c
import csv
import functools as ft
Expand All @@ -22,6 +23,7 @@
import pathlib
import re
import sys
import logging

if sys.version_info >= (3, 11):
import tomllib
Expand All @@ -38,6 +40,7 @@

from pydrex import core as _core
from pydrex import exceptions as _err
from pydrex import utils as _utils
from pydrex import logger as _log
from pydrex import velocity as _velocity

Expand Down Expand Up @@ -156,6 +159,7 @@ def extract_h5part(file, phase, fabric, n_grains, output):
)


@_utils.defined_if(sys.version_info >= (3, 12))
def parse_scsv_schema(terse_schema):
"""Parse terse scsv schema representation and return the expanded schema.
Expand All @@ -172,6 +176,9 @@ def parse_scsv_schema(terse_schema):
(which must be valid Python identifiers) and their (optional) data type, missing
data fill value, and unit/comment.
.. note:: This function is only defined if the version of your Python interpreter is
greater than 3.11.x.
>>> # delimiter
>>> # | missing data encoding column specifications
>>> # | | ______________________|______________________________
Expand Down Expand Up @@ -758,3 +765,19 @@ def data(directory):
return resolve_path(resources / directory)
else:
raise NotADirectoryError(f"{resources / directory} is not a directory")


@cl.contextmanager
def logfile_enable(path, level=logging.DEBUG, mode="w"):
"""Enable logging to a file at `path` with given `level`."""
logger_file = logging.FileHandler(resolve_path(path), mode=mode)
logger_file.setFormatter(
logging.Formatter(
"%(levelname)s [%(asctime)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
)
logger_file.setLevel(level)
_log.LOGGER.addHandler(logger_file)
yield
logger_file.close()
23 changes: 4 additions & 19 deletions src/pydrex/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@
_log.info("this message will be printed to the console")
```
To save debug logs to a file, the `logfile_enable` context manager is recommended.
To save logs to a file, the `pydrex.io.logfile_enable` context manager is recommended.
Always use the old printf style formatting for log messages, not fstrings,
otherwise compute time will be wasted on string conversions when logging is disabled:
```python
from pydrex import io as _io
_log.quiet_aliens() # Suppress third-party log messages except CRITICAL from Numba.
with _log.logfile_enable("my_log_file.log"): # Overwrite existing file unless mode="a".
with _io.logfile_enable("my_log_file.log"): # Overwrite existing file unless mode="a".
value = 42
_log.critical("critical error with value: %s", value)
_log.error("runtime error with value: %s", value)
Expand All @@ -59,7 +60,7 @@

import numpy as np

from pydrex import io as _io
# NOTE: Do NOT import any pydrex submodules here to avoid cyclical imports.

np.set_printoptions(
formatter={"float_kind": np.format_float_scientific},
Expand Down Expand Up @@ -133,22 +134,6 @@ def handler_level(level, handler=CONSOLE_LOGGER):
handler.setLevel(default_level)


@cl.contextmanager
def logfile_enable(path, level=logging.DEBUG, mode="w"):
"""Enable logging to a file at `path` with given `level`."""
logger_file = logging.FileHandler(_io.resolve_path(path), mode=mode)
logger_file.setFormatter(
logging.Formatter(
"%(levelname)s [%(asctime)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
)
logger_file.setLevel(level)
LOGGER.addHandler(logger_file)
yield
logger_file.close()


def critical(msg, *args, **kwargs):
"""Log a CRITICAL message in PyDRex."""
LOGGER.critical(msg, *args, **kwargs)
Expand Down
22 changes: 21 additions & 1 deletion src/pydrex/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class SerializedCallable:
function), use the `serializable` decorator.
"""

def __init__(self, f):
self._f = dill.dumps(f, protocol=5, byref=True)

Expand All @@ -56,10 +57,29 @@ def __call__(self, *args, **kwargs):


def serializable(f):
"""Make wrapped function serializable."""
"""Make decorated function serializable."""
return SerializedCallable(f)


def defined_if(cond):
"""Only define decorated function if `cond` is `True`."""

def _defined_if(f):
def not_f(*args, **kwargs):
# Throw the same as we would get from `type(undefined_symbol)`.
raise NameError(f"name '{f.__name__}' is not defined")

@wraps(f)
def wrapper(*args, **kwargs):
if cond:
return f(*args, **kwargs)
return not_f(*args, **kwargs)

return wrapper

return _defined_if


@nb.njit(fastmath=True)
def strain_increment(dt, velocity_gradient):
"""Calculate strain increment for a given time increment and velocity gradient.
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ it should accept the `outdir` positional argument,
and check if its value is not `None`.
If `outdir is None` then no persistent output should be produced.
If `outdir` is a directory path (string):
- logs can be saved by using the `pydrex.logger.logfile_enable` context manager,
- logs can be saved by using the `pydrex.io.logfile_enable` context manager,
which accepts a path name and an optional logging level as per Python's `logging` module
(the default is `logging.DEBUG` which implies the most verbose output),
- figures can be saved by (implementing and) calling a helper from `pydrex.visualisation`, and
Expand Down
16 changes: 8 additions & 8 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_shear_dudz(self, outdir):
test_id = "dudz"
optional_logging = cl.nullcontext()
if outdir is not None:
optional_logging = _log.logfile_enable(
optional_logging = _io.logfile_enable(
f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log"
)
with optional_logging:
Expand Down Expand Up @@ -66,7 +66,7 @@ def test_shear_dvdx(self, outdir):
test_id = "dvdx"
optional_logging = cl.nullcontext()
if outdir is not None:
optional_logging = _log.logfile_enable(
optional_logging = _io.logfile_enable(
f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log"
)
with optional_logging:
Expand Down Expand Up @@ -128,7 +128,7 @@ def test_shear_dvdx_slip_010_100(self, outdir):

optional_logging = cl.nullcontext()
if outdir is not None:
optional_logging = _log.logfile_enable(
optional_logging = _io.logfile_enable(
f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log"
)
initial_angles = []
Expand Down Expand Up @@ -245,7 +245,7 @@ def test_shear_dudz_slip_001_100(self, outdir):

optional_logging = cl.nullcontext()
if outdir is not None:
optional_logging = _log.logfile_enable(
optional_logging = _io.logfile_enable(
f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log"
)
initial_angles = []
Expand Down Expand Up @@ -363,7 +363,7 @@ def test_shear_dwdx_slip_001_100(self, outdir):

optional_logging = cl.nullcontext()
if outdir is not None:
optional_logging = _log.logfile_enable(
optional_logging = _io.logfile_enable(
f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log"
)
initial_angles = []
Expand Down Expand Up @@ -481,7 +481,7 @@ def test_shear_dvdz_slip_010_001(self, outdir):

optional_logging = cl.nullcontext()
if outdir is not None:
optional_logging = _log.logfile_enable(
optional_logging = _io.logfile_enable(
f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log"
)
initial_angles = []
Expand Down Expand Up @@ -608,7 +608,7 @@ def test_shear_dvdx_circle_inplane(self, outdir):
cos2θ = np.cos(2 * initial_angles)
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")

with optional_logging:
initial_orientations = Rotation.from_rotvec(
Expand Down Expand Up @@ -705,7 +705,7 @@ def test_shear_dvdx_circle_shearplane(self, outdir):
initial_angles = np.mgrid[0 : 2 * np.pi : 360000j]
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")

with optional_logging:
initial_orientations = Rotation.from_euler(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_corner_flow_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def test_steady4(self, outdir, seed, ncpus):
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_prescribed"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")
npzpath = pl.Path(f"{out_basepath}.npz")
labels = []
angles = []
Expand Down
8 changes: 4 additions & 4 deletions tests/test_simple_shear_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def test_dvdx_ensemble(
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_dvdx_ensemble_{_id}"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")
labels = []

with optional_logging:
Expand Down Expand Up @@ -467,7 +467,7 @@ def test_dvdx_GBM(self, outdir, seeds_nearX45, ncpus):
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_mobility"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")
labels = []

with optional_logging:
Expand Down Expand Up @@ -645,7 +645,7 @@ def test_GBM_calibration(self, outdir, seeds, ncpus):
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_calibration"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")
labels = []

with optional_logging:
Expand Down Expand Up @@ -792,7 +792,7 @@ def test_dudz_pathline(self, outdir, seed):
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")

with optional_logging:
shear_direction = Ŋ([1, 0, 0], dtype=np.float64)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_simple_shear_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_direction_change(
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_direction_change_{_id}"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")

with optional_logging:
clock_start = process_time()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_vortex_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def _run(assert_each, orientations_init):
optional_logging = cl.nullcontext()
if outdir is not None:
out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_olA"
optional_logging = _log.logfile_enable(f"{out_basepath}.log")
optional_logging = _io.logfile_enable(f"{out_basepath}.log")

assert_each_list = [
get_assert_each(i) for i, _ in enumerate(orientations_init_y)
Expand Down

0 comments on commit 964caee

Please sign in to comment.