diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..1b44b0b --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,99 @@ +FROM mcr.microsoft.com/devcontainers/base:jammy +# FROM mcr.microsoft.com/devcontainers/base:jammy + +ARG DEBIAN_FRONTEND=noninteractive +ARG USER=vscode + +RUN DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y build-essential --no-install-recommends \ + bat \ + bison \ + bsdmainutils \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + file \ + gcc \ + git \ + iputils-ping \ + jq \ + libbz2-dev \ + libedit-dev \ + libffi-dev \ + liblzma-dev \ + libncurses-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + libxmlsec1-dev \ + llvm \ + make \ + nala \ + net-tools \ + pipx \ + procps \ + software-properties-common \ + telnet \ + tk-dev \ + tzdata \ + unzip \ + vim \ + wget \ + xz-utils \ + zlib1g-dev + +# Install yq +ENV YQ_VERSION="v4.47.1" +RUN wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 \ + && chmod +x /usr/local/bin/yq + +# Run setup eza script +COPY scripts/setup-eza.sh /tmp/setup-eza.sh +RUN chmod +x /tmp/setup-eza.sh && /tmp/setup-eza.sh + +# Run setup fnm script +COPY scripts/setup-fnm.sh /tmp/setup-fnm.sh +RUN chmod +x /tmp/setup-fnm.sh && /tmp/setup-fnm.sh + +# Python and poetry installation +USER $USER +ARG HOME="/home/$USER" +ARG PYTHON_VERSION=3.9 +# ARG PYTHON_VERSION=3.10 + +ENV PYENV_ROOT="${HOME}/.pyenv" +ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${HOME}/.local/bin:$PATH" + +COPY ./dotfiles/ /tmp/setup/ + +RUN <<"EOT" bash + set -eux + + # Create Symbolic Links + sudo mkdir -p /opt/certs /opt/Documents /opt/Downloads /opt/.ssh + sudo ln -sf /opt/Documents ~/Documents + sudo ln -sf /opt/Downloads ~/Downloads +EOT + +RUN <<"EOT" bash + set -eux + + ### Setup shell + sudo cp -r /tmp/setup/. ~/ + sudo chown -R ${USER}:${USER} ~/. +EOT + +RUN echo "done 0" \ + && curl https://pyenv.run | bash \ + && echo "done 1" \ + && pyenv install ${PYTHON_VERSION} \ + && echo "done 2" \ + && pyenv global ${PYTHON_VERSION} \ + && echo "done 3" \ + && curl -sSL https://install.python-poetry.org | python3 - \ + && poetry config virtualenvs.in-project true diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ae1fee6 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,47 @@ +{ + "name": "poetry3-poetry-pyenv", + "build": { + "dockerfile": "Dockerfile" + }, + // πŸ‘‡ Features to add to the Dev Container. More info: https://containers.dev/implementors/features. + // "features": {}, + // πŸ‘‡ Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // πŸ‘‡ Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "bash .devcontainer/scripts/setup-dependencies.sh", + // πŸ‘‡ Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "njpwerner.autodocstring", + "seatonjiang.gitmoji-vscode", + "redhat.vscode-yaml", + "GitHub.vscode-github-actions", + "GitHub.vscode-pull-request-github", + "GitHub.copilot-chat", + "GitHub.copilot" + ] + } + }, + "features": { + "ghcr.io/devcontainers-community/npm-features/prettier:1": { + "version": "latest" + }, + "ghcr.io/schlich/devcontainer-features/powerlevel10k:1": {}, + "ghcr.io/nils-geistmann/devcontainers-features/zsh:0": { + "setLocale": true, + "theme": "agnoster", + "plugins": "git docker sudo python poetry eza aliases", + "desiredLocale": "en_US.UTF-8 UTF-8" + }, + "ghcr.io/devcontainers-extra/features/zsh-plugins:0": { + "plugins": "ssh-agent npm", + "omzPlugins": "https://github.com/zsh-users/zsh-autosuggestions", + "username": "vscode" + }, + "ghcr.io/braun-daniel/devcontainer-features/fzf:1": {} + } + // πŸ‘‡ Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.devcontainer/dotfiles/.aliases b/.devcontainer/dotfiles/.aliases new file mode 100644 index 0000000..731420b --- /dev/null +++ b/.devcontainer/dotfiles/.aliases @@ -0,0 +1,2 @@ +alias zrld="source ~/.zshrc" +alias zcfg="$EDITOR ~/.zshrc && zrld" \ No newline at end of file diff --git a/.devcontainer/dotfiles/.zplugins b/.devcontainer/dotfiles/.zplugins new file mode 100644 index 0000000..025ea72 --- /dev/null +++ b/.devcontainer/dotfiles/.zplugins @@ -0,0 +1,60 @@ +# shellcheck disable=SC2148 +ZINIT_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zinit/zinit.git" +[ ! -d $ZINIT_HOME ] && mkdir -p "$(dirname $ZINIT_HOME)" +[ ! -d $ZINIT_HOME/.git ] && git clone https://github.com/zdharma-continuum/zinit.git "$ZINIT_HOME" +source "${ZINIT_HOME}/zinit.zsh" + +# source ~/.zplugins +# Load starship theme +# line 1: `starship` binary as command, from github release +# line 2: starship setup at clone(create init.zsh, completion) +# line 3: pull behavior same as clone, source init.zsh +zinit ice as"command" from"gh-r" \ + atclone"./starship init zsh > init.zsh; ./starship completions zsh > _starship" \ + atpull"%atclone" src"init.zsh" +zinit light starship/starship + +# Binary release in archive, from GitHub-releases page. +# After automatic unpacking it provides program "fzf". +zi ice from"gh-r" as"program" +zi light junegunn/fzf + +# Vim repository on GitHub – a typical source code that needs compilation – Zinit +# can manage it for you if you like, run `./configure` and other `make`, etc. +# Ice-mod `pick` selects a binary program to add to $PATH. You could also install the +# package under the path $ZPFX, see: https://zdharma-continuum.github.io/zinit/wiki/Compiling-programs +# zi ice \ +# as"program" \ +# atclone"rm -f src/auto/config.cache; ./configure" \ +# atpull"%atclone" \ +# make \ +# pick"src/vim" +# zi light vim/vim + +zi light Aloxaf/fzf-tab +zi light lincheney/fzf-tab-completion +zi light ahmetb/kubectl-aliases +zi light Freed-Wu/fzf-tab-source + +zi snippet OMZP::git +zi snippet OMZP::sudo +zi snippet OMZP::poetry +zi snippet OMZP::eza +zi snippet OMZP::dotenv +zi snippet OMZP::colorize +zi snippet OMZP::aliases + + +### fzf-tab + +export LESSOPEN='|~/.lesspipe.sh %s' + +zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza -1 --color=always $realpath' +# shellcheck disable=SC2086 +# shellcheck disable=SC2296 +zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS} +zstyle ':fzf-tab:*' fzf-command ftb-tmux-popup +zstyle ':fzf-tab:*' fzf-bindings 'ctrl-j:accept' 'ctrl-a:toggle-all' + +zstyle ':fzf-tab:complete:*:*' fzf-flags --preview-window=right,60% --height=70% --border=double +zstyle ':completion:*:descriptions' format '[%d]' \ No newline at end of file diff --git a/.devcontainer/dotfiles/.zshrc b/.devcontainer/dotfiles/.zshrc new file mode 100644 index 0000000..65cb7df --- /dev/null +++ b/.devcontainer/dotfiles/.zshrc @@ -0,0 +1,39 @@ +source ~/.zplugins + +export EDITOR='vim' +export KUBE_EDITOR='code -w' +source ~/.aliases +bindkey -e +bindkey "^[[1;5C" forward-word +bindkey "^[[1;5D" backward-word + +### Brew + +export HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew"; +export HOMEBREW_CELLAR="/home/linuxbrew/.linuxbrew/Cellar"; +export HOMEBREW_REPOSITORY="/home/linuxbrew/.linuxbrew/Homebrew"; +export PATH="/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin${PATH+:$PATH}"; +export MANPATH="/home/linuxbrew/.linuxbrew/share/man${MANPATH+:$MANPATH}:"; +export INFOPATH="/home/linuxbrew/.linuxbrew/share/info:${INFOPATH:-}"; + +### Starship + +eval "$(starship init zsh)" + +### fnm + +FNM_PATH="/home/vscode/.local/share/fnm" +if [ -d "$FNM_PATH" ]; then + export PATH="$FNM_PATH:$PATH" + eval "`fnm env`" +fi + +### Pipx + +export PATH="$PATH:/home/user/.local/bin" + +### Pyenv + +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init --path)" \ No newline at end of file diff --git a/.devcontainer/scripts/setup-eza.sh b/.devcontainer/scripts/setup-eza.sh new file mode 100644 index 0000000..3e34f0b --- /dev/null +++ b/.devcontainer/scripts/setup-eza.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +sudo apt update +sudo apt install -y gpg +sudo mkdir -p /etc/apt/keyrings +wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg +echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list +sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list +sudo apt update +sudo apt install -y eza \ No newline at end of file diff --git a/.devcontainer/scripts/setup-fnm.sh b/.devcontainer/scripts/setup-fnm.sh new file mode 100644 index 0000000..ffb97d9 --- /dev/null +++ b/.devcontainer/scripts/setup-fnm.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl -fsSL https://fnm.vercel.app/install | bash \ No newline at end of file diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 922d162..78ab370 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -8,8 +8,18 @@ permissions: on: push: branches: [ "main" ] + paths-ignore: + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + - '.devcontainer/**' + - '.vscode/**' pull_request: - branches: [ "main" ] + branches: [ "main" ] + paths-ignore: + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + - '.devcontainer/**' + - '.vscode/**' jobs: @@ -41,6 +51,7 @@ jobs: pipx install poetry poetry config virtualenvs.in-project true poetry lock --verbose + poetry env use ${{ matrix.python-version }} poetry install --no-interaction --no-ansi - name: Lint with flake8 @@ -54,12 +65,15 @@ jobs: run: | ${{ github.workspace }}/.venv/bin/pytest --cov=flaggle --cov-report=xml --cov-report=term-missing - version-bump: - runs-on: ubuntu-latest + release-candidate: + runs-on: self-hosted needs: build - if: github.event_name == 'pull_request' || github.event_name == 'push' - steps: + if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize') + outputs: + version-component: ${{ steps.semver.outputs.semver }} + gh-app-id: ${{ steps.get-user-id.outputs.user-id }} + steps: - uses: actions/create-github-app-token@v2 id: app-token with: @@ -93,23 +107,30 @@ jobs: - name: Get GitHub App User ID if: steps.semver.outputs.semver != '' id: get-user-id - run: echo "user-id=$(gh api "/users/${{ steps.generate-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + run: echo "user-id=$(gh api "/users/flaggle-bot[bot]" --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - - name: Bump version - if: steps.semver.outputs.semver != '' - id: bump-version + - name: Set Pre-release Version + if: github.event.action == 'opened' && steps.semver.outputs.semver != '' + id: set-prerelease run: | echo "old_version=$(poetry version -s)" >> $GITHUB_OUTPUT - echo "Bumping version with semver: ${{ steps.semver.outputs.semver }}" + echo "Setting pre-release version based on the version component '${{ steps.semver.outputs.semver }}'" + + poetry version pre${{ steps.semver.outputs.semver }} -vvv - pipx install poetry - poetry config virtualenvs.in-project true - poetry self add poetry-bumpversion - poetry lock --verbose - poetry install --no-interaction --no-ansi - poetry version ${{ steps.semver.outputs.semver }} -vvv + echo "New version: $(poetry version -s)" + echo "new_version=$(poetry version -s)" >> $GITHUB_OUTPUT + + - name: Bump Pre-release Version + if: github.event.action == 'synchronize' && steps.semver.outputs.semver != '' + id: bump-version + run: | + echo "old_version=$(poetry version -s)" >> $GITHUB_OUTPUT + echo "Bumping pre-release version based on the version component: ${{ steps.semver.outputs.semver }}" + + poetry version prerelease -vvv echo "New version: $(poetry version -s)" echo "new_version=$(poetry version -s)" >> $GITHUB_OUTPUT @@ -117,14 +138,80 @@ jobs: - name: Set Commiter if: steps.semver.outputs.semver != '' run: | - git config --global user.name '${{ steps.generate-token.outputs.app-slug }}[bot]' - git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com' + git config --global user.name 'flaggle-bot[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+flaggle-bot[bot]@users.noreply.github.com' - name: Commit and push changes if: steps.semver.outputs.semver != '' run: | git add . - git commit -m "[skip ci] πŸ”– Bump version ${{ steps.bump-version.outputs.old_version }} -> ${{ steps.bump-version.outputs.new_version }}" + git commit -m "[skip ci] πŸ”– Bump version ${{ steps.bump-version.outputs.old_version || steps.set-prerelease.outputs.old_version }} -> ${{ steps.bump-version.outputs.new_version || steps.set-prerelease.outputs.new_version }}" git push origin HEAD:${{ github.head_ref }} --force env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + promote-release: + runs-on: self-hosted + needs: [build,release-candidate] + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Create GitHub App Token + uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ vars.FLAGGLE_BOT_ID }} + private-key: ${{ secrets.FLAGGLE_BOT_KEY }} + + - name: Promote Release + id: promote-release + run: | + echo "Promoting release for version component: ${{ needs.release-candidate.outputs.version-component }}" + + echo "old_version=$(poetry version -s)" >> $GITHUB_OUTPUT + echo "Bumping version with semver: ${{ needs.release-candidate.outputs.version-component }}" + + poetry version pre${{ needs.release-candidate.outputs.version-component }} -vvv + + echo "New version: $(poetry version -s)" + echo "new_version=$(poetry version -s)" >> $GITHUB_OUTPUT + + - name: Set Commiter + run: | + git config --global user.name 'flaggle-bot[bot]' + git config --global user.email '${{ needs.release-candidate.outputs.gh-app-id }}+flaggle-bot[bot]@users.noreply.github.com' + + - name: Commit and push changes + run: | + git add . + git commit -m "[skip ci] πŸ”– Bump version ${{ steps.promote-release.outputs.old_version }} -> ${{ steps.promote-release.outputs.new_version }}" + git push origin HEAD:${{ github.head_ref }} --force + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ steps.app-token.outputs.token }} + commit-message: "[skip ci] πŸ”– Bump version ${{ steps.promote-release.outputs.old_version }} -> ${{ steps.promote-release.outputs.new_version }}" + author: ${{ needs.release-candidate.outputs.gh-app-id }}+flaggle-bot[bot]@users.noreply.github.com + signoff: false + branch: release-${{ needs.release-candidate.outputs.version_component }} + delete-branch: true + title: "Release ${{ needs.release-candidate.outputs.version_component }}" + body: | + This pull request promotes the release for version component: ${{ needs.release-candidate.outputs.version_component }}" + labels: | + version-bump + automated pr + draft: false + + - name: Enable Pull Request Automerge + run: gh pr merge --merge --auto "${{ steps.cpr.outputs.pull-request-number }}" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 133dfaa..12c75b5 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -17,7 +17,7 @@ permissions: jobs: release-build: - runs-on: ubuntu-latest + runs-on: self-hosted steps: - uses: actions/checkout@v4 diff --git a/docs/advanced.md b/docs/advanced.md new file mode 100644 index 0000000..9e8045c --- /dev/null +++ b/docs/advanced.md @@ -0,0 +1,77 @@ +# Advanced Usage + +This section covers advanced Flaggle features for power users and production deployments. + +--- + +## Custom Flag Types + +You can define custom flag types by subclassing `Flag` and registering them with `Flaggle`. + +```python +from python_flaggle import Flag, Flaggle + +class PercentageFlag(Flag): + def is_enabled(self, context=None): + # Enable for a percentage of users + user_id = context.get("user_id") + return hash(user_id) % 100 < self.value # e.g., value=10 for 10% + +flaggle = Flaggle() +flaggle.register_flag_type("percentage", PercentageFlag) +``` + +--- + +## Dynamic Flags + +Flags can be loaded from a database, remote API, or environment variables. + +```python +import os +from python_flaggle import Flaggle + +flaggle = Flaggle() +flaggle.set_flag("new_ui", os.getenv("NEW_UI_FLAG", "off")) +``` + +--- + +## Environment-based Flags + +Use environment variables or config files to control flag state per environment (dev, staging, prod). + +```python +import os +flaggle.set_flag("beta", os.getenv("BETA_FLAG", "off")) +``` + +--- + +## User-based Flags + +Pass user or request context to `is_enabled` for per-user or per-group toggles. + +```python +flaggle.is_enabled("beta", context={"user_id": 123}) +``` + +--- + +## Feature Rollouts & Experiments + +- Use percentage or cohort-based flags for gradual rollouts. +- Combine multiple flag types for complex experiments. + +--- + +## Best Practices +- Remove unused flags regularly. +- Document flag purpose and usage. +- Write tests for all flag logic. + +--- + +See also: +- [API Reference](api/flaggle.md) +- [FAQ](faq.md) diff --git a/docs/api/flag.md b/docs/api/flag.md new file mode 100644 index 0000000..51990bd --- /dev/null +++ b/docs/api/flag.md @@ -0,0 +1,26 @@ +# Flag API Reference + +::: python_flaggle.flag.Flag + handler: python + options: + show_source: true + show_signature: true + show_docstring: true + show_bases: true + show_inheritance_diagram: false + show_root_heading: true + show_if_no_docstring: true + show_submodules: false + show_attributes: true + show_methods: true + show_classes: false + show_private: false + show_special: false + show_type_annotations: true + +--- + +See also: +- [Flaggle API Reference](flaggle.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) diff --git a/docs/api/flaggle.md b/docs/api/flaggle.md new file mode 100644 index 0000000..7716174 --- /dev/null +++ b/docs/api/flaggle.md @@ -0,0 +1,26 @@ +# Flaggle API Reference + +::: python_flaggle.flaggle.Flaggle + handler: python + options: + show_source: true + show_signature: true + show_docstring: true + show_bases: true + show_inheritance_diagram: false + show_root_heading: true + show_if_no_docstring: true + show_submodules: false + show_attributes: true + show_methods: true + show_classes: false + show_private: false + show_special: false + show_type_annotations: true + +--- + +See also: +- [Flag API Reference](flag.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..668b03a --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,21 @@ +# Changelog + +All notable changes to Flaggle will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). + +--- + +## [Unreleased] +- Initial MkDocs documentation overhaul +- Added integration guides for FastAPI, Flask, Django, Typer, and more +- Improved API reference with mkdocstrings +- Added advanced usage, supported operations, and FAQ sections + +## [0.1.0] - 2025-06-08 +- Initial public release +- Feature flag management core (`Flaggle`, `Flag`) +- FastAPI integration +- 100% test coverage + +--- + +See the [GitHub Releases](https://github.com/Flaggle/flaggle-python/releases) page for detailed release notes. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..04c17c4 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,51 @@ +# Configuration + +Flaggle can be configured in several ways to fit your project's needs. This page covers the most common configuration patterns. + +--- + +## Basic Configuration + +Create a `Flaggle` instance and set flags in your application code: + +```python +from python_flaggle import Flaggle +flaggle = Flaggle() +flaggle.set_flag("new_ui", True) +``` + +--- + +## Environment Variable Configuration + +Set flags using environment variables for different environments (dev, staging, prod): + +```python +import os +flaggle.set_flag("beta", os.getenv("BETA_FLAG", "off")) +``` + +--- + +## Configuration File Example + +You can load flags from a config file (e.g., JSON, YAML): + +```python +import json +from python_flaggle import Flaggle + +with open("flags.json") as f: + flags = json.load(f) + +flaggle = Flaggle() +for name, value in flags.items(): + flaggle.set_flag(name, value) +``` + +--- + +## Tips +- Use environment variables for deployment-specific flags. +- Document your configuration for your team. +- See [Advanced Usage](advanced.md) for custom flag types and dynamic loading. \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..13e1a1b --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,65 @@ +# Contributing to Flaggle + +We welcome contributions of all kinds! Whether you're fixing bugs, adding features, improving documentation, or helping others, your input makes Flaggle better for everyone. + +--- + +## How to Contribute + +1. **Fork the repository** and create your branch from `main`. +2. **Write clear, PEP 8-compliant code** with type annotations and Google-style docstrings. +3. **Add or update tests** in the `tests/` directory. Aim for 100% coverage. +4. **Document your changes** in the relevant Markdown files in `docs/`. +5. **Run tests** with `pytest` before submitting your pull request. +6. **Open a pull request** with a clear description of your changes. + +--- + +## Development Setup + +```bash +git clone https://github.com/Flaggle/flaggle-python.git +cd flaggle-python +poetry install +``` + +Run tests: +```bash +pytest +``` + +--- + +## Code Style +- Follow [PEP 8](https://peps.python.org/pep-0008/) +- Use type annotations for all public functions and methods +- Use Google-style docstrings +- Prefer explicit, readable code over cleverness + +--- + +## Documentation +- Update or add docstrings for all public APIs +- Add or update Markdown docs in `docs/` +- Add usage examples and tips where helpful + +--- + +## Reporting Issues +- Use [GitHub Issues](https://github.com/Flaggle/flaggle-python/issues) for bugs, feature requests, or questions +- Include steps to reproduce, expected behavior, and environment details + +--- + +## Community +- Join discussions on [GitHub Discussions](https://github.com/Flaggle/flaggle-python/discussions) +- Be respectful and constructive + +--- + +## License +By contributing, you agree that your contributions will be licensed under the [MIT License](../LICENSE). + +--- + +Thank you for helping make Flaggle better! diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..8c44389 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,81 @@ +# Frequently Asked Questions (FAQ) & Troubleshooting + +Welcome to the Flaggle FAQ! Here you'll find answers to common questions, troubleshooting tips, and best practices for using Flaggle in your projects. + +--- + +## General + +### What is Flaggle? +Flaggle is a Python library for managing feature flags, supporting dynamic toggles, gradual rollouts, and safe experimentation. It integrates easily with FastAPI, Flask, Django, Typer, and more. + +### Who should use Flaggle? +Anyone who wants to control feature availability in Python applicationsβ€”web, CLI, or scripts. It's suitable for both beginners and advanced users. + +--- + +## Installation & Setup + +### How do I install Flaggle? +```bash +pip install python-flaggle +``` +Or with Poetry: +```bash +poetry add python-flaggle +``` + +### What Python versions are supported? +Python 3.8 and above. + +### How do I configure my first flag? +See [Getting Started](first-steps.md) for a step-by-step guide. + +--- + +## Usage + +### How do I check if a flag is enabled? +```python +from python_flaggle import Flaggle +flaggle = Flaggle() +if flaggle.is_enabled("my_flag"): + # ... +``` + +### Can I use Flaggle with FastAPI/Flask/Django/Typer? +Yes! See the [Integrations](integrations/fastapi.md) section for detailed examples. + +### How do I define environment-based or user-based flags? +Use the advanced configuration options. See [Advanced Usage](advanced.md). + +--- + +## Troubleshooting + +### My flag is not updating as expected +- Ensure your flag configuration is reloaded or refreshed if changed at runtime. +- Check for typos in flag names. +- Use the debug logging to trace flag evaluation. + +### I get an ImportError or ModuleNotFoundError +- Make sure `python-flaggle` is installed in your environment. +- Check your virtual environment activation. + +### Flags are not thread-safe in my app +- Flaggle is designed to be thread-safe. If you encounter issues, please [open an issue](https://github.com/Flaggle/flaggle-python/issues) with details. + +--- + +## Best Practices & Tips +- Use descriptive flag names (e.g., `enable_new_checkout`) +- Remove unused flags regularly to avoid tech debt +- Write tests for flag logic and edge cases +- Document your flags for your team + +--- + +## Still need help? +- [Open an issue](https://github.com/Flaggle/flaggle-python/issues) +- [Join the discussion](https://github.com/Flaggle/flaggle-python/discussions) +- [Contribute improvements](contributing.md) \ No newline at end of file diff --git a/docs/first-steps.md b/docs/first-steps.md new file mode 100644 index 0000000..61e43a6 --- /dev/null +++ b/docs/first-steps.md @@ -0,0 +1,47 @@ +# First Steps + +# Getting Started + +Welcome to Flaggle! This guide will help you get up and running quickly. + +--- + +## Installation + +Install Flaggle using pip: + +```bash +pip install python-flaggle +``` + +Or with Poetry: + +```bash +poetry add python-flaggle +``` + +--- + +## Minimal Example + +```python +from python_flaggle import Flaggle + +flaggle = Flaggle() +flaggle.set_flag("new_ui", True) + +if flaggle.is_enabled("new_ui"): + print("The new UI is enabled!") +``` + +--- + +## Next Steps +- [Configuration](configuration.md) +- [API Reference](api/flaggle.md) +- [Integrations](integrations/fastapi.md) +- [Tips & FAQ](faq.md) + +--- + +For more advanced usage, see the [Advanced Usage](advanced.md) section. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9b75c15 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,52 @@ +# Flaggle: Feature Flag Management for Python + +Flaggle is a modern, easy-to-use feature flag management library for Python, designed for seamless integration with FastAPI and other popular frameworks. It helps you control feature rollouts, run experiments, and manage toggles in production with confidence. + +## Key Features +- **First-class FastAPI integration** (plus Flask, Django, Typer, and more) +- **Simple, explicit API** for defining and checking flags +- **JSON schema endpoint** for flag state +- **Environment and user-based flagging** +- **Extensive test coverage and type hints** +- **Modern Pythonic design** + +## Why Flaggle? +- **Easy to learn**: Minimal setup, clear API, and great docs +- **Flexible**: Use in web apps, CLIs, scripts, or anywhere Python runs +- **Safe**: Test and roll out features gradually +- **Extensible**: Add custom flag types and logic + +## Example: Minimal FastAPI Integration +```python +from fastapi import FastAPI +from python_flaggle import Flaggle + +app = FastAPI() +flaggle = Flaggle() + +@app.get("/feature-status") +def feature_status(): + return {"new_ui": flaggle.is_enabled("new_ui")} +``` + +## Next Steps +- [Getting Started](first-steps.md) +- [Configuration](configuration.md) +- [API Reference](api/flaggle.md) +- [Integrations](integrations/fastapi.md) +- [Tips & FAQ](faq.md) + +--- + +> **Tip:** Use the search bar or navigation to quickly find guides, API docs, and integration examples. + +--- + +## Community & Contributing +- [Contributing Guide](contributing.md) +- [Changelog](changelog.md) +- [License](license.md) + +--- + +*Flaggle is open source and welcomes your feedback, issues, and pull requests!* diff --git a/docs/integrations/django.md b/docs/integrations/django.md new file mode 100644 index 0000000..839fa64 --- /dev/null +++ b/docs/integrations/django.md @@ -0,0 +1,58 @@ +# Django Integration + +Flaggle can be integrated into Django projects to manage feature flags in your views and templates. This guide covers quickstart, advanced usage, and best practices for Django. + +--- + +## Quickstart + +```python +from django.http import JsonResponse +from python_flaggle import Flaggle + +flaggle = Flaggle() + +def feature_status(request): + return JsonResponse({"new_ui": flaggle.is_enabled("new_ui")}) +``` + +Add the view to your `urls.py`: +```python +from django.urls import path +from .views import feature_status + +urlpatterns = [ + path("feature-status/", feature_status), +] +``` + +--- + +## Advanced Usage + +- **Dynamic flags**: Load flags from a database or remote service. +- **User-based flags**: Pass user context to `is_enabled` for per-user toggles. +- **Environment flags**: Use environment variables to control flag state. + +### Example: User-based flag +```python +def beta_access(request): + user_id = request.user.id if request.user.is_authenticated else None + return JsonResponse({"beta": flaggle.is_enabled("beta", context={"user_id": user_id})}) +``` + +--- + +## Tips for Django +- Use Django settings or app config to share your `Flaggle` instance. +- Expose a `/flags/` endpoint to return the current flag state (see JSON schema docs). +- Add tests for flag logic using Django's test client and pytest. + +--- + +## See Also +- [API Reference](../api/flaggle.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) + + diff --git a/docs/integrations/fastapi.md b/docs/integrations/fastapi.md new file mode 100644 index 0000000..150f14b --- /dev/null +++ b/docs/integrations/fastapi.md @@ -0,0 +1,153 @@ +# FastAPI Integration + +Flaggle is designed for seamless integration with FastAPI, making it easy to manage feature flags in your API endpoints. Below you'll find a quickstart, advanced usage, and tips for production deployments. + +--- + +## Quickstart + +```python +from fastapi import FastAPI +from python_flaggle import Flaggle + +app = FastAPI() +flaggle = Flaggle() + +@app.get("/feature-status") +def feature_status(): + return {"new_ui": flaggle.is_enabled("new_ui")} +``` + +--- + +## Advanced Usage + +- **Dynamic flags**: Load flags from a database or remote service. +- **User-based flags**: Pass user context to `is_enabled` for per-user toggles. +- **Environment flags**: Use environment variables to control flag state. + +### Example: User-based flag +```python +@app.get("/beta-access") +def beta_access(user_id: int): + return {"beta": flaggle.is_enabled("beta", context={"user_id": user_id})} +``` + +--- + +## Example: Dependency Injection + +For larger FastAPI projects, use dependency injection to share your `Flaggle` instance across routes: + +```python +from fastapi import Depends, FastAPI +from python_flaggle import Flaggle + +def get_flaggle(): + return flaggle + +app = FastAPI() +flaggle = Flaggle() + +@app.get("/feature-status") +def feature_status(flaggle: Flaggle = Depends(get_flaggle)): + return {"new_ui": flaggle.is_enabled("new_ui")} +``` + +--- + +## Example: Exposing a Flags Endpoint + +Expose a `/flags` endpoint to return all current flag states (useful for debugging or frontends): + +```python +@app.get("/flags") +def all_flags(): + return flaggle.to_dict() # Or your preferred serialization +``` + +--- + +## Example: Testing with TestClient + +Write tests for your flag logic using FastAPI's `TestClient` and `pytest`: + +```python +from fastapi.testclient import TestClient +import pytest + +client = TestClient(app) + +def test_feature_status(): + response = client.get("/feature-status") + assert response.status_code == 200 + assert "new_ui" in response.json() +``` + +--- + +## Example: Async Flags (Advanced) + +If your flag source is async (e.g., database or remote API), you can use FastAPI's async support: + +```python +from fastapi import FastAPI +from python_flaggle import Flaggle + +app = FastAPI() +flaggle = Flaggle() + +@app.get("/async-feature-status") +async def async_feature_status(): + # Example: await an async flag check (if implemented) + return {"new_ui": await flaggle.is_enabled_async("new_ui")} +``` + +_Note: Only use async if your flag source requires it. The default Flaggle API is synchronous._ + +--- + +## Example: OpenAPI Docs for Flags + +Document your flag endpoints for automatic FastAPI docs: + +```python +@app.get("/flags", response_model=dict, summary="Get all feature flags", tags=["Flags"]) +def all_flags(): + """Return the current state of all feature flags.""" + return flaggle.to_dict() +``` + +--- + +## Example: JSON Schema Endpoint + +Expose a JSON schema for your flags (useful for frontends or validation): + +```python +@app.get("/flags/schema", response_model=dict, summary="Get flags JSON schema", tags=["Flags"]) +def flags_schema(): + return flaggle.json_schema() # Implement this method as needed +``` + +--- + +## Security Considerations +- Protect sensitive flag endpoints (e.g., `/flags`, `/flags/schema`) with authentication in production. +- Avoid exposing internal-only flags to public APIs. + +--- + +## Pro Tips +- Use [Pydantic settings](https://docs.pydantic.dev/latest/usage/pydantic_settings/) for environment-based flag configuration. +- Document your flags and endpoints for your team. +- Use [FastAPI's dependency injection](https://fastapi.tiangolo.com/tutorial/dependencies/) for scalable, testable code. + +--- + +## See Also +- [API Reference](../api/flaggle.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) + + diff --git a/docs/integrations/flask.md b/docs/integrations/flask.md new file mode 100644 index 0000000..61fee30 --- /dev/null +++ b/docs/integrations/flask.md @@ -0,0 +1,51 @@ +# Flask Integration + +Flaggle can be used with Flask to manage feature flags in your web applications. This guide provides a quickstart, advanced usage, and best practices for Flask projects. + +--- + +## Quickstart + +```python +from flask import Flask, jsonify +from python_flaggle import Flaggle + +app = Flask(__name__) +flaggle = Flaggle() + +@app.route("/feature-status") +def feature_status(): + return jsonify({"new_ui": flaggle.is_enabled("new_ui")}) +``` + +--- + +## Advanced Usage + +- **Dynamic flags**: Load flags from a database or remote service. +- **User-based flags**: Pass user context to `is_enabled` for per-user toggles. +- **Environment flags**: Use environment variables to control flag state. + +### Example: User-based flag +```python +@app.route("/beta-access") +def beta_access(): + user_id = ... # get user_id from request or session + return jsonify({"beta": flaggle.is_enabled("beta", context={"user_id": user_id})}) +``` + +--- + +## Tips for Flask +- Use application context or blueprints to share your `Flaggle` instance. +- Expose a `/flags` endpoint to return the current flag state (see JSON schema docs). +- Add tests for flag logic using Flask's test client and pytest. + +--- + +## See Also +- [API Reference](../api/flaggle.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) + + diff --git a/docs/integrations/other.md b/docs/integrations/other.md new file mode 100644 index 0000000..03d5c67 --- /dev/null +++ b/docs/integrations/other.md @@ -0,0 +1,58 @@ +# Other Integrations + +Flaggle is flexible and can be used in a variety of Python environments beyond FastAPI, Flask, Django, and Typer. Here are some additional integration ideas and tips. + +--- + +## Generic Python Scripts + +```python +from python_flaggle import Flaggle + +flaggle = Flaggle() + +if flaggle.is_enabled("new_ui"): + print("New UI is enabled!") +``` + +--- + +## Celery Tasks + +```python +from celery import shared_task +from python_flaggle import Flaggle + +flaggle = Flaggle() + +@shared_task +def my_task(): + if flaggle.is_enabled("background_feature"): + # ... +``` + +--- + +## Jupyter Notebooks + +```python +from python_flaggle import Flaggle +flaggle = Flaggle() +flaggle.is_enabled("notebook_feature") +``` + +--- + +## Tips for Other Environments +- Always use a single `Flaggle` instance per process when possible. +- For distributed systems, consider syncing flag state via a database or API. +- Add tests for flag logic in your environment's preferred test framework. + +--- + +## See Also +- [API Reference](../api/flaggle.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) + + diff --git a/docs/integrations/typer.md b/docs/integrations/typer.md new file mode 100644 index 0000000..0ded38d --- /dev/null +++ b/docs/integrations/typer.md @@ -0,0 +1,52 @@ +# Typer Integration + +Flaggle can be used in Typer-based CLI applications to control feature flags for command-line tools. This guide provides a quickstart, advanced usage, and best practices for Typer projects. + +--- + +## Quickstart + +```python +import typer +from python_flaggle import Flaggle + +app = typer.Typer() +flaggle = Flaggle() + +@app.command() +def feature_status(): + typer.echo(f"new_ui: {flaggle.is_enabled('new_ui')}") + +if __name__ == "__main__": + app() +``` + +--- + +## Advanced Usage + +- **Dynamic flags**: Load flags from a database or remote service. +- **User-based flags**: Pass user context to `is_enabled` for per-user toggles. +- **Environment flags**: Use environment variables to control flag state. + +### Example: User-based flag +```python +@app.command() +def beta_access(user_id: int): + typer.echo(f"beta: {flaggle.is_enabled('beta', context={'user_id': user_id})}") +``` + +--- + +## Tips for Typer +- Use a shared `Flaggle` instance for all commands. +- Add tests for flag logic using Typer's test runner and pytest. + +--- + +## See Also +- [API Reference](../api/flaggle.md) +- [Advanced Usage](../advanced.md) +- [FAQ](../faq.md) + + diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..ac7b666 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,29 @@ +# License + +Flaggle is licensed under the MIT License: + +``` +MIT License + +Copyright (c) 2025 Flaggle Contributors + +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. +``` + +See the [LICENSE](../LICENSE) file for the full text. diff --git a/docs/supported-operations.md b/docs/supported-operations.md new file mode 100644 index 0000000..4fa3cbc --- /dev/null +++ b/docs/supported-operations.md @@ -0,0 +1,115 @@ +# Supported Operations + +Flaggle provides a simple, explicit API for managing and evaluating feature flags in Python applications. This section covers the main operations, usage patterns, and the JSON schema for the `/flags` endpoint. + +--- + +## Defining Flags + +Flags can be defined at runtime, via configuration files, environment variables, or directly in code: + +```python +flaggle.set_flag("new_ui", True) +flaggle.set_flag("beta", False) +``` + +- Use clear, descriptive names for each flag. +- Flags can be boolean or custom types (see [Advanced Usage](advanced.md)). + +--- + +## Checking Flags + +Check if a flag is enabled: + +```python +if flaggle.is_enabled("new_ui"): + # Feature is enabled +``` + +With context (for user-based or environment-based flags): + +```python +flaggle.is_enabled("beta", context={"user_id": 123}) +``` + +- Context can include user IDs, environment, or any custom data. + +--- + +## Updating Flags + +Update flag values at runtime: + +```python +flaggle.set_flag("new_ui", False) +``` + +- Useful for toggling features during experiments or rollouts. + +--- + +## Removing Flags + +Remove a flag when it is no longer needed: + +```python +flaggle.remove_flag("beta") +``` + +- Regularly clean up unused flags to avoid tech debt. + +--- + +## Listing All Flags + +Get a dictionary of all current flags: + +```python +flags = flaggle.to_dict() +``` + +- Useful for debugging, admin panels, or exposing a `/flags` endpoint. + +--- + +## JSON Schema for Flags Endpoint + +The `/flags` endpoint returns a JSON object with the current state of all flags. Example response: + +```json +{ + "new_ui": true, + "beta": false +} +``` + +### JSON Schema + +```json +{ + "type": "object", + "patternProperties": { + "^[a-zA-Z_][a-zA-Z0-9_]*$": { "type": "boolean" } + }, + "additionalProperties": false +} +``` + +- The schema enforces that all flag names are valid Python identifiers and all values are boolean. +- Extend the schema if you use custom flag types. + +--- + +## Best Practices +- Use environment variables or config files for environment-specific flags. +- Document each flag's purpose and expected usage. +- Write tests for all flag logic and edge cases. +- Remove deprecated flags regularly. + +--- + +## See Also +- [API Reference](api/flaggle.md) +- [Advanced Usage](advanced.md) +- [FAQ](faq.md) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..cc72c0a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,62 @@ +site_name: Flaggle +site_description: Feature flag management for Python, with first-class FastAPI integration. +site_author: Flaggle Contributors +repo_url: https://github.com/Flaggle/flaggle-python +repo_name: flaggle-python +edit_uri: edit/main/docs/ +theme: + name: material + features: + - navigation.tabs + - navigation.sections + - navigation.top + - search.suggest + - search.highlight + - content.code.copy + - content.tabs.link + - toc.integrate + palette: + - scheme: default + primary: indigo + accent: indigo + - scheme: slate + primary: indigo + accent: indigo + font: + text: Roboto + code: Roboto Mono +plugins: + - search + - awesome-pages + - git-revision-date-localized: + fallback_to_build_date: true + - codeinclude + - section-index + - macros + - mkdocstrings +markdown_extensions: + - admonition + - codehilite + - footnotes + - meta + - toc: + permalink: true +nav: + - Overview: index.md + - Getting Started: first-steps.md + - Configuration: configuration.md + - API Reference: + - Flaggle: api/flaggle.md + - Flag: api/flag.md + - Supported Operations: supported-operations.md + - Integrations: + - FastAPI: integrations/fastapi.md + - Flask: integrations/flask.md + - Django: integrations/django.md + - Typer: integrations/typer.md + - Other: integrations/other.md + - Advanced Usage: advanced.md + - Tips & FAQ: faq.md + - Contributing: contributing.md + - Changelog: changelog.md + - License: license.md diff --git a/poetry.lock b/poetry.lock index 5ea8043..087f23e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,20 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + [[package]] name = "backports-tarfile" version = "1.2.0" @@ -17,13 +32,44 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] +[[package]] +name = "backrefs" +version = "5.8" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"}, + {file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"}, + {file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"}, + {file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"}, + {file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"}, + {file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"}, +] + +[package.extras] +extras = ["regex"] + +[[package]] +name = "bracex" +version = "2.5.post1" +description = "Bash style brace expander." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, + {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, +] + [[package]] name = "certifi" version = "2025.4.26" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main", "build"] +groups = ["main", "build", "dev"] files = [ {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, @@ -116,7 +162,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "build"] +groups = ["main", "build", "dev"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -212,6 +258,21 @@ files = [ {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -219,7 +280,6 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -407,6 +467,85 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.13.0,<2.14.0" pyflakes = ">=3.3.0,<3.4.0" +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "gitdb" +version = "4.0.12" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, + {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.44" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, + {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] + +[[package]] +name = "griffe" +version = "1.7.3" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75"}, + {file = "griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[[package]] +name = "hjson" +version = "3.1.0" +description = "Hjson, a user interface for JSON." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89"}, + {file = "hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75"}, +] + [[package]] name = "id" version = "1.5.0" @@ -433,7 +572,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "build"] +groups = ["main", "build", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -448,12 +587,12 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["build"] -markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\" or python_version == \"3.9\"" +groups = ["build", "dev"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, ] +markers = {build = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\" or python_version == \"3.9\"", dev = "python_version == \"3.9\""} [package.dependencies] zipp = ">=3.20" @@ -560,6 +699,24 @@ files = [ test = ["async-timeout ; python_version < \"3.11\"", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] trio = ["trio"] +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "keyring" version = "25.6.0" @@ -591,6 +748,25 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"] +[[package]] +name = "markdown" +version = "3.8" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"}, + {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -616,6 +792,77 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + [[package]] name = "mccabe" version = "0.7.0" @@ -640,6 +887,264 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.2" +description = "Automatically link across pages in MkDocs." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13"}, + {file = "mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749"}, +] + +[package.dependencies] +Markdown = ">=3.3" +markupsafe = ">=2.0.1" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-awesome-pages-plugin" +version = "2.10.1" +description = "An MkDocs plugin that simplifies configuring page titles and their order" +optional = false +python-versions = ">=3.8.1" +groups = ["dev"] +files = [ + {file = "mkdocs_awesome_pages_plugin-2.10.1-py3-none-any.whl", hash = "sha256:c6939dbea37383fc3cf8c0a4e892144ec3d2f8a585e16fdc966b34e7c97042a7"}, + {file = "mkdocs_awesome_pages_plugin-2.10.1.tar.gz", hash = "sha256:cda2cb88c937ada81a4785225f20ef77ce532762f4500120b67a1433c1cdbb2f"}, +] + +[package.dependencies] +mkdocs = ">=1" +natsort = ">=8.1.0" +wcmatch = ">=7" + +[[package]] +name = "mkdocs-codeinclude-plugin" +version = "0.2.1" +description = "A plugin to include code snippets into mkdocs pages" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "mkdocs-codeinclude-plugin-0.2.1.tar.gz", hash = "sha256:305387f67a885f0e36ec1cf977324fe1fe50d31301147194b63631d0864601b1"}, + {file = "mkdocs_codeinclude_plugin-0.2.1-py3-none-any.whl", hash = "sha256:172a917c9b257fa62850b669336151f85d3cd40312b2b52520cbcceab557ea6c"}, +] + +[package.dependencies] +mkdocs = ">=1.2" +pygments = ">=2.9.0" + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-git-revision-date-localized-plugin" +version = "1.4.7" +description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_git_revision_date_localized_plugin-1.4.7-py3-none-any.whl", hash = "sha256:056c0a90242409148f1dc94d5c9d2c25b5b8ddd8de45489fa38f7fa7ccad2bc4"}, + {file = "mkdocs_git_revision_date_localized_plugin-1.4.7.tar.gz", hash = "sha256:10a49eff1e1c3cb766e054b9d8360c904ce4fe8c33ac3f6cc083ac6459c91953"}, +] + +[package.dependencies] +babel = ">=2.7.0" +gitpython = ">=3.1.44" +mkdocs = ">=1.0" +pytz = ">=2025.1" + +[[package]] +name = "mkdocs-macros-plugin" +version = "1.3.7" +description = "Unleash the power of MkDocs with macros and variables" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_macros_plugin-1.3.7-py3-none-any.whl", hash = "sha256:02432033a5b77fb247d6ec7924e72fc4ceec264165b1644ab8d0dc159c22ce59"}, + {file = "mkdocs_macros_plugin-1.3.7.tar.gz", hash = "sha256:17c7fd1a49b94defcdb502fd453d17a1e730f8836523379d21292eb2be4cb523"}, +] + +[package.dependencies] +hjson = "*" +jinja2 = "*" +mkdocs = ">=0.17" +packaging = "*" +pathspec = "*" +python-dateutil = "*" +pyyaml = "*" +super-collections = "*" +termcolor = "*" + +[package.extras] +test = ["mkdocs-d2-plugin", "mkdocs-include-markdown-plugin", "mkdocs-macros-test", "mkdocs-material (>=6.2)", "mkdocs-test"] + +[[package]] +name = "mkdocs-material" +version = "9.6.14" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b"}, + {file = "mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +backrefs = ">=5.7.post1,<6.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.1,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + +[[package]] +name = "mkdocs-section-index" +version = "0.3.10" +description = "MkDocs plugin to allow clickable sections that lead to an index page" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocs_section_index-0.3.10-py3-none-any.whl", hash = "sha256:bc27c0d0dc497c0ebaee1fc72839362aed77be7318b5ec0c30628f65918e4776"}, + {file = "mkdocs_section_index-0.3.10.tar.gz", hash = "sha256:a82afbda633c82c5568f0e3b008176b9b365bf4bd8b6f919d6eff09ee146b9f8"}, +] + +[package.dependencies] +mkdocs = ">=1.2" + +[[package]] +name = "mkdocstrings" +version = "0.29.1" +description = "Automatic documentation from sources, for MkDocs." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocstrings-0.29.1-py3-none-any.whl", hash = "sha256:37a9736134934eea89cbd055a513d40a020d87dfcae9e3052c2a6b8cd4af09b6"}, + {file = "mkdocstrings-0.29.1.tar.gz", hash = "sha256:8722f8f8c5cd75da56671e0a0c1bbed1df9946c0cef74794d6141b34011abd42"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.11.1" +Markdown = ">=3.6" +MarkupSafe = ">=1.1" +mkdocs = ">=1.6" +mkdocs-autorefs = ">=1.4" +mkdocstrings-python = {version = ">=1.16.2", optional = true, markers = "extra == \"python\""} +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=1.16.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "1.16.12" +description = "A Python handler for mkdocstrings." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374"}, + {file = "mkdocstrings_python-1.16.12.tar.gz", hash = "sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d"}, +] + +[package.dependencies] +griffe = ">=1.6.2" +mkdocs-autorefs = ">=1.4" +mkdocstrings = ">=0.28.3" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + [[package]] name = "more-itertools" version = "10.7.0" @@ -653,6 +1158,22 @@ files = [ {file = "more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3"}, ] +[[package]] +name = "natsort" +version = "8.4.0" +description = "Simple yet flexible natural sorting in Python." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, + {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, +] + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + [[package]] name = "nh3" version = "0.2.21" @@ -699,6 +1220,22 @@ files = [ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + [[package]] name = "pastel" version = "0.2.1" @@ -711,6 +1248,35 @@ files = [ {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + [[package]] name = "pluggy" version = "1.6.0" @@ -790,7 +1356,7 @@ version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["build"] +groups = ["build", "dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -799,6 +1365,25 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pymdown-extensions" +version = "10.15" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f"}, + {file = "pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.19.1)"] + [[package]] name = "pytest" version = "8.3.5" @@ -861,6 +1446,33 @@ termcolor = ">=2.1.0" [package.extras] dev = ["black", "flake8", "pre-commit"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -880,7 +1492,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["build"] +groups = ["build", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -937,6 +1549,21 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +description = "A custom YAML tag for referencing environment variables in YAML files." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, + {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, +] + +[package.dependencies] +pyyaml = "*" + [[package]] name = "readme-renderer" version = "44.0" @@ -963,7 +1590,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "build"] +groups = ["main", "build", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1074,6 +1701,48 @@ files = [ cryptography = ">=2.0" jeepney = ">=0.6" +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "smmap" +version = "5.0.2" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, + {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, +] + +[[package]] +name = "super-collections" +version = "0.5.3" +description = "file: README.md" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "super_collections-0.5.3-py3-none-any.whl", hash = "sha256:907d35b25dc4070910e8254bf2f5c928348af1cf8a1f1e8259e06c666e902cff"}, + {file = "super_collections-0.5.3.tar.gz", hash = "sha256:94c1ec96c0a0d5e8e7d389ed8cde6882ac246940507c5e6b86e91945c2968d46"}, +] + +[package.dependencies] +hjson = "*" + +[package.extras] +test = ["pytest (>=7.0)"] + [[package]] name = "termcolor" version = "3.1.0" @@ -1178,7 +1847,7 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "build"] +groups = ["main", "build", "dev"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, @@ -1190,18 +1859,76 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcmatch" +version = "10.0" +description = "Wildcard/glob file name matcher." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, + {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, +] + +[package.dependencies] +bracex = ">=2.1.1" + [[package]] name = "zipp" version = "3.22.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["build"] -markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\" or python_version == \"3.9\"" +groups = ["build", "dev"] files = [ {file = "zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343"}, {file = "zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5"}, ] +markers = {build = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\" or python_version == \"3.9\"", dev = "python_version == \"3.9\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] @@ -1214,4 +1941,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9" -content-hash = "db9cd854b922ad43040d0c968beb4af3e176a39316ea63dc3d9461a86feba3fa" +content-hash = "29eb238353df477d6a622c4bc0352e33288f5d403f3691304f9888751306bd72" diff --git a/pyproject.toml b/pyproject.toml index f1c901a..0808c53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python_flaggle" -version = "0.3.1" +version = "0.4.0a2" description = "python-flaggle is a Python library for feature flag management." keywords = ["feature flags", "feature toggles", "feature management", "python"] classifiers = [ @@ -27,6 +27,13 @@ ruff = "^0.11.11" pytest = "^8.3.5" pytest-cov = "^6.1.1" pytest-sugar = "^1.0.0" +mkdocs-material = "^9.6.14" +mkdocs-awesome-pages-plugin = "^2.10.1" +mkdocs-codeinclude-plugin = "^0.2.1" +mkdocs-section-index = "^0.3.10" +mkdocs-macros-plugin = "^1.3.7" +mkdocs-git-revision-date-localized-plugin = "^1.4.7" +mkdocstrings = {extras = ["python"], version = "^0.29.1"} [tool.poetry.group.build.dependencies] diff --git a/python_flaggle/__init__.py b/python_flaggle/__init__.py index c1382e2..dd4a731 100644 --- a/python_flaggle/__init__.py +++ b/python_flaggle/__init__.py @@ -13,6 +13,6 @@ from python_flaggle.flag import Flag, FlagOperation, FlagType __all__ = ["FlagType", "FlagOperation", "Flag", "Flaggle"] -__version__ = "0.3.1" +__version__ = "0.4.0a2" __author__ = "Asaph Diniz" __email__ = "contato@asaph.dev.br"