├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── docs ├── Makefile ├── caching.rst ├── concurrency.rst ├── conf.py ├── debugging.rst ├── development_tools.rst ├── error_handling.rst ├── index.rst ├── make.bat ├── performance.rst └── type_safety.rst ├── poetry.lock ├── pyproject.toml ├── python_wrap ├── __init__.py └── decorators.py └── tests └── test_decorators.py /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.8' 18 | 19 | - name: Upgrade pip and setuptools 20 | run: | 21 | python -m pip install --upgrade pip setuptools 22 | 23 | - name: Install pipx 24 | run: | 25 | python -m pip install --upgrade pipx 26 | 27 | - name: Install Poetry using pipx 28 | run: | 29 | pipx install poetry 30 | 31 | - name: Configure Poetry 32 | run: | 33 | poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} 34 | 35 | - name: Regenerate lock file if needed 36 | run: | 37 | set -e 38 | if poetry check; then 39 | echo "Dependencies are consistent" 40 | else 41 | echo "Regenerating lock file..." 42 | poetry lock 43 | fi 44 | 45 | - name: Install dependencies 46 | run: poetry install --only main 47 | 48 | - name: Install dependencies (including dev) 49 | run: poetry install --with dev 50 | 51 | - name: Run tests 52 | run: poetry run pytest 53 | 54 | - name: Build and publish 55 | env: 56 | POETRY_HTTP_BASIC_PYPI_USERNAME: __token__ 57 | POETRY_HTTP_BASIC_PYPI_PASSWORD: ${{ secrets.PYPI_TOKEN }} 58 | run: | 59 | poetry build 60 | poetry publish 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # Ruff stuff: 171 | .ruff_cache/ 172 | 173 | # PyPI configuration file 174 | .pypirc -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version, and other tools you might need 8 | build: 9 | os: ubuntu-24.04 10 | tools: 11 | python: "3.13" 12 | 13 | # Build documentation in the "docs/" directory with Sphinx 14 | sphinx: 15 | configuration: docs/conf.py 16 | 17 | # Optionally, but recommended, 18 | # declare the Python requirements required to build your documentation 19 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 PythonWrap Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PythonWrap 2 | 3 | A comprehensive collection of Python decorators and utility functions to enhance your development workflow. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | pip install python-wrap 9 | ``` 10 | 11 | ## Features 12 | 13 | PythonWrap provides a rich set of decorators that can be used either as decorators or as standalone functions: 14 | 15 | ### Performance & Profiling 16 | - `@timing`: Measure function execution time 17 | - `@profile`: Profile function performance 18 | - `@benchmark`: Compare execution times 19 | - `@measure_memory`: Track memory usage 20 | 21 | ### Error Handling & Reliability 22 | - `@retry`: Retry failed operations 23 | - `@retry_on_exception`: Retry on specific exceptions 24 | - `@timeout`: Set execution time limits 25 | - `@revert_on_failure`: Automatic state rollback on failure 26 | 27 | ### Caching & Optimization 28 | - `@memoize`: Cache function results 29 | - `@cache`: Time-based result caching 30 | - `@once`: Single execution guarantee 31 | 32 | ### Debugging & Logging 33 | - `@log_args`: Log function arguments 34 | - `@log_return`: Log return values 35 | - `@trace`: Log call stack traces 36 | - `@audit`: Comprehensive function call auditing 37 | 38 | ### Type Safety & Validation 39 | - `@check_type`: Runtime type checking 40 | - `@validate_args`: Custom argument validation 41 | 42 | ### Development Tools 43 | - `@deprecate`: Mark deprecated functions 44 | - `@no_debug`: Disable debug output 45 | - `@mock_data`: Easy data mocking 46 | - `@rate_limit`: Control execution frequency 47 | 48 | ### Concurrency 49 | - `@run_in_thread`: Asynchronous execution 50 | - `@transactional`: Atomic operations 51 | 52 | ## Usage Examples 53 | 54 | ### Using as Decorators 55 | 56 | ```python 57 | from python_wrap import timing, retry, memoize 58 | 59 | # As a decorator 60 | @timing 61 | def slow_operation(): 62 | time.sleep(1) 63 | return "Done" 64 | 65 | # As a decorator with parameters 66 | @retry(max_attempts=3, delay=1.0) 67 | def unreliable_operation(): 68 | return api_call() 69 | 70 | # Simple decorator 71 | @memoize 72 | def fibonacci(n): 73 | if n < 2: 74 | return n 75 | return fibonacci(n-1) + fibonacci(n-2) 76 | ``` 77 | 78 | ### Using as Functions 79 | 80 | ```python 81 | from python_wrap import timing, retry, memoize 82 | 83 | # Using timing as a function 84 | def slow_operation(): 85 | time.sleep(1) 86 | return "Done" 87 | 88 | result = timing(slow_operation)() 89 | 90 | # Using retry as a function 91 | def unreliable_operation(): 92 | return api_call() 93 | 94 | result = retry(unreliable_operation, max_attempts=3, delay=1.0)() 95 | 96 | # Using memoize as a function 97 | def fibonacci(n): 98 | if n < 2: 99 | return n 100 | return fibonacci(n-1) + fibonacci(n-2) 101 | 102 | memoized_fib = memoize(fibonacci) 103 | result = memoized_fib(10) 104 | ``` 105 | 106 | ### Advanced Usage 107 | 108 | ```python 109 | from python_wrap import validate_args, check_type, timeout 110 | 111 | # As decorators 112 | @validate_args(x=lambda x: x > 0, y=lambda y: y < 100) 113 | def process_numbers(x, y): 114 | return x + y 115 | 116 | @check_type(name=str, age=int) 117 | def create_user(name, age): 118 | return {"name": name, "age": age} 119 | 120 | # As functions 121 | def long_running_task(): 122 | process_large_dataset() 123 | 124 | timed_task = timeout(5)(long_running_task) 125 | result = timed_task() 126 | ``` 127 | 128 | ### Combining Multiple Functions 129 | 130 | ```python 131 | from python_wrap import timing, retry, log_args 132 | 133 | # As decorators 134 | @timing 135 | @retry(max_attempts=3) 136 | @log_args 137 | def complex_operation(x, y): 138 | return expensive_calculation(x, y) 139 | 140 | # As functions 141 | def complex_operation(x, y): 142 | return expensive_calculation(x, y) 143 | 144 | # Compose functions 145 | monitored_op = timing(retry(log_args(complex_operation), max_attempts=3)) 146 | result = monitored_op(1, 2) 147 | ``` 148 | 149 | ## Contributing 150 | 151 | Contributions are welcome! Please feel free to submit a Pull Request. 152 | 153 | ## License 154 | 155 | This project is licensed under the MIT License - see the LICENSE file for details. -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/caching.rst: -------------------------------------------------------------------------------- 1 | Caching & Optimization 2 | ====================== 3 | 4 | This section covers tools for caching and optimizing function execution. 5 | 6 | @memoize 7 | -------- 8 | Cache the results of a function. 9 | 10 | **Parameters:** 11 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 12 | 13 | **Usage as a Decorator:** 14 | 15 | .. code-block:: python 16 | 17 | @memoize 18 | def expensive_calculation(x, y): 19 | # Perform a costly operation 20 | return x * y 21 | 22 | **Usage as a Function:** 23 | 24 | .. code-block:: python 25 | 26 | def expensive_calculation(x, y): 27 | # Perform a costly operation 28 | return x * y 29 | 30 | memoized_function = memoize(expensive_calculation) 31 | result = memoized_function(5, 10) # Caches the result for (5, 10) 32 | 33 | @cache 34 | ------ 35 | Cache the results of a function for a specified duration. 36 | 37 | **Parameters:** 38 | - **duration (timedelta):** The time duration for which the cached result is valid. 39 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 40 | 41 | **Usage as a Decorator:** 42 | 43 | .. code-block:: python 44 | 45 | from datetime import timedelta 46 | 47 | @cache(duration=timedelta(minutes=5)) 48 | def fetch_data(): 49 | # Retrieve data from an API or database 50 | return {"key": "value"} 51 | 52 | **Usage as a Function:** 53 | 54 | .. code-block:: python 55 | 56 | from datetime import timedelta 57 | 58 | def fetch_data(): 59 | # Retrieve data from an API or database 60 | return {"key": "value"} 61 | 62 | cached_function = cache(duration=timedelta(minutes=5))(fetch_data) 63 | result = cached_function() # Caches the result for 5 minutes 64 | 65 | @once 66 | ----- 67 | Ensure a function is executed only once. 68 | 69 | **Parameters:** 70 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 71 | 72 | **Usage as a Decorator:** 73 | 74 | .. code-block:: python 75 | 76 | @once 77 | def initialize_service(): 78 | # Perform a one-time initialization 79 | print("Service initialized") 80 | 81 | **Usage as a Function:** 82 | 83 | .. code-block:: python 84 | 85 | def initialize_service(): 86 | # Perform a one-time initialization 87 | print("Service initialized") 88 | 89 | one_time_function = once(initialize_service) 90 | one_time_function() # Executes the function 91 | one_time_function() # Does nothing, as the function has already been executed -------------------------------------------------------------------------------- /docs/concurrency.rst: -------------------------------------------------------------------------------- 1 | Concurrency 2 | =========== 3 | 4 | This section covers tools for managing concurrency. 5 | 6 | @run_in_thread 7 | -------------- 8 | Run a function asynchronously in a separate thread. 9 | 10 | **Parameters:** 11 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 12 | 13 | **Usage as a Decorator:** 14 | 15 | .. code-block:: python 16 | 17 | @run_in_thread 18 | def background_task(): 19 | print("This task is running in a separate thread.") 20 | 21 | **Usage as a Function:** 22 | 23 | .. code-block:: python 24 | 25 | def background_task(): 26 | print("This task is running in a separate thread.") 27 | 28 | threaded_function = run_in_thread(background_task) 29 | threaded_function() # Runs in a separate thread 30 | 31 | @transactional 32 | -------------- 33 | Ensure atomic operations within a transaction. 34 | 35 | **Parameters:** 36 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 37 | 38 | **Usage as a Decorator:** 39 | 40 | .. code-block:: python 41 | 42 | @transactional 43 | def update_user_balance(user_id, amount): 44 | print(f"Updating user {user_id} balance by {amount}.") 45 | 46 | **Usage as a Function:** 47 | 48 | .. code-block:: python 49 | 50 | def update_user_balance(user_id, amount): 51 | print(f"Updating user {user_id} balance by {amount}.") 52 | 53 | transactional_function = transactional(update_user_balance) 54 | transactional_function(42, 100) # Ensures atomic execution -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'PythonWrap' 10 | copyright = '2025, Mahmoud Ahmed' 11 | author = 'Mahmoud Ahmed' 12 | release = '0.1.0' 13 | 14 | # -- General configuration --------------------------------------------------- 15 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 16 | 17 | extensions = [] 18 | 19 | templates_path = ['_templates'] 20 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 21 | 22 | 23 | 24 | # -- Options for HTML output ------------------------------------------------- 25 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 26 | 27 | html_theme = 'alabaster' 28 | html_static_path = ['_static'] 29 | -------------------------------------------------------------------------------- /docs/debugging.rst: -------------------------------------------------------------------------------- 1 | Debugging & Logging 2 | =================== 3 | 4 | This section covers tools for debugging and logging function execution. 5 | 6 | @log_args 7 | --------- 8 | Log the arguments passed to a function. 9 | 10 | **Parameters:** 11 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 12 | 13 | **Usage as a Decorator:** 14 | 15 | .. code-block:: python 16 | 17 | @log_args 18 | def calculate_sum(a, b): 19 | return a + b 20 | 21 | **Usage as a Function:** 22 | 23 | .. code-block:: python 24 | 25 | def calculate_sum(a, b): 26 | return a + b 27 | 28 | logged_function = log_args(calculate_sum) 29 | result = logged_function(10, 20) # Logs arguments: a=10, b=20 30 | 31 | @log_return 32 | ----------- 33 | Log the return value of a function. 34 | 35 | **Parameters:** 36 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 37 | 38 | **Usage as a Decorator:** 39 | 40 | .. code-block:: python 41 | 42 | @log_return 43 | def fetch_data(): 44 | return {"key": "value"} 45 | 46 | **Usage as a Function:** 47 | 48 | .. code-block:: python 49 | 50 | def fetch_data(): 51 | return {"key": "value"} 52 | 53 | logged_function = log_return(fetch_data) 54 | result = logged_function() # Logs return value: {"key": "value"} 55 | 56 | @trace 57 | ------ 58 | Log the call stack trace of a function. 59 | 60 | **Parameters:** 61 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 62 | 63 | **Usage as a Decorator:** 64 | 65 | .. code-block:: python 66 | 67 | @trace 68 | def perform_task(): 69 | # Perform a task 70 | print("Task performed") 71 | 72 | **Usage as a Function:** 73 | 74 | .. code-block:: python 75 | 76 | def perform_task(): 77 | # Perform a task 78 | print("Task performed") 79 | 80 | traced_function = trace(perform_task) 81 | traced_function() # Logs the call stack trace 82 | 83 | @audit 84 | ------ 85 | Provide comprehensive auditing for function calls. 86 | 87 | **Parameters:** 88 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 89 | 90 | **Usage as a Decorator:** 91 | 92 | .. code-block:: python 93 | 94 | @audit 95 | def process_data(data): 96 | # Process the input data 97 | return len(data) 98 | 99 | **Usage as a Function:** 100 | 101 | .. code-block:: python 102 | 103 | def process_data(data): 104 | # Process the input data 105 | return len(data) 106 | 107 | audited_function = audit(process_data) 108 | result = audited_function(["item1", "item2"]) # Logs arguments, return value, and execution details -------------------------------------------------------------------------------- /docs/development_tools.rst: -------------------------------------------------------------------------------- 1 | Development Tools 2 | ================= 3 | 4 | This section covers tools for development and testing. 5 | 6 | @deprecate 7 | ---------- 8 | Mark a function as deprecated. 9 | 10 | **Parameters:** 11 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 12 | 13 | **Usage as a Decorator:** 14 | 15 | .. code-block:: python 16 | 17 | @deprecate 18 | def old_function(): 19 | print("This function is deprecated.") 20 | 21 | **Usage as a Function:** 22 | 23 | .. code-block:: python 24 | 25 | def old_function(): 26 | print("This function is deprecated.") 27 | 28 | deprecated_function = deprecate(old_function) 29 | deprecated_function() # Outputs a deprecation warning 30 | 31 | @no_debug 32 | --------- 33 | Disable debug output for a function. 34 | 35 | **Parameters:** 36 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 37 | 38 | **Usage as a Decorator:** 39 | 40 | .. code-block:: python 41 | 42 | @no_debug 43 | def sensitive_function(): 44 | print("This is a production-safe function.") 45 | 46 | **Usage as a Function:** 47 | 48 | .. code-block:: python 49 | 50 | def sensitive_function(): 51 | print("This is a production-safe function.") 52 | 53 | no_debug_function = no_debug(sensitive_function) 54 | no_debug_function() # Executes without debug output 55 | 56 | @mock_data 57 | ---------- 58 | Mock the output of a function for testing. 59 | 60 | **Parameters:** 61 | - **data (Any):** The data to return when the function is called. Can be a value or a callable. 62 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 63 | 64 | **Usage as a Decorator:** 65 | 66 | .. code-block:: python 67 | 68 | @mock_data(return_value={"id": 1, "name": "Mock User"}) 69 | def get_user_data(user_id): 70 | pass 71 | 72 | **Usage as a Function:** 73 | 74 | .. code-block:: python 75 | 76 | def get_user_data(user_id): 77 | pass 78 | 79 | mocked_function = mock_data(return_value={"id": 1, "name": "Mock User"})(get_user_data) 80 | result = mocked_function(42) # Returns {"id": 1, "name": "Mock User"} 81 | 82 | @rate_limit 83 | ----------- 84 | Limit the frequency of function execution. 85 | 86 | **Parameters:** 87 | - **calls (int):** The maximum number of calls allowed within the specified period. 88 | - **period (float):** The time period (in seconds) for rate limiting. 89 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 90 | 91 | **Usage as a Decorator:** 92 | 93 | .. code-block:: python 94 | 95 | @rate_limit(calls=5, period=10) 96 | def api_request(): 97 | print("API request made") 98 | 99 | **Usage as a Function:** 100 | 101 | .. code-block:: python 102 | 103 | def api_request(): 104 | print("API request made") 105 | 106 | rate_limited_function = rate_limit(calls=5, period=10)(api_request) 107 | rate_limited_function() # Enforces rate limiting -------------------------------------------------------------------------------- /docs/error_handling.rst: -------------------------------------------------------------------------------- 1 | Error Handling & Reliability 2 | ============================ 3 | 4 | This section covers tools for improving the reliability of your functions. 5 | 6 | @retry 7 | ------ 8 | Retry a function if it fails. 9 | 10 | **Parameters:** 11 | - **max_attempts (int):** The maximum number of retry attempts. Default is ``3``. 12 | - **delay (float):** The delay (in seconds) between retry attempts. Default is ``1.0``. 13 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 14 | 15 | **Usage as a Decorator:** 16 | 17 | .. code-block:: python 18 | 19 | @retry(max_attempts=3, delay=2.0) 20 | def unreliable_function(): 21 | # Code that may fail 22 | pass 23 | 24 | **Usage as a Function:** 25 | 26 | .. code-block:: python 27 | 28 | def unreliable_function(): 29 | # Code that may fail 30 | pass 31 | 32 | retried_function = retry(max_attempts=3, delay=2.0)(unreliable_function) 33 | retried_function() # Retries up to 3 times with a 2-second delay 34 | 35 | @retry_on_exception 36 | ------------------- 37 | Retry a function only when specific exceptions are raised. 38 | 39 | **Parameters:** 40 | - **exceptions (tuple):** One or more exception types that trigger a retry. 41 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 42 | 43 | **Usage as a Decorator:** 44 | 45 | .. code-block:: python 46 | 47 | @retry_on_exception(ValueError, KeyError) 48 | def operation_with_exceptions(): 49 | # Code that may raise exceptions 50 | pass 51 | 52 | **Usage as a Function:** 53 | 54 | .. code-block:: python 55 | 56 | def operation_with_exceptions(): 57 | # Code that may raise exceptions 58 | pass 59 | 60 | retried_function = retry_on_exception(ValueError, KeyError)(operation_with_exceptions) 61 | retried_function() # Retries only on ValueError or KeyError 62 | 63 | @timeout 64 | -------- 65 | Limit the execution time of a function. 66 | 67 | **Parameters:** 68 | - **seconds (int):** The maximum execution time for the function. 69 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 70 | 71 | **Usage as a Decorator:** 72 | 73 | .. code-block:: python 74 | 75 | @timeout(seconds=5) 76 | def time_sensitive_task(): 77 | # Code that should finish within 5 seconds 78 | pass 79 | 80 | **Usage as a Function:** 81 | 82 | .. code-block:: python 83 | 84 | def time_sensitive_task(): 85 | # Code that should finish within 5 seconds 86 | pass 87 | 88 | timeout_function = timeout(seconds=5)(time_sensitive_task) 89 | timeout_function() # Raises a timeout exception if execution exceeds 5 seconds 90 | 91 | @revert_on_failure 92 | ------------------ 93 | Roll back the system state if a failure occurs. 94 | 95 | **Parameters:** 96 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 97 | 98 | **Usage as a Decorator:** 99 | 100 | .. code-block:: python 101 | 102 | @revert_on_failure 103 | def critical_operation(): 104 | # Code that needs rollback on failure 105 | pass 106 | 107 | **Usage as a Function:** 108 | 109 | .. code-block:: python 110 | 111 | def critical_operation(): 112 | # Code that needs rollback on failure 113 | pass 114 | 115 | revert_function = revert_on_failure(critical_operation) 116 | revert_function() # Rolls back state on failure -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. PythonWrap documentation master file, created by 2 | sphinx-quickstart on Tue Jan 21 20:45:34 2025. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Python-Wrap's documentation! 7 | ======================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | performance 14 | error_handling 15 | caching 16 | debugging 17 | type_safety 18 | development_tools 19 | concurrency 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/performance.rst: -------------------------------------------------------------------------------- 1 | Performance & Profiling 2 | ======================= 3 | 4 | This section covers tools for measuring and optimizing the performance of your functions. 5 | 6 | @timing 7 | ------- 8 | Measure the execution time of a function. 9 | 10 | **Parameters:** 11 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 12 | 13 | **Usage as a Decorator:** 14 | 15 | .. code-block:: python 16 | 17 | @timing 18 | def my_function(): 19 | # Code to measure 20 | pass 21 | 22 | **Usage as a Function:** 23 | 24 | .. code-block:: python 25 | 26 | def my_function(): 27 | # Code to measure 28 | pass 29 | 30 | timed_function = timing(my_function) 31 | timed_function() # Logs execution time 32 | 33 | @profile 34 | -------- 35 | Profile the performance of a function. 36 | 37 | **Parameters:** 38 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 39 | 40 | **Usage as a Decorator:** 41 | 42 | .. code-block:: python 43 | 44 | @profile 45 | def profile_function(): 46 | # Function code to profile 47 | pass 48 | 49 | **Usage as a Function:** 50 | 51 | .. code-block:: python 52 | 53 | def profile_function(): 54 | # Function code to profile 55 | pass 56 | 57 | profiled_function = profile(profile_function) 58 | profiled_function() # Generates a performance profile 59 | 60 | @benchmark 61 | ---------- 62 | Run a function multiple times and calculate the average execution time. 63 | 64 | **Parameters:** 65 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 66 | - **iterations (int):** The number of times to run the function. Default is ``10``. 67 | 68 | **Usage as a Decorator:** 69 | 70 | .. code-block:: python 71 | 72 | @benchmark 73 | def benchmark_function(): 74 | # Function code to benchmark 75 | pass 76 | 77 | **Usage as a Function:** 78 | 79 | .. code-block:: python 80 | 81 | def benchmark_function(): 82 | # Function code to benchmark 83 | pass 84 | 85 | benchmarked_function = benchmark(benchmark_function, iterations=5) 86 | benchmarked_function() # Calculates average execution time 87 | 88 | @measure_memory 89 | --------------- 90 | Track the memory usage of a function. 91 | 92 | **Parameters:** 93 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 94 | 95 | **Usage as a Decorator:** 96 | 97 | .. code-block:: python 98 | 99 | @measure_memory 100 | def memory_intensive_function(): 101 | # Function code that uses a lot of memory 102 | pass 103 | 104 | **Usage as a Function:** 105 | 106 | .. code-block:: python 107 | 108 | def memory_intensive_function(): 109 | # Function code that uses a lot of memory 110 | pass 111 | 112 | memory_measured_function = measure_memory(memory_intensive_function) 113 | memory_measured_function() # Logs memory usage -------------------------------------------------------------------------------- /docs/type_safety.rst: -------------------------------------------------------------------------------- 1 | Type Safety & Validation 2 | ======================== 3 | 4 | This section covers tools for runtime type checking and argument validation. 5 | 6 | @check_type 7 | ----------- 8 | Enable runtime type checking for function arguments and return values. 9 | 10 | **Parameters:** 11 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 12 | 13 | **Usage as a Decorator:** 14 | 15 | .. code-block:: python 16 | 17 | @check_type 18 | def add_numbers(a: int, b: int) -> int: 19 | return a + b 20 | 21 | **Usage as a Function:** 22 | 23 | .. code-block:: python 24 | 25 | def add_numbers(a: int, b: int) -> int: 26 | return a + b 27 | 28 | checked_function = check_type(add_numbers) 29 | result = checked_function(10, 20) # Ensures types are checked at runtime 30 | 31 | @validate_args 32 | -------------- 33 | Add custom validation logic for function arguments. 34 | 35 | **Parameters:** 36 | - **func (Callable):** The function to be wrapped. Automatically passed when used as a decorator. 37 | 38 | **Usage as a Decorator:** 39 | 40 | .. code-block:: python 41 | 42 | @validate_args 43 | def create_user(username: str, age: int): 44 | if age < 18: 45 | raise ValueError("Age must be 18 or above") 46 | print(f"User {username} created") 47 | 48 | **Usage as a Function:** 49 | 50 | .. code-block:: python 51 | 52 | def create_user(username: str, age: int): 53 | if age < 18: 54 | raise ValueError("Age must be 18 or above") 55 | print(f"User {username} created") 56 | 57 | validated_function = validate_args(create_user) 58 | validated_function("JohnDoe", 25) # Validates arguments before execution -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | optional = false 8 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 9 | files = [ 10 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 11 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 12 | ] 13 | 14 | [[package]] 15 | name = "coverage" 16 | version = "7.6.1" 17 | description = "Code coverage measurement for Python" 18 | optional = false 19 | python-versions = ">=3.8" 20 | files = [ 21 | {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, 22 | {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, 23 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, 24 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, 25 | {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, 26 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, 27 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, 28 | {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, 29 | {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, 30 | {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, 31 | {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, 32 | {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, 33 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, 34 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, 35 | {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, 36 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, 37 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, 38 | {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, 39 | {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, 40 | {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, 41 | {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, 42 | {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, 43 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, 44 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, 45 | {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, 46 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, 47 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, 48 | {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, 49 | {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, 50 | {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, 51 | {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, 52 | {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, 53 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, 54 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, 55 | {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, 56 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, 57 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, 58 | {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, 59 | {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, 60 | {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, 61 | {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, 62 | {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, 63 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, 64 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, 65 | {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, 66 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, 67 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, 68 | {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, 69 | {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, 70 | {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, 71 | {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, 72 | {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, 73 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, 74 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, 75 | {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, 76 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, 77 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, 78 | {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, 79 | {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, 80 | {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, 81 | {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, 82 | {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, 83 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, 84 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, 85 | {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, 86 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, 87 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, 88 | {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, 89 | {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, 90 | {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, 91 | {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, 92 | {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, 93 | ] 94 | 95 | [package.dependencies] 96 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 97 | 98 | [package.extras] 99 | toml = ["tomli"] 100 | 101 | [[package]] 102 | name = "exceptiongroup" 103 | version = "1.2.2" 104 | description = "Backport of PEP 654 (exception groups)" 105 | optional = false 106 | python-versions = ">=3.7" 107 | files = [ 108 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 109 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 110 | ] 111 | 112 | [package.extras] 113 | test = ["pytest (>=6)"] 114 | 115 | [[package]] 116 | name = "iniconfig" 117 | version = "2.0.0" 118 | description = "brain-dead simple config-ini parsing" 119 | optional = false 120 | python-versions = ">=3.7" 121 | files = [ 122 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 123 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 124 | ] 125 | 126 | [[package]] 127 | name = "packaging" 128 | version = "24.2" 129 | description = "Core utilities for Python packages" 130 | optional = false 131 | python-versions = ">=3.8" 132 | files = [ 133 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 134 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 135 | ] 136 | 137 | [[package]] 138 | name = "pluggy" 139 | version = "1.5.0" 140 | description = "plugin and hook calling mechanisms for python" 141 | optional = false 142 | python-versions = ">=3.8" 143 | files = [ 144 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 145 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 146 | ] 147 | 148 | [package.extras] 149 | dev = ["pre-commit", "tox"] 150 | testing = ["pytest", "pytest-benchmark"] 151 | 152 | [[package]] 153 | name = "pytest" 154 | version = "7.4.4" 155 | description = "pytest: simple powerful testing with Python" 156 | optional = false 157 | python-versions = ">=3.7" 158 | files = [ 159 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 160 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 161 | ] 162 | 163 | [package.dependencies] 164 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 165 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 166 | iniconfig = "*" 167 | packaging = "*" 168 | pluggy = ">=0.12,<2.0" 169 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 170 | 171 | [package.extras] 172 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 173 | 174 | [[package]] 175 | name = "pytest-cov" 176 | version = "4.1.0" 177 | description = "Pytest plugin for measuring coverage." 178 | optional = false 179 | python-versions = ">=3.7" 180 | files = [ 181 | {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, 182 | {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, 183 | ] 184 | 185 | [package.dependencies] 186 | coverage = {version = ">=5.2.1", extras = ["toml"]} 187 | pytest = ">=4.6" 188 | 189 | [package.extras] 190 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 191 | 192 | [[package]] 193 | name = "tomli" 194 | version = "2.2.1" 195 | description = "A lil' TOML parser" 196 | optional = false 197 | python-versions = ">=3.8" 198 | files = [ 199 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 200 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 201 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 202 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 203 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 204 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 205 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 206 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 207 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 208 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 209 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 210 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 211 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 212 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 213 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 214 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 215 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 216 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 217 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 218 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 219 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 220 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 221 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 222 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 223 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 224 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 225 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 226 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 227 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 228 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 229 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 230 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 231 | ] 232 | 233 | [[package]] 234 | name = "wrapt" 235 | version = "1.17.2" 236 | description = "Module for decorators, wrappers and monkey patching." 237 | optional = false 238 | python-versions = ">=3.8" 239 | files = [ 240 | {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, 241 | {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, 242 | {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, 243 | {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, 244 | {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, 245 | {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, 246 | {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, 247 | {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, 248 | {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, 249 | {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, 250 | {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, 251 | {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, 252 | {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, 253 | {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, 254 | {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, 255 | {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, 256 | {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, 257 | {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, 258 | {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, 259 | {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, 260 | {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, 261 | {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, 262 | {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, 263 | {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, 264 | {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, 265 | {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, 266 | {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, 267 | {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, 268 | {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, 269 | {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, 270 | {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, 271 | {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, 272 | {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, 273 | {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, 274 | {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, 275 | {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, 276 | {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, 277 | {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, 278 | {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, 279 | {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, 280 | {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, 281 | {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, 282 | {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, 283 | {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, 284 | {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, 285 | {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, 286 | {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, 287 | {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, 288 | {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, 289 | {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, 290 | {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, 291 | {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, 292 | {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, 293 | {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, 294 | {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, 295 | {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, 296 | {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, 297 | {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, 298 | {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, 299 | {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, 300 | {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, 301 | {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, 302 | {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, 303 | {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, 304 | {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, 305 | {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, 306 | {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, 307 | {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, 308 | {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, 309 | {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, 310 | {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, 311 | {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, 312 | {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, 313 | {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, 314 | {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, 315 | {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, 316 | {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, 317 | {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, 318 | {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, 319 | ] 320 | 321 | [metadata] 322 | lock-version = "2.0" 323 | python-versions = "^3.8" 324 | content-hash = "8cbe0a84999f2ad27d0d3fd420c6711c6e9c213f3ff59323826856614c8e427e" 325 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "python-wrap" 3 | version = "0.1.0" 4 | description = "A comprehensive collection of Python decorators and utility functions for enhanced development workflows" 5 | authors = ["Mahmoud Ahmed "] 6 | maintainers = ["Mahmoud Ahmed "] 7 | license = "MIT" 8 | readme = "README.md" 9 | homepage = "https://github.com/Pythondeveloper6/PythonWrap" 10 | repository = "https://github.com/Pythondeveloper6/PythonWrap" 11 | documentation = "https://python-wrap.readthedocs.io" 12 | keywords = ["decorators", "utilities", "python", "development", "tools","python"] 13 | classifiers = [ 14 | "Development Status :: 4 - Beta", 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | "Topic :: Software Development :: Libraries :: Application Frameworks", 25 | "Typing :: Typed" 26 | ] 27 | packages = [{include = "python_wrap"}] 28 | 29 | [tool.poetry.dependencies] 30 | python = "^3.8" 31 | wrapt = "^1.15.0" 32 | typing-extensions = "^4.7.1" # For better type hints support 33 | 34 | [tool.poetry.group.dev.dependencies] 35 | pytest = "^7.4.0" 36 | pytest-cov = "^4.1.0" 37 | black = "^23.7.0" # Code formatting 38 | isort = "^5.12.0" # Import sorting 39 | mypy = "^1.4.1" # Type checking 40 | pylint = "^2.17.5" # Code linting 41 | pre-commit = "^3.3.3" # Git hooks 42 | sphinx = "^7.1.1" # Documentation 43 | sphinx-rtd-theme = "^1.2.2" # Documentation theme 44 | 45 | [tool.poetry.urls] 46 | "Bug Tracker" = "https://github.com/Pythondeveloper6/PythonWrap/issues" 47 | "Changelog" = "https://github.com/Pythondeveloper6/PythonWrap/blob/main/CHANGELOG.md" 48 | 49 | [build-system] 50 | requires = ["poetry-core"] 51 | build-backend = "poetry.core.masonry.api" 52 | 53 | [tool.black] 54 | line-length = 88 55 | target-version = ["py38"] 56 | include = '\.pyi?$' 57 | 58 | [tool.isort] 59 | profile = "black" 60 | multi_line_output = 3 61 | line_length = 88 62 | 63 | [tool.mypy] 64 | python_version = "3.8" 65 | warn_return_any = true 66 | warn_unused_configs = true 67 | disallow_untyped_defs = true 68 | check_untyped_defs = true 69 | 70 | [tool.pytest.ini_options] 71 | minversion = "7.0" 72 | addopts = "-ra -q --cov=python_wrap --cov-report=term-missing" 73 | testpaths = ["tests"] 74 | 75 | [tool.coverage.run] 76 | source = ["python_wrap"] 77 | branch = true 78 | 79 | [tool.coverage.report] 80 | exclude_lines = [ 81 | "pragma: no cover", 82 | "def __repr__", 83 | "if self.debug:", 84 | "raise NotImplementedError", 85 | "if __name__ == .__main__.:", 86 | "pass", 87 | "raise ImportError" 88 | ] 89 | ignore_errors = true 90 | omit = ["tests/*", "setup.py"] -------------------------------------------------------------------------------- /python_wrap/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | PythonWrap - A comprehensive collection of Python decorators and utility functions. 3 | """ 4 | 5 | from .decorators import ( 6 | timing, 7 | retry, 8 | memoize, 9 | log_args, 10 | log_return, 11 | validate_args, 12 | cache, 13 | once, 14 | deprecate, 15 | check_type, 16 | retry_on_exception, 17 | measure_memory, 18 | profile, 19 | rate_limit, 20 | mock_data, 21 | benchmark, 22 | run_in_thread, 23 | no_debug, 24 | transactional, 25 | revert_on_failure, 26 | audit, 27 | trace, 28 | timeout, 29 | ) 30 | 31 | __version__ = "0.1.0" 32 | __all__ = [ 33 | "timing", 34 | "retry", 35 | "memoize", 36 | "log_args", 37 | "log_return", 38 | "validate_args", 39 | "cache", 40 | "once", 41 | "deprecate", 42 | "check_type", 43 | "retry_on_exception", 44 | "measure_memory", 45 | "profile", 46 | "rate_limit", 47 | "mock_data", 48 | "benchmark", 49 | "run_in_thread", 50 | "no_debug", 51 | "transactional", 52 | "revert_on_failure", 53 | "audit", 54 | "trace", 55 | "timeout", 56 | ] -------------------------------------------------------------------------------- /python_wrap/decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core decorators module containing all decorator implementations. 3 | """ 4 | 5 | import time 6 | import logging 7 | import functools 8 | import threading 9 | import warnings 10 | import inspect 11 | import traceback 12 | from typing import Any, Callable, Dict, List, Type, Union 13 | from datetime import datetime, timedelta 14 | import threading 15 | import queue 16 | import signal 17 | import resource 18 | import cProfile 19 | import pstats 20 | import io 21 | from contextlib import contextmanager 22 | 23 | # Configure logging 24 | logging.basicConfig(level=logging.INFO) 25 | logger = logging.getLogger(__name__) 26 | 27 | def _timing_impl(func, *args, **kwargs): 28 | """Implementation of timing functionality.""" 29 | start_time = time.time() 30 | result = func(*args, **kwargs) 31 | end_time = time.time() 32 | logger.info(f"{func.__name__} took {end_time - start_time:.4f} seconds to execute") 33 | return result 34 | 35 | def timing(func: Union[Callable, None] = None) -> Callable: 36 | """ 37 | Measures and logs the execution time of a function. 38 | Can be used as a decorator or function: 39 | 40 | @timing 41 | def func(): pass 42 | 43 | # or 44 | result = timing(func)() 45 | """ 46 | if func is None: 47 | return timing 48 | 49 | @functools.wraps(func) 50 | def wrapper(*args, **kwargs): 51 | return _timing_impl(func, *args, **kwargs) 52 | return wrapper 53 | 54 | def _retry_impl(func, max_attempts, delay, *args, **kwargs): 55 | """Implementation of retry functionality.""" 56 | attempts = 0 57 | while attempts < max_attempts: 58 | try: 59 | return func(*args, **kwargs) 60 | except Exception as e: 61 | attempts += 1 62 | if attempts == max_attempts: 63 | raise 64 | logger.warning(f"Attempt {attempts} failed, retrying in {delay} seconds...") 65 | time.sleep(delay) 66 | return None 67 | 68 | def retry(func: Union[Callable, None] = None, *, max_attempts: int = 3, delay: float = 1.0) -> Callable: 69 | """ 70 | Retries a function in case of failure. 71 | Can be used as a decorator or function: 72 | 73 | @retry(max_attempts=3) 74 | def func(): pass 75 | 76 | # or 77 | result = retry(func, max_attempts=3)() 78 | """ 79 | if func is None: 80 | return lambda f: retry(f, max_attempts=max_attempts, delay=delay) 81 | 82 | @functools.wraps(func) 83 | def wrapper(*args, **kwargs): 84 | return _retry_impl(func, max_attempts, delay, *args, **kwargs) 85 | return wrapper 86 | 87 | def _memoize_impl(func, cache, *args, **kwargs): 88 | """Implementation of memoize functionality.""" 89 | key = str(args) + str(kwargs) 90 | if key not in cache: 91 | cache[key] = func(*args, **kwargs) 92 | return cache[key] 93 | 94 | def memoize(func: Union[Callable, None] = None) -> Callable: 95 | """ 96 | Caches the result of a function to prevent redundant calculations. 97 | Can be used as a decorator or function: 98 | 99 | @memoize 100 | def func(): pass 101 | 102 | # or 103 | result = memoize(func)() 104 | """ 105 | if func is None: 106 | return memoize 107 | 108 | cache = {} 109 | 110 | @functools.wraps(func) 111 | def wrapper(*args, **kwargs): 112 | return _memoize_impl(func, cache, *args, **kwargs) 113 | return wrapper 114 | 115 | def _log_args_impl(func, *args, **kwargs): 116 | """Implementation of log_args functionality.""" 117 | logger.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") 118 | return func(*args, **kwargs) 119 | 120 | def log_args(func: Union[Callable, None] = None) -> Callable: 121 | """ 122 | Logs the arguments passed to a function. 123 | Can be used as a decorator or function: 124 | 125 | @log_args 126 | def func(): pass 127 | 128 | # or 129 | result = log_args(func)() 130 | """ 131 | if func is None: 132 | return log_args 133 | 134 | @functools.wraps(func) 135 | def wrapper(*args, **kwargs): 136 | return _log_args_impl(func, *args, **kwargs) 137 | return wrapper 138 | 139 | def _log_return_impl(func, *args, **kwargs): 140 | """Implementation of log_return functionality.""" 141 | result = func(*args, **kwargs) 142 | logger.info(f"{func.__name__} returned: {result}") 143 | return result 144 | 145 | def log_return(func: Union[Callable, None] = None) -> Callable: 146 | """ 147 | Logs the return value of a function after execution. 148 | Can be used as a decorator or function: 149 | 150 | @log_return 151 | def func(): pass 152 | 153 | # or 154 | result = log_return(func)() 155 | """ 156 | if func is None: 157 | return log_return 158 | 159 | @functools.wraps(func) 160 | def wrapper(*args, **kwargs): 161 | return _log_return_impl(func, *args, **kwargs) 162 | return wrapper 163 | 164 | def _validate_args_impl(func, validators, *args, **kwargs): 165 | """Implementation of validate_args functionality.""" 166 | sig = inspect.signature(func) 167 | bound_args = sig.bind(*args, **kwargs) 168 | for param_name, validator in validators.items(): 169 | if param_name in bound_args.arguments: 170 | value = bound_args.arguments[param_name] 171 | if not validator(value): 172 | raise ValueError(f"Invalid value for {param_name}: {value}") 173 | return func(*args, **kwargs) 174 | 175 | def validate_args(**validators): 176 | """ 177 | Validates the arguments passed to a function based on custom rules. 178 | Can be used as a decorator or function: 179 | 180 | @validate_args(x=lambda x: x > 0) 181 | def func(): pass 182 | 183 | # or 184 | result = validate_args(x=lambda x: x > 0)(func)() 185 | """ 186 | def decorator(func): 187 | @functools.wraps(func) 188 | def wrapper(*args, **kwargs): 189 | return _validate_args_impl(func, validators, *args, **kwargs) 190 | return wrapper 191 | return decorator 192 | 193 | def _cache_impl(func, duration, cache_data, *args, **kwargs): 194 | """Implementation of cache functionality.""" 195 | key = str(args) + str(kwargs) 196 | now = datetime.now() 197 | if key in cache_data: 198 | result, timestamp = cache_data[key] 199 | if now - timestamp < duration: 200 | return result 201 | result = func(*args, **kwargs) 202 | cache_data[key] = (result, now) 203 | return result 204 | 205 | def cache(duration: timedelta): 206 | """ 207 | Caches the result of a function call for a specified duration. 208 | Can be used as a decorator or function: 209 | 210 | @cache(duration=timedelta(minutes=5)) 211 | def func(): pass 212 | 213 | # or 214 | result = cache(duration=timedelta(minutes=5))(func)() 215 | """ 216 | cache_data = {} 217 | 218 | def decorator(func): 219 | @functools.wraps(func) 220 | def wrapper(*args, **kwargs): 221 | return _cache_impl(func, duration, cache_data, *args, **kwargs) 222 | return wrapper 223 | return decorator 224 | 225 | def once(func: Union[Callable, None] = None) -> Callable: 226 | """ 227 | Ensures the function runs only once, even across multiple calls. 228 | Can be used as a decorator or function: 229 | 230 | @once 231 | def func(): pass 232 | 233 | # or 234 | result = once(func)() 235 | """ 236 | if func is None: 237 | return once 238 | 239 | # Store state in a dictionary to maintain closure state 240 | state = {'has_run': False, 'result': None} 241 | 242 | @functools.wraps(func) 243 | def wrapper(*args, **kwargs): 244 | if not state['has_run']: 245 | state['result'] = func(*args, **kwargs) 246 | state['has_run'] = True 247 | return state['result'] 248 | 249 | return wrapper 250 | 251 | def _deprecate_impl(func, message, *args, **kwargs): 252 | """Implementation of deprecate functionality.""" 253 | warn_msg = message or f"{func.__name__} is deprecated and will be removed in a future version." 254 | warnings.warn(warn_msg, DeprecationWarning, stacklevel=2) 255 | return func(*args, **kwargs) 256 | 257 | def deprecate(message: str = None): 258 | """ 259 | Marks a function as deprecated and provides a warning when used. 260 | Can be used as a decorator or function: 261 | 262 | @deprecate("Use new_func instead") 263 | def func(): pass 264 | 265 | # or 266 | result = deprecate("Use new_func instead")(func)() 267 | """ 268 | def decorator(func): 269 | @functools.wraps(func) 270 | def wrapper(*args, **kwargs): 271 | return _deprecate_impl(func, message, *args, **kwargs) 272 | return wrapper 273 | return decorator 274 | 275 | def _check_type_impl(func, type_hints, *args, **kwargs): 276 | """Implementation of check_type functionality.""" 277 | sig = inspect.signature(func) 278 | bound_args = sig.bind(*args, **kwargs) 279 | for param_name, expected_type in type_hints.items(): 280 | if param_name in bound_args.arguments: 281 | value = bound_args.arguments[param_name] 282 | if not isinstance(value, expected_type): 283 | raise TypeError(f"Parameter {param_name} must be of type {expected_type}") 284 | return func(*args, **kwargs) 285 | 286 | def check_type(**type_hints): 287 | """ 288 | Verifies that function arguments are of the correct type. 289 | Can be used as a decorator or function: 290 | 291 | @check_type(x=int, y=str) 292 | def func(): pass 293 | 294 | # or 295 | result = check_type(x=int, y=str)(func)() 296 | """ 297 | def decorator(func): 298 | @functools.wraps(func) 299 | def wrapper(*args, **kwargs): 300 | return _check_type_impl(func, type_hints, *args, **kwargs) 301 | return wrapper 302 | return decorator 303 | 304 | def _retry_on_exception_impl(func, exceptions, *args, **kwargs): 305 | """Implementation of retry_on_exception functionality.""" 306 | while True: 307 | try: 308 | return func(*args, **kwargs) 309 | except exceptions as e: 310 | logger.warning(f"Caught exception {type(e).__name__}, retrying...") 311 | continue 312 | except Exception as e: 313 | raise 314 | 315 | def retry_on_exception(*exceptions): 316 | """ 317 | Retries the function when specific exceptions are raised. 318 | Can be used as a decorator or function: 319 | 320 | @retry_on_exception(ValueError, KeyError) 321 | def func(): pass 322 | 323 | # or 324 | result = retry_on_exception(ValueError, KeyError)(func)() 325 | """ 326 | if not exceptions: 327 | exceptions = (Exception,) 328 | 329 | def decorator(func): 330 | @functools.wraps(func) 331 | def wrapper(*args, **kwargs): 332 | return _retry_on_exception_impl(func, exceptions, *args, **kwargs) 333 | return wrapper 334 | return decorator 335 | 336 | def _measure_memory_impl(func, *args, **kwargs): 337 | """Implementation of measure_memory functionality.""" 338 | initial_memory = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 339 | result = func(*args, **kwargs) 340 | final_memory = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 341 | memory_used = final_memory - initial_memory 342 | logger.info(f"{func.__name__} used {memory_used} KB of memory") 343 | return result 344 | 345 | def measure_memory(func: Union[Callable, None] = None) -> Callable: 346 | """ 347 | Measures the memory usage of a function while it runs. 348 | Can be used as a decorator or function: 349 | 350 | @measure_memory 351 | def func(): pass 352 | 353 | # or 354 | result = measure_memory(func)() 355 | """ 356 | if func is None: 357 | return measure_memory 358 | 359 | @functools.wraps(func) 360 | def wrapper(*args, **kwargs): 361 | return _measure_memory_impl(func, *args, **kwargs) 362 | return wrapper 363 | 364 | def _profile_impl(func, *args, **kwargs): 365 | """Implementation of profile functionality.""" 366 | profiler = cProfile.Profile() 367 | try: 368 | return profiler.runcall(func, *args, **kwargs) 369 | finally: 370 | s = io.StringIO() 371 | stats = pstats.Stats(profiler, stream=s).sort_stats('cumulative') 372 | stats.print_stats() 373 | logger.info(f"Profile for {func.__name__}:\n{s.getvalue()}") 374 | 375 | def profile(func: Union[Callable, None] = None) -> Callable: 376 | """ 377 | Profiles the performance of a function to aid in optimization. 378 | Can be used as a decorator or function: 379 | 380 | @profile 381 | def func(): pass 382 | 383 | # or 384 | result = profile(func)() 385 | """ 386 | if func is None: 387 | return profile 388 | 389 | @functools.wraps(func) 390 | def wrapper(*args, **kwargs): 391 | return _profile_impl(func, *args, **kwargs) 392 | return wrapper 393 | 394 | def _rate_limit_impl(func, calls, period, call_times, *args, **kwargs): 395 | """Implementation of rate_limit functionality.""" 396 | now = time.time() 397 | call_times[:] = [t for t in call_times if now - t < period] 398 | if len(call_times) >= calls: 399 | raise Exception(f"Rate limit exceeded: {calls} calls per {period} seconds") 400 | call_times.append(now) 401 | return func(*args, **kwargs) 402 | 403 | def rate_limit(calls: int, period: float): 404 | """ 405 | Limits the frequency of function calls to avoid overload. 406 | Can be used as a decorator or function: 407 | 408 | @rate_limit(calls=100, period=60) 409 | def func(): pass 410 | 411 | # or 412 | result = rate_limit(calls=100, period=60)(func)() 413 | """ 414 | call_times = [] 415 | 416 | def decorator(func): 417 | @functools.wraps(func) 418 | def wrapper(*args, **kwargs): 419 | return _rate_limit_impl(func, calls, period, call_times, *args, **kwargs) 420 | return wrapper 421 | return decorator 422 | 423 | def _mock_data_impl(func, data, *args, **kwargs): 424 | """Implementation of mock_data functionality.""" 425 | return data() if callable(data) else data 426 | 427 | def mock_data(data: Any): 428 | """ 429 | Replaces a function's output with mock data, useful for testing. 430 | Can be used as a decorator or function: 431 | 432 | @mock_data(42) 433 | def func(): pass 434 | 435 | # or 436 | result = mock_data(42)(func)() 437 | """ 438 | def decorator(func): 439 | @functools.wraps(func) 440 | def wrapper(*args, **kwargs): 441 | return _mock_data_impl(func, data, *args, **kwargs) 442 | return wrapper 443 | return decorator 444 | 445 | def _benchmark_impl(func, *args, **kwargs): 446 | """Implementation of benchmark functionality.""" 447 | times = [] 448 | for _ in range(3): # Run 3 times for averaging 449 | start_time = time.time() 450 | result = func(*args, **kwargs) 451 | end_time = time.time() 452 | times.append(end_time - start_time) 453 | avg_time = sum(times) / len(times) 454 | logger.info(f"Benchmark {func.__name__}: avg={avg_time:.4f}s over {len(times)} runs") 455 | return result 456 | 457 | def benchmark(func: Union[Callable, None] = None) -> Callable: 458 | """ 459 | Compares the execution times of different functions. 460 | Can be used as a decorator or function: 461 | 462 | @benchmark 463 | def func(): pass 464 | 465 | # or 466 | result = benchmark(func)() 467 | """ 468 | if func is None: 469 | return benchmark 470 | 471 | @functools.wraps(func) 472 | def wrapper(*args, **kwargs): 473 | return _benchmark_impl(func, *args, **kwargs) 474 | return wrapper 475 | 476 | def _run_in_thread_impl(func, *args, **kwargs): 477 | """Implementation of run_in_thread functionality.""" 478 | thread = threading.Thread(target=func, args=args, kwargs=kwargs) 479 | thread.start() 480 | return thread 481 | 482 | def run_in_thread(func: Union[Callable, None] = None) -> Callable: 483 | """ 484 | Executes a function asynchronously in a separate thread. 485 | Can be used as a decorator or function: 486 | 487 | @run_in_thread 488 | def func(): pass 489 | 490 | # or 491 | result = run_in_thread(func)() 492 | """ 493 | if func is None: 494 | return run_in_thread 495 | 496 | @functools.wraps(func) 497 | def wrapper(*args, **kwargs): 498 | return _run_in_thread_impl(func, *args, **kwargs) 499 | return wrapper 500 | 501 | def _no_debug_impl(func, *args, **kwargs): 502 | """Implementation of no_debug functionality.""" 503 | current_level = logger.getEffectiveLevel() 504 | logger.setLevel(logging.INFO) 505 | try: 506 | return func(*args, **kwargs) 507 | finally: 508 | logger.setLevel(current_level) 509 | 510 | def no_debug(func: Union[Callable, None] = None) -> Callable: 511 | """ 512 | Disables debug output in production environments. 513 | Can be used as a decorator or function: 514 | 515 | @no_debug 516 | def func(): pass 517 | 518 | # or 519 | result = no_debug(func)() 520 | """ 521 | if func is None: 522 | return no_debug 523 | 524 | @functools.wraps(func) 525 | def wrapper(*args, **kwargs): 526 | return _no_debug_impl(func, *args, **kwargs) 527 | return wrapper 528 | 529 | def _transactional_impl(func, changes, *args, **kwargs): 530 | """Implementation of transactional functionality.""" 531 | try: 532 | result = func(*args, **kwargs) 533 | return result 534 | except Exception: 535 | for change in reversed(changes): 536 | change.rollback() 537 | raise 538 | 539 | def transactional(func: Union[Callable, None] = None) -> Callable: 540 | """ 541 | Ensures that function changes are committed or rolled back atomically. 542 | Can be used as a decorator or function: 543 | 544 | @transactional 545 | def func(): pass 546 | 547 | # or 548 | result = transactional(func)() 549 | """ 550 | if func is None: 551 | return transactional 552 | 553 | @functools.wraps(func) 554 | def wrapper(*args, **kwargs): 555 | changes = [] 556 | return _transactional_impl(func, changes, *args, **kwargs) 557 | return wrapper 558 | 559 | def _revert_on_failure_impl(func, state, *args, **kwargs): 560 | """Implementation of revert_on_failure functionality.""" 561 | try: 562 | return func(*args, **kwargs) 563 | except Exception: 564 | # Restore initial state 565 | for key, value in state.items(): 566 | setattr(args[0], key, value) 567 | raise 568 | 569 | def revert_on_failure(func: Union[Callable, None] = None) -> Callable: 570 | """ 571 | Reverts all changes made by the function if an exception occurs. 572 | Can be used as a decorator or function: 573 | 574 | @revert_on_failure 575 | def func(): pass 576 | 577 | # or 578 | result = revert_on_failure(func)() 579 | """ 580 | if func is None: 581 | return revert_on_failure 582 | 583 | @functools.wraps(func) 584 | def wrapper(*args, **kwargs): 585 | state = {} # Store initial state 586 | return _revert_on_failure_impl(func, state, *args, **kwargs) 587 | return wrapper 588 | 589 | def _audit_impl(func, *args, **kwargs): 590 | """Implementation of audit functionality.""" 591 | timestamp = datetime.now() 592 | caller_frame = inspect.currentframe().f_back 593 | caller_info = f"{caller_frame.f_code.co_filename}:{caller_frame.f_lineno}" 594 | 595 | audit_data = { 596 | "function": func.__name__, 597 | "timestamp": timestamp, 598 | "caller": caller_info, 599 | "args": args, 600 | "kwargs": kwargs 601 | } 602 | 603 | try: 604 | result = func(*args, **kwargs) 605 | audit_data["status"] = "success" 606 | audit_data["result"] = result 607 | return result 608 | except Exception as e: 609 | audit_data["status"] = "error" 610 | audit_data["error"] = str(e) 611 | raise 612 | finally: 613 | logger.info(f"Audit log: {audit_data}") 614 | 615 | def audit(func: Union[Callable, None] = None) -> Callable: 616 | """ 617 | Audits and logs function calls with metadata. 618 | Can be used as a decorator or function: 619 | 620 | @audit 621 | def func(): pass 622 | 623 | # or 624 | result = audit(func)() 625 | """ 626 | if func is None: 627 | return audit 628 | 629 | @functools.wraps(func) 630 | def wrapper(*args, **kwargs): 631 | return _audit_impl(func, *args, **kwargs) 632 | return wrapper 633 | 634 | def _trace_impl(func, *args, **kwargs): 635 | """Implementation of trace functionality.""" 636 | stack = traceback.extract_stack()[:-1] # Exclude current frame 637 | logger.debug(f"Call trace for {func.__name__}:") 638 | for filename, lineno, name, line in stack: 639 | logger.debug(f" File {filename}, line {lineno}, in {name}") 640 | if line: 641 | logger.debug(f" {line.strip()}") 642 | return func(*args, **kwargs) 643 | 644 | def trace(func: Union[Callable, None] = None) -> Callable: 645 | """ 646 | Logs function calls, including call stack traces for debugging. 647 | Can be used as a decorator or function: 648 | 649 | @trace 650 | def func(): pass 651 | 652 | # or 653 | result = trace(func)() 654 | """ 655 | if func is None: 656 | return trace 657 | 658 | @functools.wraps(func) 659 | def wrapper(*args, **kwargs): 660 | return _trace_impl(func, *args, **kwargs) 661 | return wrapper 662 | 663 | def _timeout_impl(func, seconds, *args, **kwargs): 664 | """Implementation of timeout functionality.""" 665 | result = queue.Queue() 666 | 667 | def target(): 668 | try: 669 | result.put(func(*args, **kwargs)) 670 | except Exception as e: 671 | result.put(e) 672 | 673 | thread = threading.Thread(target=target) 674 | thread.daemon = True 675 | thread.start() 676 | thread.join(seconds) 677 | 678 | if thread.is_alive(): 679 | raise TimeoutError(f"Function {func.__name__} timed out after {seconds} seconds") 680 | 681 | value = result.get() 682 | if isinstance(value, Exception): 683 | raise value 684 | return value 685 | 686 | def timeout(seconds: int): 687 | """ 688 | Sets a timeout limit on function execution. 689 | Can be used as a decorator or function: 690 | 691 | @timeout(5) 692 | def func(): pass 693 | 694 | # or 695 | result = timeout(5)(func)() 696 | """ 697 | def decorator(func): 698 | @functools.wraps(func) 699 | def wrapper(*args, **kwargs): 700 | return _timeout_impl(func, seconds, *args, **kwargs) 701 | return wrapper 702 | return decorator -------------------------------------------------------------------------------- /tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the PythonWrap decorators. 3 | """ 4 | 5 | import time 6 | import pytest 7 | from python_wrap.decorators import ( 8 | timing, 9 | retry, 10 | memoize, 11 | log_args, 12 | log_return, 13 | validate_args, 14 | cache, 15 | once, 16 | deprecate, 17 | check_type, 18 | retry_on_exception, 19 | measure_memory, 20 | profile, 21 | rate_limit, 22 | mock_data, 23 | benchmark, 24 | run_in_thread, 25 | no_debug, 26 | transactional, 27 | revert_on_failure, 28 | audit, 29 | trace, 30 | timeout, 31 | ) 32 | from datetime import timedelta 33 | 34 | def test_timing(): 35 | @timing 36 | def slow_function(): 37 | time.sleep(0.1) 38 | return True 39 | 40 | assert slow_function() is True 41 | 42 | def test_retry(): 43 | attempts = 0 44 | 45 | @retry(max_attempts=3, delay=0.1) 46 | def failing_function(): 47 | nonlocal attempts 48 | attempts += 1 49 | if attempts < 3: 50 | raise ValueError("Temporary error") 51 | return True 52 | 53 | assert failing_function() is True 54 | assert attempts == 3 55 | 56 | def test_memoize(): 57 | calls = 0 58 | 59 | @memoize 60 | def expensive_function(x): 61 | nonlocal calls 62 | calls += 1 63 | return x * 2 64 | 65 | assert expensive_function(5) == 10 66 | assert expensive_function(5) == 10 67 | assert calls == 1 68 | 69 | def test_validate_args(): 70 | @validate_args(x=lambda x: x > 0) 71 | def positive_only(x): 72 | return x 73 | 74 | assert positive_only(5) == 5 75 | with pytest.raises(ValueError): 76 | positive_only(-1) 77 | 78 | def test_cache(): 79 | calls = 0 80 | 81 | @cache(duration=timedelta(seconds=1)) 82 | def cached_function(): 83 | nonlocal calls 84 | calls += 1 85 | return calls 86 | 87 | assert cached_function() == 1 88 | assert cached_function() == 1 89 | time.sleep(1.1) 90 | assert cached_function() == 2 91 | 92 | def test_once(): 93 | calls = 0 94 | 95 | @once 96 | def run_once(): 97 | nonlocal calls 98 | calls += 1 99 | return calls 100 | 101 | assert run_once() == 1 102 | assert run_once() == 1 103 | assert calls == 1 104 | 105 | def test_check_type(): 106 | @check_type(x=int, y=str) 107 | def typed_function(x, y): 108 | return f"{x}{y}" 109 | 110 | assert typed_function(1, "2") == "12" 111 | with pytest.raises(TypeError): 112 | typed_function("1", 2) 113 | 114 | def test_mock_data(): 115 | @mock_data(42) 116 | def real_function(): 117 | return 0 118 | 119 | assert real_function() == 42 120 | 121 | def test_timeout(): 122 | @timeout(1) 123 | def quick_function(): 124 | return True 125 | 126 | @timeout(1) 127 | def slow_function(): 128 | time.sleep(2) 129 | return True 130 | 131 | assert quick_function() is True 132 | with pytest.raises(TimeoutError): 133 | slow_function() 134 | 135 | # Add more tests for other decorators as needed --------------------------------------------------------------------------------