# -*- 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.1.1',
    'description': 'Handy decorator to define contracts with dependency injection in Python 3.10 and above',
    'long_description': "# Welcome to `design-by-contract`\n\nHandy decorator to define contracts with\n[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection)\nin Python 3.10 and above\nwithout the need of a domain specific language. It helps following the\n[design by contract](https://en.wikipedia.org/wiki/Design_by_contract)\nparadigm.\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\nPossible use cases are asserting mutual columns in\n[data frames](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html),\nlimiting the value range or checking data types in its columns, checking the dimensions\nof arrays and tensors, and much more. Note that validation can only occur at runtime!\n\nThe first version has been developed in a single afternoon and therefore, this package and more\nimportantly, this documentation, are still **work in progress**.\nYou probably shouldn't use it in production yet! But if you do, let me know how it went.\n\nPlease leave a star if you like this project!\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] Scope variables can be defined to simplify definition of conditions\n  * [x] [Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) based on argument names\n  * [ ] Postconditions (planned)\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 (planned)\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  * [ ] Method to insert contracts to docstrings (planned). Probably using Jinja templates.\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* [ ] Speed. Well.. maybe it is fast, I haven't tested it yet\n\n## Usage\n\n### Installation\n\nThe package is available (or will be shortly) on\n[pypi](https://pypi.org/project/design-by-contract/). Install it with\n\n```sh\npip install design-by-contract\n```\n\nTo build the package from sources, you need [Poetry](https://python-poetry.org/).\n\nDesign-by-contract depends only on the [decorator](https://github.com/micheles/decorator)\npackage at runtime!\n\n### Dependency injection\n\nThe decorator in this package uses\n[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) make the definition of\ncontracts as simple and natural as possible. That means that identifiers used in *conditions* and\nmust match either argument or *contract variable* names.\n\n### Conditions\n\nUnlike the excellent\n[pycontracts](https://github.com/AndreaCensi/contracts) package,\nno domain specific language is required. Their definition requires Lambda expressions instead\nwhich arguments are filled by dependency injection.\nThis way of defining contracts is very powerful and easy to use.\n\nConditions are defined as lambdas so imagine\na function `spam(a: List[int],b: List[str])`, a condition that enforces the same length of both\narguments looks like:\n\n```python\nlambda a, b: len(a) == len(b)\n```\n\nNote that the arguments to the lambda have to match the arguments of `spam` in order to be injected.\nIf they cannot be resolved, then a `ValueError` will be raised.\n\nConditions are associated with arguments. Therefore, they have to be specified\ntogether with the type annotations. Since Python 3.10, this is supported with\n`typing.Annotated`:\n\n```python\n@contract\ndef spam(\n    a: List[int],\n    b: Annotated[List[str], lambda a, b: len(a) == len(b)]\n)\n```\n\n**Important:** The argument that is annotated has to appear in the lambda arguments in\norder to be recognized as a condition! Also, conditions should return a boolean value.\n\nCurrently, it is not possible to define conditions in the decorator itself. The `pre` and\n`post` identifiers are reserved for this purpose but are not supported yet.\n\n### Contract variables\n\nTo organize contracts and increase readability, contract variables that can be used in the\nconditions are supported. In above example, the contract variables `m` could be assigned to\n`len(a)` and then be used in the conditions. Contract variables are defined as\nkeyword arguments to the `contract` decorator:\n\n```python\n@contract(\n    m=lambda a: len(a),\n)\ndef spam(\n    a: Annotated[List[int], lambda a, m: m <= 5], # needs to contain the argument even if unused!\n    b: Annotated[List[str], lambda b, m: m == len(b)]\n)\n```\n\n### Complete working example\n\nConsider a function that accepts two\n[numpy arrays](https://numpy.org/doc/stable/reference/generated/numpy.array.html) as\nparameters but requires that both\nhave exactly the same numbers of rows. With this package, this can be achieved by the following\ncode.\n\n\n```python\nfrom typing import Annotated\nfrom design_by_contract import contract\n\n@contract(m=lambda a: a.shape[0])\ndef spam(\n    a: np.ndarray,\n    b: Annotated[np.ndarray, lambda b, m: b.shape == (m, 3)]\n) -> None: pass\n\narray1 = np.array([[4, 5, 6, 8]])\narray2 = np.array([[1, 2, 3]])\n\nspam(array1, array2) # or (arguments are resolved correctly)\nspam(a=array1,b=array2) # or\nspam(b=array2,a=array1) # but not\nspam(a=array2,b=array1) # raises ValueError\n```\n\nHere, the decorator is initialized with a contract variable definition of `m` . It holds the number\nof rows of the array `a`, the first argument of `spam`.\nThis is achieved by passing a `m` as a keyword argument with a lambda expression that takes a single\nargument named `a`. The lambda's argument(s) have to match argument names of `spam`. The contract decorator\nwill then inject the value of the argument `a` into the lambda expression when `spam` is eventually evaluated.\n\nThe arguments of `spam` can be annotated by using `typing.Annotated` if there is a condition for them.\n`Annotated` first requires a type definition. Any following lambda expression that contains the\nsame argument name (in this case, `b`) is interpreted as a contract. The lambdas should return a boolean value!\nNote that there can be multiple conditions in the same annotation.\n\nAll the expressions arguments must have the same name as either an argument of `spam`\nor a contract variable (i.e., `a`,`b` or `m`). Again, the respective values are injected by the decorator when the function is evaluated.\n\n## What's missing?\n\nCurrently, contracts for return types (i.e., post conditions) cannot be specified.\nThe identifier `post` is reserved already but using it throws a `NotImplementedError` for now.\nImplementation, however, is straight forward\n(I am accepting pull requests). Documentation can certainly be improved.\n\nIn the future, optional run-time type checking might be worth considering.\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 Saturday afternoon 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 Python development and eventually\nmight serve as an example for new Python developers:\n\n* [x] Recent python features: [`typing.Annotation`](https://docs.python.org/3/library/typing.html#typing.Annotated) (3.9),\n  [`typing.ParamSpec`](https://docs.python.org/3/library/typing.html#typing.ParamSpec) (3.10)\n  and [`typing.get_annotations()`](get_annotations)  (3.10)\n* [x] Clean decorator design with the [decorator](https://github.com/micheles/decorator) package\n* [x] Project management with [Poetry](https://python-poetry.org/)\n* [x] Clean code (opinionated), commented code, type annotations and unit tests ([pytest](https://docs.pytest.org/en/6.2.x/)). Open for criticism.\n* [x] Leveraging logging facilities\n* [x] Sensible exceptions\n* [x] Good documentation (ok, only half a check)\n* [ ] GitHub Actions\n* [ ] Sphinx documentation\n\nIf you think it's cool, please leave a star. And who knows, it might actually be useful.\n\n## Contributions\n\nPull requests are welcome!\n\n## Changelog\n\n* v0.2 (TBP): add Postconditions\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\n\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)
