mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-22 12:40:30 +08:00
* Updated cython build scripts * Updated setup.py to to install catalyst package * Updated momentum example to use catalyst package * catalyst executable now supports loading pipelines from multiple bundles
333 lines
9.6 KiB
Python
333 lines
9.6 KiB
Python
#
|
|
# Copyright 2014 Quantopian, Inc.
|
|
#
|
|
# 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.
|
|
from collections import namedtuple
|
|
import inspect
|
|
from itertools import chain
|
|
from six.moves import map, zip_longest
|
|
|
|
from catalyst.errors import ZiplineError
|
|
|
|
|
|
Argspec = namedtuple('Argspec', ['args', 'starargs', 'kwargs'])
|
|
|
|
|
|
def singleton(cls):
|
|
instances = {}
|
|
|
|
def getinstance():
|
|
if cls not in instances:
|
|
instances[cls] = cls()
|
|
return instances[cls]
|
|
|
|
return getinstance
|
|
|
|
|
|
@singleton
|
|
class Ignore(object):
|
|
def __str__(self):
|
|
return 'Argument.ignore'
|
|
__repr__ = __str__
|
|
|
|
|
|
@singleton
|
|
class NoDefault(object):
|
|
def __str__(self):
|
|
return 'Argument.no_default'
|
|
__repr__ = __str__
|
|
|
|
|
|
@singleton
|
|
class AnyDefault(object):
|
|
def __str__(self):
|
|
return 'Argument.any_default'
|
|
__repr__ = __str__
|
|
|
|
|
|
class Argument(namedtuple('Argument', ['name', 'default'])):
|
|
"""
|
|
An argument to a function.
|
|
Argument.no_default is a value representing no default to the argument.
|
|
Argument.ignore is a value that says you should ignore the default value.
|
|
"""
|
|
no_default = NoDefault()
|
|
any_default = AnyDefault()
|
|
ignore = Ignore()
|
|
|
|
def __new__(cls, name=ignore, default=ignore):
|
|
return super(Argument, cls).__new__(cls, name, default)
|
|
|
|
def __str__(self):
|
|
if self.has_no_default(self) or self.ignore_default(self):
|
|
return str(self.name)
|
|
else:
|
|
return '='.join([str(self.name), str(self.default)])
|
|
|
|
def __repr__(self):
|
|
return 'Argument(%s, %s)' % (repr(self.name), repr(self.default))
|
|
|
|
def _defaults_match(self, arg):
|
|
return any(map(Argument.ignore_default, [self, arg])) \
|
|
or (self.default is Argument.any_default and
|
|
arg.default is not Argument.no_default) \
|
|
or (arg.default is Argument.any_default and
|
|
self.default is not Argument.no_default) \
|
|
or self.default == arg.default
|
|
|
|
def _names_match(self, arg):
|
|
return self.name == arg.name \
|
|
or self.name is Argument.ignore \
|
|
or arg.name is Argument.ignore
|
|
|
|
def matches(self, arg):
|
|
return self._names_match(arg) and self._defaults_match(arg)
|
|
__eq__ = matches
|
|
|
|
@staticmethod
|
|
def parse_argspec(callable_):
|
|
"""
|
|
Takes a callable and returns a tuple with the list of Argument objects,
|
|
the name of *args, and the name of **kwargs.
|
|
If *args or **kwargs is not present, it will be None.
|
|
This returns a namedtuple called Argspec that has three fields named:
|
|
args, starargs, and kwargs.
|
|
"""
|
|
args, varargs, keywords, defaults = inspect.getargspec(callable_)
|
|
defaults = list(defaults or [])
|
|
|
|
if getattr(callable_, '__self__', None) is not None:
|
|
# This is a bound method, drop the self param.
|
|
args = args[1:]
|
|
|
|
first_default = len(args) - len(defaults)
|
|
return Argspec(
|
|
[Argument(arg, Argument.no_default
|
|
if n < first_default else defaults[n - first_default])
|
|
for n, arg in enumerate(args)],
|
|
varargs,
|
|
keywords,
|
|
)
|
|
|
|
@staticmethod
|
|
def has_no_default(arg):
|
|
return arg.default is Argument.no_default
|
|
|
|
@staticmethod
|
|
def ignore_default(arg):
|
|
return arg.default is Argument.ignore
|
|
|
|
|
|
def _expect_extra(expected, present, exc_unexpected, exc_missing, exc_args):
|
|
"""
|
|
Checks for the presence of an extra to the argument list. Raises expections
|
|
if this is unexpected or if it is missing and expected.
|
|
"""
|
|
if present:
|
|
if not expected:
|
|
raise exc_unexpected(*exc_args)
|
|
elif expected and expected is not Argument.ignore:
|
|
raise exc_missing(*exc_args)
|
|
|
|
|
|
def verify_callable_argspec(callable_,
|
|
expected_args=Argument.ignore,
|
|
expect_starargs=Argument.ignore,
|
|
expect_kwargs=Argument.ignore):
|
|
"""
|
|
Checks the callable_ to make sure that it satisfies the given
|
|
expectations.
|
|
expected_args should be an iterable of Arguments in the order you expect to
|
|
receive them.
|
|
expect_starargs means that the function should or should not take a *args
|
|
param. expect_kwargs says the callable should or should not take **kwargs
|
|
param.
|
|
If expected_args, expect_starargs, or expect_kwargs is Argument.ignore,
|
|
then the checks related to that argument will not occur.
|
|
|
|
Example usage:
|
|
|
|
callable_check(
|
|
f,
|
|
[Argument('a'), Argument('b', 1)],
|
|
expect_starargs=True,
|
|
expect_kwargs=Argument.ignore
|
|
)
|
|
"""
|
|
if not callable(callable_):
|
|
raise NotCallable(callable_)
|
|
|
|
expected_arg_list = list(
|
|
expected_args if expected_args is not Argument.ignore else []
|
|
)
|
|
|
|
args, starargs, kwargs = Argument.parse_argspec(callable_)
|
|
|
|
exc_args = callable_, args, starargs, kwargs
|
|
|
|
# Check the *args.
|
|
_expect_extra(
|
|
expect_starargs,
|
|
starargs,
|
|
UnexpectedStarargs,
|
|
NoStarargs,
|
|
exc_args,
|
|
)
|
|
# Check the **kwargs.
|
|
_expect_extra(
|
|
expect_kwargs,
|
|
kwargs,
|
|
UnexpectedKwargs,
|
|
NoKwargs,
|
|
exc_args,
|
|
)
|
|
|
|
if expected_args is Argument.ignore:
|
|
# Ignore the argument list checks.
|
|
return
|
|
|
|
if len(args) < len(expected_arg_list):
|
|
# One or more argument that we expected was not present.
|
|
raise NotEnoughArguments(
|
|
callable_,
|
|
args,
|
|
starargs,
|
|
kwargs,
|
|
[arg for arg in expected_arg_list if arg not in args],
|
|
)
|
|
elif len(args) > len(expected_arg_list):
|
|
raise TooManyArguments(
|
|
callable_, args, starargs, kwargs
|
|
)
|
|
|
|
# Empty argument that will not match with any actual arguments.
|
|
missing_arg = Argument(object(), object())
|
|
|
|
for expected, provided in zip_longest(expected_arg_list,
|
|
args,
|
|
fillvalue=missing_arg):
|
|
if not expected.matches(provided):
|
|
raise MismatchedArguments(
|
|
callable_, args, starargs, kwargs
|
|
)
|
|
|
|
|
|
class BadCallable(TypeError, AssertionError, ZiplineError):
|
|
"""
|
|
The given callable is not structured in the expected way.
|
|
"""
|
|
_lambda_name = (lambda: None).__name__
|
|
|
|
def __init__(self, callable_, args, starargs, kwargs):
|
|
self.callable_ = callable_
|
|
self.args = args
|
|
self.starargs = starargs
|
|
self.kwargsname = kwargs
|
|
|
|
self.kwargs = {}
|
|
|
|
def format_callable(self):
|
|
if self.callable_.__name__ == self._lambda_name:
|
|
fmt = '%s %s'
|
|
name = 'lambda'
|
|
else:
|
|
fmt = '%s(%s)'
|
|
name = self.callable_.__name__
|
|
|
|
return fmt % (
|
|
name,
|
|
', '.join(
|
|
chain(
|
|
(str(arg) for arg in self.args),
|
|
('*' + sa for sa in (self.starargs,) if sa is not None),
|
|
('**' + ka for ka in (self.kwargsname,) if ka is not None),
|
|
)
|
|
)
|
|
)
|
|
|
|
@property
|
|
def msg(self):
|
|
return str(self)
|
|
|
|
|
|
class NoStarargs(BadCallable):
|
|
def __str__(self):
|
|
return '%s does not allow for *args' % self.format_callable()
|
|
|
|
|
|
class UnexpectedStarargs(BadCallable):
|
|
def __str__(self):
|
|
return '%s should not allow for *args' % self.format_callable()
|
|
|
|
|
|
class NoKwargs(BadCallable):
|
|
def __str__(self):
|
|
return '%s does not allow for **kwargs' % self.format_callable()
|
|
|
|
|
|
class UnexpectedKwargs(BadCallable):
|
|
def __str__(self):
|
|
return '%s should not allow for **kwargs' % self.format_callable()
|
|
|
|
|
|
class NotCallable(BadCallable):
|
|
"""
|
|
The provided 'callable' is not actually a callable.
|
|
"""
|
|
def __init__(self, callable_):
|
|
self.callable_ = callable_
|
|
|
|
def __str__(self):
|
|
return '%s is not callable' % self.format_callable()
|
|
|
|
def format_callable(self):
|
|
try:
|
|
return self.callable_.__name__
|
|
except AttributeError:
|
|
return str(self.callable_)
|
|
|
|
|
|
class NotEnoughArguments(BadCallable):
|
|
"""
|
|
The callback does not accept enough arguments.
|
|
"""
|
|
def __init__(self, callable_, args, starargs, kwargs, missing_args):
|
|
super(NotEnoughArguments, self).__init__(
|
|
callable_, args, starargs, kwargs
|
|
)
|
|
self.missing_args = missing_args
|
|
|
|
def __str__(self):
|
|
missing_args = list(map(str, self.missing_args))
|
|
return '%s is missing argument%s: %s' % (
|
|
self.format_callable(),
|
|
's' if len(missing_args) > 1 else '',
|
|
', '.join(missing_args),
|
|
)
|
|
|
|
|
|
class TooManyArguments(BadCallable):
|
|
"""
|
|
The callback cannot be called by passing the expected number of arguments.
|
|
"""
|
|
def __str__(self):
|
|
return '%s accepts too many arguments' % self.format_callable()
|
|
|
|
|
|
class MismatchedArguments(BadCallable):
|
|
"""
|
|
The argument lists are of the same lengths, but not in the correct order.
|
|
"""
|
|
def __str__(self):
|
|
return '%s accepts mismatched parameters' % self.format_callable()
|