Source code for astrophot.utils.decorators
from functools import wraps
import re
import warnings
from inspect import cleandoc
import caskade as ck
import numpy as np
__all__ = ("classproperty", "ignore_numpy_warnings", "combine_docstrings")
[docs]
class classproperty:
def __init__(self, fget):
self.fget = fget
def __get__(self, instance, owner):
return self.fget(owner)
[docs]
def ignore_numpy_warnings(func):
"""This decorator is used to turn off numpy warnings. This should
only be used in initialize scripts which often run heuristic code
to determine initial parameter values. These heuristics may
encounter log(0) or sqrt(-1) or other numerical artifacts and
should handle them before returning. This decorator simply cleans
up that processes to minimize clutter in the output.
"""
@wraps(func)
def wrapped(*args, **kwargs):
old_settings = np.seterr(all="ignore")
warnings.filterwarnings("ignore", category=np.exceptions.VisibleDeprecationWarning)
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=ck.InvalidValueWarning)
result = func(*args, **kwargs)
np.seterr(**old_settings)
warnings.filterwarnings("default", category=np.exceptions.VisibleDeprecationWarning)
warnings.filterwarnings("default", category=DeprecationWarning)
warnings.filterwarnings("default", category=ck.InvalidValueWarning)
return result
return wrapped
def _parse_docstring(doc):
"""Parse a docstring into (body, params_dict).
Handles both RST ``:param name: desc`` format and markdown
``**Parameters:**`` / ``**Options:**`` format.
Returns body text (params/options removed) and an ordered dict of {name: desc}.
"""
params = {}
# --- RST-style :param name: desc (possibly multi-line) ---
# Split on ":param " boundaries so multi-line descriptions are captured correctly.
for m in re.finditer(
r":param\s+(\w+):\s*((?:(?!\n:(?:param|type)\s).)*)",
doc,
re.DOTALL,
):
params[m.group(1)] = m.group(2).strip()
# --- Markdown-style **Parameters:** and **Options:** sections ---
for section_match in re.finditer(
r"\*\*(?:Parameters|Options):\*\*\n((?:[ \t]*-[^\n]*\n?)+)",
doc,
re.MULTILINE,
):
for item in re.finditer(
r"^[ \t]*-\s+`(\w+)`\s*:\s*(.+?)(?=\n[ \t]*-|\Z)",
section_match.group(1),
re.DOTALL | re.MULTILINE,
):
params[item.group(1)] = item.group(2).strip()
# Strip :param/:type entries from body (handle multi-line descriptions)
body = re.sub(
r":(?:param|type)\s+\w+:\s*(?:(?!\n:(?:param|type)\s).)*", "", doc, flags=re.DOTALL
)
# Strip markdown Parameters/Options sections from body
body = re.sub(r"\*\*(?:Parameters|Options):\*\*\n(?:[ \t]*-[^\n]*\n?)+", "", body)
body = body.strip()
return body, params
[docs]
def combine_docstrings(cls):
"""Combine docstrings from a class and all of its base classes.
Finds all ``:param`` entries and markdown ``**Parameters:**`` /
``**Options:**`` sections from every class in the MRO and merges
them into a single consolidated ``:param`` list so that Sphinx
autodoc renders them correctly.
"""
# Collect params from full MRO (base → derived so derived class wins)
all_params = {}
for klass in reversed(cls.__mro__):
if klass is object or not klass.__doc__:
continue
_, klass_params = _parse_docstring(cleandoc(klass.__doc__))
all_params.update(klass_params)
try:
main_body, _ = _parse_docstring(cleandoc(cls.__doc__))
except (AttributeError, TypeError):
main_body = ""
for base in cls.__bases__:
if base is object or not base.__doc__:
continue
base_body, _ = _parse_docstring(cleandoc(base.__doc__))
if base_body:
main_body += f"\n\n.. rubric:: {base.__name__}\n\n{base_body}"
# Append merged parameter list
if all_params:
model_params = set()
try:
model_params = set(cls.parameter_specs.keys())
except Exception:
model_params = set()
params = {}
options = {}
for name, desc in list(all_params.items()):
if name in model_params:
params[name] = f"{desc}" + ("" if "[model param]" in desc else " [model param]")
else:
options[name] = desc
main_body += (
"\n\n"
+ "\n".join(f":param {name}: {desc}" for name, desc in params.items())
+ "\n"
+ "\n".join(f":param {name}: {desc}" for name, desc in options.items())
)
cls.__doc__ = main_body.strip()
return cls