contracts
contracts copied to clipboard
Merge design-by-contract libraries?
Hi, I'm trying to push for a standardized contracts library in Python, and so far there was some discussion on python-ideas mail list, please see this thread which was later forked to this thread and this thread.
We implemented our own design-by-contract (DbC) library, icontract, since we lacked some of the features in your library as well as in other ones and wanted to move forward fast for our own code base.
However, during the discussion on python-ideas, I realized how harmful this decision actually was. DbC has a hard time at getting adopted within a Python community, and having multiple libraries will just aggravate the matters: the contracts start really to shine when you have a small ecosystem around them (automatic test generation, documentation and IDE plugins). I don't see this ecosystem emerging with multiple libraries since each of the components of the ecosystem would need to support some or all of the libraries.
Finally, I see DbC as an excellent way of documentation that almost every library would hopefully adopt. Having multiple DbC libraries makes it impossible to integrate two third-party libraries when each of them depend on a different DbC approach. This is very apparent in the case of inheritance: inheriting from for some classes from a module dependent on one DbC library would need to use different contracts than another class inheriting from yet another class dependent on another DbC library. This is confusing and I doubt anybody would like such a mess in the code base.
With all that said, do you see it possible that we merge icontract with dpcontracts somehow? We can 1) make a new library, 2) I could merge the features that we missed in dpcontracts from icontract (or the other way around), or 3) we contact developers working on python standard library and start developing a standard library from the scratch if that's OK with them?
Here is a short list of features that we missed in dpcontracts:
-
Informative messages. Writing contracts was tedious since most contract messages in our code base were duplicates of what the contract already stated (
lambda args: args.x > 0, "x positive"
). We had to add a description to a contract very rarely and having the library figure out the message was extremely helpful to reduce the pain. -
Values of the arguments in the message. We found it important that the violation message includes the values of the arguments supplied to it. That involved quite some work (since we had to parse the condition function and re-trace its AST manually on contract breach), but was invaluable in production because contract violations were either tedious or impossible to reproduce without this information. Please see the examples in this section of the icontract readme. This feature also speeds up the development quite a bit since we don't have to turn on the debugger and the message makes it often pretty obvious where the bug lies.
-
Inheritance of contracts. This was important not only for object-oriented part of the system. This feature also allowed us to make contracts on a group of static methods (written in an abstract class) which serve as an interface, and then inherit and implement them by different components. This actually made it easy to have a functional (as in functional programming) interface with predefined contracts.
-
Sphinx extension to include contracts in the automatically generated documentation.
-
Linter that statically checks that arguments of the contracts coincide with that of the function.
All these extra features are already implemented in icontract and we already use them in our code base and production without problems. If you want to test them, simply fire up a virtual environment and pip install them. The features "informative messages" and "value of arguments in the message" are only executed on contract breach hence bring no computation overhead to normal running of the system.
I'm looking forward to your response.
Hi @mristin
My apologies for the late response. I get a lot of email. :)
Thank you the very comprehensive and well-thought-out message. I agree that it would be nice to see more design-by-contract usage in the Python world. For the features you mentioned above:
Informative messages.
This is the biggest one for me; I agree that stating everything twice gets a little tedious. Originally the dpcontracts module had the conditions embedded in docstrings and parsed out, much like icontracts. This obviated the need to have separate documentation, but suffered from various problems including parsing issues and such.
Inspecting the code via the inspect module to generate a message is definitely a good idea, but it seems fragile to me. If for some reason the source code is unavailable (e.g. the file is distributed as bytecode), it doesn't work.
(I realize that if the file is distributed as bytecode contracts would likely be disabled, so maybe it's not as common a case as I fear...)
I've looked at how it's done in icontract, and I'd be open to trying to merge that in to dpcontracts.
Values of the arguments in the message.
This would be largely solved if the message generation above were to be added in; it would be trivial to include a stringification of the argument tuple.
Inheritance of contracts
In this case, I'm assuming you mean inheritance of contracts not in Python subclasses, but by defining "contract classes" themselves (please correct me if I'm wrong).
That's definitely something I'd be interested in. I've thought about similar things and Python's object model would make it relatively easy to do (famous last words...)
Sphinx extension to include contracts in the automatically generated documentation.
I'd definitely be open to it. I'm a bad Python programmer, though: I don't use Sphinx. :)
Linter that statically checks that arguments of the contracts coincide with that of the function.
I feel like this could probably be integrated into mypy, which would be the (again, IMHO) logical place to do it.
dpcontracts was hamstrung for a while by the self-imposed requirement to maintain Python 2.7 and 3.4 compatibility. Now that support for deprecated versions has been relegated to the a more-or-less static branch, I think it will be easier to make more sweeping changes.
Hi @deadpixi
My apologies for the late response. I get a lot of email. :)
No problem at all, I perfectly understand how it is ;)
Let me first clarify some of the points from your message and then see how we move forward with a merger.
Informative messages.
Inspecting the code via the inspect module to generate a message is definitely a good idea, but it seems fragile to me. If for some reason the source code is unavailable (e.g. the file is distributed as bytecode), it doesn't work.
(I realize that if the file is distributed as bytecode contracts would likely be disabled, so maybe it's not as common a case as I fear...)
I've looked at how it's done in icontract, and I'd be open to trying to merge that in to dpcontracts.
We have not yet had a case where we wanted to use bytecode without the source code. In these cases, you could use a fall-back, and just write "<unparsable condition>"
instead.
Inheritance of contracts
In this case, I'm assuming you mean inheritance of contracts not in Python subclasses, but by defining "contract classes" themselves (please correct me if I'm wrong).
I actually thought of strengthening / weakining of the contracts in Python subclasses. Re-using classes of contracts seemed always problematic in our code base -- the readability actually suffered more than it increased so we gave up the idea. Since contracts are part of the function signature, they were always so specific that they were not reusable. But this is the case for our code base -- yours might be different and it might make more sense to have contract groups.
In the meanwhile, we moved forward with icontract and added a couple of more features (snapshotting "old" values in postconditions prior to the function invocation, custom errors).
I'll spend some time next week to finalize all the work that I planned to do on icontract and then I need to move to another project, so I'll have very little time to work on contracts. Personally, I would be more than happy if you could pick up all these features we have in icontract and integrate them into dpcontracts so that there is wider community using the contracts. When that happens, we would abandon icontract in favor of dpcontracts in long-term. I'm available to help you port the code from icontract to dpcontracts in whatever limited time I have.
If you feel like merging dpcontracts to icontract, that is fine with me as well. It would be trivial for us to rename the arguments and decorators to satisfy the interface of dpcontracts, so that icontract can be used as a direct replacement. There are so few users of icontract (as far as I can tell, only our company), so changing decorator and argument names should be painless.
In both cases, I think it's important that we have a unified library that can become a de facto, if not de iure, standard for contracts in Python.