#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
xarray-based ocean analysis library
"""
# Copyright 2020-2024 Shom
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import warnings
import platform
from importlib.metadata import version as im_get_version
from .cf_configs import CF_CONFIGS, get_cf_config_file # noqa: F401
from .data_samples import (
DATA_SAMPLES, # noqa: F401
get_data_sample,
show_data_samples, # noqa: F401
open_data_sample, # noqa: F401
)
try:
from ._version import version as __version__
except ImportError:
__version__ = "0.0.0"
_RE_OPTION_MATCH = re.compile(r"^(\w+)\W(\w+)$").match
#: Specifications of configuration options
CONFIG_SPECS = """
[cf] # cf module
cache=boolean(default=False) # use the :mod:`~xoa.cf` in memory and file caches
[plot] # plot parameters
cmapdiv = string(default="cmo.balance") # defaut diverging colormap
cmappos = string(default="cmo.amp") # default positive colormap
cmapneg = string(default="cmo.tempo_r") # default negative colormap
cmapcyc = string(default="cmo.phase") # default cyclic colormap
"""
# Directory of sample files
_SAMPLE_DIR = os.path.join(os.path.dirname(__file__), '_samples')
_PACKAGES = [
"platformdirs",
"cartopy",
"cmocean",
"configobj",
"matplotlib",
"numpy",
"pandas",
"scipy",
"xarray",
"xesmf",
]
_XOA_CACHE = {}
[docs]
class XoaError(Exception):
pass
[docs]
class XoaConfigError(XoaError):
pass
[docs]
class XoaWarning(UserWarning):
pass
[docs]
def xoa_warn(message, stacklevel=2):
"""Issue a :class:`XoaWarning` warning
Example
-------
.. ipython:: python
:okwarning:
@suppress
from xoa import xoa_warn
xoa_warn('Be careful!')
"""
warnings.warn(message, XoaWarning, stacklevel=stacklevel)
def _get_cache_():
from . import _XOA_CACHE
return _XOA_CACHE
[docs]
def get_default_user_config_file():
"""Get the default user config file name"""
try:
from platformdirs import user_config_dir
except ImportError:
from appdirs import user_config_dir
warnings.warn(
"appdirs is deprecated. Please install platformdirs.", warnings.DeprecationWarning
)
return os.path.join(user_config_dir("xoa"), "xoa.cfg")
[docs]
def load_options(cfgfile=None):
"""Load specified options
Parameters
----------
cfgfile: file, list(str), dict
Example
-------
.. ipython:: python
@suppress
from xoa import load_options
# Dict
load_options({'plot': {'cmappos': 'mycmap'}})
# Lines
optlines = "[plot]\\n cmappos=mycmap".split('\\n')
load_options(optlines)
"""
import configobj
import validate
_get_cache_()
xoa_cache = _get_cache_()
if "cfgspecs" not in xoa_cache:
xoa_cache["cfgspecs"] = configobj.ConfigObj(
CONFIG_SPECS.split("\n"),
list_values=False,
interpolation=False,
raise_errors=True,
file_error=True,
)
if "options" not in xoa_cache:
default_user_config_file = get_default_user_config_file()
xoa_cache["options"] = configobj.ConfigObj(
(default_user_config_file if os.path.exists(default_user_config_file) else None),
configspec=xoa_cache["cfgspecs"],
file_error=False,
raise_errors=True,
list_values=True,
)
if cfgfile:
xoa_cache["options"].merge(
configobj.ConfigObj(cfgfile, file_error=True, raise_errors=True, list_values=True)
)
xoa_cache["options"].validate(validate.Validator(), copy=True)
def _get_options_():
xoa_cache = _get_cache_()
if "options" not in xoa_cache:
load_options()
return xoa_cache["options"]
[docs]
def get_option(section, option=None):
"""Get a config option
Example
-------
.. ipython:: python
@suppress
from xoa import get_option
print(get_option('plot', 'cmapdiv'))
print(get_option('plot.cmapdiv'))
"""
options = _get_options_()
if option is None:
m = _RE_OPTION_MATCH(section)
if m:
section, option = m.groups()
else:
raise XoaConfigError("You must provide an option name to get_option")
try:
value = options[section][option]
except Exception:
return XoaConfigError(f"Invalid section/option: {section}/{option}")
return value
[docs]
class set_options(object):
"""Set configuration options
Parameters
----------
section: str, None
**options: dict
If a key is in the format "<section>.<option>", then the section
is overwritten.
Example
-------
.. ipython:: python
@suppress
from xoa import set_options, get_option
# Classic: for the session
set_options('plot', cmapdiv='cmo.balance', cmappos='cmo.amp')
# With dict
opts = {"plot.cmapdiv": "cmo.balance"}
set_options(**opts)
# Context: temporary
with set_options('plot', cmapdiv='cmo.delta'):
print('within context:', get_option('plot.cmapdiv'))
print('after context:', get_option('plot.cmapdiv'))
"""
[docs]
def __init__(self, section=None, **options):
# Format before being ingested
self.xoa_cache = _get_cache_()
self.old_options = self.xoa_cache.get("options")
if "options" in self.xoa_cache:
del self.xoa_cache["options"]
opts = {}
for option, value in options.items():
m = _RE_OPTION_MATCH(option)
if m:
sec, option = m.groups()
else:
if section is None:
raise XoaConfigError(
"You must specify the section explicitly or through the option name"
)
sec = section
opts.setdefault(sec, {})[option] = value
# Ingest options
load_options(opts)
def __enter__(self):
return self.xoa_cache["options"]
def __exit__(self, type, value, traceback):
if self.old_options:
self.xoa_cache["options"] = self.old_options
else:
del self.xoa_cache["options"]
[docs]
def set_option(option, value):
"""Set a single option using the flat format, i.e ``section.option``
Parameters
----------
option: str
Option name in the ``section.option`` format
value:
Value to set
Example
-------
.. ipython:: python
@suppress
from xoa import set_option
set_option('plot.cmapdiv', 'cmo.balance');
"""
return set_options(None, **{option: value})
[docs]
def reset_options():
"""Restore options to their default values in the current session
Example
-------
.. ipython:: python
@suppress
from xoa import get_option, set_options, reset_options
print(get_option('plot.cmapdiv'))
set_options('plot', cmapdiv='mycmap')
print(get_option('plot.cmapdiv'))
reset_options()
print(get_option('plot.cmapdiv'))
"""
xoa_cache = _get_cache_()
del xoa_cache['options']
[docs]
def show_options(specs=False):
"""Print current xoa configuration
Parameters
----------
specs: bool
Print option specifications instead
Example
-------
.. ipython:: python
@suppress
from xoa import show_options
show_options()
show_options(specs=True)
"""
if specs:
print(CONFIG_SPECS.strip("\n"))
else:
print("\n".join(_get_options_().write()).strip("\n").replace('#', ' #'))
def _parse_requirements_(reqfile):
re_match_specs_match = re.compile(r"^(\w+)(\W+.+)?$").match
reqs = {}
with open(reqfile) as f:
for line in f:
line = line.strip().strip("\n")
if line and not line.startswith("#"):
m = re_match_specs_match(line)
if m:
reqs[m.group(1)] = m.group(2)
return reqs
[docs]
def show_versions():
"""Print the versions of xoa and of some dependencies
Example
-------
.. ipython:: python
:okexcept:
@suppress
from xoa import show_versions
show_versions()
"""
print("- python:", platform.python_version())
print("- xoa:", __version__)
for package in _PACKAGES:
try:
version = im_get_version(package)
except Exception:
version = "NOT INSTALLED or UKNOWN"
print(f"- {package}: {version}")
[docs]
def show_paths():
"""Print some xoa paths
Example
-------
.. ipython:: python
:okexcept:
@suppress
from xoa import show_paths
show_paths()
"""
print("- xoa library dir:", os.path.dirname(__file__))
from . import cf
asterix = False
default_user_config_file = get_default_user_config_file()
for label, path in [
("user config file", default_user_config_file),
("user CF specs file", cf.USER_CF_FILE),
("user CF cache file", cf.USER_CF_CACHE_FILE),
]:
if not os.path.exists(path):
asterix = True
path = path + " [*]"
print("-", label + ":", path)
print("- data samples:", " ".join(get_data_sample()))
if asterix:
print("*: file not present")
[docs]
def show_info(opt_specs=True):
"""Print xoa related info
Example
-------
.. ipython:: python
:okexcept:
@suppress
from xoa import show_info
show_info()
"""
print("# VERSIONS")
show_versions()
print("\n# FILES AND DIRECTORIES")
show_paths()
print("\n# OPTIONS")
show_options(specs=opt_specs)
[docs]
def register_accessors(xoa=True, xcf=False, decode_sigma=False):
"""Register xarray accessors
Parameters
----------
xoa: bool, str
Register the main accessors with
:func:`~xoa.cf.register_xoa_accessors`.
xcf: bool, str
Register the :mod:`xoa.cf` module accessors with
:func:`~xoa.cf.register_cf_accessors`.
decode_sigma: bool, str
Register the :mod:`xoa.sigma` module accessor with
:func:`~xoa.cf.register_sigma_accessor`.
See also
--------
xoa.accessors
"""
if xoa:
from .accessors import register_xoa_accessors
kw = {"name": xoa} if isinstance(xoa, str) else {}
register_xoa_accessors(**kw)
if xcf:
from .accessors import register_cf_accessors
kw = {"name": xcf} if isinstance(xcf, str) else {}
register_cf_accessors(**kw)
if decode_sigma:
from .accessors import register_sigma_accessor
kw = {"name": decode_sigma} if isinstance(decode_sigma, str) else {}
register_sigma_accessor(**kw)