diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0d3d252 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @caffeine-addictt diff --git a/.github/CODESTYLE.md b/.github/CODESTYLE.md new file mode 100644 index 0000000..06e5ff8 --- /dev/null +++ b/.github/CODESTYLE.md @@ -0,0 +1,118 @@ +# Code Style +The following is a general guide on how to style your work so that the project +remains consistent throughout all files. Please read this document in it's entirety +and refer to it throughout the development of your contribution. + +1. [General Guidelines](#general-guidelines) +2. [Commit Message Guidelines](#commit-message-guidelines) +3. [Markdown Guidelines](#markdown-guidelines) + + + +## General Guidelines +Listed is a example class used demonstrate general rules you should follow throughout the development of your contribution. + +- Docstrings are to follow reST (reStructuredText Docstring Format) as specified in [PEP 287](https://peps.python.org/pep-0287/) +- Private attributes are to be prefixed with an underscore +- Use of [typing](https://docs.python.org/3/library/typing.html) type hints +- All files are to use 2 space indenting + +```python +class ExampleClass: + """ + ExampleClass + ------------ + Example class for CODESTYLE.md + """ + # ^^^ reST Docstring Format + + _private_attribute : int # private attributes begin with a lowercase + public_attribute : int # type hint for integer is defined here + + def __init__( + self, + public_attribute: int # type hint for parameters + ) -> None: # the expected return value of method + """ + Initializes a ExampleClass + + Parameters + ---------- + :param public_attribute: example attribute + """ + self.public_attribute = public_attribute + self.private_attribute = square(public_attribute) + + def square(self, value: int) -> int: + """ + Example method that square roots a value + + Parameters + ---------- + :param value: value that you want squared + """ + return value**2 +``` + + + +## Commit Message Guidelines +When committing, commit messages are prefixed with one of the following depending on the type of change made. + + - `feat:` when a new feature is introduced with the changes. + - `fix:` when a bug fix has occurred. + - `chore:` for changes that do not relate to a fix or feature and do not modify *source* or *tests*. (like updating dependencies) + - `refactor:` for refactoring code that neither fixes a bug nor adds a feature. + - `docs:` when changes are made to documentation. + - `style:` when changes that do not affect the code, but modify formatting. + - `test:` when changes to tests are made. + - `perf:` for changes that improve performance. + - `ci:` for changes that affect CI. + - `build:` for changes that affect the build system or external dependencies. + - `revert:` when reverting changes. + +Commit messages are also to begin with an uppercase character. Below list some example commit messages. + +```sh +git commit -m "docs: Added README.md" +git commit -m "revert: Removed README.md" +git commit -m "docs: Moved README.md" +``` + + + +## Markdown Guidelines +Currently, documentation for this project resides in markdown files. + - Headings are to be separated with 3 lines + - Use of HTML comments is appreciated + - Use of HTML is permitted + - [reference style links](https://www.markdownguide.org/basic-syntax/#reference-style-links) are not required by are appreciated + - Exceedingly long lines are to be broken + - The indents are to be two spaces + +```markdown + +# Section +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore +magna aliqua. Ut enim ad minim veniam, quis nostrud +exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in +reprehenderit in voluptate velit esse cillum dolore eu +fugiat nulla pariatur. Excepteur sint occaecat cupidatat +non proident, sunt in culpa qui officia deserunt mollit +anim id est laborum. found [Lorem Ipsum Generator] + + + +# Section 2 + + + + +[Lorem Ipsum Generator]: https://loremipsum.io/generator/ +``` diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c2af0a1 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +thread@ngjx.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..0e2b1fc --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# **Contributing** + +When contributing to this repository, please first discuss the change you wish to make via issue, +email, or any other method with the owners of this repository before making a change. + +Please note we have a [code of conduct](CODE_OF_CONDUCT.md); please follow it in all your interactions with the project. + + + +## Pull Request Process + +1. Ensure any install or build dependencies are removed before the end of the layer when doing a + build. +2. Update the README.md with details of changes to the interface; this includes new environment variables, exposed ports, valid file locations and container parameters. +3. Increase the version numbers in any examples files and the README.md to the new version that this + Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). +4. You may merge the Pull Request once you have the sign-off of two other developers, or if you + do not have permission to do that, you may request the second reviewer to merge it for you. + + + +## Issue Report Process + +1. Go to the project's issues. +2. Select the template that better fits your issue. +3. Read the instructions carefully and write within the template guidelines. +4. Submit it and wait for support. diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md new file mode 100644 index 0000000..5dc6668 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug-report.md @@ -0,0 +1,82 @@ +--- +name: "Bug Report" +about: "Report an issue to help the project improve." +title: "[Bug] " +labels: "Type: Bug" +assignees: caffeine-addictt + +--- + +# Bug report +Your issue may already be reported! +Please check out our [active issues](https://github.com/python-thread/thread-cli/issues) before creating one. + + + +## Expected Behavior + + + + +## Current Behavior + + + + +## Is this a regression? + + + + +## Possible Solution + + + + +## Steps to Reproduce (for bugs) + +1. +2. +3. +4. + + + +## Context + + + + +## Your Environment + +* Version used: +* Python version: +* Link to your project: +* Operating System and version (desktop or mobile): diff --git a/.github/ISSUE_TEMPLATE/2-failing-test.md b/.github/ISSUE_TEMPLATE/2-failing-test.md new file mode 100644 index 0000000..f840ca0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-failing-test.md @@ -0,0 +1,40 @@ +--- +name: "Failing Test" +about: "Report failing tests or CI jobs." +title: "[Test] " +labels: "Type: Test" +assignees: caffeine-addictt + +--- + +# Failing Test +Your issue may already be reported! +Please check out our [active issues](https://github.com/python-thread/thread-cli/issues) before creating one. + + + +## Which Jobs/Tests are Failing? +* + + + +## Reason for Failure + + + + +## Media Prove + + + + +## Additional Context + diff --git a/.github/ISSUE_TEMPLATE/3-docs-bug.md b/.github/ISSUE_TEMPLATE/3-docs-bug.md new file mode 100644 index 0000000..18c0d96 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-docs-bug.md @@ -0,0 +1,41 @@ +--- +name: "Documentation or README.md issue report" +about: "Report an issue in the project's documentation or README.md file." +title: "" +labels: "Documentation" +assignees: caffeine-addictt + +--- + +# Documentation Issue Report +Your issue may already be reported! +Please check out our [active issues](https://github.com/python-thread/thread-cli/issues) before creating one. + + + +## Describe the Bug + + + + +## Steps to Reproduce + + +1. +2. +3. +4. + + + +## Additional Context + diff --git a/.github/ISSUE_TEMPLATE/4-feature-request.md b/.github/ISSUE_TEMPLATE/4-feature-request.md new file mode 100644 index 0000000..75b1d92 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-feature-request.md @@ -0,0 +1,43 @@ +--- +name: "Feature Request" +about: "Suggest an idea or possible new feature for this project." +title: "" +labels: "Type: Feature" +assignees: caffeine-addictt + +--- + +# Feature Request +Your issue may already be reported! +Please check out our [active issues](https://github.com/python-thread/thread-cli/issues) before creating one. + + + +## Is Your Feature Request Related to an Issue? + + + + +## Describe the Solution You'd Like + + + + +## Describe Alternatives You've Considered + + + + +## Additional Context + diff --git a/.github/ISSUE_TEMPLATE/5-enhancement-request.md b/.github/ISSUE_TEMPLATE/5-enhancement-request.md new file mode 100644 index 0000000..a0cc576 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/5-enhancement-request.md @@ -0,0 +1,43 @@ +--- +name: "Enhancement Request" +about: "Suggest an enhancement for this project. Improve an existing feature" +title: "" +labels: "Type: Enhancement" +assignees: caffeine-addictt + +--- + +# Enhancement Request +Your issue may already be reported! +Please check out our [active issues](https://github.com/python-thread/thread-cli/issues) before creating one. + + + +## Is Your Enhancement Request Related to an Issue? + + + + +## Describe the Solution You'd Like + + + + +## Describe Alternatives You've Considered + + + + +## Additional Context + diff --git a/.github/ISSUE_TEMPLATE/6-security-report.md b/.github/ISSUE_TEMPLATE/6-security-report.md new file mode 100644 index 0000000..e91fb6a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/6-security-report.md @@ -0,0 +1,97 @@ +--- +name: "Security Report" +about: "Report an issue to help the project improve." +title: "" +labels: "Type: Security" +assignees: caffeine-addictt + +--- + + + +# Security Report +Your issue may already be reported! +Please check out our [active issues](https://github.com/python-thread/thread-cli/issues) before creating one. + + + +## Describe the Security Issue + + + + +## Steps to Reproduce + + + + +## Expected Behavior + + + + +## Media Prove + + + + +## Additional Context + + + + +### Your Environment + +* Version used: +* Python version: +* Link to your project: +* Operating System and version (desktop or mobile): \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/7-question-support.md b/.github/ISSUE_TEMPLATE/7-question-support.md new file mode 100644 index 0000000..cc44220 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/7-question-support.md @@ -0,0 +1,20 @@ +--- +name: "Question or Support Request" +about: "Questions and requests for support." +title: "" +labels: "Type: Question" +assignees: caffeine-addictt + +--- + +# Question or Support Request + + + + +## Describe your question or ask for support + + +* \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..aff3721 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,79 @@ + + +# What type of PR is this? (check all applicable) + +- [ ] Refactor +- [ ] Feature +- [ ] Bug Fix +- [ ] Optimization +- [ ] Breaking Change +- [ ] Documentation Update + + + +## Description + + + + +## Related Tickets & Documents + + + +- Related Issue # +- Closes # + + + +## QA Instructions, Screenshots, Recordings + +_Please replace this line with instructions on how to test your changes, a note +on the devices and browsers this has been tested on, as well as any relevant +images for UI changes._ + + + +## Added/updated tests? + +_We encourage you to keep the code coverage percentage at 80% and above._ + +- [ ] Yes +- [ ] No, and this is why: _please replace this line with details on why tests + have not been included_ +- [ ] I need help with writing tests + + + +## [optional] Are there any post deployment tasks we need to perform? + + + + +## Checklist + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..89ba50c --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,11 @@ +# **Reporting Security Issues** + +The project's team and community take security issues. + +We appreciate your efforts to disclose your findings responsibly and will make every effort to acknowledge your contributions. + +To report a security issue, go to the project's issues and create a new issue using the ⚠️ Security Report 'issue template'. + +Read the instructions of this issue template carefully, and if your report could leak data or might expose how to gain access to a restricted area or break the system, please email [thread@ngjx.org](mailto:thread@ngjx.org) and include the word "SECURITY" in the subject line. + +We'll endeavour to respond quickly and keep you updated throughout the process. diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..a9fff4b --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,80 @@ +labels: + - name: "Type: Bug" + color: e80c0c + description: Something isn't working as expected. + + - name: "Type: Enhancement" + color: 54b2ff + description: Suggest an improvement for an existing feature. + + - name: "Type: Feature" + color: 54b2ff + description: Suggest a new feature. + + - name: "Type: Security" + color: fbff00 + description: A problem or enhancement related to a security issue. + + - name: "Type: Question" + color: 9309ab + description: Request for information. + + - name: "Type: Test" + color: ce54e3 + description: A problem or enhancement related to a test. + + - name: "Status: Awaiting Review" + color: 24d15d + description: Ready for review. + + - name: "Status: WIP" + color: 07b340 + description: Currently being worked on. + + - name: "Status: Waiting" + color: 38C968 + description: Waiting on something else to be ready. + + - name: "Status: Stale" + color: 66b38a + description: Has had no activity for some time. + + - name: "Duplicate" + color: EB862D + description: Duplicate of another issue. + + - name: "Invalid" + color: faef50 + description: This issue doesn't seem right. + + - name: "Priority: High +" + color: ff008c + description: Task is considered higher-priority. + + - name: "Priority: Low -" + color: 690a34 + description: Task is considered lower-priority. + + - name: "Documentation" + color: 2fbceb + description: An issue/change with the documentation. + + - name: "Won't fix" + color: C8D9E6 + description: Reported issue is working as intended. + + - name: "3rd party issue" + color: e88707 + description: This issue might be caused by a 3rd party script/package/other reasons + + - name: "Os: Windows" + color: AEB1C2 + description: Is Windows-specific + + - name: "Os: Mac" + color: AEB1C2 + description: Is Mac-specific + + - name: "Os: Linux" + color: AEB1C2 + description: Is Linux-specific diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6277b81 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Upload Python Package + +on: + workflow_dispatch: + push: + tags: + - v*.*.* + +permissions: + contents: read + +jobs: + pypi-publish: + name: Upload release to PyPI + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build and publish + uses: JRubics/poetry-publish@v1.17 + with: + pypi_token: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/test-worker.yml b/.github/workflows/test-worker.yml new file mode 100644 index 0000000..c5b8911 --- /dev/null +++ b/.github/workflows/test-worker.yml @@ -0,0 +1,42 @@ +name: Run Python tests + +on: [push] + +jobs: + build: + name: Run tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.x"] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U coverage pytest pytest-cov poetry + python -m poetry install + python -m poetry self add poetry-plugin-export + python -m poetry export -f requirements.txt --output requirements.txt + python -m pip install -r requirements.txt + + - name: Lint with Ruff + run: | + python -m pip install -U ruff + ruff --per-file-ignores="__init__.py:F401" --per-file-ignores="__init__.py:E402" . + continue-on-error: true + + - name: Test with pytest + run: | + coverage run -m pytest -v -s + + - name: Generate Coverage Report + run: | + coverage report -m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fbc277 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Python stuff +venv*/ +*.pytest_cache +__pycache__/ + +*.py[cod] +*$py.class + +*.ruff_cache + +# Build +dist/ +*.egg-info/ + +.vscode/ diff --git a/LICENSE b/LICENSE index 1173abf..578a941 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, Thread +Copyright (c) 2024, Jun Xiang Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..acd7691 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,380 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.4.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "numpy" +version = "1.26.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, + {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, + {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, + {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, + {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, + {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, + {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, + {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, + {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, + {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.0.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, + {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.3.0,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.2.0" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.2.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:638ea3294f800d18bae84a492cb5a245c8d29c90d19a91d8e338937a4c27fca0"}, + {file = "ruff-0.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3ff35433fcf4dff6d610738712152df6b7d92351a1bde8e00bd405b08b3d5759"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9faafbdcf4f53917019f2c230766da437d4fd5caecd12ddb68bb6a17d74399"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8153a3e4128ed770871c47545f1ae7b055023e0c222ff72a759f5a341ee06483"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a75a98ae989a27090e9c51f763990ad5bbc92d20626d54e9701c7fe597f399"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:87057dd2fdde297130ff99553be8549ca38a2965871462a97394c22ed2dfc19d"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d232f99d3ab00094ebaf88e0fb7a8ccacaa54cc7fa3b8993d9627a11e6aed7a"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3c641f95f435fc6754b05591774a17df41648f0daf3de0d75ad3d9f099ab92"}, + {file = "ruff-0.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3826fb34c144ef1e171b323ed6ae9146ab76d109960addca730756dc19dc7b22"}, + {file = "ruff-0.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eceab7d85d09321b4de18b62d38710cf296cb49e98979960a59c6b9307c18cfe"}, + {file = "ruff-0.2.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:30ad74687e1f4a9ff8e513b20b82ccadb6bd796fe5697f1e417189c5cde6be3e"}, + {file = "ruff-0.2.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7e3818698f8460bd0f8d4322bbe99db8327e9bc2c93c789d3159f5b335f47da"}, + {file = "ruff-0.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:edf23041242c48b0d8295214783ef543847ef29e8226d9f69bf96592dba82a83"}, + {file = "ruff-0.2.0-py3-none-win32.whl", hash = "sha256:e155147199c2714ff52385b760fe242bb99ea64b240a9ffbd6a5918eb1268843"}, + {file = "ruff-0.2.0-py3-none-win_amd64.whl", hash = "sha256:ba918e01cdd21e81b07555564f40d307b0caafa9a7a65742e98ff244f5035c59"}, + {file = "ruff-0.2.0-py3-none-win_arm64.whl", hash = "sha256:3fbaff1ba9564a2c5943f8f38bc221f04bac687cc7485e45237579fee7ccda79"}, + {file = "ruff-0.2.0.tar.gz", hash = "sha256:63856b91837606c673537d2889989733d7dffde553828d3b0f0bacfa6def54be"}, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "thread" +version = "0.1.3" +description = "Threading module extension" +optional = false +python-versions = ">=3.9,<4.0" +files = [ + {file = "thread-0.1.3-py3-none-any.whl", hash = "sha256:d7dbef51cdfb239b74df3253cdb60e004a13f1cb1dd51595e4ac41eec057ede9"}, + {file = "thread-0.1.3.tar.gz", hash = "sha256:6dcbaaa77c2c9a1f7803c6d65b16ffa93b3de0a9e7abc375b2b2f260dc8aab30"}, +] + +[package.dependencies] +numpy = ">=1.26.2,<2.0.0" +typer = {version = ">=0.9.0,<0.10.0", extras = ["all"]} +typing-extensions = ">=4.8.0,<5.0.0" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typer" +version = "0.9.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.6" +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""} +rich = {version = ">=10.11.0,<14.0.0", optional = true, markers = "extra == \"all\""} +shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""} +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "53c2df993a65a583d8c040432670a602eb9c12a4e6577ae6eef265db68c72f54" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..92a45cb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[tool.poetry] +name = "thread-cli" +version = "0.1.0" +description = "Threading module extension's CLI" +authors = ["Alex "] +license = "BSD-3-Clause" +readme = "README.md" +packages = [ + { include = "thread-cli", from = "src" }, + { include = "thread-cli/py.typed", from = "src" }, +] +include = [{ path = "tests", format = "sdist" }] +homepage = "https://github.com/python-thread/thread-cli" +repository = "https://github.com/python-thread/thread-cli" +documentation = "https://thread.ngjx.org" +keywords = ["thread", "threading", "cli", "cli-extension"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", +] + +[tool.poetry.urls] +Changelog = "https://github.com/python-thread/thread-cli/releases" +"Bug Tracker" = "https://github.com/python-thread/thread-cli/issues" + +[tool.poetry.dependencies] +python = "^3.9" +thread = "^0.1.3" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.0.0" +coverage = "^7.4.1" +ruff = "^0.2.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/src/thread-cli/__init__.py b/src/thread-cli/__init__.py new file mode 100644 index 0000000..32a3091 --- /dev/null +++ b/src/thread-cli/__init__.py @@ -0,0 +1,32 @@ +""" +## ThreadCLI Library +Documentation: https://thread.ngjx.org + + +--- + +Released under the GPG-3 License + +Copyright (c) 2020, thread.ngjx.org. All rights reserved. +""" + +__version__ = "0.1.0" +from .utils.logging import ColorLogger, logging + +# Export Core +from .base import cli_base as app +from .process import process as process_cli + +app.command( + name="process", + no_args_is_help=True, + context_settings={"allow_extra_args": True}, +)(process_cli) + + +# Setup Logging +logging.setLoggerClass(ColorLogger) + + +# Wildcard export +__all__ = ["app"] diff --git a/src/thread-cli/base.py b/src/thread-cli/base.py new file mode 100644 index 0000000..c2e6eb6 --- /dev/null +++ b/src/thread-cli/base.py @@ -0,0 +1,117 @@ +import typer +import logging + +from . import __version__ +from .utils import DebugOption, VerboseOption, QuietOption, verbose_args_processor +logger = logging.getLogger('base') + + +cli_base = typer.Typer( + no_args_is_help = True, + rich_markup_mode = 'rich', + context_settings = { + 'help_option_names': ['-h', '--help', 'help'] + } +) + + +def version_callback(value: bool): + if value: + typer.echo(f'v{__version__}') + raise typer.Exit() + + +@cli_base.callback(invoke_without_command = True) +def callback( + version: bool = typer.Option( + None, '--version', + callback = version_callback, + help = 'Get the current installed version', + is_eager = True + ), + + debug: bool = DebugOption, + verbose: bool = VerboseOption, + quiet: bool = QuietOption +): + """ + [b]Thread CLI[/b]\b\n + [white]Use thread from the terminal![/white] + + [blue][u] [/u][/blue] + + Learn more from our [link=https://github.com/python-thread/thread/blob/main/docs/command-line.md]documentation![/link] + """ + verbose_args_processor(debug, verbose, quiet) + + + +# Help and Others +@cli_base.command(rich_help_panel = 'Help and Others') +def help(): + """Get [yellow]help[/yellow] from the community. :question:""" + typer.echo('Feel free to search for or ask questions here!') + try: + logger.info('Attempting to open in web browser...') + + import webbrowser + webbrowser.open( + 'https://github.com/python-thread/thread/issues', + new = 2 + ) + typer.echo('Opening in web browser!') + + except Exception as e: + logger.warn('Failed to open web browser') + logger.debug(f'{e}') + typer.echo('https://github.com/python-thread/thread/issues') + + + +@cli_base.command(rich_help_panel = 'Help and Others') +def docs(): + """View our [yellow]documentation.[/yellow] :book:""" + typer.echo('Thanks for using Thread, here is our documentation!') + try: + logger.info('Attempting to open in web browser...') + import webbrowser + webbrowser.open( + 'https://github.com/python-thread/thread/blob/main/docs/command-line.md', + new = 2 + ) + typer.echo('Opening in web browser!') + + except Exception as e: + logger.warn('Failed to open web browser') + logger.debug(f'{e}') + typer.echo('https://github.com/python-thread/thread/blob/main/docs/command-line.md') + + + +@cli_base.command(rich_help_panel = 'Help and Others') +def report(): + """[yellow]Report[/yellow] an issue. :bug:""" + typer.echo('Sorry you run into an issue, report it here!') + try: + logger.info('Attempting to open in web browser...') + import webbrowser + webbrowser.open( + 'https://github.com/python-thread/thread/issues', + new = 2 + ) + typer.echo('Opening in web browser!') + + except Exception as e: + logger.warn('Failed to open web browser') + logger.debug(f'{e}') + typer.echo('https://github.com/python-thread/thread/issues') + + + +# Utils and Configs +@cli_base.command(rich_help_panel = 'Utils and Configs') +def config(configuration: str): + """ + [blue]Configure[/blue] the system. :wrench: + """ + typer.echo('Coming soon!') \ No newline at end of file diff --git a/src/thread-cli/process.py b/src/thread-cli/process.py new file mode 100644 index 0000000..c75c5e7 --- /dev/null +++ b/src/thread-cli/process.py @@ -0,0 +1,229 @@ +"""Parallel Processing command""" + +import os +import time +import json +import inspect +import importlib + +import typer +import logging +from typing import Union + +from rich.live import Live +from rich.panel import Panel +from rich.console import Group +from rich.progress import Progress, TaskID, SpinnerColumn, TextColumn, BarColumn, TimeRemainingColumn, TimeElapsedColumn +from .utils import DebugOption, VerboseOption, QuietOption, verbose_args_processor, kwargs_processor + +logger = logging.getLogger('base') + + +def process( + func: str = typer.Argument(help = '[blue].path.to.file[/blue]:[blue]function_name[/blue] OR [blue]lambda x: x[/blue]'), + dataset: str = typer.Argument(help = '[blue]./path/to/file.txt[/blue] OR [blue][ i for i in range(2) ][/blue]'), + + args: list[str] = typer.Option([], '--arg', '-a', help = '[blue]Arguments[/blue] passed to each thread'), + kargs: list[str] = typer.Option([], '--kwarg', '-kw', help = '[blue]Key-Value arguments[/blue] passed to each thread'), + threads: int = typer.Option(8, '--threads', '-t', help = 'Maximum number of [blue]threads[/blue] (will scale down based on dataset size)'), + + daemon: bool = typer.Option(False, '--daemon', '-d', help = 'Threads to run in [blue]daemon[/blue] mode'), + graceful_exit: bool = typer.Option(True, '--graceful-exit', '-ge', is_flag = True, help = 'Whether to [blue]gracefully exit[/blue] on abrupt exit (etc. CTRL+C)'), + output: str = typer.Option('./output.json', '--output', '-o', help = '[blue]Output[/blue] file location'), + fileout: bool = typer.Option(True, '--fileout', is_flag = True, help = 'Whether to [blue]write[/blue] output to a file'), + stdout: bool = typer.Option(False, '--stdout', is_flag = True, help = 'Whether to [blue]print[/blue] the output'), + + debug: bool = DebugOption, + verbose: bool = VerboseOption, + quiet: bool = QuietOption +): + """ + [bold]Utilise parallel processing on a dataset[/bold] + + \b\n + [bold white]:glowing_star: Important[/bold white] + Args and Kwargs can be parsed by adding multiple -a or -kw + + [green]$ thread[/green] [blue]process[/blue] ... -a 'an arg' -kw myKey=myValue -arg testing --kwarg a1=a2 + [white]=> args = [ [green]'an arg'[/green], [green]'testing'[/green] ][/white] + [white] kwargs = { [green]'myKey'[/green]: [green]'myValue'[/green], [green]'a1'[/green]: [green]'a2'[/green] }[/white] + + [blue][u] [/u][/blue] + + Learn more from our [link=https://github.com/python-thread/thread/blob/main/docs/command-line.md#parallel-processing-thread-process]documentation![/link] + """ + verbose_args_processor(debug, verbose, quiet) + kwargs = kwargs_processor(kargs) + logger.debug('Processed kwargs: %s' % kwargs) + + + # Verify output + if not fileout and not stdout: + raise typer.BadParameter('No output method specified') + + if fileout and not os.path.exists('/'.join(output.split('/')[:-1])): + raise typer.BadParameter('Output file directory does not exist') + + + + + # Loading function + f = None + try: + logger.info('Attempted to interpret function') + f = eval(func) # I know eval is bad practice, but I have yet to find a safer replacement + logger.debug('Evaluated function: %s' % f) + + if not inspect.isfunction(f): + logger.info('Invalid function') + except Exception: + logger.info('Failed to interpret function') + + if not f: + try: + logger.info('Attempting to fetch function file') + + fPath, fName = func.split(':') + f = importlib.import_module(fPath).__dict__[fName] + logger.debug('Evaluated function: %s' % f) + + if not inspect.isfunction(f): + logger.info('Not a function') + raise Exception('Not a function') + except Exception as e: + logger.warning('Failed to fetch function') + raise typer.BadParameter('Failed to fetch function') from e + + + + + # Loading dataset + ds: Union[list, tuple, set, None] = None + try: + logger.info('Attempting to interpret dataset') + ds = eval(dataset) + logger.debug('Evaluated dataset: %s' % (str(ds)[:125] + '...' if len(str(ds)) > 125 else ds)) + + if not isinstance(ds, (list, tuple, set)): + logger.info('Invalid dataset literal') + ds = None + + except Exception: + logger.info('Failed to interpret dataset') + + if not ds: + try: + logger.info('Attempting to fetch data file') + if not os.path.isfile(dataset): + logger.info('Invalid file path') + raise Exception('Invalid file path') + + with open(dataset, 'r') as a: + ds = [ i.endswith('\n') and i[:-2] for i in a.readlines() ] + except Exception as e: + logger.warning('Failed to read dataset') + raise typer.BadParameter('Failed to read dataset') from e + + logger.info('Interpreted dataset') + + + # Setup + logger.debug('Importing module') + from thread import Settings, ParallelProcessing + logger.info('Spawning threads... [Expected: {tcount} threads]'.format(tcount=min(len(ds), threads))) + + Settings.set_graceful_exit(graceful_exit) + newProcess = ParallelProcessing( + function = f, + dataset = list(ds), + args = args, + kwargs = kwargs, + daemon = daemon, + max_threads = threads + ) + + logger.info('Created parallel process') + logger.info('Starting parallel process') + + start_t = time.perf_counter() + newProcess.start() + + logger.info('Started parallel processes') + typer.echo('Waiting for parallel processes to complete, this may take a while...') + + + # Progress bar :D + threadCount = len(newProcess._threads) + + thread_progress = Progress( + SpinnerColumn(), + TextColumn('{task.description}'), + '•', + TimeRemainingColumn(), + BarColumn(bar_width = 80), + TextColumn('{task.percentage:>3.1f}%') + ) + overall_progress = Progress( + TimeElapsedColumn(), + BarColumn(bar_width = 110), + TextColumn('{task.description}') + ) + + workerjobs: list[TaskID] = [ + thread_progress.add_task( + f'[bold blue][T {threadNum}]', + total = 100 + ) + for threadNum in range(threadCount) + ] + overalljob = overall_progress.add_task('(0 of ?)', total = 100) + + + with Live( + Group( + Panel(thread_progress), + overall_progress, + ), + refresh_per_second = 10 + ): + completed = 0 + while completed != threadCount: + i = 0 + completed = 0 + progressAvg = 0 + + for jobID in workerjobs: + jobProgress = newProcess._threads[i].progress + thread_progress.update(jobID, completed = round(jobProgress * 100, 2)) + if jobProgress == 1: + thread_progress.stop_task(jobID) + thread_progress.update(jobID, description = '[bold green]Completed') + completed += 1 + + progressAvg += jobProgress + i += 1 + + # Update overall + overall_progress.update( + overalljob, + description = f'[bold {"green" if completed == threadCount else "#AAAAAA"}]({completed} of {threadCount})', + completed = round((progressAvg / threadCount) * 100, 2) + ) + time.sleep(0.1) + + + result = newProcess.get_return_values() + + typer.echo(f'Completed in {(time.perf_counter() - start_t):.5f}s') + if fileout: + typer.echo(f'Writing to {output}') + try: + with open(output, 'w') as f: + json.dump(result, f, indent = 2) + logger.info('Wrote to file') + except Exception as e: + logger.error('Failed to write to file') + logger.debug(str(e)) + + if stdout: + typer.echo(result) \ No newline at end of file diff --git a/src/thread-cli/py.typed b/src/thread-cli/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/thread-cli/utils/__init__.py b/src/thread-cli/utils/__init__.py new file mode 100644 index 0000000..4462f58 --- /dev/null +++ b/src/thread-cli/utils/__init__.py @@ -0,0 +1,9 @@ +from . import logging +from .processors import ( + verbose_args_processor, + kwargs_processor, + + DebugOption, + VerboseOption, + QuietOption +) diff --git a/src/thread-cli/utils/logging.py b/src/thread-cli/utils/logging.py new file mode 100644 index 0000000..f6da703 --- /dev/null +++ b/src/thread-cli/utils/logging.py @@ -0,0 +1,33 @@ +import logging +from colorama import init, Fore, Style + +init(autoreset=True) + + +# Stdout color configuration +class ColorFormatter(logging.Formatter): + COLORS = { + "DEBUG": Fore.BLUE, + "INFO": Fore.GREEN, + "WARNING": Fore.YELLOW, + "ERROR": Fore.RED, + "CRITICAL": Fore.RED + Style.BRIGHT, + } + + def format(self, record): + color = self.COLORS.get(record.levelname, "") + record.levelname = record.levelname if not color else color + Style.BRIGHT + f"{record.levelname:<9}|" + record.msg = record.msg if not color else color + Fore.WHITE + Style.NORMAL + record.msg + + return logging.Formatter.format(self, record) + + +# Configure logger +class ColorLogger(logging.Logger): + def __init__(self, name): + logging.Logger.__init__(self, name, logging.DEBUG) + colorFormatter = ColorFormatter("%(levelname)s %(message)s" + Style.RESET_ALL) + + console = logging.StreamHandler() + console.setFormatter(colorFormatter) + self.addHandler(console) diff --git a/src/thread-cli/utils/processors.py b/src/thread-cli/utils/processors.py new file mode 100644 index 0000000..7356eb2 --- /dev/null +++ b/src/thread-cli/utils/processors.py @@ -0,0 +1,48 @@ +# Verbose Command Processor # +import typer +import logging + + +# Verbose Options # +DebugOption = typer.Option( + False, '--debug', + help = 'Set verbosity level to [blue]DEBUG[/blue]', + is_flag = True +) +VerboseOption = typer.Option( + False, '--verbose', '-v', + help = 'Set verbosity level to [green]INFO[/green]', + is_flag = True +) +QuietOption = typer.Option( + False, '--quiet', '-q', + help = 'Set verbosity level to [red]ERROR[/red]', + is_flag = True +) + + +# Helper functions # + + +# Processors # +def verbose_args_processor(debug: bool, verbose: bool, quiet: bool): + """Handles setting and raising exceptions for verbose""" + if verbose and quiet: + raise typer.BadParameter('--quiet cannot be used with --verbose') + + if verbose and debug: + raise typer.BadParameter('--debug cannot be used with --verbose') + + logging.getLogger('base').setLevel(( + (debug and logging.DEBUG) or + (verbose and logging.INFO) or + logging.ERROR + )) + +def kwargs_processor(arguments: list[str]) -> dict[str, str]: + """Processes arguments into kwargs""" + return { + kwarg[0]: kwarg[1] + for i in arguments + if (kwarg := i.split('=')) + } \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py new file mode 100644 index 0000000..d20e63f --- /dev/null +++ b/tests/test_placeholder.py @@ -0,0 +1,4 @@ +def test_packagesExist(): + import thread + + assert thread