AndreaCensi / contracts

PyContracts is a Python package that allows to declare constraints on function parameters and return values. Contracts can be specified using Python3 annotations, or inside a docstring. PyContracts supports a basic type system, variables binding, arithmetic constraints, and has several specialized contracts and an extension API.
http://andreacensi.github.io/contracts/
Other
398 stars 62 forks source link

Error reporting for custom contracts #9

Open beloglazov opened 11 years ago

beloglazov commented 11 years ago

Hi Andrea,

I'm trying to define a custom contract using the new_contract function and the ist function from contracts.library.miscellaneous_aliases as follows:

new_contract('virConnect', ist(libvirt.virConnect))

The actual checking of the argument type works, i.e. fails when the argument is not an instance of libvirt.virConnect. However, when it fails, the error message is not properly displayed. I'm getting the following exception:

Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/pyqcy/integration.py", line 60, in test
    run_tests([prop], verbosity=0, propagate_exc=True)
  File "/usr/lib/python2.7/site-packages/pyqcy/runner.py", line 68, in run_tests
    failure.propagate_failure()
  File "/usr/lib/python2.7/site-packages/pyqcy/properties.py", line 127, in test_one
    result.tags = self.__execute_test(coroutine)
  File "/usr/lib/python2.7/site-packages/pyqcy/properties.py", line 148, in __execute_test
    obj = next(coroutine)
  File "/usr/lib/python2.7/site-packages/pyqcy/properties.py", line 81, in generator_func
    func(*args, **kwargs)
  File "/home/anton/repos/openstack-neat/tests/test_collector.py", line 58, in get_current_vms
    collector.get_current_vms(1)
  File "<string>", line 2, in get_current_vms
  File "/usr/lib/python2.7/site-packages/contracts/main.py", line 261, in contracts_checker
    accepts_parsed[arg]._check_contract(context, bound[arg])
  File "/usr/lib/python2.7/site-packages/contracts/interface.py", line 219, in _check_contract
    self.check_contract(context, value)
  File "/usr/lib/python2.7/site-packages/contracts/library/extensions.py", line 23, in check_contract
    self.contract._check_contract(context, value)
  File "/usr/lib/python2.7/site-packages/contracts/interface.py", line 219, in _check_contract
    self.check_contract(context, value)
  File "/usr/lib/python2.7/site-packages/contracts/library/extensions.py", line 69, in check_contract
    raise ValueError(msg)
CheckError: <unprintable CheckError object>

I suppose instead of , there should be a message like 'Value is not an instance of virConnect'. Do you know what the problem is?

Thanks, Anton

beloglazov commented 11 years ago

A simple definition of the contract like new_contract('virConnect', lambda x: False) results in a similar exception:


Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/pyqcy/integration.py", line 60, in test
    run_tests([prop], verbosity=0, propagate_exc=True)
  File "/usr/lib/python2.7/site-packages/pyqcy/runner.py", line 68, in run_tests
    failure.propagate_failure()
  File "/usr/lib/python2.7/site-packages/pyqcy/properties.py", line 127, in test_one
    result.tags = self.__execute_test(coroutine)
  File "/usr/lib/python2.7/site-packages/pyqcy/properties.py", line 148, in __execute_test
    obj = next(coroutine)
  File "/usr/lib/python2.7/site-packages/pyqcy/properties.py", line 81, in generator_func
    func(*args, **kwargs)
  File "/home/anton/repos/openstack-neat/tests/test_collector.py", line 58, in get_current_vms
    collector.get_current_vms(1)
  File "<string>", line 2, in get_current_vms
  File "/usr/lib/python2.7/site-packages/contracts/main.py", line 267, in contracts_checker
    raise e
CheckError: <unprintable CheckError object>
AndreaCensi commented 11 years ago

Can you give a minimal working example?

AndreaCensi commented 11 years ago

ok, let me see...

beloglazov commented 11 years ago

I've just made a simple example:

#!/usr/bin/python2

from contracts import contract, new_contract
from contracts.library.miscellaneous_aliases import ist

class ContractTest(object):
    pass

new_contract('customContract', ist(ContractTest))

@contract
def test(test):
    """ Test a custom contract definition.

    :type test: customContract
    """
    pass

if __name__ == '__main__':
    test("")

And it actually works fine, shows the following error:

Traceback (most recent call last):
  File "./test.py", line 24, in <module>
    test("")
  File "<string>", line 2, in test
  File "/usr/lib/python2.7/site-packages/contracts/main.py", line 267, in contracts_checker
    raise e
contracts.interface.ContractNotRespected: Breach for argument 'test' to test() in __main__.
Value is not an instance of ContractTest.
checking: function isinstance_of_ContractTest()      for value: Instance of str: ''   
checking: customContract                             for value: Instance of str: ''

I need to dig a bit more and understand the difference with my previous setup.

AndreaCensi commented 11 years ago

By the way, there is an undocumented feature that allows you to just give the type:

@contract(test=ContractTest) 
def test(test):
     # pycontracts understands it's a type, checks the type of test
     pass
AndreaCensi commented 11 years ago

About the original problem: are you sure that the virConnect object is printable? (i.e. object.str() and object.repr() return fine)

beloglazov commented 11 years ago

Thanks for the tip about the type feature! Does it only work when a contract is specified inside of the decorator? I've tried it with docstrings and it breaks:

@contract
def test(test):
    """ Test a custom contract definition.

    :type test: ContractTest
    """
    pass

This leads to the following error:

Traceback (most recent call last):
  File "./test.py", line 14, in <module>
    @contract
  File "/usr/lib/python2.7/site-packages/contracts/main.py", line 168, in contract_decorator
    raise ContractSyntaxError(e.error, e.where)
contracts.interface.ContractSyntaxError: Unknown identifier 'ContractTest'. Did you mean 'Container'? (at char 4), (line:1, col:5)

 line  1 >ContractTest
              ^
              |
              here or nearby

Regarding the original problem, I think it happens because pyqcy (a testing framework) somehow catches exceptions raised from inside tests and wraps them in another object (CheckError). So it's not a problem of PyContracts. Sorry for bothering you with this!

AndreaCensi commented 11 years ago

Yes, that's a limitation. This feature doesn't work inside the docstrings. It's one of the reasons it is not official... I certainly don't want to eval() any string.

In this case, you have to use new_contract. But there is no need of using ist().

from contracts import contract, new_contract

class ContractTest(object):
    pass

new_contract('ContractTest', ContractTest)

@contract
def test(test):
    """ Test a custom contract definition.

    :type test: ContractTest
    """
    pass

if __name__ == '__main__':
    test("")
beloglazov commented 11 years ago

Thanks so much for the help! It works :)