"""This module contains utility functions for working with Pastas models."""
import logging
from logging import handlers
from platform import platform
from pandas import Timestamp
from pastas.typing import Model as ModelType
from pastas.typing import TimestampType
logger = logging.getLogger(__name__)
[docs]def get_stress_tmin_tmax(ml: ModelType) -> tuple[TimestampType, TimestampType]:
"""Get the minimum and maximum time that all the stresses have data."""
from pastas import Model
tmin = Timestamp.min
tmax = Timestamp.max
if isinstance(ml, Model):
for sm in ml.stressmodels:
for st in ml.stressmodels[sm].stress:
tmin = max((tmin, st.series_original.index.min()))
tmax = min((tmax, st.series_original.index.max()))
else:
raise (TypeError("Unknown type {}".format(type(ml))))
return tmin, tmax
[docs]def initialize_logger(
logger: logging.Logger | None = None, level: int | str | None = logging.INFO
) -> None:
"""Internal method to create a logger instance to log program output.
Parameters
----------
logger : logging.Logger
A Logger-instance. Use pastas.logger to initialise the Logging instance that
handles all logging throughout pastas, including all submodules and packages.
"""
if logger is None:
logger = logging.getLogger("pastas")
logger.setLevel(level)
remove_file_handlers(logger)
set_console_handler(logger)
# add_file_handlers(logger)
[docs]def set_console_handler(
logger: logging.Logger | None = None,
level: int | None = logging.INFO,
fmt: str = "%(levelname)s: %(message)s",
) -> None:
"""Method to add a console handler to the logger of Pastas.
Parameters
----------
logger : logging.Logger
A Logger-instance. Use ps.logger to initialise the Logging instance that
handles all logging throughout pastas, including all submodules and packages.
"""
if logger is None:
logger = logging.getLogger("pastas")
remove_console_handler(logger)
ch = logging.StreamHandler()
ch.setLevel(level)
formatter = logging.Formatter(fmt=fmt)
ch.setFormatter(formatter)
logger.addHandler(ch)
[docs]def set_log_level(level: int | str) -> None:
"""Set the log-level for Pastas.
Parameters
----------
level: str
String with the level to log messages to the screen for.
Options are: "DEBUG", "INFO", "WARNING", and "ERROR".
Examples
--------
>>> import pandas as ps
>>> ps.set_log_level("ERROR")
"""
logger = logging.getLogger("pastas")
logger.setLevel(level)
[docs]def remove_console_handler(logger: logging.Logger | None = None) -> None:
"""Method to remove the console handler to the logger of Pastas.
Parameters
----------
logger : logging.Logger
A Logger-instance. Use ps.logger to initialise the Logging instance that
handles all logging throughout pastas, including all submodules and packages.
"""
if logger is None:
logger = logging.getLogger("pastas")
for handler in logger.handlers:
if isinstance(handler, logging.StreamHandler):
logger.removeHandler(handler)
[docs]def add_file_handlers(
logger: logging.Logger | None = None,
filenames: tuple[str] = ("info.log", "errors.log"),
levels: tuple[int] = (logging.INFO, logging.ERROR),
maxBytes: int = 10485760,
backupCount: int = 20,
encoding: str = "utf8",
fmt: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt: str = "%y-%m-%d %H:%M",
) -> None:
"""Method to add file handlers in the logger of Pastas.
Parameters
----------
logger : logging.Logger
A Logger-instance. Use ps.logger to initialise the Logging instance that
handles all logging throughout pastas, including all submodules and packages.
"""
if logger is None:
logger = logging.getLogger("pastas")
# create formatter
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
# create file handlers, set the level & formatter, and add it to the logger
for filename, level in zip(filenames, levels):
fh = handlers.RotatingFileHandler(
filename, maxBytes=maxBytes, backupCount=backupCount, encoding=encoding
)
fh.setLevel(level)
fh.setFormatter(formatter)
logger.addHandler(fh)
[docs]def remove_file_handlers(logger: logging.Logger | None = None) -> None:
"""Method to remove any file handlers in the logger of Pastas.
Parameters
----------
logger : logging.Logger
A Logger-instance. Use ps.logger to initialise the Logging instance that
handles all logging throughout pastas, including all submodules and packages.
"""
if logger is None:
logger = logging.getLogger("pastas")
for handler in logger.handlers:
if isinstance(handler, handlers.RotatingFileHandler):
logger.removeHandler(handler)
[docs]def validate_name(name: str, raise_error: bool = False) -> str:
"""Method to check user-provided names and log a warning if wrong.
Parameters
----------
name: str
String with the name to check for illegal characters.
raise_error: bool
raise Exception error if illegal character is found, default is False which
only logs a warning.
Returns
-------
name: str
Unchanged name string.
"""
ilchar = ["/", "\\", " ", ".", "'", '"', "`"]
if "windows" in platform().lower():
ilchar += [
"#",
"%",
"&",
"@",
"{",
"}",
"|",
"$",
"*",
"<",
">",
"?",
"!",
":",
"=",
"+",
]
name = str(name)
for char in ilchar:
if char in name:
msg = (
"User-provided name '%s' contains illegal character. Please "
"remove '%s' from name."
)
if raise_error:
logger.error(msg, name, char)
raise Exception(msg % (name, char))
else:
logger.warning(msg, name, char)
return name