diff --git a/zipline/_protocol.pyx b/zipline/_protocol.pyx index 9d981a34..0cbe81a2 100644 --- a/zipline/_protocol.pyx +++ b/zipline/_protocol.pyx @@ -27,75 +27,71 @@ from zipline.assets import Asset from zipline.zipline_warnings import ZiplineDeprecationWarning -class assert_keywords(object): +cdef bool _is_iterable(obj): + return isinstance(obj, Iterable) and not isinstance(obj, str) + + +cdef class check_parameters(object): """ Asserts that the keywords passed into the wrapped function are included in those passed into this decorator. If not, raise a TypeError with a - meaningful message, unlike the one cython returns by default. - """ + meaningful message, unlike the one Cython returns by default. - def __init__(self, arg_names, method_name): - self.names = arg_names + Also asserts that the arguments passed into the wrapped function are + consistent with the types passed into this decorator. If not, raise a + TypeError with a meaningful message. + """ + cdef tuple keyword_names + cdef object method + cdef tuple types + cdef dict keys_to_types + + def __init__(self, method_name, keyword_names, types): + self.keyword_names = keyword_names + self.types = types self.method = method_name + self.keys_to_types = dict(zip(keyword_names, types)) + def __call__(self, func): def assert_keywords_and_call(*args, **kwargs): + # verify all the keyword arguments for field in kwargs: - if field not in self.names: + if field not in self.keyword_names: raise TypeError("%s() got an unexpected keyword argument" " '%s'" % (self.method, field)) - return func(*args, **kwargs) - return assert_keywords_and_call - - -KEYWORDS = ['assets', 'fields', 'bar_count', 'frequency'] - - -class assert_types(object): - """ - Asserts that the arguments passed into the wrapped function are consistent - with the types passed into this decorator. If not, raise a TypeError with - a meaningful message. - """ - - def __init__(self, *args): - self.types = args - self.keys_to_types = dict(zip(KEYWORDS, args)) - - def _is_iterable(self, obj): - return isinstance(obj, Iterable) and not isinstance(obj, str) - - def __call__(self, func): - def assert_types_and_call(*args, **kwargs): + # verify type of each arg for i, arg in enumerate(args[1:]): if isinstance(arg, self.types[i]): continue - elif i in (0, 1) and self._is_iterable(arg): + elif i in (0, 1) and _is_iterable(arg): if isinstance(arg[0], self.types[i]): continue expected_type = self.types[i].__name__ \ - if not self._is_iterable(self.types[i]) \ + if not _is_iterable(self.types[i]) \ else ', '.join([type.__name__ for type in self.types[i]]) + raise TypeError("Expected %s argument to be of type %s%s" % - (KEYWORDS[i], + (self.keyword_names[i], 'or iterable of type ' if i in (0, 1) else '', expected_type) ) + # verify type of each kwarg for keyword, arg in iteritems(kwargs): if isinstance(arg, self.keys_to_types[keyword]): continue - elif keyword in ('assets', 'fields') and \ - self._is_iterable(arg): + elif keyword in ('assets', 'fields') and _is_iterable(arg): if isinstance(arg[0], self.keys_to_types[i]): continue expected_type = self.keys_to_types[keyword].__name__ \ - if not self._is_iterable(self.keys_to_types[keyword]) \ + if not _is_iterable(self.keys_to_types[keyword]) \ else ', '.join([type.__name__ for type in self.keys_to_types[keyword]]) + raise TypeError("Expected %s argument to be of type %s%s" % (keyword, 'or iterable of type ' if keyword in @@ -105,7 +101,7 @@ class assert_types(object): return func(*args, **kwargs) - return assert_types_and_call + return assert_keywords_and_call @contextmanager @@ -210,8 +206,7 @@ cdef class BarData: return dt - @assert_keywords(arg_names=('assets', 'fields'), method_name='current') - @assert_types((Asset, str), str) + @check_parameters('current', ('assets', 'fields'), ((Asset, str), str)) def current(self, assets, fields): """ Returns the current value of the given assets for the given fields @@ -268,8 +263,8 @@ cdef class BarData: the current trade bar. If there is no current trade bar, NaN is returned. """ - multiple_assets = self._is_iterable(assets) - multiple_fields = self._is_iterable(fields) + multiple_assets = _is_iterable(assets) + multiple_fields = _is_iterable(fields) # There's some overly verbose code in here, particularly around # 'do something if self._adjust_minutes is False, otherwise do @@ -386,10 +381,7 @@ cdef class BarData: return pd.DataFrame(data) - cdef bool _is_iterable(self, obj): - return hasattr(obj, '__iter__') and not isinstance(obj, str) - - @assert_types(Asset) + @check_parameters('can_trade', ('assets',), (Asset,)) def can_trade(self, assets): """ For the given asset or iterable of assets, returns true if the asset @@ -436,7 +428,7 @@ cdef class BarData: return False - @assert_types(Asset) + @check_parameters('is_stale', ('assets',), (Asset,)) def is_stale(self, assets): """ For the given asset or iterable of assets, returns true if the asset @@ -496,9 +488,9 @@ cdef class BarData: return not (last_traded_dt is pd.NaT) - @assert_keywords(arg_names=('assets', 'fields', 'bar_count', 'frequency'), - method_name='history') - @assert_types((Asset, str), str, int, str) + @check_parameters('history', + ('assets', 'fields', 'bar_count', 'frequency'), + ((Asset, str), str, int, str)) def history(self, assets, fields, bar_count, frequency): """ Returns a window of data for the given assets and fields.