# -*- coding: utf-8 -*-
from setuptools import setup

package_dir = \
{'': 'src'}

modules = \
['design_by_contract']
install_requires = \
['decorator>=5.1.1,<6.0.0']

setup_kwargs = {
    'name': 'design-by-contract',
    'version': '0.2.1',
    'description': 'Handy decorator to define contracts with dependency injection in Python 3.10 and above',
    'long_description': '# Welcome to `design-by-contract`\n\nA minimalistic decorator for the [design by contract pattern](https://en.wikipedia.org/wiki/Design_by_contract)\nwritten in a just little more than 100 lines of modern Python 3.10 code (not counting documentation and logging).\n\nContracts are useful to impose restrictions and constraints on function arguments in a way that\n\n* reduces boilerplate for argument validation in the function body\n  (no more if blocks that raise value errors),\n* are exposed in the function signature, that is, they serve as a means of documentation\n  that is always up-to-date,\n* allow relations between arguments.\n\nInstall with\n\n```sh\npip install design-by-contract\n```\n\n**Warning**\n\nThis project started as a weekend project to learn recent additions to the language (`typing.Annotated` and `typing.ParamSpec`, the [walrus operator](https://www.python.org/dev/peps/pep-0572/), [pattern matching](https://www.python.org/dev/peps/pep-0636/) and others). This means also that this package and its documentation should be considered as **work in progress**.\nYou probably shouldn\'t use it in production yet! But if you do, let me know how it went. Please leave a star if you like this project!\n\n## Application\n\nThe decorator has been mainly designed with [numpy arrays](https://numpy.org) and [pandas DataFrames](https://pandas.pydata.org/)\nin mind but can be universally applied.\nContracts are defined as lambda functions that are attached to the function arguments via the\n[new Annotated type](https://www.python.org/dev/peps/pep-0593/) that allows adding additional information\nto the arguments\' and return value\'s type hint. Arguments are inserted into the lambda via\n[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) and working with\nsymbols to increase readability is supported.\n\nLet\'s look at an example for for matrix multiplication!\n\n```python\nfrom typing import Annotated\nimport numpy as np\nfrom design_by_contract import contract\n\n@contract\ndef spam(\n    first: Annotated[np.ndarray, lambda first, m, n: (m, n) == first.shape], # symbols m and n represent the shape of `a`\n    second: Annotated[np.ndarray, lambda second, n, o: (n, o) == second.shape], # `b` number of columns matches the number of rows of `a`\n) -> Annotated[np.ndarray, lambda x, m, o: x.shape == (m, o)]: # `x` holds the return value. The shape of `x` must equal `x` times `o`\n    """Matrix multiplication"""\n    return a @ b\n```\n\nContracts are lamdbas with one argument named like the annotated argument. Alternatively, `x` can be used as a shortcut which means\nthat you cannot use `x` as a function argument unless you choose another reserved (using the `reserved` argument `contractor` decorator).\n\n```python\n@contract(reserved=\'y\')\ndef spam(\n    first: Annotated[np.ndarray, lambda y, m, n: (m, n) == y.shape],\n    second: Annotated[np.ndarray, lambda y, n, o: (n, o) == y.shape],\n) -> Annotated[np.ndarray, lambda y, m, o: y.shape == (m, o)]:\n    """Matrix multiplication"""\n    return a @ b\n```\n\nSymbolic  calculus is supported to certain degree to make your life easier. The symbols `m`, `n` and `o` are defined in a way\nthat\n\n$$ \\text spam: R^{m \\times x} \\times R^{n\\times o} \\rightarrow R^{m\\times o} $$\n\nNote however, that this package does **not** intend to be a symbolic calculus package and therefore, there are some strong limitations.\n\nPython does not allow for assignments (`=`) in a lambda expression and therefore,\nthe equality operator (`==`) is chosen to act a replacement. Unknown arguments are replaced under the hood by an instance of `UnresolvedSymbol`\nthat overload this operator. As a consequence, each symbol, therefore has to be first appear in an equality before it can be used *in a different* lambda expression!\n\nThe following example will raise an error for instance:\n\n```Python\n@contract\ndef spam(\n    a: Annotated[np.ndarray, lambda x, m, n: (m, n) == x.shape and m > 2], # you cannot "assign" and use `m` in the same lambda\n    #  Annotated[np.ndarray, lambda x, m, n: (m, n) == x.shape, lambda x, m:  m > 2] # this would work\n    b: Annotated[np.ndarray, lambda x, n, o: (n, o) == x.shape],\n) -> Annotated[np.ndarray, lambda x, m, o: x.shape == (m, o)]:\n    return a @ b\n\nspam(a, b) # raises: \'>\' not supported between instances of \'UnresolvedSymbol\' and \'int\'\n```\n\nThis design decision is arguably unclean but allows for elegant contract expressions and a very clean and compact implementation.\nDifferent approaches involving symbolic algebra packages like [sympy](https://www.sympy.org/en/index.html) or parsing a syntax trees were considered but turned out\nto be too complex to implement. The next best alternative is using a domain-specific language (DLS) as done in  the excellent\n[pycontracts](https://github.com/AndreaCensi/contracts) package, which\nactually inspired this project. By using python, calculus in the contract can be arbitrarily\ncomplex without the need for extending the DSL (i.e., including python functions):\n\n```python\n@contract\ndef spam(\n    a: Annotated[np.ndarray, lambda x, m, o: (m, o) == x.shape],\n    b: Annotated[np.ndarray, lambda x, n, o: (n, o) == x.shape],\n) -> Annotated[np.ndarray, lambda x, m,n,o: x.shape == (m+n, o)]:\n    print(np.vstack((a,b)).shape)\n    return np.vstack((a,b))\nspam(np.zeros((3, 2)), np.zeros(( 4, 2)))\n```\n\nThe decorator is also quite handy for being used with pandas data frames:\n\n```python\n@contract\ndef spam(a: Annotated[pd.DataFrame,\n                      lambda x, c: c == {\'C\',\'B\'}, # `x` or the argument name must be passed to the lambda\n                      lambda x, c: c.issubset(x.columns) # Remember, we need to use two lambdas here!\n                     ],\n         b: Annotated[pd.DataFrame,\n                      lambda x, c: c <= set(x.columns) # equivalent to `issubset` but more elegant\n                     ]\n        ) -> Annotated[pd.DataFrame,\n                       lambda x, c: c <= set(x.columns)]:\n    """Matrix multiplication"""\n    return pd.merge(a,b,on=[\'B\',\'C\'])\n\nspam(a, b)\n```\n\nNote that evaluation is not optimized. In production, you might consider disabling evaluation by passing\n`evaluate=False` as a parameter to the `contract` decorator.\n\n## Features\n\n* [x] Simple to used design by contract. Does not require you to learn a domain specific language necessary.\n  * [x] Uses python language features only. Some of them recently introduced (i.e., in Python 3.10)\n  * [x] Preconditions written as lambda functions\n  * [x] Additional symbols can be used to achieve compact contracts\n  * [x] [Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) based on argument names\n  * [x] Pre- and Postconditions\n* [x] Encourages static typing\n  * [x] Does not break your type checking & code completion (tested with [mypy](https://mypy.readthedocs.io/en/stable/) and [visual studio code](https://code.visualstudio.com/))\n  * [x] Uses annotations for defining conditions\n  * [ ] Optional dynamic type checking\n* [x] Preserves your docstrings (thanks to [`decorator`](https://github.com/micheles/decorator)).\n      Plays well with [Sphinx](https://www.sphinx-doc.org/en/master/)\n* [x] Small, clean (opinionated) code base\n  * [x] Implementation in a single file with ~100 lines of code!\n  * [x] Currently only one runtime dependency!\n  * [x] Documentation using [sphinx](https://www.sphinx-doc.org/en/master/), [myst](https://myst-parser.readthedocs.io/en/latest/index.html) and [sphinx book](https://sphinx-book-theme.readthedocs.io/en/stable/)\n  * [x] Tested with pytest\n  * [x] Type annotations\n  * [x] code formatted ([black](https://github.com/psf/black)), linted ([pylint](https://pylint.org/)). Linting with [mypy](http://www.mypy-lang.org/) does not support pattern matching yet.\n* [ ] Speed. Well.. maybe. I haven\'t tested it yet.\n\n## Why?\n\nI had the idea a while ago when reading about `typing.Annotated` in the release notes of Python 3.9.\nEventually, it turned out to be a nice, small Weekend project and a welcomed\nopportunity to experiment with novel features in Python 3.10.\nIn addition, it has been a good exercise to practice several aspects of modern and clean Python development and eventually\nmight serve as an example for new Python developers:\n\nIf you think it\'s cool, please leave a star. And who knows, it might actually be useful.\n\n## Related (active) projects\n\nIt appears that the related (still active) projects have significantly larger code bases\n(include parsers for a domain-specific language, automated testing, etc.) but also try to achieve\nadditional and wider goals (automated testing, pure functions, etc.). The main strength\nof this project, in my opinion, lies in its compact codebase and intuitiveness of the\ndependency injection.\n\n* [PyContracts](https://github.com/AndreaCensi/contracts).\n  Originally inspired this project. Although it requires a domain specific language, it supports implicitly defining variables for array shapes (see below). This package tries to achieve\n  a similar goal in pure Python but it requires a formal definition of variables.\n\n  ```python\n  @contract\n  @contract(a=\'list[ M ](type(x))\',\n            b=\'list[ N ](type(x))\',\n            returns=\'list[M+N](type(x))\')\n  def my_cat_equal(a, b):\n      \'\'\' Concatenate two lists together. \'\'\'\n      return a + b\n  ```\n\n* [icontract](https://github.com/Parquery/icontract) and [deal](https://github.com/life4/deal):\n  Rely on conditions defined as lambdas much like this Project. They don\'t use the `Annotated` syntax\n  and their codebases are significantly larger.\n\n## Contributions\n\nPull requests are welcome!\n\n## Changelog\n\n* v0.2 (2022-03-05): Simple symbolic support\n* v0.1.1 (2022-01-30): Better documentation\n* v0.1.0 (2022-01-29): Initial release\n\n## License\n\nMIT License, Copyright 2022 Stefan Ulbrich\n',
    'author': 'Stefan Ulbrich',
    'author_email': None,
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/StefanUlbrich/design-by-contract',
    'package_dir': package_dir,
    'py_modules': modules,
    'install_requires': install_requires,
    'python_requires': '>=3.10,<4.0',
}


setup(**setup_kwargs)
