From 625871b00076eedd8604813ffc31a5b1b9d49af9 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 6 Aug 2025 06:13:54 +0200 Subject: [PATCH 01/22] Initial commit --- .github/workflows/lint.yaml | 35 +++++++ .gitignore | 31 ++++++ .pre-commit-config.yaml | 18 ++++ LICENSE.txt | 7 ++ README.md | 186 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 63 ++++++++++++ samples/Pipfile | 15 +++ samples/pyproject.toml | 19 ++++ 8 files changed, 374 insertions(+) create mode 100644 .github/workflows/lint.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 samples/Pipfile create mode 100644 samples/pyproject.toml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..233eb87e --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..c0a8de23 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..5a04926b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..3bf4bfba --- /dev/null +++ b/README.md @@ -0,0 +1,186 @@ +# Python Discord Code Jam Repository Template + +## A primer + +Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events! + +This document contains the following information: + +1. [What does this template contain?](#what-does-this-template-contain) +2. [How do I use this template?](#how-do-i-use-this-template) +3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project) + +> [!TIP] +> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style. + +## What does this template contain? + +Here is a quick rundown of what each file in this repository contains: + +- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license. +- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly. +- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). +- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool. +- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met. + +Each of these files have comments for you to understand easily, and modify to fit your needs. + +### Ruff: general style rules + +Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines. +It is run with the command `ruff check` in the project root. + +Here is a sample output: + +```shell +$ ruff check +app.py:1:5: N802 Function name `helloWorld` should be lowercase +app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld` +app.py:2:5: D400 First line should end with a period +app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring` +app.py:3:15: W292 No newline at end of file +Found 5 errors. +``` + +Each line corresponds to an error. The first part is the file path, then the line number, and the column index. +Then comes the error code, a unique identifier of the error, and then a human-readable message. + +If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line. +For example: + +```python +def helloWorld(): # noqa: N802 + ... + +``` + +This will ignore the function naming issue and pass linting. + +> [!WARNING] +> We do not recommend ignoring errors unless you have a good reason to do so. + +### Ruff: formatting + +Ruff also comes with a formatter, which can be run with the command `ruff format`. +It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them. + +### Pre-commit: run linting before committing + +The second tool doesn't check your code, but rather makes sure that you actually *do* check it. + +It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. +The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. + +It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`. + +[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) + +#### List of hooks + +- `check-toml`: Lints and corrects your TOML files. +- `check-yaml`: Lints and corrects your YAML files. +- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file. +- `trailing-whitespace`: Removes whitespaces at the end of each line. +- `ruff-check`: Runs the Ruff linter. +- `ruff-format`: Runs the Ruff formatter. + +## How do I use this template? + +### Creating your team repository + +One person in the team, preferably the leader, will have to create the repository and add other members as collaborators. + +1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click. + ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) +2. Give the repository a name and a description. + ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png) +3. Click **Create repository from template**. +4. Click **Settings** in your newly created repository. + ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png) +5. In the "Access" section of the sidebar, click **Collaborators**. + ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b) +6. Click **Add people**. +7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository. + +You are now ready to go! Sit down, relax, and wait for the kickstart! + +> [!IMPORTANT] +> Don't forget to change the project name, description, and authors at the top of the [`pyproject.toml`](pyproject.toml) file, and swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam. + +### Using the default pip setup + +Our default setup includes a dependency group to be used with a [virtual environment](https://docs.python.org/3/library/venv.html). +It works with pip and uv, and we recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. +More on that [below](#how-do-i-adapt-this-template-to-my-project). + +Dependency groups are a relatively new feature, specified in [PEP 735](https://peps.python.org/pep-0735/). +You can read more about them in the [Python Packaging User Guide](https://packaging.python.org/en/latest/specifications/dependency-groups/). + +#### Creating the environment + +Create a virtual environment in the folder `.venv`. + +```shell +python -m venv .venv +``` + +#### Entering the environment + +It will change based on your operating system and shell. + +```shell +# Linux, Bash +$ source .venv/bin/activate +# Linux, Fish +$ source .venv/bin/activate.fish +# Linux, Csh +$ source .venv/bin/activate.csh +# Linux, PowerShell Core +$ .venv/bin/Activate.ps1 +# Windows, cmd.exe +> .venv\Scripts\activate.bat +# Windows, PowerShell +> .venv\Scripts\Activate.ps1 +``` + +#### Installing the dependencies + +Once the environment is created and activated, use this command to install the development dependencies. + +```shell +pip install --group dev +``` + +#### Exiting the environment + +Interestingly enough, it is the same for every platform. + +```shell +deactivate +``` + +Once the environment is activated, all the commands listed previously should work. + +> [!IMPORTANT] +> We highly recommend that you run `pre-commit install` as soon as possible. + +## How do I adapt this template to my project? + +If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`pyproject.toml`](pyproject.toml) to the development dependencies of your tool. + +We've included a porting to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples). +Note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost. + +When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end. +For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version. +I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager. + +> [!IMPORTANT] +> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API. + +## Final words + +> [!IMPORTANT] +> Don't forget to replace this README with an actual description of your project! Images are also welcome! + +We hope this template will be helpful. Good luck in the jam! diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..6a232d06 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[project] +# This section contains metadata about your project. +# Don't forget to change the name, description, and authors to match your project! +name = "code-jam-template" +description = "Add your description here" +authors = [ + { name = "Your Name" } +] +version = "0.1.0" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] + +[dependency-groups] +# This `dev` group contains all the development requirements for our linting toolchain. +# Don't forget to pin your dependencies! +# This list will have to be migrated if you wish to use another dependency manager. +dev = [ + "pre-commit~=4.2.0", + "ruff~=0.12.2", +] + +[tool.ruff] +# Increase the line length. This breaks PEP8 but it is way easier to work with. +# The original reason for this limit was a standard vim terminal is only 79 characters, +# but this doesn't really apply anymore. +line-length = 119 +# Target Python 3.12. If you decide to use a different version of Python +# you will need to update this value. +target-version = "py312" +# Automatically fix auto-fixable issues. +fix = true +# The directory containing the source code. If you choose a different project layout +# you will need to update this value. +src = ["src"] + +[tool.ruff.lint] +# Enable all linting rules. +select = ["ALL"] +# Ignore some of the most obnoxious linting errors. +ignore = [ + # Missing docstrings. + "D100", + "D104", + "D105", + "D106", + "D107", + # Docstring whitespace. + "D203", + "D213", + # Docstring punctuation. + "D415", + # Docstring quotes. + "D301", + # Builtins. + "A", + # Print statements. + "T20", + # TODOs. + "TD002", + "TD003", + "FIX", +] diff --git a/samples/Pipfile b/samples/Pipfile new file mode 100644 index 00000000..dedd0c50 --- /dev/null +++ b/samples/Pipfile @@ -0,0 +1,15 @@ +# Sample Pipfile. + +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] +ruff = "~=0.12.2" +pre-commit = "~=4.2.0" + +[requires] +python_version = "3.12" diff --git a/samples/pyproject.toml b/samples/pyproject.toml new file mode 100644 index 00000000..b486e506 --- /dev/null +++ b/samples/pyproject.toml @@ -0,0 +1,19 @@ +# Sample poetry configuration. + +[tool.poetry] +name = "Name" +version = "0.1.0" +description = "Description" +authors = ["Author 1 "] +license = "MIT" + +[tool.poetry.dependencies] +python = "3.12.*" + +[tool.poetry.dev-dependencies] +ruff = "~0.12.2" +pre-commit = "~4.2.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From f15c8705645d684d3511bf23c2d74563dedec49a Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 6 Aug 2025 08:36:19 +0200 Subject: [PATCH 02/22] chore: initial workspace setup (#1) --- .devcontainer/devcontainer.json | 83 ++++ .devcontainer/npm.sh | 4 + .devcontainer/pre-commit.sh | 7 + .devcontainer/uv.sh | 5 + .gitattributes | 1 + .github/workflows/lint.yaml | 73 ++-- .github/workflows/publish-docs.yaml | 46 ++ .gitignore | 320 +++++++++++++- .markdownlint.json | 16 + .pre-commit-config.yaml | 61 ++- .prettierignore | 3 + .prettierrc | 11 + LICENSE.txt | 2 +- README.md | 6 +- docs/architecture/index.md | 1 + docs/contributor-guide/index.md | 1 + docs/index.md | 14 + docs/stylesheets/table.css | 11 + docs/user-guide/index.md | 1 + mkdocs.yaml | 101 +++++ package-lock.json | 80 ++++ package.json | 9 + pyproject.toml | 88 ++-- samples/Pipfile | 15 - samples/pyproject.toml | 19 - tests/conftest.py | 0 tests/test__calm_calatheas.py | 8 + uv.lock | 628 ++++++++++++++++++++++++++++ 28 files changed, 1466 insertions(+), 148 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/npm.sh create mode 100755 .devcontainer/pre-commit.sh create mode 100755 .devcontainer/uv.sh create mode 100644 .gitattributes create mode 100644 .github/workflows/publish-docs.yaml create mode 100644 .markdownlint.json create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 docs/architecture/index.md create mode 100644 docs/contributor-guide/index.md create mode 100644 docs/index.md create mode 100644 docs/stylesheets/table.css create mode 100644 docs/user-guide/index.md create mode 100644 mkdocs.yaml create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 samples/Pipfile delete mode 100644 samples/pyproject.toml create mode 100644 tests/conftest.py create mode 100644 tests/test__calm_calatheas.py create mode 100644 uv.lock diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..451ee337 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,83 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "bierner.markdown-mermaid", + "charliermarsh.ruff", + "DavidAnson.vscode-markdownlint", + "esbenp.prettier-vscode", + "tamasfe.even-better-toml", + "-ms-python.autopep8" + ], + "settings": { + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "[toml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "files.exclude": { + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/__pycache__": true, + ".venv": true, + "node_modules": true, + "site": true + }, + "files.insertFinalNewline": true, + "files.watcherExclude": { + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/__pycache__": true, + "**/dist": true, + ".git/objects/**": true, + ".git/subtree-cache/**": true, + ".venv": true, + "node_modules": true, + "site": true + }, + "python.analysis.typeCheckingMode": "standard", + "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python", + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false + } + } + }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + }, + "ghcr.io/devcontainers/features/python:1": { + "toolsToInstall": ["uv"], + "version": "3.13" + } + }, + "forwardPorts": [8000], + "image": "mcr.microsoft.com/devcontainers/base:noble", + "name": "Calm Calatheas 🪴", + "portsAttributes": { + "8000": { + "label": "Documentation Server", + "onAutoForward": "notify" + } + }, + "postCreateCommand": { + "pre-commit": "bash .devcontainer/pre-commit.sh" + }, + "updateContentCommand": { + "npm": "bash .devcontainer/npm.sh", + "uv": "bash .devcontainer/uv.sh" + } +} diff --git a/.devcontainer/npm.sh b/.devcontainer/npm.sh new file mode 100755 index 00000000..7ffc1b66 --- /dev/null +++ b/.devcontainer/npm.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Install or update dependencies +npm install diff --git a/.devcontainer/pre-commit.sh b/.devcontainer/pre-commit.sh new file mode 100755 index 00000000..cc98e504 --- /dev/null +++ b/.devcontainer/pre-commit.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Mark the current directory as safe for Git operations +git config --global --add safe.directory $PWD + +# Install pre-commit hooks using uv +uv run pre-commit install diff --git a/.devcontainer/uv.sh b/.devcontainer/uv.sh new file mode 100755 index 00000000..e24759bd --- /dev/null +++ b/.devcontainer/uv.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Install Python dependencies using uv +uv venv --allow-existing +uv sync diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 7f67e803..2ce12cca 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,35 +1,60 @@ -# GitHub Action workflow enforcing our code style. - name: Lint -# Trigger the workflow on both push (to the main repository, on the main branch) -# and pull requests (against the main repository, but from any repo, from any branch). on: - push: - branches: - - main - pull_request: + push: + branches: + - main + pull_request: -# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. -# It is useful for pull requests coming from the main repository since both triggers will match. concurrency: lint-${{ github.sha }} jobs: - lint: - runs-on: ubuntu-latest + lint: + runs-on: ubuntu-latest + + env: + PYTHON_VERSION: "3.13" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + run: python -m pip install uv + + - name: Cache the virtualenv + uses: actions/cache@v4 + with: + path: ./.venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} + + - name: Install Python dependencies + run: uv venv --allow-existing && uv sync + + - name: Update GITHUB_PATH + run: echo "$(uv python find)" >> $GITHUB_PATH + + - name: Setup Node.js and dependencies + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + registry-url: "https://npm.pkg.github.com" - env: - # The Python version your project uses. Feel free to change this if required. - PYTHON_VERSION: "3.12" + - name: Install Node.js dependencies + run: npm install + shell: bash - steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} + - name: Run the tests + run: uv run pytest tests - - name: Run pre-commit hooks - uses: pre-commit/action@v3.0.1 + - name: Build the documentation + run: uv run mkdocs build --strict diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml new file mode 100644 index 00000000..d5a4bc0c --- /dev/null +++ b/.github/workflows/publish-docs.yaml @@ -0,0 +1,46 @@ +name: Publish Documentation + +concurrency: + group: "docs" + +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + + env: + PYTHON_VERSION: "3.13" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - run: git config --global user.email "calm-calatheas@github.com" + - run: git config --global user.name "Calm Calatheas" + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + run: python -m pip install uv + + - name: Cache the virtualenv + uses: actions/cache@v4 + with: + path: ./.venv + key: ${{ runner.os }}-venv-${{ hashFiles('**/uv.lock') }} + + - name: Install Python dependencies + run: uv venv --allow-existing && uv sync + + - name: Publish documentation + run: uv run mkdocs gh-deploy + shell: bash diff --git a/.gitignore b/.gitignore index 233eb87e..b6caec0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,311 @@ -# Files generated by the interpreter +### Python ### +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] +*$py.class -# Environment specific -.venv -venv +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments .env -env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site -# Unittest reports -.coverage* +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Node ### # Logs +logs *.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* -# PyEnv version selector -.python-version +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json -# Built objects -*.so -dist/ -build/ +# Runtime data +pids +*.pid +*.seed +*.pid.lock -# IDEs -# PyCharm -.idea/ -# VSCode -.vscode/ -# MacOS -.DS_Store +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..76de1448 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,16 @@ +{ + "MD013": { + "code_blocks": false, + "line_length": 119, + "tables": false + }, + "MD024": { + "siblings_only": true + }, + "MD030": false, + "MD033": { + "allowed_elements": ["div", "img", "figcaption", "figure", "source", "video"] + }, + "MD046": false, + "default": true +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0a8de23..518d9e4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,47 @@ -# Pre-commit configuration. -# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing - repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: check-toml - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + name: Check for merge conflicts + - id: check-json + name: Check for JSON syntax errors + - id: check-toml + name: Check for TOML syntax errors + - id: check-yaml + name: Check for YAML syntax errors + args: [--unsafe] + - id: end-of-file-fixer + name: Ensure files end with a newline + - id: trailing-whitespace + name: Trim trailing whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + name: Format code with Prettier + additional_dependencies: + - prettier@3.6.2 + - prettier-plugin-sort-json@4.1.1 + - prettier-plugin-toml@2.0.6 + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.3 + hooks: + - id: ruff + name: Check for Python linting errors + args: [--fix] + - id: ruff-format + name: Format Python code - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 - hooks: - - id: ruff-check - - id: ruff-format + - repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.403 + hooks: + - id: pyright + name: Check for Python type errors + entry: uv run pyright + language: system + "types_or": [python, pyi] + require_serial: true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..8cc9258d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..a17940f3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "jsonRecursiveSort": true, + "plugins": ["prettier-plugin-sort-json", "prettier-plugin-toml"], + "printWidth": 119, + "proseWrap": "preserve", + "reorderKeys": true, + "semi": true, + "singleQuote": false, + "tabWidth": 4, + "useTabs": false +} diff --git a/LICENSE.txt b/LICENSE.txt index 5a04926b..fe513044 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright 2021 Python Discord +Copyright 2021 Calm Calatheas 🪴 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 3bf4bfba..39077604 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,12 @@ It follows the same code style enforced by [Black](https://black.readthedocs.io/ ### Pre-commit: run linting before committing -The second tool doesn't check your code, but rather makes sure that you actually *do* check it. +The second tool doesn't check your code, but rather makes sure that you actually _do_ check it. It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. -It is *installed* by running `pre-commit install` and can be run manually by calling only `pre-commit`. +It is _installed_ by running `pre-commit install` and can be run manually by calling only `pre-commit`. [Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) @@ -105,7 +105,7 @@ One person in the team, preferably the leader, will have to create the repositor You are now ready to go! Sit down, relax, and wait for the kickstart! > [!IMPORTANT] -> Don't forget to change the project name, description, and authors at the top of the [`pyproject.toml`](pyproject.toml) file, and swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team *after* the start of the code jam. +> Don't forget to change the project name, description, and authors at the top of the [`pyproject.toml`](pyproject.toml) file, and swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team _after_ the start of the code jam. ### Using the default pip setup diff --git a/docs/architecture/index.md b/docs/architecture/index.md new file mode 100644 index 00000000..c79bec1a --- /dev/null +++ b/docs/architecture/index.md @@ -0,0 +1 @@ +# Architecture diff --git a/docs/contributor-guide/index.md b/docs/contributor-guide/index.md new file mode 100644 index 00000000..83840bba --- /dev/null +++ b/docs/contributor-guide/index.md @@ -0,0 +1 @@ +# Contributor Guide diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..9f5acc0d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,14 @@ +# Calm Calatheas 🪴 + +Welcome to the documentation for the Calm Calatheas project! + +## What is this project? + +This project is our entry for the [Python Discord Code Jam 12](https://pythondiscord.com/events/code-jams/12/). + +## About the team + +This project has been built by the Calm Calatheas team. Please feel free to reach out if you have any questions, or need +a hand with anything! + +[TFBlunt](https://github.com/thijsfranck) diff --git a/docs/stylesheets/table.css b/docs/stylesheets/table.css new file mode 100644 index 00000000..b7354a34 --- /dev/null +++ b/docs/stylesheets/table.css @@ -0,0 +1,11 @@ +/* + * Make tables full width by default + */ + +.md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md new file mode 100644 index 00000000..cd3d4522 --- /dev/null +++ b/docs/user-guide/index.md @@ -0,0 +1 @@ +# User Guide diff --git a/mkdocs.yaml b/mkdocs.yaml new file mode 100644 index 00000000..ab814459 --- /dev/null +++ b/mkdocs.yaml @@ -0,0 +1,101 @@ +copyright: © 2025 Calm Calatheas 🪴 +repo_name: code-jam-12 +repo_url: https://github.com/cj12-calm-calatheas/code-jam-12 +site_name: Calm Calatheas 🪴 + +theme: + name: material + search: true + + features: + - content.code.copy + - navigation.indexes + - navigation.instant + - navigation.instant.progress + - navigation.path + - navigation.sections + - navigation.tabs + - search.highlight + + icon: + repo: fontawesome/brands/github + + palette: + - media: "(prefers-color-scheme)" + accent: deep purple + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + accent: deep purple + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + accent: deep purple + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + +markdown_extensions: + - admonition + - attr_list + - codehilite + - md_in_html + - pymdownx.details + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + clickable_checkbox: true + custom_checkbox: true + - toc: + permalink: true + +nav: + - User Guide: + - user-guide/index.md + - Contributor Guide: + - contributor-guide/index.md + - Architecture: + - architecture/index.md + +plugins: + - autorefs + - mkdocstrings: + handlers: + python: + options: + show_root_heading: true + show_object_full_path: false + show_symbol_type_heading: true + show_symbol_type_toc: true + show_signature: true + separate_signature: true + show_signature_annotations: true + signature_crossrefs: true + show_source: false + show_if_no_docstring: true + show_docstring_examples: true + - search + - tags + +extra: + generator: false + social: + - icon: fontawesome/brands/github + link: https://github.com/cj12-calm-calatheas/code-jam-12 + +extra_css: + - stylesheets/table.css diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..7734a0bf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,80 @@ +{ + "name": "calm-calatheas", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "calm-calatheas", + "devDependencies": { + "prettier": "3.6.2", + "prettier-plugin-sort-json": "4.1.1", + "prettier-plugin-toml": "2.0.6" + } + }, + "node_modules/@taplo/core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@taplo/core/-/core-0.2.0.tgz", + "integrity": "sha512-r8bl54Zj1In3QLkiW/ex694bVzpPJ9EhwqT9xkcUVODnVUGirdB1JTsmiIv0o1uwqZiwhi8xNnTOQBRQCpizrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@taplo/lib": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@taplo/lib/-/lib-0.5.0.tgz", + "integrity": "sha512-+xIqpQXJco3T+VGaTTwmhxLa51qpkQxCjRwezjFZgr+l21ExlywJFcDfTrNmL6lG6tqb0h8GyJKO3UPGPtSCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@taplo/core": "^0.2.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-sort-json": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-4.1.1.tgz", + "integrity": "sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/prettier-plugin-toml": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-toml/-/prettier-plugin-toml-2.0.6.tgz", + "integrity": "sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@taplo/lib": "^0.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ceb9cc0e --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "devDependencies": { + "prettier": "3.6.2", + "prettier-plugin-sort-json": "4.1.1", + "prettier-plugin-toml": "2.0.6" + }, + "name": "calm-calatheas", + "private": true +} diff --git a/pyproject.toml b/pyproject.toml index 6a232d06..ab04e04f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,63 +1,51 @@ [project] -# This section contains metadata about your project. -# Don't forget to change the name, description, and authors to match your project! -name = "code-jam-template" -description = "Add your description here" -authors = [ - { name = "Your Name" } -] -version = "0.1.0" +authors = [{ name = "esmecat" }, { name = "floncdev" }, { name = "leoluy" }, { name = "tfblunt" }, { name = "zike01" }] +description = "This is the project of the Calm Calatheas team for the Python Discord Code Jam 2025" +name = "calm-calatheas" readme = "README.md" -requires-python = ">=3.12" +requires-python = ">=3.13" +version = "0.0.0" + dependencies = [] [dependency-groups] -# This `dev` group contains all the development requirements for our linting toolchain. -# Don't forget to pin your dependencies! -# This list will have to be migrated if you wish to use another dependency manager. dev = [ - "pre-commit~=4.2.0", - "ruff~=0.12.2", + "mkdocs~=1.6", + "mkdocs-material~=9.6", + "mkdocstrings~=0.29", + "mkdocstrings-python~=1.16", + "pre-commit~=4.2", + "pyright==1.1.403", + "pytest~=8.4", + "pytest-asyncio~=1.0", + "pytest-sugar~=1.0", + "python-dotenv~=1.1", + "ruff~=0.12", ] +[tool.pyright] +exclude = [".venv"] +pythonVersion = "3.13" +reportUnnecessaryTypeIgnoreComment = "error" +typeCheckingMode = "standard" +venvPath = "." + +[tool.pytest.ini_options] +asyncio_default_fixture_loop_scope = "session" +asyncio_mode = "auto" +required_plugins = ["pytest-asyncio"] + [tool.ruff] -# Increase the line length. This breaks PEP8 but it is way easier to work with. -# The original reason for this limit was a standard vim terminal is only 79 characters, -# but this doesn't really apply anymore. line-length = 119 -# Target Python 3.12. If you decide to use a different version of Python -# you will need to update this value. -target-version = "py312" -# Automatically fix auto-fixable issues. -fix = true -# The directory containing the source code. If you choose a different project layout -# you will need to update this value. -src = ["src"] +target-version = "py313" [tool.ruff.lint] -# Enable all linting rules. +ignore = ["D100", "D104", "D105", "D212", "D107", "LOG015", "S311", "FIX002", "PT001"] select = ["ALL"] -# Ignore some of the most obnoxious linting errors. -ignore = [ - # Missing docstrings. - "D100", - "D104", - "D105", - "D106", - "D107", - # Docstring whitespace. - "D203", - "D213", - # Docstring punctuation. - "D415", - # Docstring quotes. - "D301", - # Builtins. - "A", - # Print statements. - "T20", - # TODOs. - "TD002", - "TD003", - "FIX", -] + +[tool.ruff.lint.per-file-ignores] +"**/conftest.py" = ["INP001"] +"**/test__*.py" = ["INP001", "S101", "PLR2004"] + +[tool.ruff.lint.pydocstyle] +convention = "google" diff --git a/samples/Pipfile b/samples/Pipfile deleted file mode 100644 index dedd0c50..00000000 --- a/samples/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -# Sample Pipfile. - -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] -ruff = "~=0.12.2" -pre-commit = "~=4.2.0" - -[requires] -python_version = "3.12" diff --git a/samples/pyproject.toml b/samples/pyproject.toml deleted file mode 100644 index b486e506..00000000 --- a/samples/pyproject.toml +++ /dev/null @@ -1,19 +0,0 @@ -# Sample poetry configuration. - -[tool.poetry] -name = "Name" -version = "0.1.0" -description = "Description" -authors = ["Author 1 "] -license = "MIT" - -[tool.poetry.dependencies] -python = "3.12.*" - -[tool.poetry.dev-dependencies] -ruff = "~0.12.2" -pre-commit = "~4.2.0" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test__calm_calatheas.py b/tests/test__calm_calatheas.py new file mode 100644 index 00000000..abeec57f --- /dev/null +++ b/tests/test__calm_calatheas.py @@ -0,0 +1,8 @@ +def test__placeholder() -> None: + """ + Placeholder test function. + + This function is intended to be replaced with actual test cases. For now, it is required to ensure that Pytest + doesn't fail in CI due to the absence of tests. + """ + assert True, "This is a placeholder function. Replace with actual tests." diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..ac3b6b47 --- /dev/null +++ b/uv.lock @@ -0,0 +1,628 @@ +requires-python = ">=3.13" +revision = 3 +version = 1 + +[[package]] +name = "babel" +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.17.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backrefs" +sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" } +source = { registry = "https://pypi.org/simple" } +version = "5.9" +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" }, + { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9", size = 411762, upload-time = "2025-06-22T19:34:11.037Z" }, + { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, +] + +[[package]] +name = "calm-calatheas" +source = { virtual = "." } +version = "0.0.0" + +[package.dev-dependencies] +dev = [ + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings" }, + { name = "mkdocstrings-python" }, + { name = "pre-commit" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-sugar" }, + { name = "python-dotenv" }, + { name = "ruff" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "mkdocs", specifier = "~=1.6" }, + { name = "mkdocs-material", specifier = "~=9.6" }, + { name = "mkdocstrings", specifier = "~=0.29" }, + { name = "mkdocstrings-python", specifier = "~=1.16" }, + { name = "pre-commit", specifier = "~=4.2" }, + { name = "pyright", specifier = "==1.1.403" }, + { name = "pytest", specifier = "~=8.4" }, + { name = "pytest-asyncio", specifier = "~=1.0" }, + { name = "pytest-sugar", specifier = "~=1.0" }, + { name = "python-dotenv", specifier = "~=1.1" }, + { name = "ruff", specifier = "~=0.12" }, +] + +[[package]] +name = "certifi" +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +source = { registry = "https://pypi.org/simple" } +version = "2025.8.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cfgv" +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.4.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "charset-normalizer" +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.4.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +dependencies = [{ name = "colorama", marker = "sys_platform == 'win32'" }] +name = "click" +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +source = { registry = "https://pypi.org/simple" } +version = "8.2.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +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" } +source = { registry = "https://pypi.org/simple" } +version = "0.4.6" +wheels = [ + { 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" }, +] + +[[package]] +name = "distlib" +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.4.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.18.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +dependencies = [{ name = "python-dateutil" }] +name = "ghp-import" +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +dependencies = [{ name = "colorama" }] +name = "griffe" +sdist = { url = "https://files.pythonhosted.org/packages/2d/42/486d21a6c33ff69a7381511d507b6db7a7b7f4d5bec3279bc0dc45c658a9/griffe-1.9.0.tar.gz", hash = "sha256:b5531cf45e9b73f0842c2121cc4d4bcbb98a55475e191fc9830e7aef87a920a0", size = 409341, upload-time = "2025-07-28T17:45:38.712Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.9.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/65/7b3fcef8c9fb6d1023484d9caf87e78450a5c9cd1e191ce9632990b65284/griffe-1.9.0-py3-none-any.whl", hash = "sha256:bcf90ee3ad42bbae70a2a490c782fc8e443de9b84aa089d857c278a4e23215fc", size = 137060, upload-time = "2025-07-28T17:45:36.973Z" }, +] + +[[package]] +name = "identify" +sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.6.12" +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, +] + +[[package]] +name = "idna" +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.10" +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +dependencies = [{ name = "markupsafe" }] +name = "jinja2" +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.1.6" +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown" +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.8.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, +] + +[[package]] +name = "markupsafe" +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.0.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mergedeep" +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +name = "mkdocs" +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.6.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +dependencies = [{ name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }] +name = "mkdocs-autorefs" +sdist = { url = "https://files.pythonhosted.org/packages/47/0c/c9826f35b99c67fa3a7cddfa094c1a6c43fafde558c309c6e4403e5b37dc/mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749", size = 54961, upload-time = "2025-05-20T13:09:09.886Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.4.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/dc/fc063b78f4b769d1956319351704e23ebeba1e9e1d6a41b4b602325fd7e4/mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13", size = 24969, upload-time = "2025-05-20T13:09:08.237Z" }, +] + +[[package]] +dependencies = [{ name = "mergedeep" }, { name = "platformdirs" }, { name = "pyyaml" }] +name = "mkdocs-get-deps" +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.2.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, +] + +[[package]] +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +name = "mkdocs-material" +sdist = { url = "https://files.pythonhosted.org/packages/dd/84/aec27a468c5e8c27689c71b516fb5a0d10b8fca45b9ad2dd9d6e43bc4296/mkdocs_material-9.6.16.tar.gz", hash = "sha256:d07011df4a5c02ee0877496d9f1bfc986cfb93d964799b032dd99fe34c0e9d19", size = 4028828, upload-time = "2025-07-26T15:53:47.542Z" } +source = { registry = "https://pypi.org/simple" } +version = "9.6.16" +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f4/90ad67125b4dd66e7884e4dbdfab82e3679eb92b751116f8bb25ccfe2f0c/mkdocs_material-9.6.16-py3-none-any.whl", hash = "sha256:8d1a1282b892fe1fdf77bfeb08c485ba3909dd743c9ba69a19a40f637c6ec18c", size = 9223743, upload-time = "2025-07-26T15:53:44.236Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +name = "mkdocstrings" +sdist = { url = "https://files.pythonhosted.org/packages/e2/0a/7e4776217d4802009c8238c75c5345e23014a4706a8414a62c0498858183/mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444", size = 106597, upload-time = "2025-07-22T23:48:45.998Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.30.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/b4/3c5eac68f31e124a55d255d318c7445840fa1be55e013f507556d6481913/mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2", size = 36579, upload-time = "2025-07-22T23:48:44.152Z" }, +] + +[[package]] +dependencies = [{ name = "griffe" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }] +name = "mkdocstrings-python" +sdist = { url = "https://files.pythonhosted.org/packages/bf/ed/b886f8c714fd7cccc39b79646b627dbea84cd95c46be43459ef46852caf0/mkdocstrings_python-1.16.12.tar.gz", hash = "sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d", size = 206065, upload-time = "2025-06-03T12:52:49.276Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.16.12" +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/dd/a24ee3de56954bfafb6ede7cd63c2413bb842cc48eb45e41c43a05a33074/mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374", size = 124287, upload-time = "2025-06-03T12:52:47.819Z" }, +] + +[[package]] +name = "nodeenv" +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.9.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +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" } +source = { registry = "https://pypi.org/simple" } +version = "25.0" +wheels = [ + { 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" }, +] + +[[package]] +name = "paginate" +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.5.7" +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pathspec" +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" } +source = { registry = "https://pypi.org/simple" } +version = "0.12.1" +wheels = [ + { 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" }, +] + +[[package]] +name = "platformdirs" +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.3.8" +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +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" } +source = { registry = "https://pypi.org/simple" } +version = "1.6.0" +wheels = [ + { 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" }, +] + +[[package]] +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +name = "pre-commit" +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.2.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, +] + +[[package]] +name = "pygments" +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" } +source = { registry = "https://pypi.org/simple" } +version = "2.19.2" +wheels = [ + { 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" }, +] + +[[package]] +dependencies = [{ name = "markdown" }, { name = "pyyaml" }] +name = "pymdown-extensions" +sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" } +source = { registry = "https://pypi.org/simple" } +version = "10.16.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, +] + +[[package]] +dependencies = [{ name = "nodeenv" }, { name = "typing-extensions" }] +name = "pyright" +sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.403" +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" }, +] + +[[package]] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +name = "pytest" +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +source = { registry = "https://pypi.org/simple" } +version = "8.4.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +dependencies = [{ name = "pytest" }] +name = "pytest-asyncio" +sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, +] + +[[package]] +dependencies = [{ name = "packaging" }, { name = "pytest" }, { name = "termcolor" }] +name = "pytest-sugar" +sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992, upload-time = "2024-02-01T18:30:36.735Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171, upload-time = "2024-02-01T18:30:29.395Z" }, +] + +[[package]] +dependencies = [{ name = "six" }] +name = "python-dateutil" +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.9.0.post0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "pyyaml" +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.0.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +dependencies = [{ name = "pyyaml" }] +name = "pyyaml-env-tag" +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +dependencies = [{ name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }] +name = "requests" +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.32.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "ruff" +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.12.7" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, +] + +[[package]] +name = "six" +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.17.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "termcolor" +sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, +] + +[[package]] +name = "typing-extensions" +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.14.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "urllib3" +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.5.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +dependencies = [{ name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }] +name = "virtualenv" +sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160, upload-time = "2025-08-05T16:10:55.605Z" } +source = { registry = "https://pypi.org/simple" } +version = "20.33.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362, upload-time = "2025-08-05T16:10:52.81Z" }, +] + +[[package]] +name = "watchdog" +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] From 10a9e9237794fe22afe429ac3f41506cee3c91dd Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 6 Aug 2025 06:42:16 +0000 Subject: [PATCH 03/22] ci: update docs workflow permissions --- .github/workflows/publish-docs.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml index d5a4bc0c..6701fa28 100644 --- a/.github/workflows/publish-docs.yaml +++ b/.github/workflows/publish-docs.yaml @@ -12,6 +12,10 @@ jobs: publish: runs-on: ubuntu-latest + permissions: + contents: write + pages: write + env: PYTHON_VERSION: "3.13" From 24e20c2d462de31efe6c1e311cb5a6661ffcbb92 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 6 Aug 2025 10:11:37 +0200 Subject: [PATCH 04/22] docs: add contributor guide (#2) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 187 +----------------- .../development-environment.md | 138 +++++++++++++ docs/contributor-guide/documentation.md | 67 +++++++ docs/contributor-guide/index.md | 5 + docs/contributor-guide/version-control.md | 165 ++++++++++++++++ mkdocs.yaml | 4 + 6 files changed, 385 insertions(+), 181 deletions(-) create mode 100644 docs/contributor-guide/development-environment.md create mode 100644 docs/contributor-guide/documentation.md create mode 100644 docs/contributor-guide/version-control.md diff --git a/README.md b/README.md index 39077604..8ad5e40c 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,11 @@ -# Python Discord Code Jam Repository Template +# Calm Calatheas 🪴 -## A primer +Welcome to the repository for the Calm Calatheas project! -Hello code jam participants! We've put together this repository template for you to use in [our code jams](https://pythondiscord.com/events/) or even other Python events! +## Documentation -This document contains the following information: +For detailed documentation, please refer to our [GitHub Pages site](https://cj12-calm-calatheas.github.io/code-jam-12/). -1. [What does this template contain?](#what-does-this-template-contain) -2. [How do I use this template?](#how-do-i-use-this-template) -3. [How do I adapt this template to my project?](#how-do-i-adapt-this-template-to-my-project) +## For Contributors -> [!TIP] -> You can also look at [our style guide](https://pythondiscord.com/events/code-jams/code-style-guide/) to get more information about what we consider a maintainable code style. - -## What does this template contain? - -Here is a quick rundown of what each file in this repository contains: - -- [`LICENSE.txt`](LICENSE.txt): [The MIT License](https://opensource.org/licenses/MIT), an OSS approved license which grants rights to everyone to use and modify your project, and limits your liability. We highly recommend you to read the license. -- [`.gitignore`](.gitignore): A list of files and directories that will be ignored by Git. Most of them are auto-generated or contain data that you wouldn't want to share publicly. -- [`pyproject.toml`](pyproject.toml): Configuration and metadata for the project, as well as the linting tool Ruff. If you're interested, you can read more about `pyproject.toml` in the [Python Packaging documentation](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/). -- [`.pre-commit-config.yaml`](.pre-commit-config.yaml): The configuration of the [pre-commit](https://pre-commit.com/) tool. -- [`.github/workflows/lint.yaml`](.github/workflows/lint.yaml): A [GitHub Actions](https://github.com/features/actions) workflow, a set of actions run by GitHub on their server after each push, to ensure the style requirements are met. - -Each of these files have comments for you to understand easily, and modify to fit your needs. - -### Ruff: general style rules - -Our first tool is Ruff. It will check your codebase and warn you about any non-conforming lines. -It is run with the command `ruff check` in the project root. - -Here is a sample output: - -```shell -$ ruff check -app.py:1:5: N802 Function name `helloWorld` should be lowercase -app.py:1:5: ANN201 Missing return type annotation for public function `helloWorld` -app.py:2:5: D400 First line should end with a period -app.py:2:5: D403 First word of the first line should be capitalized: `docstring` -> `Docstring` -app.py:3:15: W292 No newline at end of file -Found 5 errors. -``` - -Each line corresponds to an error. The first part is the file path, then the line number, and the column index. -Then comes the error code, a unique identifier of the error, and then a human-readable message. - -If, for any reason, you do not wish to comply with this specific error on a specific line, you can add `# noqa: CODE` at the end of the line. -For example: - -```python -def helloWorld(): # noqa: N802 - ... - -``` - -This will ignore the function naming issue and pass linting. - -> [!WARNING] -> We do not recommend ignoring errors unless you have a good reason to do so. - -### Ruff: formatting - -Ruff also comes with a formatter, which can be run with the command `ruff format`. -It follows the same code style enforced by [Black](https://black.readthedocs.io/en/stable/index.html), so there's no need to pick between them. - -### Pre-commit: run linting before committing - -The second tool doesn't check your code, but rather makes sure that you actually _do_ check it. - -It makes use of a feature called [Git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) which allow you to run a piece of code before running `git commit`. -The good thing about it is that it will cancel your commit if the lint doesn't pass. You won't have to wait for GitHub Actions to report issues and have a second fix commit. - -It is _installed_ by running `pre-commit install` and can be run manually by calling only `pre-commit`. - -[Lint before you push!](https://soundcloud.com/lemonsaurusrex/lint-before-you-push) - -#### List of hooks - -- `check-toml`: Lints and corrects your TOML files. -- `check-yaml`: Lints and corrects your YAML files. -- `end-of-file-fixer`: Makes sure you always have an empty line at the end of your file. -- `trailing-whitespace`: Removes whitespaces at the end of each line. -- `ruff-check`: Runs the Ruff linter. -- `ruff-format`: Runs the Ruff formatter. - -## How do I use this template? - -### Creating your team repository - -One person in the team, preferably the leader, will have to create the repository and add other members as collaborators. - -1. In the top right corner of your screen, where **Clone** usually is, you have a **Use this template** button to click. - ![use-this-template-button](https://docs.github.com/assets/images/help/repository/use-this-template-button.png) -2. Give the repository a name and a description. - ![create-repository-name](https://docs.github.com/assets/images/help/repository/create-repository-name.png) -3. Click **Create repository from template**. -4. Click **Settings** in your newly created repository. - ![repo-actions-settings](https://docs.github.com/assets/images/help/repository/repo-actions-settings.png) -5. In the "Access" section of the sidebar, click **Collaborators**. - ![collaborators-settings](https://github.com/python-discord/code-jam-template/assets/63936253/c150110e-d1b5-4e4d-93e0-0a2cf1de352b) -6. Click **Add people**. -7. Insert the names of each of your teammates, and invite them. Once they have accepted the invitation in their email, they will have write access to the repository. - -You are now ready to go! Sit down, relax, and wait for the kickstart! - -> [!IMPORTANT] -> Don't forget to change the project name, description, and authors at the top of the [`pyproject.toml`](pyproject.toml) file, and swap "Python Discord" in the [`LICENSE.txt`](LICENSE.txt) file for the name of each of your team members or the name of your team _after_ the start of the code jam. - -### Using the default pip setup - -Our default setup includes a dependency group to be used with a [virtual environment](https://docs.python.org/3/library/venv.html). -It works with pip and uv, and we recommend this if you have never used any other dependency manager, although if you have, feel free to switch to it. -More on that [below](#how-do-i-adapt-this-template-to-my-project). - -Dependency groups are a relatively new feature, specified in [PEP 735](https://peps.python.org/pep-0735/). -You can read more about them in the [Python Packaging User Guide](https://packaging.python.org/en/latest/specifications/dependency-groups/). - -#### Creating the environment - -Create a virtual environment in the folder `.venv`. - -```shell -python -m venv .venv -``` - -#### Entering the environment - -It will change based on your operating system and shell. - -```shell -# Linux, Bash -$ source .venv/bin/activate -# Linux, Fish -$ source .venv/bin/activate.fish -# Linux, Csh -$ source .venv/bin/activate.csh -# Linux, PowerShell Core -$ .venv/bin/Activate.ps1 -# Windows, cmd.exe -> .venv\Scripts\activate.bat -# Windows, PowerShell -> .venv\Scripts\Activate.ps1 -``` - -#### Installing the dependencies - -Once the environment is created and activated, use this command to install the development dependencies. - -```shell -pip install --group dev -``` - -#### Exiting the environment - -Interestingly enough, it is the same for every platform. - -```shell -deactivate -``` - -Once the environment is activated, all the commands listed previously should work. - -> [!IMPORTANT] -> We highly recommend that you run `pre-commit install` as soon as possible. - -## How do I adapt this template to my project? - -If you wish to use Pipenv or Poetry, you will have to move the dependencies in [`pyproject.toml`](pyproject.toml) to the development dependencies of your tool. - -We've included a porting to both [Poetry](samples/pyproject.toml) and [Pipenv](samples/Pipfile) in the [`samples` folder](samples). -Note that the Poetry [`pyproject.toml`](samples/pyproject.toml) file does not include the Ruff configuration, so if you simply replace the file then the Ruff configuration will be lost. - -When installing new dependencies, don't forget to [pin](https://pip.pypa.io/en/stable/topics/repeatable-installs/#pinning-the-package-versions) them by adding a version tag at the end. -For example, if I wish to install [Click](https://click.palletsprojects.com/en/8.1.x/), a quick look at [PyPI](https://pypi.org/project/click/) tells me that `8.1.7` is the latest version. -I will then add `click~=8.1`, without the last number, to my requirements file or dependency manager. - -> [!IMPORTANT] -> A code jam project is left unmaintained after the end of the event. If the dependencies aren't pinned, the project will break after any major change in an API. - -## Final words - -> [!IMPORTANT] -> Don't forget to replace this README with an actual description of your project! Images are also welcome! - -We hope this template will be helpful. Good luck in the jam! +Please follow the instructions in the [Contributor Guide](https://cj12-calm-calatheas.github.io/code-jam-12/contributor-guide/). diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md new file mode 100644 index 00000000..dfc5c821 --- /dev/null +++ b/docs/contributor-guide/development-environment.md @@ -0,0 +1,138 @@ +# Development Environment + +Follow the steps below to set up your development environment. + +## Configure your SSH Key + +Follow the steps below to configure your SSH key for accessing the repository: + +1. [Generate an SSH key](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). +2. [Add the SSH key to your GitHub account](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account). + +## Clone the Repository + +To clone the repository, run the following command: + +```bash +git clone git@github.com:cj12-calm-calatheas/code-jam-12.git +``` + +This will clone the repository to your local machine using SSH. + +## Environment Setup + +To get started with the project, you can either install the [devcontainer](https://containers.dev) or follow the manual +setup instructions below. + +### Using the Devcontainer + +This project includes a [devcontainer](https://containers.dev) to automatically set up your development +environment, including the all tools and dependencies required for local development. + +??? NOTE "Prerequisites" + + Please ensure you have the following prerequisites installed: + + - [Docker](https://www.docker.com) must be installed on your system to use the devcontainer. + + - The [Remote Development Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) for Visual Studio Code must be installed to work with devcontainers. + +??? TIP "Use WSL on Windows" + + If you are using Windows, we **strongly** recommend cloning the repository into the [WSL](https://learn.microsoft.com/en-us/windows/wsl/) + filesystem instead of the Windows filesystem. This significantly improves I/O performance when running the devcontainer. + +#### Configure your SSH Agent + +The devcontainer will attempt to pick up your SSH key from your `ssh-agent` when it starts. Follow the +guide on [sharing git credentials with the devcontainer](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials) +to ensure your SSH key is available inside the container. + +#### Open the Repository + +To get started, navigate to the folder where you cloned the repository and run: + +```bash +code . +``` + +This will open the current directory in Visual Studio Code. + +#### Build the Environment + +Once Visual Studio Code is open, you will see a notification at the bottom right corner of the window asking if +you want to open the project in a devcontainer. Select `Reopen in Container`. + +Your development environment will now be set up automatically. + +??? QUESTION "What if I don't see the notification?" + + You can manually open the devcontainer by pressing `F1` to open the command pallette. Type + `>Dev Containers: Reopen in Container` and press `Enter` to select the command. + +??? EXAMPLE "Detailed Setup Guides" + + For more details, refer to the setup guide for your IDE: + + - [Visual Studio Code](https://code.visualstudio.com/docs/devcontainers/tutorial) + - [PyCharm](https://www.jetbrains.com/help/pycharm/connect-to-devcontainer.html) + +### Manual Setup + +Alternatively, you can set up the development environment manually by following the steps below. + +??? NOTE "Prerequisites" + + Please ensure you have the following prerequisites installed: + + - [Python 3.13](https://www.python.org/downloads/) must be installed on your system. + - [Node.js](https://nodejs.org) must be installed on your system for linting non-Python files. + + You can check your Python version with: + + ```bash + python --version + ``` + +#### Open the Repository + +Start by opening the repository in your terminal or command prompt. + +```bash +cd path/to/your/repository +``` + +#### Set up your Python Environment + +This project uses [uv](https://docs.astral.sh/uv/) for dependency management. If you don't have `uv` installed, you can +install it using pip: + +```bash +python -m pip install uv +``` + +To install the dependencies, run: + +```bash +uv venv --allow-existing && uv sync +``` + +This sets up a virtual environment and installs all required packages. + +#### Install Node.js Dependencies + +For linting non-Python files, we also require some Node.js dependencies. To install them, run: + +```bash +npm install +``` + +#### Set up Pre-commit Hooks + +To ensure code quality, this project uses pre-commit hooks. Install them by running: + +```bash +uv run pre-commit install +``` + +This will set up the pre-commit hooks to run automatically on each commit. diff --git a/docs/contributor-guide/documentation.md b/docs/contributor-guide/documentation.md new file mode 100644 index 00000000..7da0a531 --- /dev/null +++ b/docs/contributor-guide/documentation.md @@ -0,0 +1,67 @@ +# Documentation + +This page provides guidelines for contributing to the documentation. + +## Tools + +The documentation is built using [MkDocs](https://www.mkdocs.org/), a static site generator that converts Markdown +files into a website. + +Markdown is a lightweight markup language with plain-text formatting syntax. Refer to the [Markdown Guide](https://www.markdownguide.org) +for more information on how to use Markdown. + +This project uses the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme to generate the +documentation. Please review the theme documentation for guidance on how to use its various features. + +## Running the Documentation + +!!! NOTE "Prerequisites" + + Ensure you have [set up your development environment](./development-environment.md) before running the documentation. + +To view the documentation locally, you can use the following command: + +```bash +uv run mkdocs serve +``` + +Open your browser and navigate to [`http://localhost:8000`](http://localhost:8000) to view the documentation. +The changes you make to the documentation will be automatically reflected in the browser. + +## Adding a New Page + +To add a new page to the documentation, create a new Markdown file in the `docs` directory. + +Next, update the `nav` section in the `mkdocs.yaml` file to include the new page. The `nav` section defines the +structure of the documentation and the order in which the pages are displayed in the navigation bar. + +Please ensure that the folder structure in the `docs` directory matches the structure defined in the `nav` section. + +## Linting + +This project is configured to use [markdownlint](https://github.com/DavidAnson/markdownlint) to ensure consistent +Markdown styling and formatting across the documentation. The linter is automatically run when you commit changes +to the repository. + +You can configure the linter rules in the `.markdownlint.json` file. Refer to the [markdownlint rules](https://github.com/DavidAnson/markdownlint?tab=readme-ov-file#rules--aliases) +for more information on the available rules. + +!!! TIP "Use a Markdown Linter Extension" + + We recommend installing a Markdown linter extension in your editor to help identify and fix issues as you write. + The devcontainer is pre-configured with the [`markdownlint`](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) + extension for Visual Studio Code. + +## Formatting + +The documentation is formatted using [Prettier](https://prettier.io/), an opinionated code formatter that ensures +consistent style across the project. Prettier is automatically run when you save a Markdown file in the editor. + +You can configure the formatting rules in the `.prettierrc.json` file. Refer to the [Prettier options](https://prettier.io/docs/en/options.html) +for more information on the available options. + +## Publishing the Documentation + +The documentation is published automatically when changes are merged into the `main` branch. A GitHub Action workflow +is triggered to build the documentation and push it to the `gh-pages` branch. The published documentation is hosted +on GitHub Pages. diff --git a/docs/contributor-guide/index.md b/docs/contributor-guide/index.md index 83840bba..4623788d 100644 --- a/docs/contributor-guide/index.md +++ b/docs/contributor-guide/index.md @@ -1 +1,6 @@ # Contributor Guide + +This guide provides information on how to contribute to the project, including setting up your development environment, +using version control, and contributing to the documentation. + +It is intended for project members as well as the code jam judges. diff --git a/docs/contributor-guide/version-control.md b/docs/contributor-guide/version-control.md new file mode 100644 index 00000000..246c0e87 --- /dev/null +++ b/docs/contributor-guide/version-control.md @@ -0,0 +1,165 @@ +# Version Control + +Follow the steps below when contributing to the project. These steps ensure that all changes are properly tracked and reviewed. + +## Create a New Branch + +Always create a new branch for your changes. This makes it easier to handle multiple contributions simultaneously. + +??? QUESTION "Why should I create a new branch?" + + Creating a new branch allows you to work on your changes without affecting the `main` branch. This makes it + easier to collaborate with others and keep the codebase clean. + +First, pull the latest changes from the `main` branch: + +```bash +git pull main +``` + +Next, create a new branch with the following command: + +```bash +git checkout -b "" +``` + +Replace `` with a short, descriptive name for your branch. For example, `add-uptime-command`. + +## Commit your Changes + +On your local branch, you can make changes to the code such as adding new features, fixing bugs, or updating documentation. +Once you have made your changes, you can commit them to your branch. + +```bash +git add . +git commit -m "feat: add uptime command" +``` + +Make sure to write a clear and concise commit message that describes the changes you have made. + +??? QUESTION "How often should I commit my changes?" + + It's a good practice to commit your changes often. This allows you to track your progress and revert changes if needed. + +### Automated Checks + +The project includes pre-commit hooks to ensure your code meets the quality standards. These hooks run automatically +before each commit. + +??? QUESTION "What if the pre-commit hooks fail?" + + If the pre-commit hooks fail, you will need to address the issues before committing your changes. Follow the + instructions provided by the pre-commit hooks to identify and fix the issues. + +??? QUESTION "How do I run the pre-commit hooks manually?" + + Pre-commit hooks can also be run manually using the following command: + + ```bash + uv run pre-commit + ``` + +The pre-commit hooks are intended to help us keep the codebase maintainable. If there are rules that you believe +are too strict, please discuss them with the team. + +## Create a Pull Request + +Once you have completed your changes, it's time to create a pull request. A pull request allows your changes to +be reviewed and merged into the `main` branch. + +Before creating a pull request, ensure your branch is up to date with the latest changes from the `main` branch: + +```bash +git pull main +``` + +Next, push your changes to the repository: + +```bash +git push +``` + +Finally, [create a pull request on GitHub](https://github.com/cj12-calm-calatheas/code-jam-12/compare). Select +your branch as the source and the `main` branch as the base. + +Give your pull request a descriptive title that summarizes the changes you have made. In the pull request description, +provide a brief overview of the changes and any relevant information for reviewers. + +??? EXAMPLE "Pull Request Description" + + Here's an example of a good pull request description: + + ```plaintext + # feat: add uptime command + + This pull request adds a new uptime command to display the bot's uptime. + + ## Changes + + - Added a new command to display the bot's uptime + - Updated the help command to include information about the new command + + ## Notes + + - The new command is implemented in a separate file for better organization + - The command has been tested locally and works as expected + ``` + +### Automated Checks + +The same pre-commit hooks that run locally will also run automatically on the pull request. The workflow also +runs the tests to ensure everything is working correctly, and checks the docs for any broken links. + +??? QUESTION "What if the checks fail on the pull request?" + + If the checks fail on the pull request, you will need to address the issues in your branch and push + the changes. The checks will run again automatically. + + Please address any issues identified by the checks before requesting a review. + +## Ask for a Review + +All pull requests should be reviewed by at least one other team member before merging. The reviewer will provide +feedback and suggestions for improvement. + +Once the reviewer approves the pull request, you can merge it into the `main` branch. + +??? QUESTION "How do I request a review?" + + Request a review from a team member by [assigning them as a reviewer](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) + to your pull request. + +### Giving Feedback + +When providing feedback on a pull request, be constructive and specific. Point out areas for improvement and suggest +possible solutions. If you have any questions or concerns, don't hesitate to ask the author for clarification. + +A code review should focus on the following aspects: + +- Correctness and functionality +- Code quality and readability +- Adherence to the project guidelines + +??? EXAMPLE "Good Code Review Feedback" + + Here are some examples of good code review feedback: + + ```plaintext + - Great work on the new command! The implementation looks good overall. + - I noticed a small typo in the docstring. Could you update it to fix the typo? + - The logic in the new command is a bit complex. Consider breaking it down into smaller functions for clarity. + - The tests cover most of the functionality, but we are missing a test case for edge case X. Could you add a test for that? + ``` + +Always be respectful and considerate when giving feedback. Remember that the goal is to improve the code and help +the author grow as a developer. + +!!! SUCCESS "Be Positive" + + Don't forget to acknowledge the positive aspects of the contribution as well! + +## Merge the Pull Request + +Once the pull request has been approved and all checks have passed, you can merge it into the `main` branch. +To merge the pull request, click the "Merge" button on the pull request page. After merging, your branch will be automatically +deleted. diff --git a/mkdocs.yaml b/mkdocs.yaml index ab814459..1a412fd2 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -2,6 +2,7 @@ copyright: © 2025 Calm Calatheas 🪴 repo_name: code-jam-12 repo_url: https://github.com/cj12-calm-calatheas/code-jam-12 site_name: Calm Calatheas 🪴 +site_url: https://cj12-calm-calatheas.github.io/code-jam-12/ theme: name: material @@ -68,6 +69,9 @@ nav: - user-guide/index.md - Contributor Guide: - contributor-guide/index.md + - Development Environment: contributor-guide/development-environment.md + - Version Control: contributor-guide/version-control.md + - Documentation: contributor-guide/documentation.md - Architecture: - architecture/index.md From 523d6d3654af6da5e033e558dab4501fdb146644 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Wed, 6 Aug 2025 09:31:23 +0000 Subject: [PATCH 05/22] chore: update license year --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index fe513044..a3c2eed9 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright 2021 Calm Calatheas 🪴 +Copyright 2025 Calm Calatheas 🪴 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 92fc700d46104b89360ad185628019781453624c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard?= <133528118+leolhuile@users.noreply.github.com> Date: Wed, 6 Aug 2025 17:54:08 +0200 Subject: [PATCH 06/22] added my portrait (#3) --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 9f5acc0d..94f1640c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,3 +12,4 @@ This project has been built by the Calm Calatheas team. Please feel free to reac a hand with anything! [TFBlunt](https://github.com/thijsfranck) +[leoluy](https://github.com/leolhuile) From 7de9692dcb073aa6e050837e59e4da2bd526d746 Mon Sep 17 00:00:00 2001 From: Zike01 <71328711+Zike01@users.noreply.github.com> Date: Wed, 6 Aug 2025 18:03:37 +0100 Subject: [PATCH 07/22] Add user profile (#4) --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 94f1640c..5fdfb5fe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,3 +13,4 @@ a hand with anything! [TFBlunt](https://github.com/thijsfranck) [leoluy](https://github.com/leolhuile) +[zike01](https://github.com/Zike01) From db738b900802d46ceadd2c1d1c619edf3057623e Mon Sep 17 00:00:00 2001 From: Flonc <37045958+FloncDev@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:38:35 +0100 Subject: [PATCH 08/22] docs: add profile (#5) --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 5fdfb5fe..56ac8256 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,3 +14,4 @@ a hand with anything! [TFBlunt](https://github.com/thijsfranck) [leoluy](https://github.com/leolhuile) [zike01](https://github.com/Zike01) +[Flonc](https://github.com/FloncDev) From 6762dfa9bd4a60e5e618867f5417c80ff3628d8a Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Fri, 8 Aug 2025 11:31:27 +0200 Subject: [PATCH 09/22] ci: support signed commits in devcontainer (#6) Add gpg to the devcontainer in order to support signed commits. See the guide on sharing [GPG keys with the devcontainer](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials#_sharing-gpg-keys) for details. For me the following scenario works: **On the host machine (WSL/Ubuntu):** - Install `gnupg2` - Generate a GPG key and register it in GitHub - Configure git as follows: ```shell git config --global commit.gpgsign true git config --global user.signingkey ``` **In the devcontainer:** - Rebuild the container with the feature added in this PR (installing `gnupg2`) --- .devcontainer/devcontainer.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 451ee337..0d66583d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,6 +6,7 @@ "charliermarsh.ruff", "DavidAnson.vscode-markdownlint", "esbenp.prettier-vscode", + "GitHub.vscode-pull-request-github", "tamasfe.even-better-toml", "-ms-python.autopep8" ], @@ -55,6 +56,9 @@ } }, "features": { + "ghcr.io/devcontainers-extra/features/apt-packages": { + "packages": ["gnupg2"] + }, "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/node:1": { "version": "lts" @@ -67,6 +71,10 @@ "forwardPorts": [8000], "image": "mcr.microsoft.com/devcontainers/base:noble", "name": "Calm Calatheas 🪴", + "onCreateCommand": { + "npm": "bash .devcontainer/npm.sh", + "uv": "bash .devcontainer/uv.sh" + }, "portsAttributes": { "8000": { "label": "Documentation Server", @@ -75,9 +83,5 @@ }, "postCreateCommand": { "pre-commit": "bash .devcontainer/pre-commit.sh" - }, - "updateContentCommand": { - "npm": "bash .devcontainer/npm.sh", - "uv": "bash .devcontainer/uv.sh" } } From 9575df40390089df2fd301b15e38a235ecc5f2e7 Mon Sep 17 00:00:00 2001 From: Flonc <37045958+FloncDev@users.noreply.github.com> Date: Sat, 9 Aug 2025 05:21:28 +0100 Subject: [PATCH 10/22] chore(dev): add devenv support (#13) Add [devenv.sh](https://devenv.sh/)/[direnv](https://direnv.net/) as an option for setting up a development environment. Comes with: - Python - uv - Node - pre-commit --- .envrc | 3 ++ .gitignore | 7 ++++ devenv.lock | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++ devenv.nix | 18 +++++++++ 4 files changed, 131 insertions(+) create mode 100644 .envrc create mode 100644 devenv.lock create mode 100644 devenv.nix diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..894571bf --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k=" + +use devenv diff --git a/.gitignore b/.gitignore index b6caec0c..16d84377 100644 --- a/.gitignore +++ b/.gitignore @@ -309,3 +309,10 @@ dist # SvelteKit build / generate output .svelte-kit + +# Devenv +.devenv* +devenv.local.nix + +# Direnv +.direnv diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 00000000..2250f543 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1754418859, + "owner": "cachix", + "repo": "devenv", + "rev": "e13cd53579f6a0f441ac09230178dccb3008dd36", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1754416808, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "9c52372878df6911f9afc1e2a1391f55e4dfc864", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1753719760, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "0f871fffdc0e5852ec25af99ea5f09ca7be9b632", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 00000000..2c50bc3e --- /dev/null +++ b/devenv.nix @@ -0,0 +1,18 @@ +{ pkgs, ... }: +{ + packages = [ + pkgs.pre-commit + ]; + + languages.python = { + enable = true; + uv = { + enable = true; + sync.enable = true; + }; + }; + + languages.javascript = { + enable = true; + }; +} From bb696502862a75ae70cd3be9f73527f74ba19339 Mon Sep 17 00:00:00 2001 From: Flonc <37045958+FloncDev@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:40:46 +0100 Subject: [PATCH 11/22] chore: create skeleton app (#14) Create a very basic skeleton app to be expanded on in the future, and some complimentary docs on how to run. Closes #9 --------- Co-authored-by: thijsfranck --- .devcontainer/devcontainer.json | 7 +- .devcontainer/playwright.sh | 4 + .github/workflows/lint.yaml | 11 +- .gitignore | 3 + Dockerfile | 8 + README.md | 8 + app/__init__.py | 0 app/index.html | 16 + app/js.pyi | 226 +++++++++++ app/main.py | 3 + app/pyscript.toml | 0 .../development-environment.md | 9 + mkdocs.yaml | 1 + package-lock.json | 374 ++++++++++++++++++ package.json | 1 + pyproject.toml | 13 + tests/conftest.py | 36 ++ tests/docker-compose.yaml | 9 + tests/test__calm_calatheas.py | 15 +- uv.lock | 261 +++++++++++- 20 files changed, 985 insertions(+), 20 deletions(-) create mode 100755 .devcontainer/playwright.sh create mode 100644 Dockerfile create mode 100644 app/__init__.py create mode 100644 app/index.html create mode 100644 app/js.pyi create mode 100644 app/main.py create mode 100644 app/pyscript.toml create mode 100644 tests/docker-compose.yaml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0d66583d..15b34508 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -68,7 +68,7 @@ "version": "3.13" } }, - "forwardPorts": [8000], + "forwardPorts": [8000, 9000], "image": "mcr.microsoft.com/devcontainers/base:noble", "name": "Calm Calatheas 🪴", "onCreateCommand": { @@ -77,11 +77,16 @@ }, "portsAttributes": { "8000": { + "label": "Development Server", + "onAutoForward": "notify" + }, + "9000": { "label": "Documentation Server", "onAutoForward": "notify" } }, "postCreateCommand": { + "playwright": "bash .devcontainer/playwright.sh", "pre-commit": "bash .devcontainer/pre-commit.sh" } } diff --git a/.devcontainer/playwright.sh b/.devcontainer/playwright.sh new file mode 100755 index 00000000..0aa5ad7a --- /dev/null +++ b/.devcontainer/playwright.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Install playwright dependencies +uv run playwright install --with-deps diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 2ce12cca..c27f0bd9 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -53,8 +53,11 @@ jobs: - name: Run pre-commit hooks uses: pre-commit/action@v3.0.1 - - name: Run the tests - run: uv run pytest tests - - name: Build the documentation - run: uv run mkdocs build --strict + run: uv run task build-docs + + - name: Install Playwright + run: uv run playwright install --with-deps + + - name: Run the tests + run: uv run task test diff --git a/.gitignore b/.gitignore index 16d84377..4d8efb00 100644 --- a/.gitignore +++ b/.gitignore @@ -170,6 +170,9 @@ poetry.toml # LSP config files pyrightconfig.json +# Playwright +test-results/ + ### Node ### # Logs logs diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..895c649c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM nginx:1.29 + +# Copy the built application files to the Nginx HTML directory +COPY --chown=root:root --chmod=0644 ./app /usr/share/nginx/html + +# Periodically check if the Nginx service is running +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD [ "service", "nginx", "status", "||", "exit", "1" ] diff --git a/README.md b/README.md index 8ad5e40c..14ad074a 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,11 @@ For detailed documentation, please refer to our [GitHub Pages site](https://cj12 ## For Contributors Please follow the instructions in the [Contributor Guide](https://cj12-calm-calatheas.github.io/code-jam-12/contributor-guide/). + +### Running Locally + +Until we have a backend setup, use the following commands to serve the static files: + +```bash +uv run task serve +``` diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/index.html b/app/index.html new file mode 100644 index 00000000..3dd1ba30 --- /dev/null +++ b/app/index.html @@ -0,0 +1,16 @@ + + + + + + Calm Calatheas + + + + + +

Loading python..

+ + + + diff --git a/app/js.pyi b/app/js.pyi new file mode 100644 index 00000000..3fd41598 --- /dev/null +++ b/app/js.pyi @@ -0,0 +1,226 @@ +# ruff: noqa: A001, A002, ANN401, N801, N802, N803, PYI052, N815, PYI001, UP046 +# Pyodide already has a js.pyi but it is not complete for us, +# and is not even able to be used. + +# Use https://developer.mozilla.org/en-US/docs/Web/API as reference. + +from collections.abc import Callable, Iterable, Sequence +from typing import Any, Generic, Literal, TypeVar, overload + +from _pyodide._core_docs import _JsProxyMetaClass +from pyodide.ffi import ( + JsArray, + JsException, + JsFetchResponse, + JsProxy, + JsTypedArray, +) +from pyodide.ffi import ( + JsDomElement as OldJSDomElement, +) +from pyodide.webloop import PyodideFuture + +class JsDomElement(OldJSDomElement): + classList: DOMTokenList + innerText: str + innerHTML: JsDomElement | str + + @property + def style(self) -> CSSStyleDeclaration: ... + @property + def children(self) -> Sequence[JsDomElement]: ... + def getAttribute(self, name: str) -> str: ... + def setAttribute(self, name: str, value: str) -> None: ... + def closest(self, selectors: str) -> JsDomElement: ... + def getElementsByTagName(self, tagName: str) -> JsArray[JsDomElement]: ... + def hasAttribute(self, attrName: str) -> bool: ... + def removeAttribute(self, attrName: str) -> None: ... + def getBoundingClientRect(self) -> DOMRect: ... + # These are on Node, which an Element is. + # As far as this project is concerned, they are just elements. + firstChild: JsDomElement | None + nextSibling: JsDomElement | None + def removeChild(self, child: JsDomElement) -> None: ... + def insertBefore(self, newChild: JsDomElement, refChild: JsDomElement) -> None: ... + def contains(self, otherNode: JsDomElement) -> bool: ... + +class JsImgElement(JsDomElement): + src: str + width: int + height: int + naturalWidth: int + naturalHeight: int + +class JsInputElement(JsDomElement): + value: str + +class JsCanvasElement(JsDomElement): + width: int + height: int + @overload + def getContext(self, contextId: Literal["2d"]) -> CanvasRenderingContext2D: ... + @overload + def getContext(self, contextId: str) -> JsProxy: ... + def getContext(self, contextId: str) -> JsProxy: ... + +class DOMRect(JsProxy): + top: float + left: float + bottom: float + right: float + width: float + height: float + +class CanvasRenderingContext2D(JsProxy): + canvas: JsCanvasElement + @overload + def drawImage(self, image: JsImgElement, dx: int, dy: int) -> None: ... + @overload + def drawImage( + self, + image: JsImgElement, + dx: int, + dy: int, + dWidth: int, + dHeight: int, + ) -> None: ... + def drawImage( + self, + image: JsImgElement, + dx: int, + dy: int, + dWidth: int = ..., + dHeight: int = ..., + ) -> None: ... + def translate(self, x: float, y: float) -> None: ... + def rotate(self, angle: float) -> None: ... + +class DOMTokenList(JsProxy): + def add(self, token: str) -> None: ... + def remove(self, *tokens: str) -> None: ... + def contains(self, token: str) -> bool: ... + +class CSSStyleDeclaration(JsProxy): + def removeProperty(self, name: str) -> None: ... + def setProperty(self, name: str, value: str) -> None: ... + def __setattr__(self, name: str, value: str) -> None: ... + +ElementT = TypeVar("ElementT", bound=JsDomElement) + +class LoadEvent(JsProxy, Generic[ElementT]): + target: ElementT + +class Event(JsProxy): + target: JsDomElement + def preventDefault(self) -> None: ... + +class UIEvent(Event): ... + +class MouseEvent(Event): + relatedTarget: JsDomElement + pageX: float + pageY: float + +class DragEvent(MouseEvent): + dataTransfer: DataTransfer + +class DataTransfer(JsProxy): + dropEffect: str + def setData(self, format: str, data: str) -> None: ... + def setDragImage(self, image: JsDomElement, x: int, y: int) -> None: ... + def getData(self, format: str) -> str: ... + +def eval(code: str) -> Any: ... + +# in browser the cancellation token is an int, in node it's a special opaque +# object. +type _CancellationToken = int | JsProxy + +def setTimeout(cb: Callable[[], Any], timeout: float) -> _CancellationToken: ... +def clearTimeout(id: _CancellationToken) -> None: ... +def setInterval(cb: Callable[[], Any], interval: float) -> _CancellationToken: ... +def clearInterval(id: _CancellationToken) -> None: ... +def fetch( + url: str, + options: JsProxy | None = None, +) -> PyodideFuture[JsFetchResponse]: ... + +self: Any = ... +window: Any = ... + +# Shenanigans to convince skeptical type system to behave correctly: +# +# These classes we are declaring are actually JavaScript objects, so the class +# objects themselves need to be instances of JsProxy. So their type needs to +# subclass JsProxy. We do this with a custom metaclass. + +class _JsMeta(_JsProxyMetaClass, JsProxy): ... +class _JsObject(metaclass=_JsMeta): ... + +class XMLHttpRequest(_JsObject): + response: str + + @staticmethod + def new() -> XMLHttpRequest: ... + def open(self, method: str, url: str, sync: bool) -> None: ... + def send(self, body: JsProxy | None = None) -> None: ... + +class Object(_JsObject): + @staticmethod + def fromEntries(it: Iterable[JsArray[Any]]) -> JsProxy: ... + +class Array(_JsObject): + @staticmethod + def new() -> JsArray[Any]: ... + +class ImageData(_JsObject): + @staticmethod + def new(width: int, height: int, settings: JsProxy | None = None) -> ImageData: ... + + width: int + height: int + +class _TypedArray(_JsObject): + @staticmethod + def new( + a: int | Iterable[int | float] | JsProxy | None, + byteOffset: int = 0, + length: int = 0, + ) -> JsTypedArray: ... + +class Uint8Array(_TypedArray): + BYTES_PER_ELEMENT = 1 + +class Float64Array(_TypedArray): + BYTES_PER_ELEMENT = 8 + +class JSON(_JsObject): + @staticmethod + def stringify(a: JsProxy) -> str: ... + @staticmethod + def parse(a: str) -> JsProxy: ... + +class document(_JsObject): + body: JsDomElement + children: list[JsDomElement] + @staticmethod + def getElementById(id: str) -> JsDomElement: ... + @overload + @staticmethod + def createElement(tagName: Literal["img"]) -> JsImgElement: ... + @overload + @staticmethod + def createElement(tagName: Literal["canvas"]) -> JsCanvasElement: ... + @overload + @staticmethod + def createElement(tagName: str) -> JsDomElement: ... + @staticmethod + def createElement(tagName: str) -> JsDomElement: ... + @staticmethod + def appendChild(child: JsDomElement) -> None: ... + +class ArrayBuffer(_JsObject): + @staticmethod + def isView(x: Any) -> bool: ... + +class DOMException(JsException): ... diff --git a/app/main.py b/app/main.py new file mode 100644 index 00000000..295535ea --- /dev/null +++ b/app/main.py @@ -0,0 +1,3 @@ +from js import document + +document.body.innerHTML = "

Hello from Python!

" diff --git a/app/pyscript.toml b/app/pyscript.toml new file mode 100644 index 00000000..e69de29b diff --git a/docs/contributor-guide/development-environment.md b/docs/contributor-guide/development-environment.md index dfc5c821..abd732b3 100644 --- a/docs/contributor-guide/development-environment.md +++ b/docs/contributor-guide/development-environment.md @@ -136,3 +136,12 @@ uv run pre-commit install ``` This will set up the pre-commit hooks to run automatically on each commit. + +#### Install Playwright + +This project uses [Playwright](https://playwright.dev/python/) to simulate user interactions for testing. To install the +required dependencies, run the following command: + +```bash +uv run playwright install --with-deps +``` diff --git a/mkdocs.yaml b/mkdocs.yaml index 1a412fd2..932d0ef0 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -3,6 +3,7 @@ repo_name: code-jam-12 repo_url: https://github.com/cj12-calm-calatheas/code-jam-12 site_name: Calm Calatheas 🪴 site_url: https://cj12-calm-calatheas.github.io/code-jam-12/ +dev_addr: 127.0.0.1:9000 theme: name: material diff --git a/package-lock.json b/package-lock.json index 7734a0bf..598235dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "calm-calatheas", "devDependencies": { + "nodemon": "^3.1.10", "prettier": "3.6.2", "prettier-plugin-sort-json": "4.1.1", "prettier-plugin-toml": "2.0.6" @@ -28,6 +29,290 @@ "@taplo/core": "^0.2.0" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prettier": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", @@ -75,6 +360,95 @@ "peerDependencies": { "prettier": "^3.0.3" } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index ceb9cc0e..973ab7f8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { + "nodemon": "3.1.10", "prettier": "3.6.2", "prettier-plugin-sort-json": "4.1.1", "prettier-plugin-toml": "2.0.6" diff --git a/pyproject.toml b/pyproject.toml index ab04e04f..034f1735 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,13 +14,18 @@ dev = [ "mkdocs-material~=9.6", "mkdocstrings~=0.29", "mkdocstrings-python~=1.16", + "playwright~=1.54", "pre-commit~=4.2", + "pyodide-py~=0.28", "pyright==1.1.403", "pytest~=8.4", "pytest-asyncio~=1.0", + "pytest-playwright~=0.7", "pytest-sugar~=1.0", "python-dotenv~=1.1", "ruff~=0.12", + "taskipy~=1.14", + "testcontainers~=4.12", ] [tool.pyright] @@ -31,9 +36,11 @@ typeCheckingMode = "standard" venvPath = "." [tool.pytest.ini_options] +addopts = "--browser chromium --browser firefox --browser webkit --tracing retain-on-failure" asyncio_default_fixture_loop_scope = "session" asyncio_mode = "auto" required_plugins = ["pytest-asyncio"] +testpaths = ["tests"] [tool.ruff] line-length = 119 @@ -49,3 +56,9 @@ select = ["ALL"] [tool.ruff.lint.pydocstyle] convention = "google" + +[tool.taskipy.tasks] +build-docs = "mkdocs build --strict" +docs = "mkdocs serve" +serve = "npx nodemon -x \"python -m http.server -d ./app\" -w \"./app\" -e \"*\"" +test = "pytest" diff --git a/tests/conftest.py b/tests/conftest.py index e69de29b..f6ab6e06 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,36 @@ +from collections.abc import Generator +from pathlib import Path + +import pytest +from playwright.sync_api import Page +from testcontainers.compose import DockerCompose + +PYSCRIPT_READY_TIMEOUT_MS = 20000 + + +@pytest.fixture(scope="session") +def compose() -> Generator[DockerCompose]: + """Return a Docker Compose instance.""" + with DockerCompose(context=Path(__file__).parent.absolute()) as compose: + yield compose + + +@pytest.fixture(scope="session") +def base_url(compose: DockerCompose) -> str: + """Return the base URL for the application.""" + port = compose.get_service_port("app", 80) + return f"http://localhost:{port}" + + +@pytest.fixture() +def app(base_url: str, page: Page) -> Page: + """Navigate to the home page, wait for PyScript load and return the page instance.""" + page.goto(base_url) + + page.wait_for_event( + event="console", + predicate=lambda event: "PyScript Ready" in event.text, + timeout=PYSCRIPT_READY_TIMEOUT_MS, + ) + + return page diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml new file mode 100644 index 00000000..a9af7662 --- /dev/null +++ b/tests/docker-compose.yaml @@ -0,0 +1,9 @@ +name: calm-calatheas-test + +services: + app: + build: + context: .. + dockerfile: Dockerfile + ports: + - 80 diff --git a/tests/test__calm_calatheas.py b/tests/test__calm_calatheas.py index abeec57f..12d904e8 100644 --- a/tests/test__calm_calatheas.py +++ b/tests/test__calm_calatheas.py @@ -1,8 +1,13 @@ -def test__placeholder() -> None: +import re + +from playwright.sync_api import Page, expect + + +def test__main_page_has_welcome_message(app: Page) -> None: """ - Placeholder test function. + Test that the main page has a welcome message. - This function is intended to be replaced with actual test cases. For now, it is required to ensure that Pytest - doesn't fail in CI due to the absence of tests. + Asserts: + - The welcome message is visible on the page. """ - assert True, "This is a placeholder function. Replace with actual tests." + expect(app.get_by_text(re.compile("Hello from Python!"))).to_be_visible() diff --git a/uv.lock b/uv.lock index ac3b6b47..becd9f96 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ requires-python = ">=3.13" -revision = 3 +revision = 2 version = 1 [[package]] @@ -36,13 +36,18 @@ dev = [ { name = "mkdocs-material" }, { name = "mkdocstrings" }, { name = "mkdocstrings-python" }, + { name = "playwright" }, { name = "pre-commit" }, + { name = "pyodide-py" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-playwright" }, { name = "pytest-sugar" }, { name = "python-dotenv" }, { name = "ruff" }, + { name = "taskipy" }, + { name = "testcontainers" }, ] [package.metadata] @@ -53,13 +58,18 @@ dev = [ { name = "mkdocs-material", specifier = "~=9.6" }, { name = "mkdocstrings", specifier = "~=0.29" }, { name = "mkdocstrings-python", specifier = "~=1.16" }, + { name = "playwright", specifier = "~=1.54" }, { name = "pre-commit", specifier = "~=4.2" }, + { name = "pyodide-py", specifier = "~=0.28" }, { name = "pyright", specifier = "==1.1.403" }, { name = "pytest", specifier = "~=8.4" }, { name = "pytest-asyncio", specifier = "~=1.0" }, + { name = "pytest-playwright", specifier = "~=0.7" }, { name = "pytest-sugar", specifier = "~=1.0" }, { name = "python-dotenv", specifier = "~=1.1" }, { name = "ruff", specifier = "~=0.12" }, + { name = "taskipy", specifier = "~=1.14" }, + { name = "testcontainers", specifier = "~=4.12" }, ] [[package]] @@ -130,6 +140,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +dependencies = [{ name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "requests" }, { name = "urllib3" }] +name = "docker" +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +source = { registry = "https://pypi.org/simple" } +version = "7.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + [[package]] name = "filelock" sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } @@ -149,6 +169,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "greenlet" +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +source = { registry = "https://pypi.org/simple" } +version = "3.2.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + [[package]] dependencies = [{ name = "colorama" }] name = "griffe" @@ -344,6 +388,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/dd/a24ee3de56954bfafb6ede7cd63c2413bb842cc48eb45e41c43a05a33074/mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374", size = 124287, upload-time = "2025-06-03T12:52:47.819Z" }, ] +[[package]] +name = "mslex" +sdist = { url = "https://files.pythonhosted.org/packages/e0/97/7022667073c99a0fe028f2e34b9bf76b49a611afd21b02527fbfd92d4cd5/mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d", size = 11583, upload-time = "2024-10-16T13:16:18.523Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820, upload-time = "2024-10-16T13:16:17.566Z" }, +] + [[package]] name = "nodeenv" sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } @@ -382,20 +435,36 @@ wheels = [ [[package]] name = "platformdirs" -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916, upload-time = "2024-05-15T03:18:23.372Z" } source = { registry = "https://pypi.org/simple" } -version = "4.3.8" +version = "4.2.2" wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, + { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146, upload-time = "2024-05-15T03:18:21.209Z" }, +] + +[[package]] +dependencies = [{ name = "greenlet" }, { name = "pyee" }] +name = "playwright" +source = { registry = "https://pypi.org/simple" } +version = "1.54.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/09/33d5bfe393a582d8dac72165a9e88b274143c9df411b65ece1cc13f42988/playwright-1.54.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bf3b845af744370f1bd2286c2a9536f474cc8a88dc995b72ea9a5be714c9a77d", size = 40439034, upload-time = "2025-07-22T13:58:04.816Z" }, + { url = "https://files.pythonhosted.org/packages/e1/7b/51882dc584f7aa59f446f2bb34e33c0e5f015de4e31949e5b7c2c10e54f0/playwright-1.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:780928b3ca2077aea90414b37e54edd0c4bbb57d1aafc42f7aa0b3fd2c2fac02", size = 38702308, upload-time = "2025-07-22T13:58:08.211Z" }, + { url = "https://files.pythonhosted.org/packages/73/a1/7aa8ae175b240c0ec8849fcf000e078f3c693f9aa2ffd992da6550ea0dff/playwright-1.54.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:81d0b6f28843b27f288cfe438af0a12a4851de57998009a519ea84cee6fbbfb9", size = 40439037, upload-time = "2025-07-22T13:58:11.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/a9/45084fd23b6206f954198296ce39b0acf50debfdf3ec83a593e4d73c9c8a/playwright-1.54.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:09919f45cc74c64afb5432646d7fef0d19fff50990c862cb8d9b0577093f40cc", size = 45920135, upload-time = "2025-07-22T13:58:14.494Z" }, + { url = "https://files.pythonhosted.org/packages/02/d4/6a692f4c6db223adc50a6e53af405b45308db39270957a6afebddaa80ea2/playwright-1.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ae206c55737e8e3eae51fb385d61c0312eeef31535643bb6232741b41b6fdc", size = 45302695, upload-time = "2025-07-22T13:58:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/4ee60a1c3714321db187bebbc40d52cea5b41a856925156325058b5fca5a/playwright-1.54.0-py3-none-win32.whl", hash = "sha256:0b108622ffb6906e28566f3f31721cd57dda637d7e41c430287804ac01911f56", size = 35469309, upload-time = "2025-07-22T13:58:21.917Z" }, + { url = "https://files.pythonhosted.org/packages/aa/77/8f8fae05a242ef639de963d7ae70a69d0da61d6d72f1207b8bbf74ffd3e7/playwright-1.54.0-py3-none-win_amd64.whl", hash = "sha256:9e5aee9ae5ab1fdd44cd64153313a2045b136fcbcfb2541cc0a3d909132671a2", size = 35469311, upload-time = "2025-07-22T13:58:24.707Z" }, + { url = "https://files.pythonhosted.org/packages/33/ff/99a6f4292a90504f2927d34032a4baf6adb498dc3f7cf0f3e0e22899e310/playwright-1.54.0-py3-none-win_arm64.whl", hash = "sha256:a975815971f7b8dca505c441a4c56de1aeb56a211290f8cc214eeef5524e8d75", size = 31239119, upload-time = "2025-07-22T13:58:27.56Z" }, ] [[package]] name = "pluggy" -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" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } source = { registry = "https://pypi.org/simple" } -version = "1.6.0" +version = "1.5.0" wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] @@ -414,6 +483,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, ] +[[package]] +name = "psutil" +sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502, upload-time = "2024-12-19T18:21:20.568Z" } +source = { registry = "https://pypi.org/simple" } +version = "6.1.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511, upload-time = "2024-12-19T18:21:45.163Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985, upload-time = "2024-12-19T18:21:49.254Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488, upload-time = "2024-12-19T18:21:51.638Z" }, + { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477, upload-time = "2024-12-19T18:21:55.306Z" }, + { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017, upload-time = "2024-12-19T18:21:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602, upload-time = "2024-12-19T18:22:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444, upload-time = "2024-12-19T18:22:11.335Z" }, +] + +[[package]] +dependencies = [{ name = "typing-extensions" }] +name = "pyee" +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +source = { registry = "https://pypi.org/simple" } +version = "13.0.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, +] + [[package]] name = "pygments" 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" } @@ -433,6 +527,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, ] +[[package]] +name = "pyodide-py" +sdist = { url = "https://files.pythonhosted.org/packages/85/3d/bdb623dc6b1165c906b958b25f81ea2b0ba2f06ded5e491d272f9a54c35d/pyodide_py-0.28.1.tar.gz", hash = "sha256:11464c6e0e3063e7c0bfc2802f53c74f75fb0834190b86bb78769f84c635a18e", size = 52569, upload-time = "2025-08-04T21:31:55.431Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.28.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/f9/a2533ac6831763c36dd1f4cb6ba9eebb8f41b1880be9e89e3b5994c6c1f8/pyodide_py-0.28.1-py3-none-any.whl", hash = "sha256:e158e58da4cee77d1dc825bdeddd689335a41b8a79b4fa4483a0f3c50e0454e7", size = 58478, upload-time = "2025-08-04T21:31:54.07Z" }, +] + [[package]] dependencies = [{ name = "nodeenv" }, { name = "typing-extensions" }] name = "pyright" @@ -469,6 +572,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, ] +[[package]] +dependencies = [{ name = "pytest" }, { name = "requests" }] +name = "pytest-base-url" +sdist = { url = "https://files.pythonhosted.org/packages/ae/1a/b64ac368de6b993135cb70ca4e5d958a5c268094a3a2a4cac6f0021b6c4f/pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45", size = 6702, upload-time = "2024-01-31T22:43:00.81Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.1.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/1c/b00940ab9eb8ede7897443b771987f2f4a76f06be02f1b3f01eb7567e24a/pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6", size = 5302, upload-time = "2024-01-31T22:42:58.897Z" }, +] + +[[package]] +dependencies = [ + { name = "playwright" }, + { name = "pytest" }, + { name = "pytest-base-url" }, + { name = "python-slugify" }, +] +name = "pytest-playwright" +sdist = { url = "https://files.pythonhosted.org/packages/e3/47/38e292ad92134a00ea05e6fc4fc44577baaa38b0922ab7ea56312b7a6663/pytest_playwright-0.7.0.tar.gz", hash = "sha256:b3f2ea514bbead96d26376fac182f68dcd6571e7cb41680a89ff1673c05d60b6", size = 16666, upload-time = "2025-01-31T11:06:05.453Z" } +source = { registry = "https://pypi.org/simple" } +version = "0.7.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/96/5f8a4545d783674f3de33f0ebc4db16cc76ce77a4c404d284f43f09125e3/pytest_playwright-0.7.0-py3-none-any.whl", hash = "sha256:2516d0871fa606634bfe32afbcc0342d68da2dbff97fe3459849e9c428486da2", size = 16618, upload-time = "2025-01-31T11:06:08.075Z" }, +] + [[package]] dependencies = [{ name = "packaging" }, { name = "pytest" }, { name = "termcolor" }] name = "pytest-sugar" @@ -498,6 +626,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +dependencies = [{ name = "text-unidecode" }] +name = "python-slugify" +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +source = { registry = "https://pypi.org/simple" } +version = "8.0.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + +[[package]] +name = "pywin32" +source = { registry = "https://pypi.org/simple" } +version = "311" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + [[package]] name = "pyyaml" sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } @@ -528,11 +679,11 @@ wheels = [ [[package]] dependencies = [{ name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }] name = "requests" -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794, upload-time = "2023-05-22T15:12:44.175Z" } source = { registry = "https://pypi.org/simple" } -version = "2.32.4" +version = "2.31.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574, upload-time = "2023-05-22T15:12:42.313Z" }, ] [[package]] @@ -569,6 +720,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +dependencies = [ + { name = "colorama" }, + { name = "mslex", marker = "sys_platform == 'win32'" }, + { name = "psutil" }, + { name = "tomli", marker = "python_full_version < '4'" }, +] +name = "taskipy" +sdist = { url = "https://files.pythonhosted.org/packages/c7/44/572261df3db9c6c3332f8618fafeb07a578fd18b06673c73f000f3586749/taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed", size = 14475, upload-time = "2024-11-26T16:37:46.155Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.14.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/97/4e4cfb1391c81e926bebe3d68d5231b5dbc3bb41c6ba48349e68a881462d/taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1", size = 13052, upload-time = "2024-11-26T16:37:44.546Z" }, +] + [[package]] name = "termcolor" sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } @@ -578,6 +744,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, ] +[[package]] +dependencies = [ + { name = "docker" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "wrapt" }, +] +name = "testcontainers" +sdist = { url = "https://files.pythonhosted.org/packages/d3/62/01d9f648e9b943175e0dcddf749cf31c769665d8ba08df1e989427163f33/testcontainers-4.12.0.tar.gz", hash = "sha256:13ee89cae995e643f225665aad8b200b25c4f219944a6f9c0b03249ec3f31b8d", size = 66631, upload-time = "2025-07-21T20:32:26.37Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.12.0" +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/e8/9e2c392e5d671afda47b917597cac8fde6a452f5776c4c9ceb93fbd2889f/testcontainers-4.12.0-py3-none-any.whl", hash = "sha256:26caef57e642d5e8c5fcc593881cf7df3ab0f0dc9170fad22765b184e226ab15", size = 111791, upload-time = "2025-07-21T20:32:25.038Z" }, +] + +[[package]] +name = "text-unidecode" +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.3" +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "tomli" +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +source = { registry = "https://pypi.org/simple" } +version = "2.2.1" +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + [[package]] name = "typing-extensions" sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } @@ -626,3 +836,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] + +[[package]] +name = "wrapt" +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" } +source = { registry = "https://pypi.org/simple" } +version = "1.17.2" +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" }, +] From c325273e8281625d714a63fb1a63179fecb8dad6 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Sat, 9 Aug 2025 17:06:00 +0000 Subject: [PATCH 12/22] chore: update package-lock.json --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 598235dc..dce2767f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "calm-calatheas", "devDependencies": { - "nodemon": "^3.1.10", + "nodemon": "3.1.10", "prettier": "3.6.2", "prettier-plugin-sort-json": "4.1.1", "prettier-plugin-toml": "2.0.6" From d86e95952e1c71127c11e0b6c933708606159878 Mon Sep 17 00:00:00 2001 From: esmaycat <71032999+esmaycat@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:49:31 +0100 Subject: [PATCH 13/22] docs: add my profile (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🥳 --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 56ac8256..a6a7d830 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,3 +15,4 @@ a hand with anything! [leoluy](https://github.com/leolhuile) [zike01](https://github.com/Zike01) [Flonc](https://github.com/FloncDev) +[esmaycat](https://github.com/esmaycat) From af6c31e74b2a029119a2c7904c19838f0f9b182b Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Mon, 11 Aug 2025 07:30:12 +0200 Subject: [PATCH 14/22] feat: add basic components for the app (#18) - The app now has a header, footer, and main body - Introduce bulma.css as a styling framework, as well as a custom font and color palette - Include a menu to switch themes for some interactivity --- Dockerfile | 2 +- app/__init__.py | 0 app/assets/pokemongb.ttf | Bin 0 -> 12184 bytes app/calm_calatheas/__init__.py | 3 + app/calm_calatheas/app.py | 48 ++++++++++ app/calm_calatheas/base/__init__.py | 3 + app/calm_calatheas/base/component.py | 52 +++++++++++ app/calm_calatheas/components/__init__.py | 5 ++ app/calm_calatheas/components/footer.py | 25 ++++++ app/calm_calatheas/components/header.py | 73 +++++++++++++++ app/calm_calatheas/components/theme.py | 78 ++++++++++++++++ app/calm_calatheas/services/__init__.py | 3 + app/calm_calatheas/services/theme.py | 48 ++++++++++ app/index.html | 5 +- app/main.py | 11 ++- app/pyscript.toml | 20 +++++ app/styles/theme.css | 22 +++++ pyproject.toml | 6 +- tests/conftest.py | 2 +- tests/test__calm_calatheas.py | 104 ++++++++++++++++++++++ {app => typings}/js.pyi | 59 ++++++------ uv.lock | 14 ++- 22 files changed, 544 insertions(+), 39 deletions(-) delete mode 100644 app/__init__.py create mode 100644 app/assets/pokemongb.ttf create mode 100644 app/calm_calatheas/__init__.py create mode 100644 app/calm_calatheas/app.py create mode 100644 app/calm_calatheas/base/__init__.py create mode 100644 app/calm_calatheas/base/component.py create mode 100644 app/calm_calatheas/components/__init__.py create mode 100644 app/calm_calatheas/components/footer.py create mode 100644 app/calm_calatheas/components/header.py create mode 100644 app/calm_calatheas/components/theme.py create mode 100644 app/calm_calatheas/services/__init__.py create mode 100644 app/calm_calatheas/services/theme.py create mode 100644 app/styles/theme.css rename {app => typings}/js.pyi (81%) diff --git a/Dockerfile b/Dockerfile index 895c649c..7ce48812 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM nginx:1.29 # Copy the built application files to the Nginx HTML directory -COPY --chown=root:root --chmod=0644 ./app /usr/share/nginx/html +COPY --chown=root:root --chmod=0755 ./app /usr/share/nginx/html # Periodically check if the Nginx service is running HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/app/assets/pokemongb.ttf b/app/assets/pokemongb.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b5025f06293a9a0ce040a2513fb61bc6494f8416 GIT binary patch literal 12184 zcmd^_d7NBDwa3rB-Lqzrkc1&D3K=HsAq2t@G>8~cP(eT-tSS*gBFQ8fCfI493D6jc z3xf!_Au4W&qNv~+cif(c8-h!esHhP&Mr}syH`Dihf2Z#4Wf*+#^WLBD_RO!VZ{Ip~ z>g-ir7MWR(U2Qo#?v&FHJK}ZMZ~TN=bRu^zSbF@)XAeDCTyB=T*{p5bg@eo19dyou zE6uvrb8p*GrF)n9X0u=e@;={?EVshBv=*`Y}&fS8h9Z>>Kxe zpf9}N{gq@a*_eAgH^}EMi*=WaKJkvEojGmi`&#aCrxd;mc!l4&!seMc_a5PkI_7e} zk8ZR!%g0;d*K_xc;XV4L9bwh_Q=yS-{=BYu>98GVb8IZ-VbHGOdw2pW9jEg)N1W+% zcvqYMVs3jhERI84pR<46%kF3Gr+XKTjLy?Nlie5nto?s^j|Ut4Wjs&zF1~DZ!d%nO zlRcl^%XrH^XEd_=6W_DvCcEEsZo3+Y{W|_5$~@XMl6;naO`zpX0u^H|>7g2b^nj z!2PTb+~4*E53v5EY6sd}@F2&7Z9m$F*#6)=I{-Y?4g}}hLEvF_aPlu(V26N*J04;4 zXfL!w!6R)xIADi?N7({!ksY2qX-C@;;IkZ`Z3}5Hwj;q~Yyf?}JOJlkFho@1wg=Q^He zr_w&(P6JR^I!8hC0;7xW7 z_!h^T?b_tec8it4TkSgVt@c{*ZMF$~yS)y4hg}c8)A2Ujoczh&Wv>Tsw>Nc%Qu+{ItCX{EWR9{H)ype$L(pe%{^> zegXW0ZLtr4U$hT`U$PH@U$#5Juh@sdui9PU*X$$7@9pdMQSg4pZ`j?mziA%>zhxf> zzisz`-?2}C|6!j@erMmcPk|3Oe$Vcu{h-|ke&0R~{=hy1{?I-P{>VO;JZ3+(&w~#+ z{=~jO`(fJxK4M=4e`;R>e`a3>e{Nq%ervz5uYy}0e`#N%{VV%A_^90vZnJNIzqW5C zzp>xgx4_@px5398e`nvJ4X5BA?7QH0djR~SeJ}a7J#G(zf3oj`e|G$f{ebok`yu#O z`w_U)ehmK29!j>^-|Z*hKkQ+!;`oF;LVMJH3jWi620m#&2mfWiNFF8EZw3F;ehH4* zufTD~nmtN8v27sfJN(B_hCENC&8ZddV(n3d@64V^zwr+aQN3Qt>XycLtu3z|pjhqUqSmE0%(s^jr(Q#3GU9OKTGnX(4_l`Z%_m+t;`#sXQXO|DkzUtXcAmeVOFTCs zdmallWH*P_Or$&)$u34x=MnoaC(hi1uilUUeGfl+1P|GY)jx) zeV#|3WkjfZ@b&xg+z0T(hw-hg_|9Ya!{6xhFYmL|EZLg;Ect2jNb(Tv_arwZHzsdN z-jH0MY)W36T$fy%T$5}}u1ZRY>fa-rMbLgangB2#riy;2PZs7T$|O`+gFS$<2_Na ztT{qsjBm#D-J-r3eMlj}YGpK z>;MXZ5^}{BO5>a1BO0o0uGTikJIkelm@k(qGOC6wrW4PDEZ~6|h*sZzy%=JzD9tZKCaLaUQ$lRM_{AiGK7s59z?-q(4es&d`KcK zBx)l_B&E?#3UvEUbqWtIQ)YlngLeD!xgno{IF~$kN(j|dnhe@x6SGsRkWJL33Z?!? za!t8`j;154K(r(JcZYM~MpmeeJVn0Q+=6UGsXv>WXhjGqr6x+XcFj+7i{MPdRCqa?=#XK@(S-4+%bkRruu=N<~4&GvN4zBqD(6|4RY)$$E(I~iFVXx62)qpVYFN+ zBSnM}bPSDVuXkR8$NFp)3lUMZRIbhBeFq`5Jp@y6NjP<8Gns{WJVg`3u)ol(Ub>UVwXy`k+$trp+@om|kkT)F8?Iq9 zZDFoN=WU{DIcya|xS^L2!Cv`XapT8k8?-&f zxF;&s_Rm6B#CmZcWscv%Eb%`Xl^n3qZ#L_JR-VuknrY>WxTRQ8=dE0D&&jw$l8Te! zui%mh9MW##F~qYh&&m>lESvd-2o!29TuV>fOL?E5XKYe*nlqhpKK$p?&>^*wA0&p6 zQoJ)5mZGK%tXbDYbosG72rsT}F4s289cIQmT78&kj<3lh@Hp847DaSY4$2K`4B~{l z!oRmkS$`^lx=n`ZL#FDN!mh$^>-cWpA)mI|WS}UIl3^n>nYr9aM7)L()s{IQ;&Vn9 zZk5iFX!7!zLDB0{zI9!xHYG10JY5Je)DHRp&y11|+Gzee83caiov9R^VQ5L;nP1Wl z^+ropmUlcr(hnO4>Mu@wH&=dv(@4g;z z+ekA>gqIoKUx+9U zOdNM0wP|H?`c`DrWo2**O{Ed9g6jR4vsVCNWYt2l8qL_pC1VcN`!jN4iU?ZZr+hPC z_@?@xpIbo|8_9wd$qF&c*vfpw8KXiOyHeg0-&%h(jqiDhkq7z%@1%PdSx?IUsK%M5TUiYCJ)#cG}nJXHtwZ~o(z}ILJYQ7F-*hOhw zG>d0PaShy=XiRn4Udj!5{;tb^w)B@)0kY~Q4=k1oRLAmM`YT^4J*vRNo{0>;7WMUH zjt_7qu4J4Ie?d-ZohA#Bq{Ay8l_(m$Dl2;cud#_GXRGx{N294-`&!uhA)!&P$FTfi zs*~_!vpr0~3sOsTji3!yjQ!KT?6>eG&bXF%Rr ze6bFfP3MNfnoIjCzV>^nKQMPJCiE>=D$+aC4}mq*Wy(pjw4ducE8^ODNhU^` z&ZYP+6{;l`f{hx|>HBU}R^PXd(4W!AFPv;fzv|>E$waPZX7F!X5@>Jm32UgtiGW zfaaW2U%StiZ=~9j1l>D=H?SupdseK#U!a>>m*{BjT9&chG|Ijne=d{~PN_vjBPK(g z?9VIJPw)_=IUt{_$HFW|OJyMvrYoxYM>U${IW_DNHm^d0hc~ZAD43wd8&38W}P!Zdk~vg`=#}y2@QR=((HI@7Ng-^9r8t2Kq zF7rt!a-Y3x5pAJKfYd^kU1VpLH3XF$yyok4wH667P(3w)Pj+)$FHc>Uvul0ta5EA$2!%8MR9_Yuz{ygsll0BoJI;UAYC>2RGMtVV|$Q~^Tw>SE}PN6h(MsaNeRg7 zEkPQ$hoC4&W|*(T#mbeb%yzFKOSRhqwKRSla`eVOql+pp)r7K^G= zYnW3?Em)KLrpMndtrXJ=n~dra6pPAM9#XgxN?3ld!5H$uzv=H%blHDm)n%PBllqLSNVMY;QZP7_4&KP-7jsovpzqJh!^F%xU(xKW0fIwq_Y8Y7XaY&jNof=7der&)6)U&`-T4 zqttp&Pe~qrCHMJxlJKD!uRw4yPvwx0&L~ZGLTUGwlS%vjw=lYV8+{N2rIoL6xQZaF939^gFEk2&+Uf^%aJ zbH;5YClxQ@bkZs$*f^Jzol4rkiJqce!P&@-*vr+Ng6tv=ceKr4bJ^ +
+
+
+

Hello from Python!

+

This is a simple app using Pyodide.

+
+
+

+ A general description of the app goes here. +

+
+ +
+
+
+ + +""" + + +class App(Component): + """The main application class.""" + + @override + def build(self) -> str: + return TEMPLATE + + @override + def pre_destroy(self) -> None: + self._footer.destroy() + self._header.destroy() + + @override + def on_render(self) -> None: + self._footer = Footer(document.getElementById("app-footer")) + self._footer.render() + + self._header = Header(document.getElementById("app-header")) + self._header.render() diff --git a/app/calm_calatheas/base/__init__.py b/app/calm_calatheas/base/__init__.py new file mode 100644 index 00000000..c2a94cc6 --- /dev/null +++ b/app/calm_calatheas/base/__init__.py @@ -0,0 +1,3 @@ +from .component import Component + +__all__ = ["Component"] diff --git a/app/calm_calatheas/base/component.py b/app/calm_calatheas/base/component.py new file mode 100644 index 00000000..9d2235b6 --- /dev/null +++ b/app/calm_calatheas/base/component.py @@ -0,0 +1,52 @@ +from abc import ABC, abstractmethod + +from js import DOMParser +from pyodide.ffi import JsDomElement + + +class Component(ABC): + """A base class for all components.""" + + parser = DOMParser.new() # type: ignore[] + + def __init__(self, root: JsDomElement) -> None: + self.root = root + + @abstractmethod + def build(self) -> str: + """Build the component's template and output it as an HTML string.""" + + def destroy(self) -> None: + """Destroy the component and clean up resources.""" + self.element.remove() + self.on_destroy() + + def on_destroy(self) -> None: + """Hook to perform actions after the component is destroyed.""" + return + + def on_render(self) -> None: + """Hook to perform actions after rendering the component.""" + return + + def pre_destroy(self) -> None: + """Hook to perform actions before the component is destroyed.""" + return + + def pre_render(self) -> None: + """Hook to perform actions before rendering the component.""" + return + + def render(self) -> None: + """Create a new DOM element for the component and append it to the root element.""" + self.pre_render() + + # Render the given template as an HTML document + template = self.build() + document = self.parser.parseFromString(template, "text/html") + + # Take the first child of the body and append it to the root element + self.element = document.body.firstChild + self.root.appendChild(self.element) + + self.on_render() diff --git a/app/calm_calatheas/components/__init__.py b/app/calm_calatheas/components/__init__.py new file mode 100644 index 00000000..97861957 --- /dev/null +++ b/app/calm_calatheas/components/__init__.py @@ -0,0 +1,5 @@ +from .footer import Footer +from .header import Header +from .theme import Theme + +__all__ = ["Footer", "Header", "Theme"] diff --git a/app/calm_calatheas/components/footer.py b/app/calm_calatheas/components/footer.py new file mode 100644 index 00000000..cc6cba54 --- /dev/null +++ b/app/calm_calatheas/components/footer.py @@ -0,0 +1,25 @@ +from typing import override + +from calm_calatheas.base import Component + +TEMPLATE = """ + +""" + + +class Footer(Component): + """Footer for the application.""" + + @override + def build(self) -> str: + return TEMPLATE diff --git a/app/calm_calatheas/components/header.py b/app/calm_calatheas/components/header.py new file mode 100644 index 00000000..fd2a23db --- /dev/null +++ b/app/calm_calatheas/components/header.py @@ -0,0 +1,73 @@ +from typing import override + +from js import Event, document +from pyodide.ffi import JsDomElement +from pyodide.ffi.wrappers import add_event_listener + +from calm_calatheas.base import Component + +from .theme import Theme + +TEMPLATE = """ + +""" + + +class Header(Component): + """The main header for the application.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + self._expanded = False + + @override + def build(self) -> str: + return TEMPLATE + + @override + def pre_destroy(self) -> None: + self._theme_selector.destroy() + + @override + def on_render(self) -> None: + self._theme_selector = Theme(document.getElementById("navbar-end")) + self._theme_selector.render() + + self._main_navigation = document.getElementById("main-navigation") + self._navbar_burger = document.getElementById("navbar-burger") + + add_event_listener(self._navbar_burger, "click", self._toggle_navbar) + + @property + def expanded(self) -> bool: + """Whether or not the navbar menu is expanded.""" + return self._expanded + + @expanded.setter + def expanded(self, value: bool) -> None: + self._expanded = value + + if value: + self._main_navigation.classList.add("is-active") + self._navbar_burger.classList.add("is-active") + else: + self._main_navigation.classList.remove("is-active") + self._navbar_burger.classList.remove("is-active") + + def _toggle_navbar(self, _: Event) -> None: + self.expanded = not self.expanded diff --git a/app/calm_calatheas/components/theme.py b/app/calm_calatheas/components/theme.py new file mode 100644 index 00000000..a15493a6 --- /dev/null +++ b/app/calm_calatheas/components/theme.py @@ -0,0 +1,78 @@ +from typing import override + +from js import Event, document +from pyodide.ffi import JsDomElement +from pyodide.ffi.wrappers import add_event_listener + +from calm_calatheas.base import Component +from calm_calatheas.services import Theme_, theme + +TEMPLATE = """ + +""" + + +class Theme(Component): + """A component for selecting the theme.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root=root) + self._theme = theme + + @override + def build(self) -> str: + return TEMPLATE + + @override + def on_destroy(self) -> None: + self._current_theme_listener.dispose() + + @override + def on_render(self) -> None: + self._select_theme_light = document.getElementById("select-theme-light") + self._select_theme_dark = document.getElementById("select-theme-dark") + self._select_theme_auto = document.getElementById("select-theme-auto") + + add_event_listener(self._select_theme_light, "click", self._set_theme_light) + add_event_listener(self._select_theme_dark, "click", self._set_theme_dark) + add_event_listener(self._select_theme_auto, "click", self._set_theme_auto) + + self._current_theme_listener = self._theme.current.subscribe(lambda theme: self._update_current_theme(theme)) + + def _set_theme_light(self, _: Event) -> None: + self._theme.current.on_next("light") + + def _set_theme_dark(self, _: Event) -> None: + self._theme.current.on_next("dark") + + def _set_theme_auto(self, _: Event) -> None: + self._theme.current.on_next(None) + + def _update_current_theme(self, theme: Theme_) -> None: + if theme == "light": + self._select_theme_light.classList.add("is-active") + self._select_theme_dark.classList.remove("is-active") + self._select_theme_auto.classList.remove("is-active") + elif theme == "dark": + self._select_theme_dark.classList.add("is-active") + self._select_theme_light.classList.remove("is-active") + self._select_theme_auto.classList.remove("is-active") + else: + self._select_theme_auto.classList.add("is-active") + self._select_theme_light.classList.remove("is-active") + self._select_theme_dark.classList.remove("is-active") diff --git a/app/calm_calatheas/services/__init__.py b/app/calm_calatheas/services/__init__.py new file mode 100644 index 00000000..e827c805 --- /dev/null +++ b/app/calm_calatheas/services/__init__.py @@ -0,0 +1,3 @@ +from .theme import Theme, Theme_, theme + +__all__ = ["Theme", "Theme_", "theme"] diff --git a/app/calm_calatheas/services/theme.py b/app/calm_calatheas/services/theme.py new file mode 100644 index 00000000..7efb90b1 --- /dev/null +++ b/app/calm_calatheas/services/theme.py @@ -0,0 +1,48 @@ +from typing import Literal, cast + +from js import document, localStorage +from reactivex.subject import BehaviorSubject + +type Theme_ = Literal["light", "dark"] | None + +ATTRIBUTE_NAME = "data-theme" + + +def _update_document_theme(theme: Theme_) -> None: + """Set the theme of the document.""" + if theme: + document.documentElement.setAttribute(ATTRIBUTE_NAME, theme) # type: ignore[setAttribute not defined] + else: + document.documentElement.removeAttribute(ATTRIBUTE_NAME) # type: ignore[removeAttribute not defined] + + +LOCAL_STORAGE_KEY = "theme" + + +def _update_local_storage(theme: Theme_) -> None: + """Set the theme in local storage.""" + if theme: + localStorage.setItem(LOCAL_STORAGE_KEY, theme) + else: + localStorage.removeItem(LOCAL_STORAGE_KEY) + + +class Theme: + """Service to manage the theme of the application.""" + + def __init__(self) -> None: + self.current = BehaviorSubject[Theme_]( + cast("Theme_", theme) + if (theme := localStorage.getItem(LOCAL_STORAGE_KEY)) and theme in {"light", "dark"} + else None, + ) + + self.current.subscribe(_update_document_theme) + self.current.subscribe(_update_local_storage) + + def destroy(self) -> None: + """Clean up the theme service.""" + self.current.dispose() + + +theme = Theme() diff --git a/app/index.html b/app/index.html index 3dd1ba30..9804c4c1 100644 --- a/app/index.html +++ b/app/index.html @@ -5,12 +5,11 @@ Calm Calatheas + + - -

Loading python..

- diff --git a/app/main.py b/app/main.py index 295535ea..378a455a 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,12 @@ +from calm_calatheas import App from js import document -document.body.innerHTML = "

Hello from Python!

" + +def bootstrap() -> None: + """Bootstrap the application to the DOM.""" + app = App(document.body) + app.render() + + +if __name__ == "__main__": + bootstrap() diff --git a/app/pyscript.toml b/app/pyscript.toml index e69de29b..3a7270d9 100644 --- a/app/pyscript.toml +++ b/app/pyscript.toml @@ -0,0 +1,20 @@ +docked = false +terminal = false + +packages = ['reactivex'] + +[[fetch]] +files = [ + '__init__.py', + 'app.py', + 'base/__init__.py', + 'base/component.py', + 'components/__init__.py', + 'components/footer.py', + 'components/header.py', + 'components/theme.py', + 'services/__init__.py', + 'services/theme.py', +] +from = 'calm_calatheas' +to_folder = 'calm_calatheas' diff --git a/app/styles/theme.css b/app/styles/theme.css new file mode 100644 index 00000000..91850bf7 --- /dev/null +++ b/app/styles/theme.css @@ -0,0 +1,22 @@ +@import "https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css"; + +:root { + --bulma-primary-h: 0deg; + --bulma-primary-l: 40%; + --bulma-success-h: 120deg; + --bulma-success-s: 50%; + --bulma-success-l: 40%; + --bulma-warning-h: 52deg; + --bulma-warning-l: 50%; + --bulma-danger-h: 0deg; + --bulma-danger-l: 50%; +} + +@font-face { + font-family: "pokemongb"; + src: url("/assets/pokemongb.ttf"); +} + +body { + font-family: "pokemongb"; +} diff --git a/pyproject.toml b/pyproject.toml index 034f1735..fbe5cb44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dev = [ "pytest-playwright~=0.7", "pytest-sugar~=1.0", "python-dotenv~=1.1", + "reactivex~=4.0", "ruff~=0.12", "taskipy~=1.14", "testcontainers~=4.12", @@ -31,6 +32,7 @@ dev = [ [tool.pyright] exclude = [".venv"] pythonVersion = "3.13" +reportMissingModuleSource = "none" # Pyodide "js" module is dynamic. reportUnnecessaryTypeIgnoreComment = "error" typeCheckingMode = "standard" venvPath = "." @@ -53,6 +55,8 @@ select = ["ALL"] [tool.ruff.lint.per-file-ignores] "**/conftest.py" = ["INP001"] "**/test__*.py" = ["INP001", "S101", "PLR2004"] +"app/main.py" = ["INP001"] +"typings/js.pyi" = ["ALL"] [tool.ruff.lint.pydocstyle] convention = "google" @@ -60,5 +64,5 @@ convention = "google" [tool.taskipy.tasks] build-docs = "mkdocs build --strict" docs = "mkdocs serve" -serve = "npx nodemon -x \"python -m http.server -d ./app\" -w \"./app\" -e \"*\"" +serve = "python -m http.server -d ./app" test = "pytest" diff --git a/tests/conftest.py b/tests/conftest.py index f6ab6e06..2890233c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,7 @@ @pytest.fixture(scope="session") def compose() -> Generator[DockerCompose]: """Return a Docker Compose instance.""" - with DockerCompose(context=Path(__file__).parent.absolute()) as compose: + with DockerCompose(context=Path(__file__).parent.absolute(), build=True) as compose: yield compose diff --git a/tests/test__calm_calatheas.py b/tests/test__calm_calatheas.py index 12d904e8..48710313 100644 --- a/tests/test__calm_calatheas.py +++ b/tests/test__calm_calatheas.py @@ -1,5 +1,6 @@ import re +from conftest import PYSCRIPT_READY_TIMEOUT_MS from playwright.sync_api import Page, expect @@ -11,3 +12,106 @@ def test__main_page_has_welcome_message(app: Page) -> None: - The welcome message is visible on the page. """ expect(app.get_by_text(re.compile("Hello from Python!"))).to_be_visible() + + +def test__default_theme_is_system_preferred(app: Page) -> None: + """ + Test that the default theme is the system preferred theme. + + Asserts: + - The system preferred theme is applied by default. + """ + expect(app.locator("html")).not_to_have_attribute("data-theme", re.compile("dark|light")) + + +def test__switch_to_dark_theme(app: Page) -> None: + """ + Test that switching to the dark theme works. + + Asserts: + - The dark theme is applied when the user selects it. + """ + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + dark_mode_selector = theme_selector.locator(".navbar-item", has_text="Dark") + expect(dark_mode_selector).to_be_visible() + dark_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + +def test__switch_to_light_theme(app: Page) -> None: + """ + Test that switching to the light theme works. + + Asserts: + - The light theme is applied when the user selects it. + """ + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + light_mode_selector = theme_selector.locator(".navbar-item", has_text="Light") + expect(light_mode_selector).to_be_visible() + light_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "light") + + +def test__switch_to_system_theme(app: Page) -> None: + """ + Test that switching to the system theme works. + + Asserts: + - The system theme is applied when the user selects it. + """ + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + # First switch to light theme to ensure a theme is applied + dark_mode_selector = theme_selector.locator(".navbar-item", has_text="Dark") + expect(dark_mode_selector).to_be_visible() + dark_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + # Next switch (back) to the system theme + auto_mode_selector = theme_selector.locator(".navbar-item", has_text="Auto") + expect(auto_mode_selector).to_be_visible() + auto_mode_selector.click() + + expect(app.locator("html")).not_to_have_attribute("data-theme", re.compile("dark|light")) + + +def test__theme_is_restored_after_refresh(app: Page) -> None: + """ + Test that the selected theme is restored after a page refresh. + + Asserts: + - The theme remains consistent after refreshing the page. + """ + # Switch to dark theme + theme_selector = app.locator(".navbar-item", has_text="Theme") + expect(theme_selector).to_be_visible() + theme_selector.hover() + + dark_mode_selector = theme_selector.locator(".navbar-item", has_text="Dark") + expect(dark_mode_selector).to_be_visible() + dark_mode_selector.click() + + expect(app.locator("html")).to_have_attribute("data-theme", "dark") + + # Refresh the page + app.reload() + + app.wait_for_event( + event="console", + predicate=lambda event: "PyScript Ready" in event.text, + timeout=PYSCRIPT_READY_TIMEOUT_MS, + ) + + # Check that the dark theme is still applied + expect(app.locator("html")).to_have_attribute("data-theme", "dark") diff --git a/app/js.pyi b/typings/js.pyi similarity index 81% rename from app/js.pyi rename to typings/js.pyi index 3fd41598..73a10879 100644 --- a/app/js.pyi +++ b/typings/js.pyi @@ -1,38 +1,32 @@ -# ruff: noqa: A001, A002, ANN401, N801, N802, N803, PYI052, N815, PYI001, UP046 +# ruff: noqa # Pyodide already has a js.pyi but it is not complete for us, # and is not even able to be used. # Use https://developer.mozilla.org/en-US/docs/Web/API as reference. -from collections.abc import Callable, Iterable, Sequence -from typing import Any, Generic, Literal, TypeVar, overload +from collections.abc import Callable, Iterable +from typing import Any, Generic, Literal, Sequence, TypeAlias, TypeVar, overload from _pyodide._core_docs import _JsProxyMetaClass from pyodide.ffi import ( JsArray, + JsDomElement as OldJSDomElement, JsException, JsFetchResponse, JsProxy, JsTypedArray, ) -from pyodide.ffi import ( - JsDomElement as OldJSDomElement, -) from pyodide.webloop import PyodideFuture class JsDomElement(OldJSDomElement): classList: DOMTokenList innerText: str - innerHTML: JsDomElement | str - @property - def style(self) -> CSSStyleDeclaration: ... @property def children(self) -> Sequence[JsDomElement]: ... def getAttribute(self, name: str) -> str: ... def setAttribute(self, name: str, value: str) -> None: ... def closest(self, selectors: str) -> JsDomElement: ... - def getElementsByTagName(self, tagName: str) -> JsArray[JsDomElement]: ... def hasAttribute(self, attrName: str) -> bool: ... def removeAttribute(self, attrName: str) -> None: ... def getBoundingClientRect(self) -> DOMRect: ... @@ -76,24 +70,10 @@ class CanvasRenderingContext2D(JsProxy): @overload def drawImage(self, image: JsImgElement, dx: int, dy: int) -> None: ... @overload - def drawImage( - self, - image: JsImgElement, - dx: int, - dy: int, - dWidth: int, - dHeight: int, - ) -> None: ... - def drawImage( - self, - image: JsImgElement, - dx: int, - dy: int, - dWidth: int = ..., - dHeight: int = ..., - ) -> None: ... - def translate(self, x: float, y: float) -> None: ... - def rotate(self, angle: float) -> None: ... + def drawImage(self, image: JsImgElement, dx: int, dy: int, dWidth: int, dHeight: int) -> None: ... + def drawImage(self, image: JsImgElement, dx: int, dy: int, dWidth: int = ..., dHeight: int = ...) -> None: ... + def translate(self, x: int | float, y: int | float) -> None: ... + def rotate(self, angle: int | float) -> None: ... class DOMTokenList(JsProxy): def add(self, token: str) -> None: ... @@ -134,20 +114,37 @@ def eval(code: str) -> Any: ... # in browser the cancellation token is an int, in node it's a special opaque # object. -type _CancellationToken = int | JsProxy +_CancellationToken: TypeAlias = int | JsProxy -def setTimeout(cb: Callable[[], Any], timeout: float) -> _CancellationToken: ... +def setTimeout(cb: Callable[[], Any], timeout: int | float) -> _CancellationToken: ... def clearTimeout(id: _CancellationToken) -> None: ... -def setInterval(cb: Callable[[], Any], interval: float) -> _CancellationToken: ... +def setInterval(cb: Callable[[], Any], interval: int | float) -> _CancellationToken: ... def clearInterval(id: _CancellationToken) -> None: ... def fetch( url: str, options: JsProxy | None = None, ) -> PyodideFuture[JsFetchResponse]: ... +localStorage: LocalStorage = ... +sessionStorage: SessionStorage = ... self: Any = ... window: Any = ... +class DOMParser(JsProxy): + def parseFromString(self, string: str, type: str) -> document: ... + +class LocalStorage: + def getItem(self, key: str) -> str | None: ... + def setItem(self, key: str, value: str) -> None: ... + def removeItem(self, key: str) -> None: ... + def clear(self) -> None: ... + +class SessionStorage: + def getItem(self, key: str) -> str | None: ... + def setItem(self, key: str, value: str) -> None: ... + def removeItem(self, key: str) -> None: ... + def clear(self) -> None: ... + # Shenanigans to convince skeptical type system to behave correctly: # # These classes we are declaring are actually JavaScript objects, so the class diff --git a/uv.lock b/uv.lock index becd9f96..0e3aa22d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ requires-python = ">=3.13" -revision = 2 +revision = 3 version = 1 [[package]] @@ -45,6 +45,7 @@ dev = [ { name = "pytest-playwright" }, { name = "pytest-sugar" }, { name = "python-dotenv" }, + { name = "reactivex" }, { name = "ruff" }, { name = "taskipy" }, { name = "testcontainers" }, @@ -67,6 +68,7 @@ dev = [ { name = "pytest-playwright", specifier = "~=0.7" }, { name = "pytest-sugar", specifier = "~=1.0" }, { name = "python-dotenv", specifier = "~=1.1" }, + { name = "reactivex", specifier = "~=4.0" }, { name = "ruff", specifier = "~=0.12" }, { name = "taskipy", specifier = "~=1.14" }, { name = "testcontainers", specifier = "~=4.12" }, @@ -676,6 +678,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +dependencies = [{ name = "typing-extensions" }] +name = "reactivex" +sdist = { url = "https://files.pythonhosted.org/packages/ef/63/f776322df4d7b456446eff78c4e64f14c3c26d57d46b4e06c18807d5d99c/reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8", size = 119177, upload-time = "2022-07-16T07:11:53.689Z" } +source = { registry = "https://pypi.org/simple" } +version = "4.0.4" +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/3f/2ed8c1b8fe3fc2ed816ba40554ef703aad8c51700e2606c139fcf9b7f791/reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a", size = 217791, upload-time = "2022-07-16T07:11:52.061Z" }, +] + [[package]] dependencies = [{ name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3" }] name = "requests" From 45a49a1c0014c9b4114682746420abef828ae481 Mon Sep 17 00:00:00 2001 From: esmaycat <71032999+esmaycat@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:27:25 +0100 Subject: [PATCH 15/22] fix: configure pyright with venv path (#19) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index fbe5cb44..82fbed56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ pythonVersion = "3.13" reportMissingModuleSource = "none" # Pyodide "js" module is dynamic. reportUnnecessaryTypeIgnoreComment = "error" typeCheckingMode = "standard" +venv = ".venv" venvPath = "." [tool.pytest.ini_options] From 594b93e47573fe63aa587bb2b56c8c61dae288d5 Mon Sep 17 00:00:00 2001 From: thijsfranck Date: Tue, 12 Aug 2025 20:09:53 +0200 Subject: [PATCH 16/22] feat: add camera capture (#20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functionality to access the user's camera, visualize the feed on the screen and let the user capture an image. For now the image is saved to the users's device. This is placeholder behavior as the image should eventually feed into the object recognition. How to use: - Open the app and click the camera button at the bottom of the screen - The browser will request permission to use the camera, approve the request - The camera feed should now appear. - Make a pose and click the capture button 😁 - An snapshot taken from the camera stream should now be downloaded to your device Unfortunately I didn't figure out how to fake the camera for testing, so no automated tests are included. --- app/calm_calatheas/components/camera.py | 119 ++++++++++++++++++++++++ app/calm_calatheas/components/footer.py | 34 ++++++- app/calm_calatheas/components/header.py | 11 ++- app/calm_calatheas/components/theme.py | 11 ++- app/calm_calatheas/services/__init__.py | 3 +- app/calm_calatheas/services/camera.py | 102 ++++++++++++++++++++ app/pyscript.toml | 2 + pyproject.toml | 2 +- typings/js.pyi | 82 +++++++++++++++- 9 files changed, 348 insertions(+), 18 deletions(-) create mode 100644 app/calm_calatheas/components/camera.py create mode 100644 app/calm_calatheas/services/camera.py diff --git a/app/calm_calatheas/components/camera.py b/app/calm_calatheas/components/camera.py new file mode 100644 index 00000000..338cf304 --- /dev/null +++ b/app/calm_calatheas/components/camera.py @@ -0,0 +1,119 @@ +from typing import TYPE_CHECKING, Optional, cast, override + +from js import URL, Blob, MediaStream, document +from pyodide.ffi import JsDomElement, create_once_callable +from pyodide.ffi.wrappers import add_event_listener + +from calm_calatheas.base import Component +from calm_calatheas.services import camera + +if TYPE_CHECKING: + from js import JsVideoElement + + +TEMPLATE = """ + +""" + + +class Camera(Component): + """Component for displaying the camera feed.""" + + def __init__(self, root: JsDomElement) -> None: + super().__init__(root) + self._camera = camera + + @override + def build(self) -> str: + return TEMPLATE + + @override + def on_destroy(self) -> None: + self._subscription_is_acquiring_media_stream.dispose() + self._subscription_media_stream.dispose() + self._camera.dispose_media_stream() + + @override + def on_render(self) -> None: + self._camera_capture = document.getElementById("camera-capture") + self._camera_container = document.getElementById("camera-container") + self._camera_close = document.getElementById("camera-close") + self._camera_stream = cast("JsVideoElement", document.getElementById("camera-stream")) + self._camera_switch = document.getElementById("camera-switch") + + add_event_listener(self._camera_capture, "click", lambda _: self._handle_capture()) + add_event_listener(self._camera_close, "click", lambda _: self.destroy()) + add_event_listener(self._camera_switch, "click", lambda _: self._camera.toggle_facing_mode()) + + self._subscription_is_acquiring_media_stream = self._camera.is_acquiring_media_stream.subscribe( + lambda status: self._handle_is_acquiring_media_stream(status=status), + ) + + self._subscription_media_stream = self._camera.media_stream.subscribe(self._handle_media_stream) + + self._camera.acquire_media_stream() + + def _handle_capture(self) -> None: + """Capture a snapshot from the camera stream.""" + canvas = document.createElement("canvas") + canvas.width = self._camera_stream.videoWidth + canvas.height = self._camera_stream.videoHeight + + context = canvas.getContext("2d") + + context.drawImage( + self._camera_stream, + 0, + 0, + self._camera_stream.videoWidth, + self._camera_stream.videoHeight, + ) + + canvas.toBlob(create_once_callable(self._handle_post_capture), "image/png") + + def _handle_is_acquiring_media_stream(self, *, status: bool) -> None: + """Handle updates to the acquiring media stream status.""" + if status: + self._camera_capture.classList.add("is-loading") + else: + self._camera_capture.classList.remove("is-loading") + + def _handle_media_stream(self, stream: Optional[MediaStream]) -> None: + """Handle updates to the media stream.""" + self._camera_stream.srcObject = stream + + if not stream: + self._camera_capture.setAttribute("disabled", "") + self._camera_switch.setAttribute("disabled", "") + self._camera_container.classList.add("is-skeleton") + else: + self._camera_capture.removeAttribute("disabled") + self._camera_switch.removeAttribute("disabled") + self._camera_container.classList.remove("is-skeleton") + + def _handle_post_capture(self, photo: Blob) -> None: + """Download the captured photo.""" + url = URL.createObjectURL(photo) + + link = document.createElement("a") + link.href = url + link.download = "capture.png" + link.click() + + URL.revokeObjectURL(url) diff --git a/app/calm_calatheas/components/footer.py b/app/calm_calatheas/components/footer.py index cc6cba54..fb302017 100644 --- a/app/calm_calatheas/components/footer.py +++ b/app/calm_calatheas/components/footer.py @@ -1,12 +1,22 @@ -from typing import override +from typing import TYPE_CHECKING, cast, override + +from js import Event, document +from pyodide.ffi import JsDomElement +from pyodide.ffi.wrappers import add_event_listener from calm_calatheas.base import Component +from calm_calatheas.services import camera + +from .camera import Camera + +if TYPE_CHECKING: + from js import JsButtonElement TEMPLATE = """ """ @@ -32,7 +46,7 @@ class Footer(Component): def __init__(self, root: JsDomElement) -> None: super().__init__(root) - self._camera = camera + self._reader = reader @override def build(self) -> str: @@ -46,8 +60,21 @@ def pre_destroy(self) -> None: @override def on_render(self) -> None: self._camera_button = cast("JsButtonElement", document.getElementById("camera-button")) + self._file_input = cast("JsFileInputElement", document.getElementById("file-input")) + self._upload_button = cast("JsButtonElement", document.getElementById("upload-button")) + add_event_listener(self._camera_button, "click", self._on_camera_button_click) + add_event_listener(self._file_input, "change", self._on_file_input_change) + add_event_listener(self._upload_button, "click", self._on_upload_button_click) def _on_camera_button_click(self, _: Event) -> None: self._overlay = Camera(self.root) self._overlay.render() + + def _on_file_input_change(self, _: Event) -> None: + files = self._file_input.files + if files.length: + self._reader.read(files.item(0)) + + def _on_upload_button_click(self, _: Event) -> None: + self._file_input.click() diff --git a/app/calm_calatheas/components/header.py b/app/frontend/components/header.py similarity index 93% rename from app/calm_calatheas/components/header.py rename to app/frontend/components/header.py index a0b65726..eb3d7e43 100644 --- a/app/calm_calatheas/components/header.py +++ b/app/frontend/components/header.py @@ -4,7 +4,7 @@ from pyodide.ffi import JsDomElement from pyodide.ffi.wrappers import add_event_listener -from calm_calatheas.base import Component +from frontend.base import Component from .theme import Theme @@ -15,7 +15,8 @@