Closed elistevens closed 9 years ago
Just to see if I understand the problem, if you remove the contract in the docstring, then also the "Tried to redefine 'myContract' " error goes away, right?
If so, I think what's happening is that Nose keeps trying to import the module again even if it fails the first time. Every time, a "new" function "myContract" is instantiated. What do you think should happen at this point?
Removing the syntax error in the docstring causes all of the errors to go away. I think that having a decorated function with no contract causes a different error (ContractException vs. ContractSyntaxError), but I haven't tested that directly.
And yes, I think that reimportation is what's causing the issue. As for what should happen... Maybe something like this?
# https://github.com/AndreaCensi/contracts/blob/master/src/contracts/main.py#L572
if not(inspect.getsource(contract) == inspect.getsource(old)):
msg = '...'
Not sure if a textual representation of the source is good enough or not. Redefining a contract function that uses a closure might cause a false negative, but that seems like it's pretty deep into "you should know better" territory to me. I think that this approach would make this problem go away. For me, that's a good tradeoff, but I haven't thought about the problem much. ;) I totally understand if you decide otherwise. :)
I think that perhaps the best way to check this is using the function name ("my.module.myfunction").
I'm not sure though I can implement a robust way to do that check, that works for all callable things (lambdas, functions from C extensions, etc.).
What does the module.funcname get you over using inspect.getsource?
Although I should note that inspect.getsource doesn't work on lambda either. Maybe something like this:
import inspect
def functionId(f):
try:
return inspect.getsource(f)
except IOError:
return id(f)
And then use that:
if not(functionId(contract) == functionId(old)):
What does the module.funcname get you over using inspect.getsource?
I was thinking it would be better as it would not involve any inspect
ion. Perhaps id
is the best way overall. But I don't think it would work in your original example, where the module is only partially initialized. In that case, probably id() gives a different id to the two incarnations of the same function.
Yeah, the id of the two functions is different. Note the "at 0x..." parts below:
ValueError: Tried to redefine 'myContract' with a definition that looks different to me.
- old: CheckCallable(<function myContract at 0x3198410>)
- new: CheckCallable(<function myContract at 0x39427d0>)
I suspect that function objects don't define equality differently than object (ie. it uses "is" to determine equality).
There is also the possibility of using the .func_code object; it's possible that code objects have a richer definition of equality. Not sure. See:
I believe this was fixed some time ago.
Consider a file foo.py (note, this is condensed from the real test case; apologies for typos, etc.):
If foo gets imported in multiple test cases (we're running them via python setup.py nosetests), then the following occurs:
The ValueError is repeated for every subsequent test case that imports foo, even if the class with the broken definition is not used. This can make it difficult to find the real error when there are a large number of test cases.