Git Product home page Git Product logo

typedecorator's Introduction

Typedecorator

A decorator-based implementation of type checks for Python.

Build Status

Provides @params, @returns and @void decorators for describing the type of the function arguments and return values. If the types mismatch, an exception can be thrown, the mismatch can be logged, or it can be ignored.

Example:

@returns({str: int})
@params(data={str: int}, key=str, val=int)
def add_to_data(data, key, val):
    data[key] = val
    return data

Works on Python 2.5+ and Python 3.2+.

Quickstart

Install the package using pip:

pip install typedecorator

You can now start using it in your code

# import the decorators and the typecheck setup function
from typedecorator import params, returns, setup_typecheck

# decorate your functions
@returns(int)
@params(a=int, b=int)
def add(a, b):
    return a + b

# set up the type checking
setup_typecheck()

add(1, 2)  # works fine
add('one', 2)  # will raise a TypeError

You ony need to call setup_typecheck once to enable it and optionally configure exceptions thrown and logging level. You can use it multiple time to change, disable, or re-enable typechecks at runtime. See the Setup section for more information.

Type Signatures

Both @params and @returns take type signatures that can describe both simple and complex types. The @void decorator is a shorthand for @returns(type(None)), describing a function returning nothing.

A type signature can be:

  1. A type, such as int, str, bool, object, dict, list, or a custom class, requiring that the value be of specified type or a subclass of the specified type. Since every type is a subclass of object, object matches any type.

  2. A list containing a single element, requiring that the value be a list of values, all matching the type signature of that element. For example, a type signature specifying a list of integers would be [int].

  3. A tuple containing one or more elements, requiring that the value be a tuple whose elements match the type signatures of each element of the tuple. For eample, type signature (int, str, bool) matches tuples whose first element is an integer, second a string and third a boolean value.

  4. A dictionary containing a single element, requiring that the value be a dictionary with keys of type matching the dict key, and values of type matching the dict value. For example, {str:object} describes a dictionary with string keys, and anything as values.

  5. A set containing a single element, requiring that the value be a set with elements of type matching the set element. For example, {str} matches any set consisting solely of strings.

  6. xrange (or range in Python 3), matching any iterable (including generators and lists).

  7. An instance of typedecorator.Union, requiring that the value be of any of the types listed when creating the Union instance. For example, Union(int, str, type(None)) matches integers, strings and None.

  8. An instance of typedecorator.Nullable, requiring that the value is either of the type specified when creating the Nullable instance, or None. For example, Nullable(str) matches strings and None.

  9. A string containing the type name. This is useful in places where the type is not yet available when the decorator is wrapping the function, for example in method definitions (where class is not yet available)

These rules are recursive, so it is possible to construct arbitrarily complex type signatures. Here are a few examples:

  • {str: (int, [MyClass])} - dictionary with string keys, where values are tuples with first element being an integer, and a second being a list of instances of MyClass

  • {str: types.FunctionType} - dictionary mapping strings to functions

  • {xrange} - set of iterators

  • {str: Union(int, float)} - dictionary mapping strings to values that are either integers or floating point numbers

  • MyObject - any type named MyObject (more precisely, any type whose __name__ attribute is equal to MyObject)

Note that [object] is the same as list, {object:object} is the same as dict and {object} is the same as set.

Setup

The function setup_typecheck takes care of enabling, disabling, and configuring type checks at "compile" (parse) time and at runtime.

The function takes three optional arguments:

  • enabled - whether to enable checks of any kind (default: True)
  • exception - which exception to raise if type check fails (default: TypeError), or None to disable raising the exception
  • loglevel - the log level at which to log the type error (see the standard logging module for possible levels), or None to disable type error logging.

By default, the type checking system is inactive unless activated through this function. However, the type-checking wrappers are in place, so the type checking can be enabled or disabled at runtime (multiple times). These wrappers do incur a very small but real performance cost. If you want to disable the checks at "compile" time, call this function with enabled=False before defining any functions or methods using the typecheck decorator.

For example, if you have a config.py file with USE_TYPECHECK constant specifying whether you want the type checks enabled:

#!/usr/bin/env python

from typecheck import setup_typecheck, params
import config

setup_typecheck(enabled=config.USE_TYPECHECK)

@params(a=int, b=int):
def add(a, b):
    return a + b

Note that in this case, the checks cannot be enabled at runtime.

Type checking methods

When using @params with instance methods, you should specify object as the type of the self argument. This is required because when the decorator is executing, the class itself still isn't created so the type that self will have doesn't exist yet.

There is no problem in using a catch-all object type though, since Python will enforce correct type for the instance upon method invocation anyways.

When using @params on a class method, there is no problem since the class itself is instance of the type type.

But there's still a problem if a method takes additional parameters that must be instances of the same class, or if it returns an instance of the class. In this case, and in any other case where the type might not be available at the time of decorator running, a string specifying the type name can be used instead. Using strings should be avoided in other cases, because they're more prone to type errors (the existence of the type is never checked, the check only does a name match).

When combining type checking decorators with @classmethod, @staticmethod or @property decorators, the type checking decorators must run first (ie. be "closer" to the body of the function being defined).

Here's an example:

class Accumulator(object):
    sum = 0

    @void
    @params(self=object, a=int)
    def add(self, a):
        self.sum = self.sum + a

    @classmethod
    @returns(int)
    @params(cls=type, a=int, b=int)
    def add2(cls, a, b):
        return a + b

    @property
    @returns(int)
    def total(self):
        return self.sum

    @returns('Accumulator')
    @params(self=object, other='Accumulator')
    def __add__(self, other):
        result = Accumulator()
        result.sum = self.sum + other.sum
        return result

Mocking

Since the type signatures compare the actual value types, the parameters can't be mocked. To minimize the problem, Mock type from mock library (the most used mocking library in Python, and part of Python 3 standard library) is special cased - an instance of Mock (or any of its subclasses, such as MagicMock) will pass any check.

Python 3 annotations

If used with Python 3, the parameters and return type signatures can also be specified using the Python 3 function annotation syntax. To enable these, use the @typed decorator on an annotated function. For example:

@typed
def add_to_data(data: {str: int}, key: str, val: int) -> {str: int}:
    data[key] = val
    return data

The behaviour is identical as if @params and @returns were used, the only difference is in nicer syntax.

License

Copyright (C) 2014. Senko Rasic [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

typedecorator's People

Contributors

senko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

typedecorator's Issues

typedecorator and multiprocessing

Using Python version 2.7

When you use type decorator with a function, then try to call that function using multiprocessing's apply_async function, it results in a pickling error. Working example below, this was tested with a clean virtualenv with only typedecorator and the standard libraries installed.

from multiprocessing import Pool
from typedecorator import *

@returns(int)
@params(a=int,b=int)
def f(a,b):
    print "Function called"
    return a + b

def main():
    pool = Pool(processes=4)
    pool.apply_async(f,[1,2])
    pool.close()
    pool.join()

if __name__ == '__main__':
    main()

This results in:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 811, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 764, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib64/python2.7/multiprocessing/pool.py", line 342, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Removing the type decorations for the function 'f' fixes the problem. So far I really like your solution though, and will be implementing it where possible when moving forward. Keep up the great work!

Add a warning mode

Python provides a standard module for emitting warnings. This is a reasonable action to take for a type mismatch issue. Please make this one of the built in options for responding to type mismatches.

Allow annotations to be optional when @typed

class Foo:
    @typed
    def validate(self, string: str): # TypeError: Annotation doesn't match function signature
        ...

It'd be great if this would not give an error, but just limit the checking to the annotated variables.

Contracts

Should we support contracts?

It would be straightforward to support contracts by supporting custom callables in type signatures. For example:

@params(a=int, b=lambda b: b != 0)
def div(a, b):
    return a / b

However, I find that syntax clunky (for contracts), compared to something like:

@contract
def div(a, b):
    return a / b

@div.requires
def div(a, b):
    assert isinstance(a, int)
    assert b != 0

But this being typedecorator, not a full-blown contracts library, I'm hesitant to grow it in that direction (unless there's popular demand).

Check for a list containing multiple elements

I understand a list containing a single element can be checked as the document states as follows:

A list containing a single element, requiring that the value be a list of values, all matching the type signature of that element. For example, a type signature specifying a list of integers would be [int].

I was wondering if it is possible to check for a list containing multiple elements. For example, if a function expects a 2D coordinate, it would require the following type:

[int, int]

More precisely, I would like to check for the following type:

typedecorator.Union((int, int), [int, int])

but that does not seem possible as I'm getting TypeError: Invalid type signature. If I change my code a bit like:

typedecorator.Union((int, int), list)

it seems working okay but that's not quite what I want.

TypeError on class method

Hi,

I'm just starting to play with this package, and it looks fantastic. However, I can't find any information about making it work with a class method.

For example:

class Foo(object):
    @returns(int)
    @params(a=int, b=int):
    def add(self, a, b):
        return a + b

returns a TypeError. And if I change it to @params(self=self, a=int, b=int) I get a NameError (name 'self' if not defined).

So what is the proper way to annotate the method signature of an object method?

Thanks!

Cannot use self in method params

Hi. Sorry to bother you again. I have the following situation:

class Thing(object):
    @params(self=other, thing=Thing)
    def do_something(self, thing):
        pass

The thing=Thing gives me an "Unresolved reference error". Is there syntax for this already and I'm just missing it? Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.