├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── stale.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── cspell.json ├── docs ├── .prettierrc ├── assets │ └── images │ │ └── favicon.png └── index.md ├── mkdocs.yml ├── pyproject.toml ├── template_python ├── __init__.py ├── examples │ ├── __init__.py │ ├── fibonacci.py │ ├── fizzbuzz.py │ └── palindrome.py └── py.typed ├── tests ├── __init__.py ├── examples │ ├── __init__.py │ ├── test_fibonacci.py │ ├── test_fizzbuzz.py │ └── test_palindrome.py └── test_version.py └── vercel.json /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.pdf filter=lfs diff=lfs merge=lfs -text 3 | *.ppt* filter=lfs diff=lfs merge=lfs -text 4 | *.tar* filter=lfs diff=lfs merge=lfs -text 5 | *.xls* filter=lfs diff=lfs merge=lfs -text 6 | *.zip filter=lfs diff=lfs merge=lfs -text 7 | *.doc* filter=lfs diff=lfs merge=lfs -text 8 | # *.jp*g filter=lfs diff=lfs merge=lfs -text 9 | # *.png filter=lfs diff=lfs merge=lfs -text 10 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][faq]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 130 | [mozilla coc]: https://github.com/mozilla/diversity 131 | [faq]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidelines for contributing 2 | 3 | ## Table of Contents 4 | 5 | - [Summary](#summary) 6 | - [Git](#git) 7 | - [Python](#python) 8 | - [Hatch](#hatch) 9 | - [Testing with pytest](#testing-with-pytest) 10 | - [Code quality](#code-quality) 11 | - [Running code quality checks](#running-code-quality-checks) 12 | - [Code style](#code-style) 13 | - [Static type checking](#static-type-checking) 14 | - [Spell check](#spell-check) 15 | - [GitHub Actions workflows](#github-actions-workflows) 16 | - [Maintainers](#maintainers) 17 | 18 | ## Summary 19 | 20 | **PRs welcome!** 21 | 22 | - **Consider starting a discussion to see if there's interest in what you want to do.** 23 | - **Submit PRs from feature branches on forks to the `develop` branch.** 24 | - **Ensure PRs pass all CI checks.** 25 | - **Maintain test coverage at 100%.** 26 | 27 | ## Git 28 | 29 | - _[Why use Git?](https://www.git-scm.com/about)_ Git enables creation of multiple versions of a code repository called branches, with the ability to track and undo changes in detail. 30 | - Install Git by [downloading](https://www.git-scm.com/downloads) from the website, or with a package manager like [Homebrew](https://brew.sh/). 31 | - [Configure Git to connect to GitHub with SSH](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh). 32 | - [Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) this repo. 33 | - Create a [branch](https://www.git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) in your fork. 34 | - Commit your changes with a [properly-formatted Git commit message](https://chris.beams.io/posts/git-commit/). 35 | - Create a [pull request (PR)](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) to incorporate your changes into the upstream project you forked. 36 | 37 | ## Python 38 | 39 | ### Hatch 40 | 41 | This project uses [Hatch](https://hatch.pypa.io/latest/) for dependency management and packaging. 42 | 43 | #### Highlights 44 | 45 | - **Automatic virtual environment management**: [Hatch automatically manages the application environment](https://hatch.pypa.io/latest/environment/). 46 | - **Dependency resolution**: Hatch will automatically resolve any dependency version conflicts using the [`pip` dependency resolver](https://pip.pypa.io/en/stable/topics/dependency-resolution/). 47 | - **Dependency separation**: [Hatch supports separate lists of optional dependencies in the _pyproject.toml_](https://hatch.pypa.io/latest/config/dependency/). Production installs can skip optional dependencies for speed. 48 | - **Builds**: [Hatch has features for easily building the project into a Python package](https://hatch.pypa.io/latest/build/) and [publishing the package to PyPI](https://hatch.pypa.io/latest/publish/). 49 | 50 | #### Installation 51 | 52 | [Hatch can be installed with Homebrew or `pipx`](https://hatch.pypa.io/latest/install/). 53 | 54 | **Install project with all dependencies: `hatch env create`**. 55 | 56 | #### Key commands 57 | 58 | ```sh 59 | # Basic usage: https://hatch.pypa.io/latest/cli/reference/ 60 | hatch env create # create virtual environment and install dependencies 61 | hatch env find # show path to virtual environment 62 | hatch env show # show info about available virtual environments 63 | hatch run COMMAND # run a command within the virtual environment 64 | hatch shell # activate the virtual environment, like source venv/bin/activate 65 | hatch version # list or update version of this package 66 | export HATCH_ENV_TYPE_VIRTUAL_PATH=.venv # install virtualenvs into .venv 67 | ``` 68 | 69 | ### Testing with pytest 70 | 71 | - Tests are in the _tests/_ directory. 72 | - [pytest](https://docs.pytest.org/en/latest/) features used include: 73 | - [capturing `stdout` with `capfd`](https://docs.pytest.org/en/latest/how-to/capture-stdout-stderr.html) 74 | - [fixtures](https://docs.pytest.org/en/latest/how-to/fixtures.html) 75 | - [monkeypatch](https://docs.pytest.org/en/latest/how-to/monkeypatch.html) 76 | - [parametrize](https://docs.pytest.org/en/latest/how-to/parametrize.html) 77 | - [temporary directories and files (`tmp_path` and `tmp_dir`)](https://docs.pytest.org/en/latest/how-to/tmpdir.html) 78 | - [pytest plugins](https://docs.pytest.org/en/latest/how-to/plugins.html) include: 79 | - [pytest-mock](https://github.com/pytest-dev/pytest-mock) 80 | - [pytest configuration](https://docs.pytest.org/en/latest/reference/customize.html) is in _pyproject.toml_. 81 | - Run tests with pytest and [coverage.py](https://github.com/nedbat/coveragepy) with `hatch run coverage run`. 82 | - Test coverage reports are generated by [coverage.py](https://github.com/nedbat/coveragepy). To generate test coverage reports: 83 | 1. Run tests with `hatch run coverage run` 84 | 2. Generate a report with `coverage report`. To see interactive HTML coverage reports, run `coverage html` instead of `coverage report`. 85 | 86 | ## Code quality 87 | 88 | ### Running code quality checks 89 | 90 | Code quality checks can be run using the Hatch scripts in _pyproject.toml_. 91 | 92 | - Check: `hatch run check` 93 | - Format: `hatch run format` 94 | 95 | ### Code style 96 | 97 | - Python code is formatted with [Ruff](https://docs.astral.sh/ruff/). Ruff configuration is stored in _pyproject.toml_. 98 | - Other web code (JSON, Markdown, YAML) is formatted with [Prettier](https://prettier.io/). 99 | 100 | ### Static type checking 101 | 102 | - To learn type annotation basics, see the [Python typing module docs](https://docs.python.org/3/library/typing.html), [Python type annotations how-to](https://docs.python.org/3/howto/annotations.html), the [Real Python type checking tutorial](https://realpython.com/python-type-checking/), and [this gist](https://gist.github.com/987bdc6263217895d4bf03d0a5ff114c). 103 | - Type annotations are not used at runtime. The standard library `typing` module includes a `TYPE_CHECKING` constant that is `False` at runtime, but `True` when conducting static type checking prior to runtime. Type imports are included under `if TYPE_CHECKING:` conditions so that they are not imported at runtime. These conditions are ignored when calculating test coverage. 104 | - Type annotations can be provided inline or in separate stub files. Much of the Python standard library is annotated with stubs. For example, the Python standard library [`logging.config` module uses type stubs](https://github.com/python/typeshed/blob/main/stdlib/logging/config.pyi). The typeshed types for the `logging.config` module are used solely for type-checking usage of the `logging.config` module itself. They cannot be imported and used to type annotate other modules. 105 | - The standard library `typing` module includes a `NoReturn` type. This would seem useful for [unreachable code](https://typing.readthedocs.io/en/stable/source/unreachable.html), including functions that do not return a value, such as test functions. Unfortunately mypy reports an error when using `NoReturn`, "Implicit return in function which does not return (misc)." To avoid headaches from the opaque "misc" category of [mypy errors](https://mypy.readthedocs.io/en/stable/error_code_list.html), these functions are annotated as returning `None`. 106 | - [Mypy](https://mypy.readthedocs.io/en/stable/) is used for type-checking. [Mypy configuration](https://mypy.readthedocs.io/en/stable/config_file.html) is included in _pyproject.toml_. 107 | - Mypy strict mode is enabled. Strict includes `--no-explicit-reexport` (`implicit_reexport = false`), which means that objects imported into a module will not be re-exported for import into other modules. Imports can be made into explicit exports with the syntax `from module import x as x` (i.e., changing from `import logging` to `import logging as logging`), or by including imports in `__all__`. This explicit import syntax can be confusing. Another option is to apply mypy overrides to any modules that need to leverage implicit exports. 108 | 109 | ### Spell check 110 | 111 | Spell check is performed with [CSpell](https://cspell.org/). The CSpell command is included in the Hatch script for code quality checks (`hatch run check`). 112 | 113 | ## GitHub Actions workflows 114 | 115 | [GitHub Actions](https://github.com/features/actions) is a continuous integration/continuous deployment (CI/CD) service that runs on GitHub repos. It replaces other services like Travis CI. Actions are grouped into workflows and stored in _.github/workflows_. See [Getting the Gist of GitHub Actions](https://gist.github.com/br3ndonland/f9c753eb27381f97336aa21b8d932be6) for more info. 116 | 117 | ## Maintainers 118 | 119 | - **The default branch is `develop`.** 120 | - **PRs should be merged into `develop`.** Head branches are deleted automatically after PRs are merged. 121 | - **The only merges to `main` should be fast-forward merges from `develop`.** 122 | - **Branch protection is enabled on `develop` and `main`.** 123 | - `develop`: 124 | - Require signed commits 125 | - Include administrators 126 | - Allow force pushes 127 | - `main`: 128 | - Require signed commits 129 | - Include administrators 130 | - Do not allow force pushes 131 | - Require status checks to pass before merging (commits must have previously been pushed to `develop` and passed all checks) 132 | - **To create a release:** 133 | - Bump the version number in `__version__` with `hatch version` and commit the changes to `develop`. 134 | - Follow [SemVer](https://semver.org/) guidelines when choosing a version number. Note that [PEP 440](https://peps.python.org/pep-0440/) Python version specifiers and SemVer version specifiers differ, particularly with regard to specifying prereleases. Use syntax compatible with both. 135 | - The PEP 440 default (like `1.0.0a0`) is different from SemVer. Hatch and PyPI will use this syntax by default. 136 | - An alternative form of the Python prerelease syntax permitted in PEP 440 (like `1.0.0-alpha.0`) is compatible with SemVer, and this form should be used when tagging releases. As Hatch uses PEP 440 syntax by default, prerelease versions need to be written directly into `__version__`. 137 | - Examples of acceptable tag names: `1.0.0`, `1.0.0-alpha.0`, `1.0.0-beta.1` 138 | - Push to `develop` and verify all CI checks pass. 139 | - Fast-forward merge to `main`, push, and verify all CI checks pass. 140 | - Create an [annotated and signed Git tag](https://www.git-scm.com/book/en/v2/Git-Basics-Tagging). 141 | - List PRs and commits in the tag message: 142 | ```sh 143 | git log --pretty=format:"- %s (%h)" \ 144 | "$(git describe --abbrev=0 --tags)"..HEAD 145 | ``` 146 | - Omit the leading `v` (use `1.0.0` instead of `v1.0.0`) 147 | - Example: `git tag -a -s 1.0.0` 148 | - Push the tag. GitHub Actions will build and publish the Python package. 149 | - Consider [keeping a changelog](https://keepachangelog.com/en/1.0.0/). There are many tools and approaches for this. Most of them work like this: 150 | 1. Accumulate "fragments" as the project is developed. These could be text files in a directory under version control, or could also be Git commit/PR/tag messages. Fragments should contain human-readable summaries of code changes. 151 | 2. Collect the fragments and combine them into a text file like CHANGELOG.md. 152 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a problem 4 | --- 5 | 6 | ## Description 7 | 8 | 9 | 10 | - System info 11 | - Operating system and version: 12 | - Browser and version: 13 | - Steps to reproduce: 14 | - Step 15 | - Next step 16 | 17 | 18 | 19 | ## Suggestions 20 | 21 | 22 | 23 | 24 | 25 | ## Related 26 | 27 | Relates to organization/repo#number 28 | 29 | - [ ] I have reviewed the [Guidelines for Contributing](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | ## Description 7 | 8 | 9 | 10 | 11 | 12 | ## Suggestions 13 | 14 | 15 | 16 | 17 | 18 | ## Related 19 | 20 | Relates to organization/repo#number 21 | 22 | - [ ] I have reviewed the [Guidelines for Contributing](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | This repository needs 4 | 5 | The commits in this pull request will 6 | 7 | ## Changes 8 | 9 | - Imperative commit or change title (commit ID) 10 | - Add some bullet points to explain the change 11 | - Next commit or change (commit ID) 12 | 13 | ## Related 14 | 15 | Relates to organization/repo#number 16 | 17 | 18 | 19 | Closes organization/repo#number 20 | 21 | 22 | 23 | - [ ] I have reviewed the [Guidelines for Contributing](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [develop, main] 7 | tags: ["[0-9]+.[0-9]+.[0-9]+*"] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.10", "3.11", "3.12", "3.13"] 16 | env: 17 | HATCH_ENV: "ci" 18 | HATCH_VERSION: "1.14.0" 19 | PIPX_VERSION: "1.7.1" 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-python@v5 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Set up pip cache 26 | if: runner.os == 'Linux' 27 | uses: actions/cache@v4 28 | with: 29 | path: ~/.cache/pip 30 | key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }} 31 | restore-keys: ${{ runner.os }}-pip- 32 | - name: Install pipx for Python ${{ matrix.python-version }} 33 | run: python -m pip install "pipx==$PIPX_VERSION" 34 | - name: Install Hatch 35 | run: pipx install "hatch==$HATCH_VERSION" 36 | - name: Test Hatch version 37 | run: | 38 | HATCH_VERSION_INSTALLED=$(hatch --version) 39 | echo "The HATCH_VERSION environment variable is set to $HATCH_VERSION." 40 | echo "The installed Hatch version is ${HATCH_VERSION_INSTALLED##Hatch, version }." 41 | case $HATCH_VERSION_INSTALLED in 42 | *$HATCH_VERSION) echo "Hatch version correct." ;; 43 | *) echo "Hatch version incorrect." && exit 1 ;; 44 | esac 45 | - name: Install dependencies 46 | run: hatch env create ci 47 | - name: Test virtualenv location 48 | run: | 49 | EXPECTED_VIRTUALENV_PATH=$GITHUB_WORKSPACE/.venv 50 | INSTALLED_VIRTUALENV_PATH=$(hatch env find) 51 | echo "The virtualenv should be at $EXPECTED_VIRTUALENV_PATH." 52 | echo "Hatch is using a virtualenv at $INSTALLED_VIRTUALENV_PATH." 53 | case "$INSTALLED_VIRTUALENV_PATH" in 54 | "$EXPECTED_VIRTUALENV_PATH") echo "Correct Hatch virtualenv." ;; 55 | *) echo "Incorrect Hatch virtualenv." && exit 1 ;; 56 | esac 57 | - name: Test that Git tag version and Python package version match 58 | if: github.ref_type == 'tag' && matrix.python-version == '3.13' 59 | run: | 60 | GIT_TAG_VERSION=$GITHUB_REF_NAME 61 | PACKAGE_VERSION=$(hatch version) 62 | echo "The Python package version is $PACKAGE_VERSION." 63 | echo "The Git tag version is $GIT_TAG_VERSION." 64 | if [ "$PACKAGE_VERSION" = "$GIT_TAG_VERSION" ]; then 65 | echo "Versions match." 66 | else 67 | echo "Versions do not match." && exit 1 68 | fi 69 | - name: Run Hatch script for code quality checks 70 | run: hatch run ${{ env.HATCH_ENV }}:check 71 | - name: Run tests 72 | run: hatch run coverage run 73 | - name: Enforce test coverage 74 | run: hatch run coverage report 75 | - name: Build Python package 76 | run: hatch build 77 | - name: Publish Python package to PyPI 78 | if: github.ref_type == 'tag' && matrix.python-version == '3.13' 79 | run: hatch publish -n -u __token__ -a ${{ secrets.PYPI_TOKEN }} 80 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: codeql 2 | 3 | on: 4 | push: 5 | branches: [develop, main] 6 | pull_request: 7 | branches: [develop, main] 8 | schedule: 9 | - cron: "0 13 * * 1" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | analyze: 14 | permissions: 15 | actions: read 16 | contents: read 17 | security-events: write 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-python@v5 22 | with: 23 | python-version: "3.x" 24 | - uses: github/codeql-action/init@v3 25 | with: 26 | languages: python 27 | setup-python-dependencies: false 28 | - uses: github/codeql-action/analyze@v3 29 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: stale 2 | 3 | on: 4 | schedule: 5 | - cron: "0 13 * * 1" 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v3 12 | with: 13 | days-before-stale: 30 14 | days-before-close: 1 15 | stale-issue-label: "stale :zzz:" 16 | stale-issue-message: "This issue is stale. Remove stale label or it will be closed tomorrow." 17 | stale-pr-label: "stale :zzz:" 18 | stale-pr-message: "This PR is stale. Remove stale label or it will be closed tomorrow." 19 | exempt-issue-labels: "in progress :construction:" 20 | exempt-pr-labels: "in progress :construction:" 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,python,virtualenv,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,python,virtualenv,visualstudiocode 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | ### Python ### 33 | # Byte-compiled / optimized / DLL files 34 | __pycache__/ 35 | *.py[cod] 36 | *$py.class 37 | 38 | # C extensions 39 | *.so 40 | 41 | # Distribution / packaging 42 | .Python 43 | build/ 44 | develop-eggs/ 45 | dist/ 46 | downloads/ 47 | eggs/ 48 | .eggs/ 49 | parts/ 50 | sdist/ 51 | var/ 52 | wheels/ 53 | pip-wheel-metadata/ 54 | share/python-wheels/ 55 | *.egg-info/ 56 | .installed.cfg 57 | *.egg 58 | MANIFEST 59 | 60 | # PyInstaller 61 | *.manifest 62 | *.spec 63 | 64 | # Installer logs 65 | pip-log.txt 66 | pip-delete-this-directory.txt 67 | 68 | # Unit test / coverage reports 69 | htmlcov/ 70 | .tox/ 71 | .nox/ 72 | .coverage 73 | .coverage.* 74 | .cache 75 | nosetests.xml 76 | coverage.xml 77 | *.cover 78 | *.py,cover 79 | .hypothesis/ 80 | .pytest_cache/ 81 | pytestdebug.log 82 | 83 | # Translations 84 | *.mo 85 | *.pot 86 | 87 | # Django stuff: 88 | *.log 89 | local_settings.py 90 | db.sqlite3 91 | db.sqlite3-journal 92 | 93 | # Flask stuff: 94 | instance/ 95 | .webassets-cache 96 | 97 | # Scrapy stuff: 98 | .scrapy 99 | 100 | # Sphinx documentation 101 | docs/_build/ 102 | doc/_build/ 103 | 104 | # PyBuilder 105 | target/ 106 | 107 | # Jupyter Notebook 108 | .ipynb_checkpoints 109 | 110 | # IPython 111 | profile_default/ 112 | ipython_config.py 113 | 114 | # pyenv 115 | .python-version 116 | 117 | # pipenv 118 | # Pipfile.lock 119 | 120 | # poetry 121 | # poetry.lock 122 | 123 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 124 | __pypackages__/ 125 | 126 | # Celery stuff 127 | celerybeat-schedule 128 | celerybeat.pid 129 | 130 | # SageMath parsed files 131 | *.sage.py 132 | 133 | # Environments 134 | *.env* 135 | .env/ 136 | .hatch* 137 | .venv* 138 | .virtualenv* 139 | env/ 140 | venv/ 141 | ENV/ 142 | env.bak/ 143 | venv.bak/ 144 | pythonenv* 145 | 146 | # Spyder project settings 147 | .spyderproject 148 | .spyproject 149 | 150 | # Rope project settings 151 | .ropeproject 152 | 153 | # mkdocs documentation 154 | /public 155 | /site 156 | 157 | # mypy 158 | .mypy_cache/ 159 | .dmypy.json 160 | dmypy.json 161 | 162 | # Pyre type checker 163 | .pyre/ 164 | 165 | # pytype static type analyzer 166 | .pytype/ 167 | 168 | # operating system-related files 169 | # *.DS_Store 170 | Thumbs.db 171 | 172 | # profiling data 173 | .prof 174 | 175 | ### VirtualEnv ### 176 | # Virtualenv 177 | [Bb]in 178 | [Ii]nclude 179 | [Ll]ib 180 | [Ll]ib64 181 | [Ll]ocal 182 | [Ss]cripts 183 | pyvenv.cfg 184 | .venv 185 | pip-selfcheck.json 186 | 187 | ### VisualStudioCode ### 188 | *.code-workspace 189 | .vscode/* 190 | !.vscode/tasks.json 191 | !.vscode/launch.json 192 | .history 193 | .ionide 194 | 195 | # End of https://www.toptal.com/developers/gitignore/api/macos,python,virtualenv,visualstudiocode 196 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *cache* 2 | *venv* 3 | htmlcov 4 | public 5 | site 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Brendon Smith 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python template repository 2 | 3 | [![ci](https://github.com/br3ndonland/template-python/workflows/ci/badge.svg)](https://github.com/br3ndonland/template-python/actions/workflows/ci.yml) 4 | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) 5 | 6 | Brendon Smith ([br3ndonland](https://github.com/br3ndonland)) 7 | 8 | ## Description 9 | 10 | **Welcome!** This is a template repository for Python projects, engineered for use as a [GitHub template repository](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template). To use the template, click on "Use this template" or browse to [template-python/generate](https://github.com/br3ndonland/template-python/generate). GitHub will create a new repository without the commit history from this one. 11 | 12 | The `template-python` repo name can be replaced with a one-line terminal command: `git grep -l 'template-python' | xargs sed -i '' 's/template-python/repo-name/g'` (replace `repo-name` with the name of the repository you generate). There may also be a few edits to the _pyproject.toml_ needed. See the [quickstart](#quickstart) section for more. 13 | 14 | Another common approach, especially for Python, is to use [cookiecutter](https://github.com/cookiecutter/cookiecutter). In a cookiecutter repo, the developer adds template variables throughout, like `{{cookiecutter.repo_name}}`. When a user runs `cookiecutter` using the template repository, the template variables are replaced with the information the user provides. This repo is simple enough that I haven't needed to add cookiecutter yet. 15 | 16 | [Copier](https://copier.readthedocs.io/en/stable/) and [PyScaffold](https://pyscaffold.org/en/stable/) are similar to cookiecutter, with some additional benefits. I may consider updating this repo for Copier or PyScaffold. 17 | 18 | ## Quickstart 19 | 20 | [Install Hatch](https://hatch.pypa.io/latest/install/), rename the project, then install the project: 21 | 22 | ```sh 23 | ❯ cd path/to/repo 24 | # Replace instances of template-python with new repo name 25 | # In the command below, use your repo name instead of 'repo-name' 26 | ❯ git grep -l 'template-python' | xargs sed -i '' 's|template-python|repo-name|g' 27 | ❯ git grep -l 'template_python' | xargs sed -i '' 's|template_python|repo-name|g' 28 | # Try running the tests 29 | ❯ hatch run coverage run 30 | ``` 31 | 32 | ## Further information 33 | 34 | See [CONTRIBUTING.md](.github/CONTRIBUTING.md). 35 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePaths": ["CHANGELOG.md", "**/*changelog.md"], 3 | "ignoreRegExpList": ["/(`{3})[\\s\\S]+?\\1/g", "/`([\\s\\S]+?)`/g"], 4 | "languageSettings": [ 5 | { 6 | "languageId": "python", 7 | "includeRegExpList": ["/#.*/", "/('''|\"\"\")[^\\1]+?\\1/g"] 8 | }, 9 | { 10 | "languageId": "javascript", 11 | "includeRegExpList": ["CStyleComment"] 12 | }, 13 | { 14 | "languageId": "json", 15 | "includeRegExpList": ["CStyleComment"] 16 | }, 17 | { 18 | "languageId": "typescript", 19 | "includeRegExpList": ["CStyleComment"] 20 | } 21 | ], 22 | "useGitignore": true, 23 | "words": [ 24 | "anyio", 25 | "asgi", 26 | "asyncio", 27 | "autoformat", 28 | "autoformatter", 29 | "autoformatters", 30 | "autoformatting", 31 | "boto", 32 | "br3ndonland", 33 | "brendon", 34 | "ci", 35 | "cicd", 36 | "codespace", 37 | "codespaces", 38 | "codium", 39 | "configurator", 40 | "cookiecutter", 41 | "cpython", 42 | "cython", 43 | "dependabot", 44 | "dotenv", 45 | "dotfiles", 46 | "extensibly", 47 | "fastapi's", 48 | "fastapi", 49 | "fastenv", 50 | "gunicorn", 51 | "heredoc", 52 | "heredocs", 53 | "httpx", 54 | "isort", 55 | "kwarg", 56 | "kwargs", 57 | "macos", 58 | "mkdocs", 59 | "monkeypatch", 60 | "monkeypatches", 61 | "monkeypatching", 62 | "mypy", 63 | "ndonland", 64 | "noqa", 65 | "oidc", 66 | "pipx", 67 | "postinstallation", 68 | "prereleases", 69 | "prestart", 70 | "prettier", 71 | "prettierignore", 72 | "pydantic", 73 | "pypi", 74 | "pypoetry", 75 | "pyproject", 76 | "pytest", 77 | "pythonpath", 78 | "quickstart", 79 | "redeclare", 80 | "redeclared", 81 | "redeclares", 82 | "redeclaring", 83 | "regexes", 84 | "repo", 85 | "repos", 86 | "roadmap", 87 | "setuptools", 88 | "sha", 89 | "shas", 90 | "sourcery", 91 | "starlette's", 92 | "starlette", 93 | "stdlib", 94 | "subpackages", 95 | "typeshed", 96 | "unittests", 97 | "uvicorn", 98 | "vendorizes", 99 | "venv", 100 | "virtualenv", 101 | "virtualenvs" 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /docs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4 3 | } 4 | -------------------------------------------------------------------------------- /docs/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/template-python/03f2e57cbc136915a4c9a52e9f68b3e7d61be07f/docs/assets/images/favicon.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to MkDocs 2 | 3 | For full documentation visit [mkdocs.org](https://www.mkdocs.org). 4 | 5 | ## Commands 6 | 7 | - `mkdocs new [dir-name]` - Create a new project. 8 | - `mkdocs serve` - Start the live-reloading docs server. 9 | - `mkdocs build` - Build the documentation site. 10 | - `mkdocs -h` - Print help message and exit. 11 | 12 | ## Project layout 13 | 14 | mkdocs.yml # The configuration file. 15 | docs/ 16 | index.md # The documentation homepage. 17 | ... # Other markdown pages, images and other files. 18 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | edit_uri: "" 2 | markdown_extensions: 3 | - admonition 4 | - pymdownx.inlinehilite 5 | - pymdownx.snippets 6 | - pymdownx.superfences 7 | - pymdownx.tasklist: 8 | clickable_checkbox: false 9 | custom_checkbox: true 10 | - toc: 11 | permalink: true 12 | toc_depth: 3 13 | nav: 14 | - "index.md" 15 | repo_name: br3ndonland/template-python 16 | repo_url: https://github.com/br3ndonland/template-python 17 | site_name: template-python docs 18 | site_url: "" 19 | theme: 20 | favicon: assets/images/favicon.png 21 | features: 22 | - content.code.copy 23 | - header.autohide 24 | - navigation.instant 25 | - navigation.top 26 | font: false 27 | icon: 28 | logo: octicons/book-24 29 | repo: octicons/mark-github-16 30 | name: material 31 | palette: 32 | - media: "(prefers-color-scheme: dark)" 33 | accent: cyan 34 | primary: cyan 35 | scheme: slate 36 | toggle: 37 | icon: material/weather-night 38 | name: Switch to light mode 39 | - media: "(prefers-color-scheme: light)" 40 | accent: cyan 41 | primary: cyan 42 | scheme: default 43 | toggle: 44 | icon: material/weather-sunny 45 | name: Switch to dark mode 46 | use_directory_urls: false 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "hatchling.build" 3 | requires = ["hatchling>=1.26.3,<2"] 4 | 5 | [project] 6 | authors = [{ email = "you@example.com", name = "Your Name" }] 7 | classifiers = [ 8 | "Natural Language :: English", 9 | "Programming Language :: Python :: 3", 10 | "Programming Language :: Python :: 3.10", 11 | "Programming Language :: Python :: 3.11", 12 | "Programming Language :: Python :: 3.12", 13 | "Programming Language :: Python :: 3.13", 14 | "Typing :: Typed", 15 | ] 16 | dependencies = [] 17 | description = "Your project description here." 18 | dynamic = ["version"] 19 | license = "MIT" 20 | name = "template-python" 21 | readme = "README.md" 22 | requires-python = ">=3.10,<4" 23 | 24 | [project.optional-dependencies] 25 | checks = [ 26 | "mypy==1.15.0", 27 | "ruff>=0.11,<0.12", 28 | ] 29 | docs = [ 30 | "mkdocs-material>=9,<10", 31 | ] 32 | tests = [ 33 | "coverage[toml]>=7,<8", 34 | "httpx>=0.23,<1", 35 | "pytest>=8.1.1,<9", 36 | "pytest-mock>=3,<4", 37 | ] 38 | 39 | [tool.coverage.report] 40 | exclude_lines = ["if TYPE_CHECKING:", "pragma: no cover"] 41 | fail_under = 70 42 | show_missing = true 43 | 44 | [tool.coverage.run] 45 | command_line = "-m pytest" 46 | source = ["template_python", "tests"] 47 | 48 | [tool.hatch.build.targets.sdist] 49 | include = ["/template_python"] 50 | 51 | [tool.hatch.build.targets.wheel] 52 | packages = ["template_python"] 53 | 54 | [tool.hatch.envs.ci] 55 | dev-mode = false 56 | features = [ 57 | "checks", 58 | "tests", 59 | ] 60 | path = ".venv" 61 | 62 | [tool.hatch.envs.default] 63 | dev-mode = true 64 | features = [ 65 | "checks", 66 | "docs", 67 | "tests", 68 | ] 69 | path = ".venv" 70 | 71 | [tool.hatch.envs.default.scripts] 72 | check = [ 73 | "ruff check", 74 | "ruff format --check", 75 | "mypy", 76 | "npx -s -y prettier@'^3.4' . --check", 77 | "npx -s -y cspell --dot --gitignore *.md **/*.md", 78 | ] 79 | format = [ 80 | "ruff check --fix", 81 | "ruff format", 82 | "npx -s -y prettier@'^3.4' . --write", 83 | ] 84 | 85 | [tool.hatch.envs.production] 86 | dev-mode = false 87 | features = [] 88 | path = ".venv" 89 | 90 | [tool.hatch.version] 91 | path = "template_python/__init__.py" 92 | 93 | [tool.mypy] 94 | files = ["**/*.py"] 95 | show_error_codes = true 96 | strict = true 97 | 98 | [tool.pytest.ini_options] 99 | addopts = "-q" 100 | minversion = "6.0" 101 | testpaths = ["tests"] 102 | 103 | [tool.ruff] 104 | src = ["template_python", "tests"] 105 | 106 | [tool.ruff.format] 107 | docstring-code-format = true 108 | 109 | [tool.ruff.lint] 110 | extend-select = ["I"] 111 | 112 | [tool.ruff.lint.isort] 113 | known-first-party = ["template_python", "tests"] 114 | -------------------------------------------------------------------------------- /template_python/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | -------------------------------------------------------------------------------- /template_python/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/template-python/03f2e57cbc136915a4c9a52e9f68b3e7d61be07f/template_python/examples/__init__.py -------------------------------------------------------------------------------- /template_python/examples/fibonacci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Fibonacci numbers""" 3 | 4 | 5 | def user_input() -> str: 6 | """Accept user input for Fibonacci function 7 | --- 8 | - Accept an integer from user input. 9 | - Throw exception if user inputs non-integer. 10 | """ 11 | try: 12 | n = input("Please enter a number: ") 13 | return ( 14 | f"Fibonacci number {n} is: {f(int(n))}. The full list is:\n{f_list(int(n))}" 15 | ) 16 | except Exception as e: 17 | raise e 18 | 19 | 20 | def f(n: int) -> int: 21 | """Return the nth Fibonacci number.""" 22 | a, b = 0, 1 23 | for _ in range(n): 24 | a, b = b, a + b 25 | return a 26 | 27 | 28 | def f_list(n: int) -> list[int]: 29 | """Return a list of the first n Fibonacci numbers.""" 30 | out = [] 31 | a, b = 0, 1 32 | for _ in range(n): 33 | a, b = b, a + b 34 | out.append(a) 35 | return out 36 | 37 | 38 | if __name__ == "__main__": 39 | print(user_input()) 40 | -------------------------------------------------------------------------------- /template_python/examples/fizzbuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FizzBuzz 4 | https://medium.freecodecamp.org/a-software-engineering-survival-guide-fe3eafb47166 5 | https://medium.freecodecamp.org/coding-interviews-for-dummies-5e048933b82b 6 | This solution uses the following syntax features: 7 | Modulo (%, remainder) 8 | Strict equality (==) 9 | Addition assignment (+=) 10 | """ 11 | 12 | 13 | def fizzbuzz_print() -> None: 14 | """Print 1-100 15 | --- 16 | - Multiples of 3: Fizz 17 | - Multiples of 5: Buzz 18 | - Multiples of 3 and 5: FizzBuzz 19 | """ 20 | for i in range(1, 101): 21 | out = "" 22 | if i % 3 == 0: 23 | out += "Fizz" 24 | if i % 5 == 0: 25 | out += "Buzz" 26 | print(out or i) 27 | 28 | 29 | def fizzbuzz_list() -> list[int | str]: 30 | """Create a list 1-100 31 | --- 32 | - Multiples of 3 and 5: FizzBuzz 33 | - Multiples of 3: Fizz 34 | - Multiples of 5: Buzz 35 | - Else: integer 36 | """ 37 | out: list[int | str] = [] 38 | for i in range(100): 39 | if i % 3 == 0 and i % 5 == 0: 40 | out.insert(i, "FizzBuzz") 41 | elif i % 3 == 0: 42 | out.insert(i, "Fizz") 43 | elif i % 5 == 0: 44 | out.insert(i, "Buzz") 45 | else: 46 | out.insert(i, i) 47 | return out 48 | 49 | 50 | if __name__ == "__main__": 51 | print(fizzbuzz_list()) 52 | -------------------------------------------------------------------------------- /template_python/examples/palindrome.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Palindromes""" 3 | 4 | import re 5 | 6 | 7 | def user_input() -> str: 8 | """Accept user input for palindrome function""" 9 | try: 10 | s = input("Please provide an input to test: ") 11 | prep_input(s) 12 | return s 13 | except Exception: 14 | raise 15 | 16 | 17 | def prep_input(s: str) -> bool: 18 | """Clean and validate user input for palindrome function 19 | --- 20 | - Convert user input string to lowercase. 21 | - Remove any characters other than word and digit. 22 | - Verify that length is at least 3 characters. 23 | """ 24 | try: 25 | s = re.sub(r"[^\w\d]", "", s.lower()) 26 | if len(s) > 2 and palindrome(s): 27 | print(f"Success! The input {s} is a palindrome.") 28 | return True 29 | else: 30 | print(f"The input {s} is not a palindrome.") 31 | return False 32 | except Exception as e: 33 | print(f"An exception occurred:\n{e}\nPlease try again.") 34 | raise 35 | 36 | 37 | def palindrome(s: str) -> bool: 38 | """Identify palindromes 39 | --- 40 | - Accept a string from user input, after validation. 41 | - Create a new object with the reversed string. 42 | - Compare forward and reversed strings. 43 | """ 44 | backwards = s[::-1] 45 | return s == backwards 46 | 47 | 48 | if __name__ == "__main__": 49 | print(user_input()) 50 | -------------------------------------------------------------------------------- /template_python/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/template-python/03f2e57cbc136915a4c9a52e9f68b3e7d61be07f/template_python/py.typed -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/template-python/03f2e57cbc136915a4c9a52e9f68b3e7d61be07f/tests/__init__.py -------------------------------------------------------------------------------- /tests/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/template-python/03f2e57cbc136915a4c9a52e9f68b3e7d61be07f/tests/examples/__init__.py -------------------------------------------------------------------------------- /tests/examples/test_fibonacci.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from template_python.examples import fibonacci 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "input_n,output_n", 8 | [ 9 | (1, 1), 10 | (2, 1), 11 | (3, 2), 12 | (4, 3), 13 | (5, 5), 14 | (6, 8), 15 | (7, 13), 16 | (8, 21), 17 | (9, 34), 18 | (10, 55), 19 | (11, 89), 20 | (12, 144), 21 | (13, 233), 22 | (14, 377), 23 | (15, 610), 24 | ], 25 | ) 26 | def test_fibonacci(input_n: int, output_n: int) -> None: 27 | assert fibonacci.f(input_n) == output_n 28 | -------------------------------------------------------------------------------- /tests/examples/test_fizzbuzz.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from template_python.examples import fizzbuzz 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "input_item,output", 8 | [(1, 1), (2, 2), (3, "Fizz"), (4, 4), (5, "Buzz"), (15, "FizzBuzz")], 9 | ) 10 | def test_fizzbuzz(input_item: int, output: int | str) -> None: 11 | assert fizzbuzz.fizzbuzz_list()[input_item] == output 12 | -------------------------------------------------------------------------------- /tests/examples/test_palindrome.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from template_python.examples import palindrome 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "input_string,output_boolean", 8 | [ 9 | ("racecar", True), 10 | ("A man, A Plan, A Canal, Panama", True), 11 | ("Foo bar", False), 12 | ("[]", False), 13 | ], 14 | ) 15 | def test_palindrome(input_string: str, output_boolean: bool) -> None: 16 | assert palindrome.prep_input(input_string) is output_boolean 17 | 18 | 19 | def test_palindrome_incorrect_input_type() -> None: 20 | with pytest.raises(AttributeError): 21 | assert palindrome.prep_input([]) is False # type: ignore 22 | raise 23 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import packaging.version 2 | import pytest 3 | 4 | import template_python 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "version,expected_version", 9 | ((template_python.__version__, "0.1.0"),), 10 | ) 11 | def test_version_matches_expected(version: str, expected_version: str) -> None: 12 | assert version == expected_version 13 | 14 | 15 | def test_version_is_valid() -> None: 16 | packaging.version.parse(template_python.__version__) 17 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanUrls": true, 3 | "trailingSlash": false 4 | } 5 | --------------------------------------------------------------------------------