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

packages = \
['paprika']

package_data = \
{'': ['*']}

install_requires = \
['tabulate>=0.8.9,<0.9.0']

setup_kwargs = {
    'name': 'paprika',
    'version': '1.3.0',
    'description': 'Paprika is a python library that reduces boilerplate. Heavily inspired by Project Lombok.',
    'long_description': '![A plate filled with paprika spice](static/paprika.jpg)\n_Image courtesy of Anna Quaglia (Photographer)_\n\n![GitHub Workflow Status](https://img.shields.io/github/workflow/status/rayanht/paprika/Test%20and%20collect%20coverage)\n[![PyPI license](https://img.shields.io/pypi/l/paprika.svg)](https://pypi.python.org/pypi/paprika/)\n[![Maintainability](https://api.codeclimate.com/v1/badges/eae8b5fab78e30cbab21/maintainability)](https://codeclimate.com/github/rayanht/paprika/maintainability)\n[![codecov](https://codecov.io/gh/rayanht/paprika/branch/main/graph/badge.svg?token=21FA3K95AM)](https://codecov.io/gh/rayanht/paprika)\n![PyPI](https://img.shields.io/pypi/v/paprika)\n[![Downloads](https://static.pepy.tech/personalized-badge/paprika?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads)](https://pepy.tech/project/paprika)\n\n# Paprika\n\nPaprika is a python library that reduces boilerplate. It is heavily inspired by\nProject Lombok.\n\n## Table of Contents\n- [Installation](#installation)\n- [Usage](#usage)\n- [Features & Examples](#features-and-examples)\n  - [Object-oriented decorators](#general-utility-decorators)\n    - [`@to_string`](#to_string)\n    - [`@equals_and_hashcode`](#equals_and_hashcode)\n    - [`@data`](#data)\n      - [On `@data` and NonNull](#on-data-and-nonnull)\n    - [`@singleton`](#singleton)\n      - [Important note on combining `@data` and `@singleton`](#important-note-on-combining-data-and-singleton)\n  - [General utility decorators](#general-utility-decorators)\n    - [`@threaded`](#threaded)\n    - [`@repeat`](#repeat)\n    - [`@pickled`](#pickled)\n  - [Benchmark decorators](#benchmark-decorators)\n    - [`@timeit`](#timeit)\n    - [`@access_counter`](#access_counter)\n    - [`@hotspots`](#hotspots)\n    - [`@profile`](#profile)\n  - [Error-handling decorators](#error-handling-decorators)\n    - [`@catch`](#catch)\n    - [`@silent_catch`](#silent_catch)\n- [Contributing](#contributing)\n- [Authors](#authors)\n- [License](#license)\n\n\n## Installation\n\npaprika is available on PyPi.\n\n```bash\n$ pip install paprika\n```\n\n## Usage\n\n`paprika` is a decorator-only library and all decorators are exposed at the\ntop-level of the module. If you want to use shorthand notation (i.e. `@data`),\nyou can import all decorators as follows:\n\n```python3\nfrom paprika import *\n```\n\nAlternatively, you can opt to use the longhand notation (i.e. `@paprika.data`)\nby importing `paprika` as follows:\n\n```python3\nimport paprika\n```\n\n## Features and Examples\n\n## Object-oriented decorators\n\n### @to_string\n\nThe `@to_string` decorator automatically overrides `__str__`\n\n#### Python\n\n```python3\nclass Person:\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n\n    def __str__(self):\n        return f"{self.__name__}@[name={self.name}, age={self.age}]"\n```\n\n#### Python with paprika\n\n```python3\n@to_string\nclass Person:\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n```\n\n----\n\n### @equals_and_hashcode\n\nThe `@equals_and_hashcode` decorator automatically overrides `__eq__`\nand `__hash__`\n\n#### Python\n\n```python3\nclass Person:\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n\n    def __eq__(self, other):\n        return (self.__class__ == other.__class__\n                and\n                self.__dict__ == other.__dict__)\n\n    def __hash__(self):\n        return hash((self.name, self.age))\n```\n\n#### Python with paprika\n\n```python3\n@equals_and_hashcode\nclass Person:\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n```\n\n---\n\n### @data\n\nThe `@data` decorator creates a dataclass by combining `@to_string`\nand `@equals_and_hashcode` and automatically creating a constructor!\n\n#### Python\n\n```python3\nclass Person:\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n\n    def __str__(self):\n        return f"{self.__name__}@[name={self.name}, age={self.age}]"\n\n    def __eq__(self, other):\n        return (self.__class__ == other.__class__\n                and\n                self.__dict__ == other.__dict__)\n\n    def __hash__(self):\n        return hash((self.name, self.age))\n```\n\n#### Python with paprika\n\n```python3\n@data\nclass Person:\n    name: str\n    age: int\n```\n\n#### On @data and NonNull\n\n`paprika` exposes a `NonNull` generic type that can be used in conjunction with\nthe `@data` decorator to enforce that certain arguments passed to the\nconstructor are not null. The following snippet will raise a `ValueError`:\n\n```python3\n@data\nclass Person:\n    name: NonNull[str]\n    age: int\n\np = Person(name=None, age=42)  # ValueError ❌\n```\n\n----\n\n### @singleton\n\nThe `@singleton` decorator can be used to enforce that a class only gets\ninstantiated once within the lifetime of a program. Any subsequent instantiation\nwill return the original instance.\n\n```python3\n@singleton\nclass Person:\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n\np1 = Person(name="Rayan", age=19)\np2 = Person()\nprint(p1 == p2 and p1 is p2)  # True ✅\n```\n\n`@singleton` can be seamlessly combined with `@data`!\n\n```python3\n@singleton\n@data\nclass Person:\n    name: str\n    age: int\n\np1 = Person(name="Rayan", age=19)\np2 = Person()\nprint(p1 == p2 and p1 is p2)  # True ✅\n```\n\n#### Important note on combining @data and @singleton\n\nWhen combining `@singleton` with `@data`, `@singleton` should come\nbefore `@data`. Combining them the other way around will work in most cases but\nis not thoroughly tested and relies on assumptions that _might_ not hold.\n\n## General utility decorators\n\n### @threaded\n\nThe `@threaded` decorator will run the decorated function in a thread by\nsubmitting it to a `ThreadPoolExecutor`. When the decorated function is called,\nit will immediately return a `Future` object. The result can be extracted by\ncalling `.result()` on that `Future`\n\n```python3\n@threaded\ndef waste_time(sleep_time):\n    thread_name = threading.current_thread().name\n    time.sleep(sleep_time)\n    print(f"{thread_name} woke up after {sleep_time}s!")\n    return 42\n\nt1 = waste_time(5)\nt2 = waste_time(2)\n\nprint(t1)           # <Future at 0x104130a90 state=running>\nprint(t1.result())  # 42\n```\n\n```\nThreadPoolExecutor-0_1 woke up after 2s!\nThreadPoolExecutor-0_0 woke up after 5s!\n```\n\n---\n\n### @repeat\n\nThe `@repeat` decorator will run the decorated function consecutively, as many\ntimes as specified.\n\n```python3\n@repeat(n=5)\ndef hello_world():\n    print("Hello world!")\n\nhello_world()\n```\n\n```\nHello world!\nHello world!\nHello world!\nHello world!\nHello world!\n```\n\n### @pickled\nThe `@pickled` decorator adds `__dump__` and `__load__` to a class for pickling convenience.\n\n`__dump__` and `__load__` take in the target and source pickle file paths respectively.\n\nThis decorator takes in an optional `protocol` argument (e.g. `@pickled(protocol=5)`) specifiying the [pickle protocol](https://docs.python.org/3/library/pickle.html#data-stream-format).\n\n#### Python\n\n```python3\nclass Person:\n    def __init__(self, name: str):\n        self.name = name\n\n    def __dump__(self, file_path):\n        with open(file_path, "wb") as f:\n            pickle_dump(self, f, protocol=5)\n\n    @staticmethod\n    def __load__(file_path):\n        with open(file_path, "rb") as f:\n            return pickle.load(f)\n```\n\n#### Python with paprika\n\n```python3\n@data\n@pickled(protocol=5)\nclass Person:\n    name: str\n```\n\n\n## Benchmark decorators\n\n### timeit\n\nThe `@timeit` decorator times the total execution time of the decorated\nfunction. It uses a `timer::perf_timer` by default but that can be replaced by\nany object of type `Callable[None, int]`.\n\n```python3\ndef time_waster1():\n    time.sleep(2)\n\ndef time_waster2():\n    time.sleep(5)\n\n@timeit\ndef test_timeit():\n    time_waster1()\n    time_waster2()\n```\n\n```python\ntest_timeit executed in 7.002189894999999 seconds\n```\n\nHere\'s how you can replace the default timer:\n\n```python\n@timeit(timer: lambda: 0) # Or something actually useful like time.time()\ndef test_timeit():\n    time_waster1()\n    time_waster2()\n```\n\n```python\ntest_timeit executed in 0 seconds\n```\n\n---\n\n### @access_counter\n\nThe `@access_counter` displays a summary of how many times each of the\nstructures that are passed to the decorated function are accessed\n(number of reads and number of writes).\n\n```python3\n@access_counter\ndef test_access_counter(list, dict, person, tuple):\n    for i in range(500):\n        list[0] = dict["key"]\n        dict["key"] = person.age\n        person.age = tuple[0]\n\n\ntest_access_counter([1, 2, 3, 4, 5], {"key": 0}, Person(name="Rayan", age=19),\n                    (0, 0))\n```\n\n```\ndata access summary for function: test\n+------------+----------+-----------+\n| Arg Name   |   nReads |   nWrites |\n+============+==========+===========+\n| list       |        0 |       500 |\n+------------+----------+-----------+\n| dict       |      500 |       500 |\n+------------+----------+-----------+\n| person     |      500 |       500 |\n+------------+----------+-----------+\n| tuple      |      500 |         0 |\n+------------+----------+-----------+\n```\n\n___\n\n### @hotspots\n\nThe `@hotspots` automatically runs `cProfiler` on the decorated function and\ndisplay the `top_n` (default = 10) most expensive function calls sorted by\ncumulative time taken (this metric will be customisable in the future). The\nsample error can be reduced by using a higher `n_runs` (default = 1) parameter.\n\n```python3\ndef time_waster1():\n    time.sleep(2)\n\ndef time_waster2():\n    time.sleep(5)\n\n@hotspots(top_n=5, n_runs=2)  # You can also do just @hotspots\ndef test_hotspots():\n    time_waster1()\n    time_waster2()\n\ntest_hotspots()\n```\n\n```\n   11 function calls in 14.007 seconds\n\n   Ordered by: cumulative time\n\n   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n        2    0.000    0.000   14.007    7.004 main.py:27(test_hot)\n        4   14.007    3.502   14.007    3.502 {built-in method time.sleep}\n        2    0.000    0.000   10.004    5.002 main.py:23(time_waster2)\n        2    0.000    0.000    4.003    2.002 main.py:19(time_waster1)\n        1    0.000    0.000    0.000    0.000 {method \'disable\' of \'_lsprof.Profiler\' objects}\n```\n\n---\n\n### @profile\n\nThe `@profile` decorator is simply syntatic sugar that allows to perform both\nhotspot analysis and data access analysis. Under the hood, it simply\nuses `@access_counter` followed by `@hotspots`.\n\n## Error-handling decorators\n\n### @catch\n\nThe `@catch` decorator can be used to wrap a function inside a `try/catch`\nblock. `@catch` expects to receive in the `exceptions` argument at least one\nexception that we want to catch.\n\nIf no exception is provided, `@catch` will by default catch _all_ exceptions (\nexcluding `SystemExit`, `KeyboardInterrupt`\nand `GeneratorExit` since they do not subclass the generic `Exception` class).\n\n`@catch` can take a custom exception handler as a parameter. If no handler is\nsupplied, a stack trace is logged to `stderr` and the program will continue\nexecuting.\n\n```python\n@catch(exception=ValueError)\ndef test_catch1():\n    raise ValueError\n\n@catch(exception=[EOFError, KeyError])\ndef test_catch2():\n    raise ValueError\n\ntest_catch1()\nprint("Still alive!")  # This should get printed since we\'re catching the ValueError.\n\ntest_catch2()\nprint("Still alive?")  # This will not get printed since we\'re not catching ValueError in this case.\n```\n\n```\nTraceback (most recent call last):\n  File "/Users/rayan/Desktop/paprika/paprika/__init__.py", line 292, in wrapper_catch\n    return func(*args, **kwargs)\n  File "/Users/rayan/Desktop/paprika/main.py", line 29, in test_exception1\n    raise ValueError\nValueError\n\nStill alive!\n\nTraceback (most recent call last):\n  File "/Users/rayan/Desktop/paprika/main.py", line 40, in <module>\n    test_exception2()\n  File "/Users/rayan/Desktop/paprika/paprika/__init__.py", line 292, in wrapper_catch\n    return func(*args, **kwargs)\n  File "/Users/rayan/Desktop/paprika/main.py", line 37, in test_exception2\n    raise ValueError\nValueError\n```\n\n#### Using a custom exception handler\n\nIf provided, a custom exception handler must be of\ntype `Callable[Exception, Generic[T]]`. In other words, its signature must take\none parameter of type Exception.\n\n```python\n@catch(exception=ValueError,\n       handler=lambda x: print(f"Ohno, a {repr(x)} was raised!"))\ndef test_custom_handler():\n    raise ValueError\n\ntest_custom_handler()\n```\n\n```\nOhno, a ValueError() was raised!\n```\n\n---\n\n### @silent_catch\n\nThe `@silent_catch` decorator is very similar to the `@catch` decorator in its\nusage. It takes one or more exceptions but then simply catches them silently.\n\n```python\n@silent_catch(exception=[ValueError, TypeError])\ndef test_silent_catch():\n    raise TypeError\n\ntest_silent_catch()\nprint("Still alive!")\n```\n\n```\nStill alive!\n```\n\n## Contributing\n\n### Issues\n\nEncountered a bug? Have an idea for a new feature? This project is open to all\nsorts of contribution! Feel free to head to the `Issues` tab and describe your\nrequest!\n\n### Development Setup\n\nThis project requires [poetry](https://github.com/python-poetry/poetry).\n\n#### Recommended Steps\n1. Initialize a virtual environment: `python -m venv .env`\n2. Enter your virtual environment.\n3. Install poetry: `pip install poetry`.\n4. Install dependencies: `poetry install`.\n5. Initialize pre-commit: `pre-commit install`.\n\n## Authors\n\n* **Rayan Hatout** - [GitHub](https://github.com/rayanht)\n  | [Twitter](https://twitter.com/rayanhtt)\n  | [LinkedIn](https://www.linkedin.com/in/rayan-hatout/)\n\nSee also the list of [contributors](https://github.com/rayanht/paprika) who\nparticipated in this project.\n\n## License\n\nThis project is licensed under the MIT License - see\nthe [LICENSE](LICENSE) file for details\n',
    'author': 'Rayan Hatout',
    'author_email': 'rayan.hatout@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/rayanht/paprika',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
