{"id":24316497,"url":"https://github.com/jferard/python-toy-contract","last_synced_at":"2026-06-05T11:31:37.030Z","repository":{"id":170593578,"uuid":"258477974","full_name":"jferard/python-toy-contract","owner":"jferard","description":"A toy project. Attempt to use dynamic nature of Python to implement  programming by contract.","archived":false,"fork":false,"pushed_at":"2023-07-08T08:12:55.000Z","size":68,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-10T12:56:24.895Z","etag":null,"topics":["contracts-programming","metaclasses","python-3","toy-project"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jferard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-24T10:16:58.000Z","updated_at":"2023-07-08T07:31:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"9f8504db-7712-49dd-8a7f-a5896770c1cb","html_url":"https://github.com/jferard/python-toy-contract","commit_stats":null,"previous_names":["jferard/python-toy-contract"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jferard/python-toy-contract","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jferard%2Fpython-toy-contract","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jferard%2Fpython-toy-contract/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jferard%2Fpython-toy-contract/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jferard%2Fpython-toy-contract/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jferard","download_url":"https://codeload.github.com/jferard/python-toy-contract/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jferard%2Fpython-toy-contract/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33939225,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["contracts-programming","metaclasses","python-3","toy-project"],"created_at":"2025-01-17T12:56:41.799Z","updated_at":"2026-06-05T11:31:37.009Z","avatar_url":"https://github.com/jferard.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Python Toy Contract](https://github.com/jferard/python-toy-contract/actions/workflows/workflow.yml/badge.svg)](\nhttps://github.com/jferard/python-toy-contract/actions/workflows/workflow.yml)\n[![codecov](https://codecov.io/gh/jferard/python-toy-contract/branch/master/graph/badge.svg?token=O0MMKMM3DY)](https://codecov.io/gh/jferard/python-toy-contract)\n\nA toy project. Attempt to use dynamic nature of Python to implement \nprogramming by contract.\n\nDespite being a toy project, the goal is to have a working implementation. But\nperformance is less important than documentation.\n\n# References\nA wonderful book by B. Meyer: [Object-Oriented Software Construction, 2nd \nEdition](https://www.eiffel.org/doc/eiffel/Object-Oriented_Software_Construction%2C_2nd_Edition).\n\nThe reference implementation of Eiffel at https://www.eiffel.org/.\n\nThe [PEP 316 -- Programming by Contract for Python](\nhttps://www.python.org/dev/peps/pep-0316/)\n\nSome (serious) contract libraries in Python:\n* https://github.com/Parquery/icontract: uses decorators + lambdas;\n* https://github.com/AndreaCensi/contracts: uses decorator + custom syntax; \n\n# Python Toy Contract goal\nI was wondering whether it was easy to implement a toy \"programming by \ncontract\" feature in Python.\n\nFunction contracts can easily be achieved with the `assert` statement:\n\n    def f(x, y, z):\n        assert \u003cprecondition\u003e, \"Precondition violation\"\n        \u003ccode\u003e\n        assert \u003cpostcondition\u003e, \"Postcondition violation\"\n\nHence I focused on class contracts, since they are the most interesting: \ninvariants, precondtions and postconditions should be inherited from a parent\nclass if they exist.\n\n## An Eiffel version of the example\nBonus: I wrote an Eiffel version of the example. I'm far from being an Eiffel \nexpert, hence the code is probably clumsy. Please fill an issue if you know \nhow to improve the code.\n\n# Usage\nAssertions are defined at class level:\n\n    class X(Contract):\n        def __invariant__(self):\n            assert \u003cmy test\u003e, \u003cmsg\u003e\n\n        def func(self, x, y, z):\n            def __require__(self, x, y, z):\n                assert \u003cmy test\u003e, \u003cmsg\u003e\n                \n            def __ensure__(self, ret, old, x, y, z):\n                assert \u003cmy test\u003e, \u003cmsg\u003e\n\n            \u003cbody of the function\u003e\n\nWhen deriving the class, invariants and postconditions may be strenghtened, \nbut preconditions may only be weakened:\n\n    class Y(X):\n        def __invariant__(self):\n            assert \u003cmy test\u003e, \u003cmsg\u003e\n\n        def func(self, x, y, z):\n            def __require_else__(self, x, y, z):\n                assert \u003cmy test\u003e, \u003cmsg\u003e\n                \n            def __ensure_then__(self, ret, old, x, y, z):\n                assert \u003cmy test\u003e, \u003cmsg\u003e\n\n            \u003cbody of the function, don't forget too call the super class\u003e\n\n# Running the tests\nTo run the tests of the Python version, you have to type:\n\n    python-toy-contract$ python3 -m pytest\n    \nTo run the test of the Eiffel version:\n\n    python-toy-contract$ pushd eiffel/\n    python-toy-contract/eiffel$ ec -config eiffel/python-toy-contract.ecf -tests\n    python-toy-contract$ popd\n    \nthe Python tests pass, because of `assertRaises` tests. The Eiffel tests fail.\n\n# Design\n## Assertions using `assert` statement \nUnlike other libraries:\n\n* assertions are not coded as lambdas or strings, but using plain old \n`assert` statements.\n* I did not use decorators but a method (for invariants) and nested \nfunctions (for pre and post conditions). \n\nThese functions are executed before and after the body of the function. If an\nassertions fails, you have the whole stacktrace.\n\n## When assertions are disabled\nContracts are enabled only if assertions are enabled. This is true by design,\nbut there is a shortcut with virtually no performance penaly: \n`ContractMeta.__new__` simply does not create a version of the class with \ncontracts. \n\n---\n\nUnlike many other languages, but like Eiffel assertions, Python assertions are\nenabled by default. They are disabled [when the command line option `-O` is set\n](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement).\nThis can be checked with the `__debug__` constant:\n\n    python/python-toy-contract$ python3.7\n    \u003e\u003e\u003e __debug__\n    True\n    \u003e\u003e\u003e assert False\n    Traceback (most recent call last):\n    ...\n    AssertionError\n\nBut:\n\n    jferard@jferard-Z170XP-SLI:~/prog/python/python-toy-contract$ python3.7 -O\n    Python 3.7.5 (default, Nov  7 2019, 10:50:52) \n    [GCC 8.3.0] on linux\n    Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n    \u003e\u003e\u003e __debug__\n    False\n    \u003e\u003e\u003e assert False\n    \u003e\u003e\u003e # nothing\n\n## Non paranoid mode\nThe default mode is the \"paranoid mode\": invariants are checked after \n`__init__` (is the initial state ok) and then *before* and *after* each \nfunction call (along with pre or post conditions). You could imagine that \nchecking the invariant *after* a function call is sufficient, but it's very\neasy to alter manually the state of an object:\n\n\u003e We don't use the term \"private\" here, since no attribute is really \n\u003e private in Python -- [*PEP 8 - Designing for Inheritance*](https://www.python.org/dev/peps/pep-0008/#designing-for-inheritance)  \n\nThough, there is a \"non paranoid mode\". Invariants are checked only after \nmethod calls:\n\n    class X(Contract):\n        __paranoid__ = False\n\n\n# Implementation\nThere are some challenges:\n* explore the class hierarchy to find the `__invariant__` methods in parent\nclasses, using the [MRO](https://www.python.org/download/releases/2.3/mro/);\n* explore the class hierarchy to find, for each public method, the \n`__require__` and `__ensure__` nested functions, following the MRO;\n* be able to turn off assertions inside assertions.\n* check all the invariants and postconditions, but allow a weakening of \npreconditions. \n\n## Find the `__invariant__` method in parent classes\nWe just follow the class `__mro__` attribute.\n\n## Find for each public method, the `__require__` and `__ensure__` nested functions\nThis is less easy. First, we need a list of public method, whether they are in \na class or its parents. This is what `dir()` was made for.\n\nNow, we have the names of the public methods, we follow the MRO until we\nfind a method in a class `C`. Then, we switch to `C`s MRO to go up until\nwe meet the `object` class. Obviously, the list of methods (and classes)\nis different for each function.\n\nThen, we take this list and look for nested `__require__` and `__ensure__`\nfunctions. We can build a list of those nested functions, for each name of \na public function.\n\n## Turn off assertions\nIf you use a method inside an assertion (pre/postcondition or invariant), then\nyou are in great risk to trigger an endless loop. Example: the invariant is \nchecked after each method, but it calls a method `m`. Then, inside the \ninvariant, you'll call the invariant and you are stuck.\n\nUsually, we use a trigger: turn off the assertions, check the assertions, turn\non the assertions. But Python is dynamic, hence I choose to have \ntwo classes: `contracted_C`, with assertions, `relaxed_C` with no assertions. \n\nWhen we check the assertions, we change the class of the object to relaxed,\nwhen we execute the method, we change the class of the object to contracted. \nThis is achieved using a [metaclass](\nhttps://docs.python.org/3/reference/datamodel.html#metaclasses): for each \ndeclared class, two classes are create and linked: \n\n* `contracted_C.__relaxed__ = relaxed_C`\n* `relaxed_C.__contracted__ = contracted_C`\n\nThe class `contracted_C` is returned.\n\n## Check assertions - weakening of preconditions\nInvariants and postcondtions are all checked in reverse order (from parents to \nchildren). This allows to raise the first infraction from the most generic \nclass. \n\nPreconditions are checked in reverse order, until we find a precondition that is\nmet. If none of the preconditions is met, then the precondition fails. Python \nToy Contract returns the violation of the precondition in the most generic\nclass.\n\n# Some thoughts on programming by contract\n## What went wrong?\nThe first version of Eiffel was released in 1986, more than 30 years ago. \nDesign by contract is, in my mind, a genius idea. Most \nlanguages have only a poor, defaced version of contracts, that is the `assert` \nkeyword. So what went wrong?\n\nFrankly, I don't know, but the \"Do you eat your own dog food\" is enlightening:\nI read carefully the code, thinking of invariants, pre or post conditions, and\nthe conclusion is unequivocal: the only place where I need a state is, when\nlooking for assertions, I have to ensure that one and only `__require__`\npreceeds one or many `__require_else__` (same for `__require__` and \n`__ensure_then__`). And three `assert` statements seemed enough, because\nthe real question here is: do I have all the assertions? and not: are these \nassertions in the right order?\n\nIf I can draw a conclusion from such a small example, it would be the \nfollowing.\n\nB. Meyer insists on correctness of programs. But that's not always \nthe primary concern of program makers. It may be performance (some games), \ncost, ease of development, ... But even if correctness is the primary goal, \ncorrectness cannot always be expressed using contracts:\n\n* some classes are only disguised structures (you know, those \nclasses that have only getters and setters), helper classes like factories or \nbuilders (no real state).\n* some classes are too dependent of a global state (they need \ntests, not invariants). Think of the classes that process an input.\n* some classes do not belong to a hierarchy, and you don't need to inherit\npre/post conditions and invariants: `assert` is enough.\n\nDon't get me wrong, I would like to have real contracts in Python, Kotlin, \nJava, Rust, ..., but they didn't seem a panacea to languages designers.\n\n## Liskov substitution principle\nRemember the LSP?\n\n\u003e Subtype Requirement: Let *ϕ(x)* be a property provable about objects *x* \nof type *T*. Then *ϕ(y)* should be true for objects *y* of type *S* where *S* is a subtype of *T*. \n\nBasically, in a given program, I should be able to replace a instance of a \nclass by an instance of any subclass of this class. If it doesn't work the \nsubclass is *not* a subtype, because it doesn't follow the semantic of the\nparent type, just it's syntax. In other word, subclassing is about method \nsignatures, while subtyping is about method behavior.\n\nWith design by contract, assertions are part of the signature that should \ncarry at least a part of the semantic. Thus, subtyping should be closer \nto subclassing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjferard%2Fpython-toy-contract","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjferard%2Fpython-toy-contract","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjferard%2Fpython-toy-contract/lists"}