├── tests
├── __init__.py
└── test_python_boilerplate.py
├── .python-version
├── .gitattributes
├── src
└── python_boilerplate
│ ├── __init__.py
│ └── main.py
├── .editorconfig
├── .renovaterc.json5
├── Dockerfile
├── .github
└── workflows
│ ├── codeql.yml
│ ├── ci.yml
│ └── pr.yml
├── LICENSE
├── .pre-commit-config.yaml
├── pyproject.toml
├── .gitignore
├── README.md
└── uv.lock
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.14.2
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/src/python_boilerplate/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.1.0"
2 |
--------------------------------------------------------------------------------
/tests/test_python_boilerplate.py:
--------------------------------------------------------------------------------
1 | from python_boilerplate.main import hello_world
2 |
3 |
4 | def test_hello_world() -> None:
5 | if hello_world() != "Hello World":
6 | raise ValueError('Expected value to be "Hello World"')
7 |
--------------------------------------------------------------------------------
/src/python_boilerplate/main.py:
--------------------------------------------------------------------------------
1 | def hello_world() -> str:
2 | return "Hello World"
3 |
4 |
5 | def main() -> None:
6 | print(hello_world()) # noqa: T201 print only used as placeholder :)
7 |
8 |
9 | if __name__ == "__main__":
10 | main()
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.py]
12 | indent_size = 4
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.renovaterc.json5:
--------------------------------------------------------------------------------
1 | {
2 | $schema: "https://docs.renovatebot.com/renovate-schema.json",
3 | extends: [
4 | "config:recommended",
5 | "schedule:daily",
6 | "group:all",
7 | ":prConcurrentLimitNone",
8 | ":prHourlyLimitNone",
9 | ":prImmediately",
10 | ],
11 | labels: ["dependencies"],
12 | enabledManagers: [
13 | "dockerfile",
14 | "github-actions",
15 | "pre-commit",
16 | "pep621",
17 | "pyenv",
18 | ],
19 | "pre-commit": {
20 | enabled: true,
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.14.2-slim-trixie AS python-base
2 |
3 | ENV PYTHONUNBUFFERED=1 \
4 | PYTHONDONTWRITEBYTECODE=1 \
5 | WORKDIR_PATH="/opt/python-boilerplate" \
6 | VIRTUAL_ENV="/opt/python-boilerplate/.venv"
7 |
8 | ENV PATH="$VIRTUAL_ENV/bin:$PATH"
9 |
10 | FROM python-base AS builder-base
11 |
12 | COPY --from=ghcr.io/astral-sh/uv:0.9.18 /uv /uvx /bin/
13 |
14 | WORKDIR $WORKDIR_PATH
15 |
16 | COPY . .
17 |
18 | RUN uv sync --frozen
19 |
20 | FROM builder-base AS development
21 |
22 | CMD ["python","-m", "python_boilerplate.main"]
23 |
24 | FROM python-base AS production
25 |
26 | COPY --from=builder-base $VIRTUAL_ENV $VIRTUAL_ENV
27 |
28 | WORKDIR $WORKDIR_PATH
29 |
30 | COPY ./src/ ./
31 |
32 | USER 10000
33 |
34 | CMD ["python","-m", "python_boilerplate.main"]
35 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "codeql"
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 | pull_request:
7 | branches: ["main"]
8 |
9 | jobs:
10 | analyze:
11 | name: Analyze
12 | runs-on: ubuntu-24.04
13 | permissions:
14 | actions: read
15 | contents: read
16 | security-events: write
17 |
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | language: [python]
22 |
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v6
26 |
27 | - name: Initialize CodeQL
28 | uses: github/codeql-action/init@v4
29 | with:
30 | languages: ${{ matrix.language }}
31 | queries: +security-and-quality
32 |
33 | - name: Autobuild
34 | uses: github/codeql-action/autobuild@v4
35 |
36 | - name: Perform CodeQL Analysis
37 | uses: github/codeql-action/analyze@v4
38 | with:
39 | category: "/language:${{ matrix.language }}"
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Samuel MARLHENS
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 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | build:
14 | env:
15 | UV_CACHE_DIR: /tmp/.uv-cache
16 | runs-on: ubuntu-24.04
17 | steps:
18 | - name: Checkout source code
19 | uses: actions/checkout@v6
20 | - name: Set up python
21 | id: setup-python
22 | uses: actions/setup-python@v6
23 | with:
24 | python-version-file: ".python-version"
25 | - name: Set up uv
26 | uses: astral-sh/setup-uv@v7
27 | with:
28 | enable-cache: true
29 | version-file: "pyproject.toml"
30 | - name: Install dependencies
31 | run: uv sync --all-extras --dev --frozen
32 | - name: Test with pytest
33 | run: uv run pytest tests --cov=src
34 | - name: Build a wheel
35 | run: uv build
36 | - name: Minimize uv cache
37 | run: uv cache prune --ci
38 |
39 | build-image:
40 | runs-on: ubuntu-24.04
41 |
42 | steps:
43 | - name: Checkout source code
44 | uses: actions/checkout@v6
45 | - name: Set up Docker Buildx
46 | uses: docker/setup-buildx-action@v3
47 | - name: Build docker image
48 | uses: docker/build-push-action@v6
49 | with:
50 | context: .
51 | push: false
52 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: pr
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - "**"
7 |
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | build:
14 | env:
15 | UV_CACHE_DIR: /tmp/.uv-cache
16 | runs-on: ubuntu-24.04
17 | steps:
18 | - name: Checkout source code
19 | uses: actions/checkout@v6
20 | - name: Set up python
21 | id: setup-python
22 | uses: actions/setup-python@v6
23 | with:
24 | python-version-file: ".python-version"
25 | - name: Set up uv
26 | uses: astral-sh/setup-uv@v7
27 | with:
28 | enable-cache: true
29 | version-file: "pyproject.toml"
30 | - name: Install dependencies
31 | run: uv sync --all-extras --dev --frozen
32 | - name: Test with pytest
33 | run: uv run pytest tests --cov=src
34 | - name: Build a wheel
35 | run: uv build
36 | - name: Minimize uv cache
37 | run: uv cache prune --ci
38 |
39 | build-image:
40 | runs-on: ubuntu-24.04
41 |
42 | steps:
43 | - name: Checkout source code
44 | uses: actions/checkout@v6
45 | - name: Set up Docker Buildx
46 | uses: docker/setup-buildx-action@v3
47 | - name: Build docker image
48 | uses: docker/build-push-action@v6
49 | with:
50 | context: .
51 | push: false
52 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ci:
2 | skip: [pytest]
3 |
4 | default_language_version:
5 | python: python3.14
6 |
7 | repos:
8 | # general checks (see here: https://pre-commit.com/hooks.html)
9 | - repo: https://github.com/pre-commit/pre-commit-hooks
10 | rev: v6.0.0
11 | hooks:
12 | - id: check-yaml
13 | args: [--allow-multiple-documents]
14 | - id: end-of-file-fixer
15 | - id: trailing-whitespace
16 |
17 | # ruff - linting + formatting
18 | - repo: https://github.com/astral-sh/ruff-pre-commit
19 | rev: v0.14.9
20 | hooks:
21 | - id: ruff
22 | name: ruff
23 | - id: ruff-format
24 | name: ruff-format
25 |
26 | # mypy - lint-like type checking
27 | - repo: https://github.com/pre-commit/mirrors-mypy
28 | rev: v1.19.1
29 | hooks:
30 | - id: mypy
31 | name: mypy
32 |
33 | # bandit - find common security issues
34 | - repo: https://github.com/pycqa/bandit
35 | rev: 1.9.2
36 | hooks:
37 | - id: bandit
38 | name: bandit
39 | exclude: ^tests/
40 | args:
41 | - -r
42 | - src
43 |
44 | - repo: local
45 | hooks:
46 | - id: pytest
47 | name: pytest
48 | entry: uv run pytest tests --cov=src
49 | language: system
50 | types: [python]
51 | pass_filenames: false
52 |
53 | # prettier - formatting JS, CSS, JSON, Markdown, ...
54 | - repo: https://github.com/pre-commit/mirrors-prettier
55 | rev: v3.1.0
56 | hooks:
57 | - id: prettier
58 | exclude: ^uv.lock
59 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "python-boilerplate"
3 | version = "0.1.0"
4 | description = "Python boilerplate"
5 | requires-python = ">=3.14"
6 | license = { file = "LICENSE" }
7 | authors = [
8 | { name = "Samuel MARLHENS", email = "samuel.marlhens@proton.me" },
9 | ]
10 | readme = { file = "README.md", content-type = "text/markdown" }
11 |
12 | [project.urls]
13 | homepage = "https://github.com/smarlhens/python-boilerplate#readme"
14 | repository = "https://github.com/smarlhens/python-boilerplate"
15 |
16 | [build-system]
17 | requires = ["uv_build>=0.9.0,<0.10.0"]
18 | build-backend = "uv_build"
19 |
20 | [dependency-groups]
21 | dev = [
22 | "pytest>=9.0.2",
23 | "pytest-cov>=7.0.0",
24 | "mypy>=1.19.1",
25 | "bandit>=1.9.2",
26 | "ruff>=0.14.9",
27 | ]
28 |
29 | [tool.uv]
30 | required-version = ">=0.9.16"
31 |
32 | [tool.pytest.ini_options]
33 | addopts = "-vvv"
34 | testpaths = "tests"
35 |
36 | [tool.ruff]
37 | extend-exclude = [
38 | "__pycache__",
39 | "build",
40 | "dist",
41 | ]
42 | target-version = "py314"
43 | line-length = 90
44 | src = ["src", "tests"]
45 |
46 | [tool.ruff.lint]
47 | extend-select = [
48 | "C4",
49 | "D200",
50 | "D201",
51 | "D204",
52 | "D205",
53 | "D206",
54 | "D210",
55 | "D211",
56 | "D213",
57 | "D300",
58 | "D400",
59 | "D402",
60 | "D403",
61 | "D404",
62 | "D419",
63 | "E",
64 | "F",
65 | "FURB",
66 | "G010",
67 | "I",
68 | "INP001",
69 | "N805",
70 | "PERF101",
71 | "PERF102",
72 | "PERF401",
73 | "PERF402",
74 | "PGH004",
75 | "PGH005",
76 | "PIE794",
77 | "PIE796",
78 | "PIE807",
79 | "PIE810",
80 | "PLR",
81 | "RET502",
82 | "RET503",
83 | "RET504",
84 | "RET505",
85 | "RUF015",
86 | "RUF032",
87 | "RUF033",
88 | "RUF034",
89 | "RUF041",
90 | "RUF043",
91 | "RUF046",
92 | "RUF057",
93 | "RUF059",
94 | "RUF100",
95 | "S101",
96 | "T20",
97 | "UP",
98 | "W",
99 | ]
100 |
101 | [tool.ruff.lint.pydocstyle]
102 | convention = "pep257"
103 |
104 | [tool.ruff.lint.per-file-ignores]
105 | "tests/*.py" = ["S101"]
106 |
107 | [tool.mypy]
108 | files = ["src", "tests"]
109 | strict = "true"
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python,pycharm+all,visualstudiocode
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm+all,visualstudiocode
3 |
4 | ### PyCharm+all ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/**/usage.statistics.xml
12 | .idea/**/dictionaries
13 | .idea/**/shelf
14 |
15 | # AWS User-specific
16 | .idea/**/aws.xml
17 |
18 | # Generated files
19 | .idea/**/contentModel.xml
20 |
21 | # Sensitive or high-churn files
22 | .idea/**/dataSources/
23 | .idea/**/dataSources.ids
24 | .idea/**/dataSources.local.xml
25 | .idea/**/sqlDataSources.xml
26 | .idea/**/dynamic.xml
27 | .idea/**/uiDesigner.xml
28 | .idea/**/dbnavigator.xml
29 |
30 | # Gradle
31 | .idea/**/gradle.xml
32 | .idea/**/libraries
33 |
34 | # Gradle and Maven with auto-import
35 | # When using Gradle or Maven with auto-import, you should exclude module files,
36 | # since they will be recreated, and may cause churn. Uncomment if using
37 | # auto-import.
38 | # .idea/artifacts
39 | # .idea/compiler.xml
40 | # .idea/jarRepositories.xml
41 | # .idea/modules.xml
42 | # .idea/*.iml
43 | # .idea/modules
44 | # *.iml
45 | # *.ipr
46 |
47 | # CMake
48 | cmake-build-*/
49 |
50 | # Mongo Explorer plugin
51 | .idea/**/mongoSettings.xml
52 |
53 | # File-based project format
54 | *.iws
55 |
56 | # IntelliJ
57 | out/
58 |
59 | # mpeltonen/sbt-idea plugin
60 | .idea_modules/
61 |
62 | # JIRA plugin
63 | atlassian-ide-plugin.xml
64 |
65 | # Cursive Clojure plugin
66 | .idea/replstate.xml
67 |
68 | # SonarLint plugin
69 | .idea/sonarlint/
70 |
71 | # Crashlytics plugin (for Android Studio and IntelliJ)
72 | com_crashlytics_export_strings.xml
73 | crashlytics.properties
74 | crashlytics-build.properties
75 | fabric.properties
76 |
77 | # Editor-based Rest Client
78 | .idea/httpRequests
79 |
80 | # Android studio 3.1+ serialized cache file
81 | .idea/caches/build_file_checksums.ser
82 |
83 | ### PyCharm+all Patch ###
84 | # Ignore everything but code style settings and run configurations
85 | # that are supposed to be shared within teams.
86 |
87 | .idea/*
88 |
89 | !.idea/codeStyles
90 | !.idea/runConfigurations
91 |
92 | ### Python ###
93 | # Byte-compiled / optimized / DLL files
94 | __pycache__/
95 | *.py[cod]
96 | *$py.class
97 |
98 | # C extensions
99 | *.so
100 |
101 | # Distribution / packaging
102 | .Python
103 | build/
104 | develop-eggs/
105 | dist/
106 | downloads/
107 | eggs/
108 | .eggs/
109 | lib/
110 | lib64/
111 | parts/
112 | sdist/
113 | var/
114 | wheels/
115 | share/python-wheels/
116 | *.egg-info/
117 | .installed.cfg
118 | *.egg
119 | MANIFEST
120 |
121 | # PyInstaller
122 | # Usually these files are written by a python script from a template
123 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
124 | *.manifest
125 | *.spec
126 |
127 | # Installer logs
128 | pip-log.txt
129 | pip-delete-this-directory.txt
130 |
131 | # Unit test / coverage reports
132 | htmlcov/
133 | .tox/
134 | .nox/
135 | .coverage
136 | .coverage.*
137 | .cache
138 | nosetests.xml
139 | coverage.xml
140 | *.cover
141 | *.py,cover
142 | .hypothesis/
143 | .pytest_cache/
144 | cover/
145 |
146 | # Translations
147 | *.mo
148 | *.pot
149 |
150 | # Django stuff:
151 | *.log
152 | local_settings.py
153 | db.sqlite3
154 | db.sqlite3-journal
155 |
156 | # Flask stuff:
157 | instance/
158 | .webassets-cache
159 |
160 | # Scrapy stuff:
161 | .scrapy
162 |
163 | # Sphinx documentation
164 | docs/_build/
165 |
166 | # PyBuilder
167 | .pybuilder/
168 | target/
169 |
170 | # Jupyter Notebook
171 | .ipynb_checkpoints
172 |
173 | # IPython
174 | profile_default/
175 | ipython_config.py
176 |
177 | # pyenv
178 | # For a library or package, you might want to ignore these files since the code is
179 | # intended to run in multiple environments; otherwise, check them in:
180 | # .python-version
181 |
182 | # pipenv
183 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
184 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
185 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
186 | # install all needed dependencies.
187 | #Pipfile.lock
188 |
189 | # poetry
190 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
191 | # This is especially recommended for binary packages to ensure reproducibility, and is more
192 | # commonly ignored for libraries.
193 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
194 | #poetry.lock
195 |
196 | # pdm
197 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
198 | #pdm.lock
199 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
200 | # in version control.
201 | # https://pdm.fming.dev/#use-with-ide
202 | .pdm.toml
203 |
204 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
205 | __pypackages__/
206 |
207 | # Celery stuff
208 | celerybeat-schedule
209 | celerybeat.pid
210 |
211 | # SageMath parsed files
212 | *.sage.py
213 |
214 | # Environments
215 | .env
216 | .venv
217 | env/
218 | venv/
219 | ENV/
220 | env.bak/
221 | venv.bak/
222 |
223 | # Spyder project settings
224 | .spyderproject
225 | .spyproject
226 |
227 | # Rope project settings
228 | .ropeproject
229 |
230 | # mkdocs documentation
231 | /site
232 |
233 | # mypy
234 | .mypy_cache/
235 | .dmypy.json
236 | dmypy.json
237 |
238 | # Pyre type checker
239 | .pyre/
240 |
241 | # pytype static type analyzer
242 | .pytype/
243 |
244 | # Cython debug symbols
245 | cython_debug/
246 |
247 | # PyCharm
248 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
249 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
250 | # and can be added to the global gitignore or merged into this file. For a more nuclear
251 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
252 | #.idea/
253 |
254 | ### Python Patch ###
255 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
256 | poetry.toml
257 |
258 | # ruff
259 | .ruff_cache/
260 |
261 | # LSP config files
262 | pyrightconfig.json
263 |
264 | ### VisualStudioCode ###
265 | .vscode/*
266 | !.vscode/settings.json
267 | !.vscode/tasks.json
268 | !.vscode/launch.json
269 | !.vscode/extensions.json
270 | !.vscode/*.code-snippets
271 |
272 | # Local History for Visual Studio Code
273 | .history/
274 |
275 | # Built Visual Studio Code Extensions
276 | *.vsix
277 |
278 | ### VisualStudioCode Patch ###
279 | # Ignore all local history of files
280 | .history
281 | .ionide
282 |
283 | # End of https://www.toptal.com/developers/gitignore/api/python,pycharm+all,visualstudiocode
284 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | # Python boilerplate
15 |
16 | [](https://github.com/smarlhens/python-boilerplate/actions/workflows/codeql.yml)
17 | [](https://github.com/smarlhens/python-boilerplate/actions/workflows/ci.yml)
18 | [](https://github.com/smarlhens/python-boilerplate)
19 |
20 | ---
21 |
22 | ## Table of Contents
23 |
24 | - [Prerequisites](#prerequisites)
25 | - [Installation](#installation)
26 | - [What's in the box ?](#whats-in-the-box-)
27 | - [Testing](#testing)
28 | - [Docker](#docker)
29 |
30 | ---
31 |
32 | ## Prerequisites
33 |
34 | - [Python](https://www.python.org/downloads/) **>=3.14.0 <3.15.0** (_tested with 3.14.2_)
35 | - [pre-commit](https://pre-commit.com/#install) **>=3.2.0 <5.0.0** (_tested with 4.5.0_)
36 | - [uv](https://docs.astral.sh/uv/getting-started/installation/) **>=0.9.16** (_tested with 0.9.18_)
37 | - [docker](https://docs.docker.com/get-docker/) (_optional_)
38 |
39 | ---
40 |
41 | ## Installation
42 |
43 | 1. Clone the git repository
44 |
45 | ```bash
46 | git clone https://github.com/smarlhens/python-boilerplate.git
47 | ```
48 |
49 | 2. Go into the project directory
50 |
51 | ```bash
52 | cd python-boilerplate/
53 | ```
54 |
55 | 3. Checkout working branch
56 |
57 | ```bash
58 | git checkout
59 | ```
60 |
61 | 4. Enable pre-commit hooks
62 |
63 | ```bash
64 | pre-commit install
65 | ```
66 |
67 | 5. Configure Python environment
68 |
69 | ```bash
70 | uv python install
71 | uv venv
72 | source .venv/bin/activate
73 | ```
74 |
75 | ---
76 |
77 | ## What's in the box ?
78 |
79 | ### uv
80 |
81 | [uv](https://github.com/astral-sh/uv) is an extremely fast Python package and project manager, written in Rust.
82 |
83 | **pyproject.toml file** ([`pyproject.toml`](pyproject.toml)): orchestrate your project and its dependencies
84 | **uv.lock file** ([`uv.lock`](uv.lock)): ensure that the package versions are consistent for everyone
85 | working on your project
86 |
87 | For more configuration options and details, see the [configuration docs](https://docs.astral.sh/uv/).
88 |
89 | ### pre-commit
90 |
91 | [pre-commit](https://pre-commit.com/) is a framework for managing and maintaining multi-language pre-commit hooks.
92 |
93 | **.pre-commit-config.yaml file** ([`.pre-commit-config.yaml`](.pre-commit-config.yaml)): describes what repositories and
94 | hooks are installed
95 |
96 | For more configuration options and details, see the [configuration docs](https://pre-commit.com/).
97 |
98 | ### ruff
99 |
100 | [ruff](https://github.com/astral-sh/ruff) is an extremely fast Python linter, written in Rust.
101 |
102 | Rules are defined in the [`pyproject.toml`](pyproject.toml).
103 |
104 | For more configuration options and details, see the [configuration docs](https://github.com/astral-sh/ruff#configuration).
105 |
106 | ### mypy
107 |
108 | [mypy](http://mypy-lang.org/) is an optional static type checker for Python that aims to combine the benefits of
109 | dynamic (or "duck") typing and static typing.
110 |
111 | Rules are defined in the [`pyproject.toml`](pyproject.toml).
112 |
113 | For more configuration options and details, see the [configuration docs](https://mypy.readthedocs.io/).
114 |
115 | ### bandit
116 |
117 | [bandit](https://bandit.readthedocs.io/) is a tool designed to find common security issues in Python code.
118 |
119 | Rules are defined in the [`pyproject.toml`](pyproject.toml).
120 |
121 | For more configuration options and details, see the [configuration docs](https://bandit.readthedocs.io/).
122 |
123 | ---
124 |
125 | ## Testing
126 |
127 | We are using [pytest](https://docs.pytest.org/) & [pytest-cov](https://github.com/pytest-dev/pytest-cov) to write tests.
128 |
129 | To run tests:
130 |
131 | ```bash
132 | uv run pytest tests
133 | ```
134 |
135 |
136 |
137 | Output
138 |
139 | ```text
140 | collected 1 item
141 |
142 | tests/test_python_boilerplate.py::test_hello_world PASSED
143 | ```
144 |
145 |
146 |
147 | To run tests with coverage:
148 |
149 | ```bash
150 | uv run pytest tests --cov=src
151 | ```
152 |
153 |
154 |
155 | Output
156 |
157 | ```text
158 | collected 1 item
159 |
160 | tests/test_python_boilerplate.py::test_hello_world PASSED
161 |
162 | ---------- coverage: platform linux, python 3.14.2-final-0 -----------
163 | Name Stmts Miss Cover
164 | --------------------------------------------------------
165 | src/python_boilerplate/__init__.py 1 0 100%
166 | src/python_boilerplate/main.py 6 2 67%
167 | --------------------------------------------------------
168 | TOTAL 7 2 71%
169 | ```
170 |
171 |
172 |
173 | ---
174 |
175 | ## Docker
176 |
177 | ### Build
178 |
179 | To build the docker `production` image using [`Dockerfile`](Dockerfile):
180 |
181 | ```bash
182 | docker build . -t my-python-application:latest
183 | ```
184 |
185 | To build the docker `development` image using [`Dockerfile`](Dockerfile):
186 |
187 | ```bash
188 | docker build . --target development -t my-python-application:dev
189 | ```
190 |
191 | ### Run
192 |
193 | To run the python app example inside Docker:
194 |
195 | ```bash
196 | docker run -it --rm my-python-application:latest # or :dev for development
197 | ```
198 |
199 |
200 |
201 | Output
202 |
203 | ```text
204 | Hello World
205 | ```
206 |
207 |
208 |
209 | ### Execute command inside container
210 |
211 | ```bash
212 | docker run -it --rm my-python-application:latest bash
213 | ```
214 |
215 | ---
216 |
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | revision = 3
3 | requires-python = ">=3.14"
4 |
5 | [[package]]
6 | name = "bandit"
7 | version = "1.9.2"
8 | source = { registry = "https://pypi.org/simple" }
9 | dependencies = [
10 | { name = "colorama", marker = "sys_platform == 'win32'" },
11 | { name = "pyyaml" },
12 | { name = "rich" },
13 | { name = "stevedore" },
14 | ]
15 | sdist = { url = "https://files.pythonhosted.org/packages/cf/72/f704a97aac430aeb704fa16435dfa24fbeaf087d46724d0965eb1f756a2c/bandit-1.9.2.tar.gz", hash = "sha256:32410415cd93bf9c8b91972159d5cf1e7f063a9146d70345641cd3877de348ce", size = 4241659, upload-time = "2025-11-23T21:36:18.722Z" }
16 | wheels = [
17 | { url = "https://files.pythonhosted.org/packages/55/1a/5b0320642cca53a473e79c7d273071b5a9a8578f9e370b74da5daa2768d7/bandit-1.9.2-py3-none-any.whl", hash = "sha256:bda8d68610fc33a6e10b7a8f1d61d92c8f6c004051d5e946406be1fb1b16a868", size = 134377, upload-time = "2025-11-23T21:36:17.39Z" },
18 | ]
19 |
20 | [[package]]
21 | name = "colorama"
22 | version = "0.4.6"
23 | source = { registry = "https://pypi.org/simple" }
24 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
25 | wheels = [
26 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
27 | ]
28 |
29 | [[package]]
30 | name = "coverage"
31 | version = "7.13.0"
32 | source = { registry = "https://pypi.org/simple" }
33 | sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" }
34 | wheels = [
35 | { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343, upload-time = "2025-12-08T13:13:50.811Z" },
36 | { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672, upload-time = "2025-12-08T13:13:52.284Z" },
37 | { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715, upload-time = "2025-12-08T13:13:53.791Z" },
38 | { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225, upload-time = "2025-12-08T13:13:55.274Z" },
39 | { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559, upload-time = "2025-12-08T13:13:57.161Z" },
40 | { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724, upload-time = "2025-12-08T13:13:58.692Z" },
41 | { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582, upload-time = "2025-12-08T13:14:00.642Z" },
42 | { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538, upload-time = "2025-12-08T13:14:02.556Z" },
43 | { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349, upload-time = "2025-12-08T13:14:04.015Z" },
44 | { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011, upload-time = "2025-12-08T13:14:05.505Z" },
45 | { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091, upload-time = "2025-12-08T13:14:07.127Z" },
46 | { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904, upload-time = "2025-12-08T13:14:08.542Z" },
47 | { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480, upload-time = "2025-12-08T13:14:10.958Z" },
48 | { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074, upload-time = "2025-12-08T13:14:13.345Z" },
49 | { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342, upload-time = "2025-12-08T13:14:15.02Z" },
50 | { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713, upload-time = "2025-12-08T13:14:16.907Z" },
51 | { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825, upload-time = "2025-12-08T13:14:18.68Z" },
52 | { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233, upload-time = "2025-12-08T13:14:20.55Z" },
53 | { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779, upload-time = "2025-12-08T13:14:22.367Z" },
54 | { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700, upload-time = "2025-12-08T13:14:24.309Z" },
55 | { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302, upload-time = "2025-12-08T13:14:26.068Z" },
56 | { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136, upload-time = "2025-12-08T13:14:27.604Z" },
57 | { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467, upload-time = "2025-12-08T13:14:29.09Z" },
58 | { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875, upload-time = "2025-12-08T13:14:31.106Z" },
59 | { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982, upload-time = "2025-12-08T13:14:33.1Z" },
60 | { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016, upload-time = "2025-12-08T13:14:34.601Z" },
61 | { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" },
62 | ]
63 |
64 | [[package]]
65 | name = "iniconfig"
66 | version = "2.3.0"
67 | source = { registry = "https://pypi.org/simple" }
68 | sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
69 | wheels = [
70 | { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
71 | ]
72 |
73 | [[package]]
74 | name = "librt"
75 | version = "0.7.4"
76 | source = { registry = "https://pypi.org/simple" }
77 | sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" }
78 | wheels = [
79 | { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" },
80 | { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" },
81 | { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" },
82 | { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" },
83 | { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" },
84 | { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" },
85 | { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" },
86 | { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" },
87 | { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" },
88 | { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" },
89 | { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" },
90 | { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" },
91 | { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" },
92 | { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" },
93 | { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" },
94 | { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" },
95 | { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" },
96 | { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" },
97 | { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" },
98 | { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" },
99 | { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" },
100 | { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" },
101 | ]
102 |
103 | [[package]]
104 | name = "markdown-it-py"
105 | version = "4.0.0"
106 | source = { registry = "https://pypi.org/simple" }
107 | dependencies = [
108 | { name = "mdurl" },
109 | ]
110 | sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
111 | wheels = [
112 | { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
113 | ]
114 |
115 | [[package]]
116 | name = "mdurl"
117 | version = "0.1.2"
118 | source = { registry = "https://pypi.org/simple" }
119 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
120 | wheels = [
121 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
122 | ]
123 |
124 | [[package]]
125 | name = "mypy"
126 | version = "1.19.1"
127 | source = { registry = "https://pypi.org/simple" }
128 | dependencies = [
129 | { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
130 | { name = "mypy-extensions" },
131 | { name = "pathspec" },
132 | { name = "typing-extensions" },
133 | ]
134 | sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" }
135 | wheels = [
136 | { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" },
137 | { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" },
138 | { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" },
139 | { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" },
140 | { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" },
141 | { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" },
142 | { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" },
143 | ]
144 |
145 | [[package]]
146 | name = "mypy-extensions"
147 | version = "1.1.0"
148 | source = { registry = "https://pypi.org/simple" }
149 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
150 | wheels = [
151 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
152 | ]
153 |
154 | [[package]]
155 | name = "packaging"
156 | version = "25.0"
157 | source = { registry = "https://pypi.org/simple" }
158 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
159 | wheels = [
160 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
161 | ]
162 |
163 | [[package]]
164 | name = "pathspec"
165 | version = "0.12.1"
166 | source = { registry = "https://pypi.org/simple" }
167 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
168 | wheels = [
169 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
170 | ]
171 |
172 | [[package]]
173 | name = "pluggy"
174 | version = "1.6.0"
175 | source = { registry = "https://pypi.org/simple" }
176 | sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
177 | wheels = [
178 | { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
179 | ]
180 |
181 | [[package]]
182 | name = "pygments"
183 | version = "2.19.2"
184 | source = { registry = "https://pypi.org/simple" }
185 | sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
186 | wheels = [
187 | { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
188 | ]
189 |
190 | [[package]]
191 | name = "pytest"
192 | version = "9.0.2"
193 | source = { registry = "https://pypi.org/simple" }
194 | dependencies = [
195 | { name = "colorama", marker = "sys_platform == 'win32'" },
196 | { name = "iniconfig" },
197 | { name = "packaging" },
198 | { name = "pluggy" },
199 | { name = "pygments" },
200 | ]
201 | sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
202 | wheels = [
203 | { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
204 | ]
205 |
206 | [[package]]
207 | name = "pytest-cov"
208 | version = "7.0.0"
209 | source = { registry = "https://pypi.org/simple" }
210 | dependencies = [
211 | { name = "coverage" },
212 | { name = "pluggy" },
213 | { name = "pytest" },
214 | ]
215 | sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
216 | wheels = [
217 | { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
218 | ]
219 |
220 | [[package]]
221 | name = "python-boilerplate"
222 | version = "0.1.0"
223 | source = { editable = "." }
224 |
225 | [package.dev-dependencies]
226 | dev = [
227 | { name = "bandit" },
228 | { name = "mypy" },
229 | { name = "pytest" },
230 | { name = "pytest-cov" },
231 | { name = "ruff" },
232 | ]
233 |
234 | [package.metadata]
235 |
236 | [package.metadata.requires-dev]
237 | dev = [
238 | { name = "bandit", specifier = ">=1.9.2" },
239 | { name = "mypy", specifier = ">=1.19.1" },
240 | { name = "pytest", specifier = ">=9.0.2" },
241 | { name = "pytest-cov", specifier = ">=7.0.0" },
242 | { name = "ruff", specifier = ">=0.14.9" },
243 | ]
244 |
245 | [[package]]
246 | name = "pyyaml"
247 | version = "6.0.3"
248 | source = { registry = "https://pypi.org/simple" }
249 | sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
250 | wheels = [
251 | { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
252 | { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
253 | { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
254 | { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
255 | { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
256 | { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
257 | { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
258 | { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
259 | { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
260 | { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
261 | { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
262 | { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
263 | { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
264 | { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
265 | { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
266 | { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
267 | { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
268 | { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
269 | ]
270 |
271 | [[package]]
272 | name = "rich"
273 | version = "14.2.0"
274 | source = { registry = "https://pypi.org/simple" }
275 | dependencies = [
276 | { name = "markdown-it-py" },
277 | { name = "pygments" },
278 | ]
279 | sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
280 | wheels = [
281 | { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
282 | ]
283 |
284 | [[package]]
285 | name = "ruff"
286 | version = "0.14.9"
287 | source = { registry = "https://pypi.org/simple" }
288 | sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" }
289 | wheels = [
290 | { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" },
291 | { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" },
292 | { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" },
293 | { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" },
294 | { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" },
295 | { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" },
296 | { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" },
297 | { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" },
298 | { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" },
299 | { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" },
300 | { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" },
301 | { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" },
302 | { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" },
303 | { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" },
304 | { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" },
305 | { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" },
306 | { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" },
307 | { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" },
308 | ]
309 |
310 | [[package]]
311 | name = "stevedore"
312 | version = "5.6.0"
313 | source = { registry = "https://pypi.org/simple" }
314 | sdist = { url = "https://files.pythonhosted.org/packages/96/5b/496f8abebd10c3301129abba7ddafd46c71d799a70c44ab080323987c4c9/stevedore-5.6.0.tar.gz", hash = "sha256:f22d15c6ead40c5bbfa9ca54aa7e7b4a07d59b36ae03ed12ced1a54cf0b51945", size = 516074, upload-time = "2025-11-20T10:06:07.264Z" }
315 | wheels = [
316 | { url = "https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" },
317 | ]
318 |
319 | [[package]]
320 | name = "typing-extensions"
321 | version = "4.15.0"
322 | source = { registry = "https://pypi.org/simple" }
323 | sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
324 | wheels = [
325 | { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
326 | ]
327 |
--------------------------------------------------------------------------------