Skip to content
2 changes: 1 addition & 1 deletion src/contracts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from .main import (check, fail, check_multiple, contract_decorator,
contracts_decorate as decorate,
parse_flexible_spec as parse)
parse_flexible_spec as parse, Attribute)


# Just make them appear as belonging to the "contracts" Module
Expand Down
64 changes: 61 additions & 3 deletions src/contracts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ def contracts_decorate(function_, modify_docstring=True, **kwargs):
The decorator :py:func:`decorate` calls this function internally.
"""

if hasattr(function_,'__call__') and hasattr(function_.__call__,'im_func'):
""" For classes that implement __call__ replace the object with
a bound __call__.
"""
class_name=function_.__class__.__name__
function_=function_.__call__
function_.__dict__['__name__']=class_name +'.__call__'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not use function_.__name__ = class_name +'.__call__' here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason it generates an error:

AttributeError: 'instancemethod' object has no attribute 'name'


if isinstance(function_, classmethod):
msg = """
The function is a classmethod; PyContracts cannot decorate a classmethod.
Expand Down Expand Up @@ -308,10 +316,9 @@ def write_contract_as_rst(c):
contracts_checker.__name__ = 'checker-for-%s' % function_.__name__
contracts_checker.__module__ = function_.__module__

# TODO: is using functools.wraps better?
from decorator import decorator
from functools import partial,wraps

wrapper = decorator(contracts_checker, function_)
wrapper = wraps(function_)(partial(contracts_checker,function_))

wrapper.__doc__ = new_docs
wrapper.__name__ = function_.__name__
Expand Down Expand Up @@ -717,4 +724,55 @@ def can_accept_self_plus_one_argument(callable_thing):
return True


class Attribute(object):
""" Attribute is a descriptor for object attributes that
enforces a check on any object the attribute is set
to.

Usage example:

from contracts import Attribute, ContractNotRespected

class spam(object):
x=Attribute('float,>0')

eggs=spam()

print "Attempting eggs.x=1.0"
eggs.x=1.0
print "eggs.x=" + str(eggs.x)

print "Attempting eggs.x=-1.0"
try:
eggs.x=-1.0
except ContractNotRespected as detail:
print detail
"""

def __init__(self, check_string):

if all_disabled():
return

self.check_string=check_string

def __get__(self, instance, owner):
if not hasattr(instance, '__contracts__'):
instance.__contracts__ = {}

if self not in instance.__contracts__:
raise AttributeError("Attribute not set yet.")

return instance.__contracts__.get(self)
# return the stored data.

def __set__(self, instance, value):
if not hasattr(instance, '__contracts__'):
instance.__contracts__ = {}

if not all_disabled():
check(self.check_string,value)

instance.__contracts__[self]=value