Source code for pyvo.utils.prototype
import inspect
import warnings
from functools import wraps
from typing import Dict, Iterable
from .protofeature import Feature
from pyvo.dal.exceptions import PyvoUserWarning
__all__ = ['features', 'prototype_feature', 'activate_features', 'PrototypeWarning', 'PrototypeError']
features: Dict[str, "Feature"] = {
'cadc-tb-upload': Feature('cadc-tb-upload',
'https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next',
False)
}
[docs]def prototype_feature(*args):
"""
Decorator for functions and classes that implement unstable standards
which haven't been approved yet.
The decorator can be used to tag individual functions or methods.
Please refer to the user documentation for details.
Parameters
----------
args: iterable of arguments.
Currently, the decorator must always be called with one and only one
argument, a string representing the feature's name associated with
the decorated class or functions. Additional arguments will be ignored,
while using the decorator without any arguments will result in a
``PrototypeError`` error.
Returns
-------
The class or function it decorates, which will be associated to the
feature provided as argument.
"""
feature_name = _parse_args(*args)
decorator = _make_decorator(feature_name)
return decorator
def _set_features(flag, *feature_names: Iterable[str]):
names = feature_names or set(features.keys())
for name in names:
if not _validate(name):
continue
features[name].on = flag
[docs]def activate_features(*feature_names: Iterable[str]):
"""
Activate one or more prototype features.
Parameters
----------
feature_names: Iterable[str]
An arbitrary number of feature names. If a feature with that name does
not exist, a `PrototypeWarning` will be issued. If no arguments are
provided, all features will be activated
Returns
-------
"""
_set_features(True, *feature_names)
def deactivate_features(*feature_names: Iterable[str]):
"""
De-activate one or more prototype features.
Parameters
----------
feature_names: Iterable[str]
An arbitrary number of feature names. If a feature with that name does
not exist, a `PrototypeWarning` will be issued. If no arguments are
provided, all features will be de-activated
Returns
-------
"""
_set_features(False, *feature_names)
[docs]class PrototypeError(Exception):
pass
[docs]class PrototypeWarning(PyvoUserWarning):
pass
def _parse_args(*args):
if not args or callable(args[0]):
raise PrototypeError("The `prototype_feature` decorator must always be called with the "
"feature name as an argument")
return args[0]
def _make_decorator(feature_name):
def decorator(decorated):
if inspect.isfunction(decorated):
return _make_wrapper(feature_name, decorated)
if inspect.isclass(decorated):
method_infos = inspect.getmembers(decorated, predicate=_should_wrap)
_wrap_class_methods(decorated, method_infos, feature_name)
return decorated
return decorator
def _validate(feature_name):
if feature_name not in features:
warnings.warn(f'No such feature "{feature_name}"', category=PrototypeWarning)
return False
return True
def _warn_or_raise(function, feature_name):
_validate(feature_name)
feature = features[feature_name]
if feature.should_error():
raise PrototypeError(feature.error(function.__name__))
def _should_wrap(member):
return inspect.isfunction(member) and not member.__name__.startswith('_')
def _wrap_class_methods(decorated_class, method_infos, feature_name):
for method_info in method_infos:
setattr(decorated_class, method_info[0], _make_wrapper(feature_name, method_info[1]))
def _make_wrapper(feature_name, function):
@wraps(function)
def wrapper(*args, **kwargs):
_warn_or_raise(function, feature_name)
return function(*args, **kwargs)
return wrapper