ENH: Coerce user input with API method decorator

Previously we have capitalized input strings at different levels in
our code: in the user-facing API methods and in the asset finder.
This commit moves input string capitalization exclusively to the API
method to which the string was supplied. Specifically, the string is
capitalized by a preprocess API method decorator. The preprocess
decorator passes the input string to the newly defined
ensure_upper_case() method, which returns a TypeError if the argument
supplied is not a string.

ensure_upper_case() is defined in a new file, zipline/utils/input_validation.py.
The existing expect_types() method is also moved there.

Various tests in tests/test_assets.py are modified to account for the
fact that the asset finder method lookup_symol() no longer capitalizes
its supplied argument.
This commit is contained in:
Stewart Douglas
2015-10-08 15:41:33 -04:00
parent 69b734129f
commit 4e2039c9b0
8 changed files with 202 additions and 118 deletions
+1 -97
View File
@@ -6,45 +6,12 @@ from functools import wraps
from inspect import getargspec
from uuid import uuid4
from six import iteritems, viewkeys, exec_
from toolz import valmap
from six import viewkeys, exec_
NO_DEFAULT = object()
def expect_types(*_pos, **named):
"""
Preprocessing decorator that verifies inputs have expected types.
Usage
-----
>>> @expect_types(x=int, y=str)
... def foo(x, y):
... return x, y
...
>>> foo(2, '3')
(2, '3')
>>> foo(2.0, '3')
Traceback (most recent call last):
...
TypeError: foo() expected an argument of type 'int' for argument 'x', but got float instead. # noqa
"""
if _pos:
raise TypeError("expect_types() only takes keyword arguments.")
for name, type_ in iteritems(named):
if not isinstance(type_, (type, tuple)):
raise TypeError(
"expect_types() expected a type or tuple of types for "
"argument '{name}', but got {type_} instead.".format(
name=name, type_=type_,
)
)
return preprocess(**valmap(_expect_type, named))
def preprocess(*_unused, **processors):
"""
Decorator that applies pre-processors to the arguments of a function before
@@ -157,69 +124,6 @@ def call(f):
return processor
def _qualified_name(obj):
"""
Return the fully-qualified name (ignoring inner classes) of a type.
"""
module = obj.__module__
if module in ('__builtin__', '__main__', 'builtins'):
return obj.__name__
return '.'.join([module, obj.__name__])
def _expect_type(type_):
"""
Factory for type-checking functions that work the @preprocess decorator.
"""
# Slightly different messages for type and tuple of types.
_template = (
"{{funcname}}() expected a value of type {type_or_types} "
"for argument '{{argname}}', but got {{actual}} instead."
)
if isinstance(type_, tuple):
template = _template.format(
type_or_types=' or '.join(map(_qualified_name, type_))
)
else:
template = _template.format(type_or_types=_qualified_name(type_))
def _check_type(func, argname, argvalue):
if not isinstance(argvalue, type_):
raise TypeError(
template.format(
funcname=_qualified_name(func),
argname=argname,
actual=_qualified_name(type(argvalue)),
)
)
return argvalue
return _check_type
def optional(type_):
"""
Helper for use with `expect_types` when an input can be `type_` or `None`.
Returns an object such that both `None` and instances of `type_` pass
checks of the form `isinstance(obj, optional(type_))`.
Parameters
----------
type_ : type
Type for which to produce an option.
Examples
--------
>>> isinstance({}, optional(dict))
True
>>> isinstance(None, optional(dict))
True
>>> isinstance(1, optional(dict))
False
"""
return (type_, type(None))
def _build_preprocessed_function(func, processors, args_defaults):
"""
Build a preprocessed function with the same signature as `func`.