Contracts does not stop execution immediately having found an unexpected OR null input
AldarisPale opened this issue · 1 comments
Hi.
I think I have stumbled upon a bug or if not a bug then atleast an counterintuitive feature.
Consider the following code:
#!/usr/bin/python3
# -*- coding: utf8 -*-
from contracts import contract
class ObjectThatWillSeeDeathBeforeBeingBorn(object):
@contract
def __init__(self, TestingString: 'str[>0]'):
print('String given as parameter: %s.' % TestingString)
print('ID of ObjectThatWillSeeDeathBeforeBeingBorn: %s.' % id(self))
def __del__(self):
print('ID of object being killed: %s.' % id(self))
NewObjectForTestingTheBug = ObjectThatWillSeeDeathBeforeBeingBorn()
When executed as listed above we will get this as output:
$ python3 contracts_possible_bug.py
ID of object being killed: 139899159500840.
Traceback (most recent call last):
File "contracts_possible_bug.py", line 17, in <module>
NewObjectForTestingTheBug = ObjectThatWillSeeDeathBeforeBeingBorn()
TypeError: __init__() missing 1 required positional argument: 'TestingString'
The notable thing here is that we don't get any output from init but we do get output from del.
If we modify the last line so it looks like this:
NewObjectForTestingTheBug = ObjectThatWillSeeDeathBeforeBeingBorn(42)
the output looks like this:
$ python3 contracts_possible_bug.py
Traceback (most recent call last):
File "contracts_possible_bug.py", line 17, in <module>
NewObjectForTestingTheBug = ObjectThatWillSeeDeathBeforeBeingBorn(42)
File "<decorator-gen-1>", line 2, in __init__
File "/usr/local/lib/python3.5/dist-packages/contracts/main.py", line 260, in contracts_checker
raise e
File "/usr/local/lib/python3.5/dist-packages/contracts/main.py", line 255, in contracts_checker
accepts_parsed[arg]._check_contract(context, bound[arg], silent=False)
File "/usr/local/lib/python3.5/dist-packages/contracts/interface.py", line 439, in _check_contract
self.check_contract(context, value, silent)
File "/usr/local/lib/python3.5/dist-packages/contracts/library/strings.py", line 19, in check_contract
value=value, context=context)
contracts.interface.ContractNotRespected: Breach for argument 'TestingString' to ObjectThatWillSeeDeathBeforeBeingBorn:__init__().
Expected a string, got 'int'.
checking: str[>0] for value: Instance of <class 'int'>: 42
Variables bound in inner context:
- self: Instance of <class '__main__.ObjectThatWillSeeDeathBeforeBeingBorn'>: <__main__.ObjectThatWillSeeDeathBeforeBeingBorn object at 0x7fe... [clip]
ID of object being killed: 140638653347880.
I had expected that when a breach of contract has been found everything grinds to a halt but we can see that in both cases code still gets executed.
Last but not least, thank You for Your great work on contracts!
Hi again.
After getting deeper into python and learning that __del__
is a bad idea anyway I have refactored the code:
#!/usr/bin/python3
# -*- coding: utf8 -*-
from contracts import contract
class ObjectThatWillSeeDeathBeforeBeingBorn(object):
@contract
def __init__(self, TestingString: 'str[>0]'):
print('String given as parameter: %s.' % TestingString)
print('ID of ObjectThatWillSeeDeathBeforeBeingBorn: %s.' % id(self))
def __enter__(self):
pass
def __exit__(self, type, value, tb):
print('ID of object being killed: %s.' % id(self))
with ObjectThatWillSeeDeathBeforeBeingBorn() as NewObjectForTestingTheBug:
pass
we now get:
$ python3 contracts_possible_bug.py
Traceback (most recent call last):
File "contracts_possible_bug.py", line 20, in <module>
with ObjectThatWillSeeDeathBeforeBeingBorn() as NewObjectForTestingTheBug:
TypeError: __init__() missing 1 required positional argument: 'TestingString'
Please note, that code in __exit__
did not get executed.
Lets try initating the object with a wrong type:
with ObjectThatWillSeeDeathBeforeBeingBorn(42) as NewObjectForTestingTheBug:
Result:
$ python3 contracts_possible_bug.py
Traceback (most recent call last):
File "contracts_possible_bug.py", line 20, in <module>
with ObjectThatWillSeeDeathBeforeBeingBorn(42) as NewObjectForTestingTheBug:
File "<decorator-gen-1>", line 2, in __init__
File "/usr/local/lib/python3.5/dist-packages/contracts/main.py", line 260, in contracts_checker
raise e
File "/usr/local/lib/python3.5/dist-packages/contracts/main.py", line 255, in contracts_checker
accepts_parsed[arg]._check_contract(context, bound[arg], silent=False)
File "/usr/local/lib/python3.5/dist-packages/contracts/interface.py", line 439, in _check_contract
self.check_contract(context, value, silent)
File "/usr/local/lib/python3.5/dist-packages/contracts/library/strings.py", line 19, in check_contract
value=value, context=context)
contracts.interface.ContractNotRespected: Breach for argument 'TestingString' to ObjectThatWillSeeDeathBeforeBeingBorn:__init__().
Expected a string, got 'int'.
checking: str[>0] for value: Instance of <class 'int'>: 42
Variables bound in inner context:
- self: Instance of <class '__main__.ObjectThatWillSeeDeathBeforeBeingBorn'>: <__main__.ObjectThatWillSeeDeathBeforeBeingBorn object at 0x7f6... [clip]
Now let's try with the correct type:
with ObjectThatWillSeeDeathBeforeBeingBorn("42") as NewObjectForTestingTheBug:
Result:
$ python3 contracts_possible_bug.py
String given as parameter: 42.
ID of ObjectThatWillSeeDeathBeforeBeingBorn: 139824865924656.
ID of object being killed: 139824865924656.
So the lesson here is not to use __del__
when dealing with objects.