-
Notifications
You must be signed in to change notification settings - Fork 7
gwrun argspec #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
gwrun argspec #30
Changes from 2 commits
5e99493
3bbbab0
4d27401
6f2d578
672d1f4
237cf79
519fe45
11c6102
ac4755d
9999eba
99cd62d
3d1d255
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| from inspect import getdoc | ||
| from inspect import getdoc, cleandoc | ||
| try: | ||
| from inspect import signature | ||
| from inspect import signature, Parameter | ||
| except ImportError: # pragma: nocover | ||
| from funcsigs import signature | ||
| from funcsigs import signature, Parameter | ||
|
|
||
| import six | ||
|
|
||
|
|
||
|
|
@@ -16,13 +17,190 @@ class MissingInputException(Exception): | |
|
|
||
| def get_description_attribute(func): | ||
| """Get the private description attribute from a function.""" | ||
| func = getattr(func, 'run', func) | ||
| description = getattr(func, '_girder_description', None) | ||
| # func = getattr(func, 'run', func) | ||
| description = getattr(func, GWFuncDesc._func_desc_attr, None) | ||
| if description is None: | ||
| raise MissingDescriptionException('Function is missing description decorators') | ||
| return description | ||
|
|
||
|
|
||
| class Argument(object): | ||
| def __init__(self, name, **kwargs): | ||
| self.name = name | ||
| for k, v in six.iteritems(kwargs): | ||
| setattr(self, k, v) | ||
|
|
||
| # No default value for this argument | ||
| class Arg(Argument): pass | ||
| # Has a default argument for the value | ||
| class KWArg(Argument): pass | ||
| class Varargs(Argument): pass | ||
| class Kwargs(Argument): pass | ||
| # class Return(Argument): pass | ||
|
|
||
|
|
||
| def _clean_function_doc(f): | ||
| doc = getdoc(f) or '' | ||
| if isinstance(doc, bytes): | ||
| doc = doc.decode('utf-8') | ||
| else: | ||
| doc = cleandoc(doc) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, not sure where that came from |
||
| return doc | ||
|
|
||
|
|
||
| class GWFuncDesc(object): | ||
| _func_desc_attr = "_gw_function_description" | ||
| _parameter_repr = ['POSITIONAL_ONLY', | ||
| 'POSITIONAL_OR_KEYWORD', | ||
| 'VAR_POSITIONAL', | ||
| 'KEYWORD_ONLY', | ||
| 'VAR_KEYWORD'] | ||
|
|
||
| @classmethod | ||
| def get_description(cls, func): | ||
| # HACK - potentially unwrap celery task | ||
| # func = getattr(func, 'run', func) | ||
| if hasattr(func, cls._func_desc_attr) and \ | ||
| isinstance(getattr(func, cls._func_desc_attr), cls): | ||
| return getattr(func, cls._func_desc_attr) | ||
| return None | ||
|
|
||
| def __init__(self, func): | ||
| self.func_name = func.__name__ | ||
| self.func_help = _clean_function_doc(func) | ||
| self._metadata = {} | ||
| self._signature = signature(func) | ||
|
|
||
| def __repr__(self): | ||
| # TODO - make less ugly | ||
| return "<{}(".format(self.__class__.__name__) + ", ".join(["{}:{}".format( | ||
| name, self._parameter_repr[self._signature.parameters[name].kind]) | ||
| for name in self._signature.parameters]) + ")>" | ||
|
|
||
| def __getitem__(self, key): | ||
| return self._construct_argument( | ||
| self._get_class(self._signature.parameters[key]), key) | ||
|
|
||
| def _construct_argument(self, cls, name, **kwargs): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| p = self._signature.parameters[name] | ||
| metadata = {} | ||
|
|
||
| if p.default != p.empty: | ||
| metadata['default'] = p.default | ||
| if p.annotation != p.empty: | ||
| # TODO: make sure annotation is a type and not just garbage | ||
| metadata['data_type'] = p.annotation | ||
|
|
||
| metadata.update(self._metadata.get(name, {})) | ||
|
|
||
| return cls(name, **metadata) | ||
|
|
||
| def _is_varargs(self, p): | ||
| return p.kind == Parameter.VAR_POSITIONAL | ||
|
|
||
| def _is_kwargs(self, p): | ||
| return p.kind == Parameter.VAR_KEYWORD | ||
|
|
||
| def _is_kwarg(self, p): | ||
| return p.kind == Parameter.KEYWORD_ONLY or ( | ||
| p.kind == Parameter.POSITIONAL_OR_KEYWORD and p.default != p.empty) | ||
|
|
||
| def _is_posarg(self, p): | ||
| return p.kind == Parameter.POSITIONAL_ONLY or ( | ||
| p.kind == Parameter.POSITIONAL_OR_KEYWORD and p.default == p.empty) | ||
|
|
||
| def _get_class(self, p): | ||
| if self._is_varargs(p): | ||
| return Varargs | ||
| elif self._is_kwargs(p): | ||
| return Kwargs | ||
| elif self._is_posarg(p): | ||
| return Arg | ||
| elif self._is_kwarg(p): | ||
| return KWArg | ||
| else: | ||
| raise RuntimeError("Could not determine parameter type!") | ||
|
|
||
|
|
||
| def set_metadata(self, name, key, value): | ||
| if name not in self._signature.parameters: | ||
| raise RuntimeError("{} is not a valid argument to this function!") | ||
|
|
||
| if name not in self._metadata: | ||
| self._metadata[name] = {} | ||
|
|
||
| self._metadata[name][key] = value | ||
|
|
||
| @property | ||
| def arguments(self): | ||
| # Only return arguments if we've declared them as paramaters | ||
| # This prevents us from returning things like 'self' of bound | ||
| # methods (e.g. celery tasks) etc. This is a dubious design | ||
| # decision. | ||
| return [ | ||
| self._construct_argument( | ||
| self._get_class(self._signature.parameters[name]), name) | ||
| for name in self._signature.parameters if name in self._metadata] | ||
|
|
||
| @property | ||
| def varargs(self): | ||
| for name in self._signature.parameters: | ||
| if name in self._metadata and \ | ||
| self._is_varargs(self._signature.parameters[name]): | ||
| return self._construct_argument(Varargs, name) | ||
| return None | ||
|
|
||
| @property | ||
| def kwargs(self): | ||
| for name in self._signature.parameters: | ||
| if name in self._metadata and \ | ||
| self._is_kwargs(self._signature.parameters[name]): | ||
| return self._construct_argument(Kwargs, name) | ||
| return None | ||
|
kotfic marked this conversation as resolved.
|
||
|
|
||
| @property | ||
| def positional_args(self): | ||
| return [arg for arg in self.arguments if isinstance(arg, Arg)] | ||
|
|
||
| @property | ||
| def keyword_args(self): | ||
| return [arg for arg in self.arguments if isinstance(arg, KWArg)] | ||
|
|
||
|
|
||
| def parameter(name, **kwargs): | ||
| if not isinstance(name, six.string_types): | ||
| raise TypeError('Expected argument name to be a string') | ||
|
|
||
| data_type = kwargs.get("data_type", None) | ||
| if data_type is not None and callable(data_type): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just |
||
| kwargs['data_type'] = data_type(name, **kwargs) | ||
|
|
||
| def argument_wrapper(func): | ||
| if not hasattr(func, GWFuncDesc._func_desc_attr): | ||
| setattr(func, GWFuncDesc._func_desc_attr, GWFuncDesc(func)) | ||
|
|
||
| desc = getattr(func, GWFuncDesc._func_desc_attr) | ||
|
|
||
| # Make sure the metadata key exists even if we don't set any | ||
| # values on it. This ensures that metadata's keys represent | ||
| # the full list of parameters that have been identified by the | ||
| # user (even if there is no actual metadata associated with | ||
| # the argument). | ||
| desc._metadata[name] = {} | ||
|
|
||
| for key, value in six.iteritems(kwargs): | ||
| desc.set_metadata(name, key, value) | ||
|
kotfic marked this conversation as resolved.
|
||
|
|
||
| def description(): | ||
| return getattr(func, GWFuncDesc._func_desc_attr) | ||
|
|
||
| func.description = description | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why have both this and
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mainly for compatibility with code that's already been written (e.g. item_tasks?) I can just drop it if nothing is actually using it |
||
|
|
||
| return func | ||
|
|
||
| return argument_wrapper | ||
|
|
||
|
|
||
| def argument(name, data_type, *args, **kwargs): | ||
| """Describe an argument to a function as a function decorator. | ||
|
|
||
|
|
@@ -38,8 +216,10 @@ def argument(name, data_type, *args, **kwargs): | |
| data_type = data_type(name, *args, **kwargs) | ||
|
|
||
| def argument_wrapper(func): | ||
| func._girder_description = getattr(func, '_girder_description', {}) | ||
| args = func._girder_description.setdefault('arguments', []) | ||
| setattr(func, GWFuncDesc._func_desc_attr, | ||
| getattr(func, GWFuncDesc._func_desc_attr, {})) | ||
|
|
||
| args = getattr(func, GWFuncDesc._func_desc_attr).setdefault('arguments', []) | ||
| sig = signature(func) | ||
|
|
||
| if name not in sig.parameters: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import six | ||
|
|
||
| collect_ignore = [] | ||
| if six.PY2: | ||
| collect_ignore.append("py3_decorators_test.py") |
Uh oh!
There was an error while loading. Please reload this page.