Compare commits

...

16 Commits
3.0 ... main

Author SHA1 Message Date
BJ Dierkes
3527ade7b5
Merge pull request #766 from datafolklabs/feat/python-3.14
feat(dev): Python 3.14 default development target, drop 3.8 support
2025-11-02 20:51:58 -06:00
BJ Dierkes
dad85d287a feat(dev): Python 3.14 default development target, drop 3.8 support 2025-11-02 20:27:38 -06:00
BJ Dierkes
7c347abe43 fix(dev): add requests to dev dependencies
Resolves Issue #765
2025-11-02 18:45:21 -06:00
BJ Dierkes
bfb3b8c01b fix(ext.smtp): fix test related to mailpit api update 2025-11-02 18:42:22 -06:00
BJ Dierkes
3ee6b5157b feat(dev): update devbox 2025-11-02 18:41:48 -06:00
BJ Dierkes
cc857e70a7
Merge pull request #761 from datafolklabs/dep/update-pdm-lock
chore: Update pdm.lock
2025-11-02 17:49:34 -06:00
github-actions[bot]
ac410db146
chore: Update pdm.lock 2025-10-27 03:34:29 +00:00
BJ Dierkes
8b038170d8 feat: add direnv/devbox configurations and fix tests 2025-06-10 01:44:15 -05:00
BJ Dierkes
23b9b95d93 chore: add claude config 2025-06-09 23:32:38 -05:00
BJ Dierkes
9df6b3a3d3
Merge pull request #757 from datafolklabs/feat/github-actions
feat: setup github actions
2025-05-06 12:52:40 -05:00
BJ Dierkes
bd0d5eb878 ci: add minimal permissions for github actions 2025-05-06 12:29:15 -05:00
BJ Dierkes
80da0029ed ci: execute github actions on pull_request 2025-05-06 12:27:05 -05:00
BJ Dierkes
b46ce15833 feat: setup github actions 2025-05-06 12:23:13 -05:00
BJ Dierkes
41f2180976
Merge pull request #756 from sigma67/fix-all-exports
fix __all__
2025-05-06 09:16:11 -05:00
BJ Dierkes
8f5eaa817d chore: bump development version 2025-05-06 09:14:11 -05:00
sigma67
2bc559a30d fix __all__ 2025-05-06 10:07:03 +02:00
28 changed files with 2295 additions and 227 deletions

14
.envrc Normal file
View File

@ -0,0 +1,14 @@
# Automatically sets up your devbox environment whenever you cd into this
# directory via our direnv integration:
eval "$(devbox generate direnv --print-envrc)"
# check out https://www.jetpack.io/devbox/docs/ide_configuration/direnv/
# for more details
source_env_if_exists .envrc.local
export SMTP_HOST=localhost
export SMTP_PORT=1025
export MEMCACHED_HOST=localhost
export REDIS_HOST=localhost

98
.github/workflows/build_and_test.yml vendored Normal file
View File

@ -0,0 +1,98 @@
name: Build & Test
permissions:
contents: read
pull-requests: write
on: [pull_request]
env:
SMTP_HOST: localhost
SMTP_PORT: 1025
MEMCACHED_HOST: localhost
REDIS_HOST: localhost
jobs:
comply:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ConorMacBride/install-package@v1
with:
apt: libmemcached-dev
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
architecture: "x64"
- name: Setup PDM
uses: pdm-project/setup-pdm@v4
- name: Install dependencies
run: pdm install
- name: Make Comply
run: make comply
test:
needs: comply
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ConorMacBride/install-package@v1
with:
apt: libmemcached-dev
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
architecture: "x64"
- uses: hoverkraft-tech/compose-action@v2.0.1
with:
compose-file: "./docker/compose-services-only.yml"
- name: Setup PDM
uses: pdm-project/setup-pdm@v4
- name: Install dependencies
run: pdm install
- name: Make Test
run: make test
test-all:
needs: test
runs-on: ${{ matrix.os }}
strategy:
matrix:
# FIXME ?
# os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.10"]
steps:
- uses: actions/checkout@v4
- uses: ConorMacBride/install-package@v1
with:
apt: libmemcached-dev
- uses: hoverkraft-tech/compose-action@v2.0.1
with:
compose-file: "./docker/compose-services-only.yml"
- name: Setup PDM
uses: pdm-project/setup-pdm@v4
- name: Install dependencies
run: pdm install
- name: Make Test
run: make test
cli-smoke-test:
needs: test-all
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hoverkraft-tech/compose-action@v2.0.1
with:
compose-file: "./docker-compose.yml"
- name: CLI Smoke Tests
run: ./scripts/cli-smoke-test.sh
- if: always()
name: Review Output
run: cat ./tmp/cli-smoke-test.out

2
.gitignore vendored
View File

@ -52,7 +52,7 @@ pip-log.txt
# Documentation
doc/build
# Unit test / coverage reports
.coverage
.coverage*
htmlcov
coverage-report
.tox

View File

@ -1,5 +1,29 @@
# ChangeLog
## 3.0.15 - DEVELOPMENT (will be released as stable/3.0.16)
Bugs:
- None
Features:
- None
Refactoring:
- `[dev]` Python 3.14 Default Development Target
- `[dev]` Remove Support for Python 3.8 (EOL)
Misc:
- None
Deprecations:
- None
## 3.0.14 - May 5, 2025
Bugs:

78
CLAUDE.md Normal file
View File

@ -0,0 +1,78 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
**Testing and Compliance:**
- `make test` - Run full test suite with coverage and PEP8 compliance
- `make test-core` - Run only core library tests
- `make comply` - Run both ruff and mypy compliance checks
- `make comply-ruff` - Run ruff linting
- `make comply-ruff-fix` - Auto-fix ruff issues
- `make comply-mypy` - Run mypy type checking
- `pdm run pytest --cov=cement tests/` - Direct pytest execution
- `pdm run pytest --cov=cement.core tests/core` - Test only core components
**Development Environment:**
- `pdm venv create && pdm install` - Set up local development environment
- `pdm run cement --help` - Run the cement CLI
**Documentation:**
- `make docs` - Build Sphinx documentation
**Build and Distribution:**
- `pdm build` - Build distribution packages
## Architecture Overview
Cement is a CLI application framework built around a handler/interface pattern with the following core concepts:
**Core Application (`cement.core.foundation.App`):**
- The main `App` class in `cement/core/foundation.py` is the central orchestrator
- Uses a Meta class pattern for configuration
- Manages lifecycle through setup(), run(), and close() methods
- Supports signal handling and application reloading
**Handler System:**
- Interface/Handler pattern where interfaces define contracts and handlers provide implementations
- Core handlers: arg, config, log, output, cache, controller, extension, plugin, template
- Handlers are registered and resolved through `HandlerManager`
- Located in `cement/core/` with corresponding modules (arg.py, config.py, etc.)
**Extensions System:**
- Extensions in `cement/ext/` provide additional functionality
- Examples: ext_yaml.py, ext_jinja2.py, ext_argparse.py, etc.
- Optional dependencies managed through pyproject.toml extras
**CLI Structure:**
- Main CLI application in `cement/cli/main.py`
- Uses CementApp class that extends core App
- Includes code generation templates in `cement/cli/templates/`
**Controllers:**
- MVC-style controllers handle command routing
- Base controller pattern in controllers/base.py files
- Support nested sub-commands and argument parsing
## Key Development Practices
- 100% test coverage required (pytest with coverage reporting)
- 100% PEP8 compliance enforced via ruff
- Type annotation compliance via mypy
- PDM for dependency management
- Zero external dependencies for core framework (optional for extensions)
## Testing Notes
- Tests located in `tests/` directory mirroring source structure
- Core tests can run independently via `make test-core`
- Coverage reports generated in `coverage-report/` directory
## Extension Development
When working with extensions:
- Check `cement/ext/` for existing extension patterns
- Optional dependencies declared in pyproject.toml under `[project.optional-dependencies]`
- Extensions follow naming pattern `ext_<name>.py`
- Must implement proper interface contracts

View File

@ -1,4 +1,4 @@
FROM python:3.13-alpine
FROM python:3.14-alpine
LABEL MAINTAINER="BJ Dierkes <derks@datafolklabs.com>"
ENV PS1="\[\e[0;33m\]|> cement <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
ENV PATH="${PATH}:/root/.local/bin"

View File

@ -3,11 +3,11 @@
dev:
docker compose up -d
docker compose exec cement pdm install
docker compose exec cement-py38 pdm install
docker compose exec cement-py39 pdm install
docker compose exec cement-py310 pdm install
docker compose exec cement-py311 pdm install
docker compose exec cement-py312 pdm install
docker compose exec cement-py313 pdm install
docker compose exec cement /bin/bash
test: comply

View File

@ -42,7 +42,7 @@ Cement core features include (but are not limited to):
- 100% PEP8 compliance (`ruff`)
- Type annotation compliance (`mypy`)
- Extensive API Reference (`sphinx`)
- Tested on Python 3.8+
- Tested on Python 3.9+
## Optional Extensions
@ -92,17 +92,17 @@ All execution is done *inside the docker containers*.
**Testing Alternative Versions of Python**
The latest stable version of Python 3 is the default, and target version accessible as the `cement` container within Docker Compose. For testing against alternative versions of python, additional containers are created (ex: `cement-py38`, `cement-py39`, etc). You can access these containers via:
The latest stable version of Python 3 is the default, and target version accessible as the `cement` container within Docker Compose. For testing against alternative versions of python, additional containers are created (ex: `cement-py39`, `cement-py310`, etc). You can access these containers via:
```
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------
cement_cement-py38_1 /bin/bash Up
cement_cement-py39_1 /bin/bash Up
cement_cement-py310_1 /bin/bash Up
cement_cement-py311_1 /bin/bash Up
cement_cement-py312_1 /bin/bash Up
cement_cement-py313_1 /bin/bash Up
cement_cement_1 /bin/bash Up
cement_memcached_1 docker-entrypoint.sh memcached Up 11211/tcp
cement_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp

View File

@ -10,3 +10,21 @@ from .ext.ext_argparse import expose as ex
from .utils.misc import init_defaults, minimal_logger
from .utils import misc, fs, shell
from .utils.version import get_version
__all__ = [
"App",
"TestApp",
"Interface",
"Handler",
"FrameworkError",
"InterfaceError",
"CaughtSignal",
"Controller",
"ex",
"init_defaults",
"minimal_logger",
"misc",
"fs",
"shell",
"get_version",
]

View File

@ -1,3 +1,3 @@
"""Cement core backend module."""
VERSION = (3, 0, 14, 'final', 0) # pragma: nocover
VERSION = (3, 0, 15, 'final', 0) # pragma: nocover

View File

@ -5,7 +5,6 @@ Cement plugin extension module.
from __future__ import annotations
import os
import sys
import importlib
import importlib.util
import importlib.machinery
import re

1316
devbox.d/redis/redis.conf Normal file

File diff suppressed because it is too large Load Diff

24
devbox.json Normal file
View File

@ -0,0 +1,24 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.0/.schema/devbox.schema.json",
"packages": {
"python": "3.14",
"pdm": "latest",
"libmemcached": "latest",
"zlib": {
"version": "latest",
"outputs": ["out", "dev"]
},
"redis": "latest",
"memcached": {
"version": "latest",
"outputs": ["out"]
},
"mailpit": "latest"
},
"shell": {
"init_hook": [],
"scripts": {
"test": ["echo \"Error: no test specified\" && exit 1"]
}
}
}

402
devbox.lock Normal file
View File

@ -0,0 +1,402 @@
{
"lockfile_version": "1",
"packages": {
"github:NixOS/nixpkgs/nixpkgs-unstable": {
"resolved": "github:NixOS/nixpkgs/a7fc11be66bdfb5cdde611ee5ce381c183da8386?lastModified=1761880412&narHash=sha256-QoJjGd4NstnyOG4mm4KXF%2BweBzA2AH%2F7gn1Pmpfcb0A%3D"
},
"libmemcached@latest": {
"last_modified": "2025-10-07T08:41:47Z",
"resolved": "github:NixOS/nixpkgs/bce5fe2bb998488d8e7e7856315f90496723793c#libmemcached",
"source": "devbox-search",
"version": "1.0.18",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/hvq13f4g2yzg4ir8zms3qljdg70kx2nk-libmemcached-1.0.18",
"default": true
}
],
"store_path": "/nix/store/hvq13f4g2yzg4ir8zms3qljdg70kx2nk-libmemcached-1.0.18"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/pgbw4r2l1k6r4jkj3rx9z1sjvffkzlgj-libmemcached-1.0.18",
"default": true
}
],
"store_path": "/nix/store/pgbw4r2l1k6r4jkj3rx9z1sjvffkzlgj-libmemcached-1.0.18"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/22p6w02llxgwwvzgrjhiawfhprxs0gn1-libmemcached-1.0.18",
"default": true
}
],
"store_path": "/nix/store/22p6w02llxgwwvzgrjhiawfhprxs0gn1-libmemcached-1.0.18"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/1zqgqb0iv0q53q0laxmwbhbvzrpid72d-libmemcached-1.0.18",
"default": true
}
],
"store_path": "/nix/store/1zqgqb0iv0q53q0laxmwbhbvzrpid72d-libmemcached-1.0.18"
}
}
},
"mailpit@latest": {
"last_modified": "2025-10-13T09:56:54Z",
"resolved": "github:NixOS/nixpkgs/c12c63cd6c5eb34c7b4c3076c6a99e00fcab86ec#mailpit",
"source": "devbox-search",
"version": "1.27.10",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/hygcxbx8nbzgmzj2861pq8371sbk9vxx-mailpit-1.27.10",
"default": true
}
],
"store_path": "/nix/store/hygcxbx8nbzgmzj2861pq8371sbk9vxx-mailpit-1.27.10"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/i562vdjasq196kw3zxsq2faqxk0jia35-mailpit-1.27.10",
"default": true
}
],
"store_path": "/nix/store/i562vdjasq196kw3zxsq2faqxk0jia35-mailpit-1.27.10"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/iw496lj2zl7yyhr584gl3gmlpwfgwywz-mailpit-1.27.10",
"default": true
}
],
"store_path": "/nix/store/iw496lj2zl7yyhr584gl3gmlpwfgwywz-mailpit-1.27.10"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/31p7d3r6ngcyy19asv92590aihcpcw2y-mailpit-1.27.10",
"default": true
}
],
"store_path": "/nix/store/31p7d3r6ngcyy19asv92590aihcpcw2y-mailpit-1.27.10"
}
}
},
"memcached@latest": {
"last_modified": "2025-10-07T08:41:47Z",
"resolved": "github:NixOS/nixpkgs/bce5fe2bb998488d8e7e7856315f90496723793c#memcached",
"source": "devbox-search",
"version": "1.6.39",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/ihnmlzfs4bj1iza5jrhd69j7cfxk98c9-memcached-1.6.39",
"default": true
}
],
"store_path": "/nix/store/ihnmlzfs4bj1iza5jrhd69j7cfxk98c9-memcached-1.6.39"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/ikqynsvdx49dyfyvjc9dzal3793x3q12-memcached-1.6.39",
"default": true
}
],
"store_path": "/nix/store/ikqynsvdx49dyfyvjc9dzal3793x3q12-memcached-1.6.39"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/pmljf231q2z42c3fqphy16nwb1238rvw-memcached-1.6.39",
"default": true
}
],
"store_path": "/nix/store/pmljf231q2z42c3fqphy16nwb1238rvw-memcached-1.6.39"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/mr6k45aw0srb2vl5azd2ndyh77fdqkp7-memcached-1.6.39",
"default": true
}
],
"store_path": "/nix/store/mr6k45aw0srb2vl5azd2ndyh77fdqkp7-memcached-1.6.39"
}
}
},
"pdm@latest": {
"last_modified": "2025-10-07T08:41:47Z",
"resolved": "github:NixOS/nixpkgs/bce5fe2bb998488d8e7e7856315f90496723793c#pdm",
"source": "devbox-search",
"version": "2.25.9",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/fali4xk6c9dv1cfp6yshc83k7knn4jkm-pdm-2.25.9",
"default": true
},
{
"name": "dist",
"path": "/nix/store/84h5gp6xqz6ivgwdv1hs72nkrhr1jp6w-pdm-2.25.9-dist"
}
],
"store_path": "/nix/store/fali4xk6c9dv1cfp6yshc83k7knn4jkm-pdm-2.25.9"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/d444f8wq1zf4w5vmm95lsh25m60dp3b0-pdm-2.25.9",
"default": true
},
{
"name": "dist",
"path": "/nix/store/dhx4hdxizq8zrvwajs4qiz26n5bk4g71-pdm-2.25.9-dist"
}
],
"store_path": "/nix/store/d444f8wq1zf4w5vmm95lsh25m60dp3b0-pdm-2.25.9"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/b3628r7jbv1i70jajx3z25q4pwxrq1dg-pdm-2.25.9",
"default": true
},
{
"name": "dist",
"path": "/nix/store/i4349788frmbv2kcpnnh6dh91hhgcbss-pdm-2.25.9-dist"
}
],
"store_path": "/nix/store/b3628r7jbv1i70jajx3z25q4pwxrq1dg-pdm-2.25.9"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/w9mvh1lpwwyxh1h2z5bypvvvvxwdjbyd-pdm-2.25.9",
"default": true
},
{
"name": "dist",
"path": "/nix/store/q4dp9wx7g79hknqgly1nccrdwvxr274j-pdm-2.25.9-dist"
}
],
"store_path": "/nix/store/w9mvh1lpwwyxh1h2z5bypvvvvxwdjbyd-pdm-2.25.9"
}
}
},
"python@3.14": {
"last_modified": "2025-10-08T01:30:28Z",
"plugin_version": "0.0.4",
"resolved": "github:NixOS/nixpkgs/8b5c9dd8856f0c0cf46cc91f2c21c106a9d42e25#python314",
"source": "devbox-search",
"version": "3.14.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/2sqv05h8017f38w5rvppb2f5wbbisnwp-python3-3.14.0",
"default": true
}
],
"store_path": "/nix/store/2sqv05h8017f38w5rvppb2f5wbbisnwp-python3-3.14.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/zlph3shgrkfmrhkxbgmi6qa26gfzl58q-python3-3.14.0",
"default": true
},
{
"name": "debug",
"path": "/nix/store/3zmfrpjfpqaxcl68hlg5nfzvx49awjks-python3-3.14.0-debug"
}
],
"store_path": "/nix/store/zlph3shgrkfmrhkxbgmi6qa26gfzl58q-python3-3.14.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/bq9ss2vlr05zdrhcfmvclm0gsrc7i6xb-python3-3.14.0",
"default": true
}
],
"store_path": "/nix/store/bq9ss2vlr05zdrhcfmvclm0gsrc7i6xb-python3-3.14.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/76lchhz5hhik0j5hjy6lwwn3ik0x54aa-python3-3.14.0",
"default": true
},
{
"name": "debug",
"path": "/nix/store/rjm597f2d9bjllyjnv3y20261bwxd108-python3-3.14.0-debug"
}
],
"store_path": "/nix/store/76lchhz5hhik0j5hjy6lwwn3ik0x54aa-python3-3.14.0"
}
}
},
"redis@latest": {
"last_modified": "2025-10-07T08:41:47Z",
"plugin_version": "0.0.2",
"resolved": "github:NixOS/nixpkgs/bce5fe2bb998488d8e7e7856315f90496723793c#redis",
"source": "devbox-search",
"version": "8.2.2",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/cnwxss9snn773vwr2vb0b6jx7lznmac2-redis-8.2.2",
"default": true
}
],
"store_path": "/nix/store/cnwxss9snn773vwr2vb0b6jx7lznmac2-redis-8.2.2"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/nml062djj2nfc7zgn2q93g0swgrbdqbc-redis-8.2.2",
"default": true
}
],
"store_path": "/nix/store/nml062djj2nfc7zgn2q93g0swgrbdqbc-redis-8.2.2"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/vixhsgdlv2d8hm66jkclqdgyfi3vi86b-redis-8.2.2",
"default": true
}
],
"store_path": "/nix/store/vixhsgdlv2d8hm66jkclqdgyfi3vi86b-redis-8.2.2"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/z6sw7nrc62l9z1mm05dqx4vz3a65lgb6-redis-8.2.2",
"default": true
}
],
"store_path": "/nix/store/z6sw7nrc62l9z1mm05dqx4vz3a65lgb6-redis-8.2.2"
}
}
},
"zlib@latest": {
"last_modified": "2025-10-08T10:03:27Z",
"resolved": "github:NixOS/nixpkgs/fb5cf53218b987f2703a5bbc292a030c0fe33443#zlib",
"source": "devbox-search",
"version": "1.3.1",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/5vnba43n1w87cs2i2dd242zy88k4dwf9-zlib-1.3.1",
"default": true
},
{
"name": "dev",
"path": "/nix/store/86xjpgsysygxdhfczssrlj24mi22wxiq-zlib-1.3.1-dev"
},
{
"name": "static",
"path": "/nix/store/39mpcp31vw48raa6lj7k2di0ln922bjp-zlib-1.3.1-static"
}
],
"store_path": "/nix/store/5vnba43n1w87cs2i2dd242zy88k4dwf9-zlib-1.3.1"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/h5gyhqv452n16izdh86s3rvdq1fcg2pb-zlib-1.3.1",
"default": true
},
{
"name": "dev",
"path": "/nix/store/c7jl10xakizqzj2wv5arnjggwv1vh3r0-zlib-1.3.1-dev"
},
{
"name": "static",
"path": "/nix/store/plix72ay1sq7l91qm97a0ls7haa0hmv8-zlib-1.3.1-static"
}
],
"store_path": "/nix/store/h5gyhqv452n16izdh86s3rvdq1fcg2pb-zlib-1.3.1"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/yy41r1jfz748y6xq4dc54qm4xlravz95-zlib-1.3.1",
"default": true
},
{
"name": "dev",
"path": "/nix/store/kw5wnbgivfxif9m2g8i4ks0whdgnh158-zlib-1.3.1-dev"
},
{
"name": "static",
"path": "/nix/store/l6mprw6il6i4dx78pcnn7as09287f9nh-zlib-1.3.1-static"
}
],
"store_path": "/nix/store/yy41r1jfz748y6xq4dc54qm4xlravz95-zlib-1.3.1"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/6qi8skh85ci2k9gvl27nnh3kiy32qnsz-zlib-1.3.1",
"default": true
},
{
"name": "dev",
"path": "/nix/store/6fqij3sy1mlsnq1n679bzbl5bkqs7yvk-zlib-1.3.1-dev"
},
{
"name": "static",
"path": "/nix/store/b1l0v5czhw4c410nxvk5lbjfv9r8h7h7-zlib-1.3.1-static"
}
],
"store_path": "/nix/store/6qi8skh85ci2k9gvl27nnh3kiy32qnsz-zlib-1.3.1"
}
}
}
}
}

View File

@ -53,20 +53,13 @@ services:
- memcached
- mailpit
cement-py38:
<<: *DEFAULTS
image: "cement:dev-py38"
build:
context: .
dockerfile: docker/Dockerfile.dev-py38
cement-py39:
<<: *DEFAULTS
image: "cement:dev-py39"
build:
context: .
dockerfile: docker/Dockerfile.dev-py39
cement-py310:
<<: *DEFAULTS
image: "cement:dev-py310"
@ -87,3 +80,10 @@ services:
build:
context: .
dockerfile: docker/Dockerfile.dev-py312
cement-py313:
<<: *DEFAULTS
image: "cement:dev-py313"
build:
context: .
dockerfile: docker/Dockerfile.dev-py313

View File

@ -1,6 +1,6 @@
FROM python:3.13-alpine
FROM python:3.14-alpine
LABEL MAINTAINER="BJ Dierkes <derks@datafolklabs.com>"
ENV PS1="\[\e[0;33m\]|> cement-py313 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
ENV PS1="\[\e[0;33m\]|> cement-py314 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
ENV PATH="${PATH}:/root/.local/bin"
WORKDIR /src

View File

@ -1,4 +1,4 @@
FROM python:3.13-rc-alpine
FROM python:3.13-alpine
LABEL MAINTAINER="BJ Dierkes <derks@datafolklabs.com>"
ENV PS1="\[\e[0;33m\]|> cement-py313 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
ENV PATH="${PATH}:/root/.local/bin"

View File

@ -1,6 +1,6 @@
FROM python:3.8-alpine
FROM python:3.14-alpine
LABEL MAINTAINER="BJ Dierkes <derks@datafolklabs.com>"
ENV PS1="\[\e[0;33m\]|> cement-py38 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
ENV PS1="\[\e[0;33m\]|> cement-py314 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
ENV PATH="${PATH}:/root/.local/bin"
WORKDIR /src
@ -22,7 +22,7 @@ RUN apk update \
&& ln -sf /usr/bin/vim /usr/bin/vi
RUN pipx install pdm
COPY . /src
COPY docker/vimrc /root/.vimrc
COPY docker/bashrc /root/.bashrc
COPY ./docker/vimrc /root/.vimrc
COPY ./docker/bashrc /root/.bashrc
RUN pdm install
CMD ["/bin/bash"]

View File

@ -0,0 +1,33 @@
volumes:
mailpit-data:
services:
redis:
image: redis:latest
hostname: redis
ports:
- 6379:6379
memcached:
image: memcached:latest
hostname: memcached
ports:
- 11211:11211
mailpit:
image: axllent/mailpit
hostname: mailpit
restart: always
volumes:
- mailpit-data:/data
- ../docker/mailpit:/certificates
ports:
- 8025:8025
- 1025:1025
environment:
MP_MAX_MESSAGES: 5000
MP_DATA_FILE: /data/mailpit.db
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
MP_SMTP_TLS_CERT: /certificates/dev-cert.pem
MP_SMTP_TLS_KEY: /certificates/dev-key.pem

212
pdm.lock
View File

@ -3,12 +3,12 @@
[metadata]
groups = ["default", "alarm", "argparse", "cli", "colorlog", "configparser", "daemon", "dev", "docs", "dummy", "generate", "jinja2", "json", "logging", "memcached", "mustache", "plugin", "print", "redis", "scrub", "smtp", "tabulate", "watchdog", "yaml"]
strategy = ["cross_platform", "inherit_metadata"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:edb453570d1f7e2dcd14a71e866fa1814be1c5341a716c96164858feae2e3662"
content_hash = "sha256:88e91afb3db1d8d71ebff4abd3d4d3e29b3415592657bd9fe104948d542d1ee5"
[[metadata.targets]]
requires_python = ">=3.8"
requires_python = ">=3.9"
[[package]]
name = "alabaster"
@ -55,7 +55,7 @@ name = "certifi"
version = "2024.2.2"
requires_python = ">=3.6"
summary = "Python package for providing Mozilla's CA Bundle."
groups = ["docs"]
groups = ["dev", "docs"]
files = [
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
@ -66,7 +66,7 @@ name = "charset-normalizer"
version = "3.3.2"
requires_python = ">=3.7.0"
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
groups = ["docs"]
groups = ["dev", "docs"]
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
@ -161,7 +161,7 @@ files = [
[[package]]
name = "colorlog"
version = "6.9.0"
version = "6.10.1"
requires_python = ">=3.6"
summary = "Add colours to the output of Python's logging module."
groups = ["colorlog"]
@ -169,8 +169,8 @@ dependencies = [
"colorama; sys_platform == \"win32\"",
]
files = [
{file = "colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff"},
{file = "colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2"},
{file = "colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c"},
{file = "colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321"},
]
[[package]]
@ -380,7 +380,7 @@ name = "idna"
version = "3.6"
requires_python = ">=3.5"
summary = "Internationalized Domain Names in Applications (IDNA)"
groups = ["docs"]
groups = ["dev", "docs"]
files = [
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
@ -429,7 +429,7 @@ name = "jinja2"
version = "3.1.6"
requires_python = ">=3.7"
summary = "A very fast and expressive template engine."
groups = ["docs", "jinja2"]
groups = ["cli", "docs", "jinja2"]
dependencies = [
"MarkupSafe>=2.0",
]
@ -443,7 +443,7 @@ name = "markupsafe"
version = "2.1.5"
requires_python = ">=3.7"
summary = "Safely add untrusted strings to HTML/XML markup."
groups = ["docs", "jinja2"]
groups = ["cli", "docs", "jinja2"]
files = [
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
@ -702,82 +702,91 @@ files = [
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
]
[[package]]
name = "pytz"
version = "2024.1"
summary = "World timezone definitions, modern and historical"
groups = ["docs"]
marker = "python_version < \"3.9\""
files = [
{file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
]
[[package]]
name = "pyyaml"
version = "6.0.2"
version = "6.0.3"
requires_python = ">=3.8"
summary = "YAML parser and emitter for Python"
groups = ["generate", "yaml"]
groups = ["cli", "generate", "yaml"]
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"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
{file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"},
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"},
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"},
{file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"},
{file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"},
{file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"},
{file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"},
{file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
{file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"},
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"},
{file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"},
{file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"},
{file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"},
{file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"},
{file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"},
{file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"},
{file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"},
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"},
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"},
{file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"},
{file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"},
{file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"},
{file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"},
{file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"},
{file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"},
{file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"},
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"},
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"},
{file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"},
{file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"},
{file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"},
{file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"},
{file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"},
{file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"},
{file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"},
{file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"},
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"},
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"},
{file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"},
{file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"},
{file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"},
{file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"},
{file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"},
{file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"},
{file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"},
{file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"},
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"},
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"},
{file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"},
{file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"},
{file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"},
{file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"},
{file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"},
{file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"},
{file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"},
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"},
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"},
{file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"},
{file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"},
{file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"},
{file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"},
{file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"},
{file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"},
{file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"},
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"},
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"},
{file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"},
{file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"},
{file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"},
{file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"},
{file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"},
{file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
]
[[package]]
name = "redis"
version = "6.0.0"
version = "6.1.1"
requires_python = ">=3.8"
summary = "Python client for Redis database and key-value store"
groups = ["redis"]
@ -785,8 +794,8 @@ dependencies = [
"async-timeout>=4.0.3; python_full_version < \"3.11.3\"",
]
files = [
{file = "redis-6.0.0-py3-none-any.whl", hash = "sha256:a2e040aee2cdd947be1fa3a32e35a956cd839cc4c1dbbe4b2cdee5b9623fd27c"},
{file = "redis-6.0.0.tar.gz", hash = "sha256:5446780d2425b787ed89c91ddbfa1be6d32370a636c8fdb687f11b1c26c1fa88"},
{file = "redis-6.1.1-py3-none-any.whl", hash = "sha256:ed44d53d065bbe04ac6d76864e331cfe5c5353f86f6deccc095f8794fd15bb2e"},
{file = "redis-6.1.1.tar.gz", hash = "sha256:88c689325b5b41cedcbdbdfd4d937ea86cf6dab2222a83e86d8a466e4b3d2600"},
]
[[package]]
@ -794,7 +803,7 @@ name = "requests"
version = "2.31.0"
requires_python = ">=3.7"
summary = "Python HTTP for Humans."
groups = ["docs"]
groups = ["dev", "docs"]
dependencies = [
"certifi>=2017.4.17",
"charset-normalizer<4,>=2",
@ -808,29 +817,30 @@ files = [
[[package]]
name = "ruff"
version = "0.11.8"
version = "0.14.2"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"]
files = [
{file = "ruff-0.11.8-py3-none-linux_armv6l.whl", hash = "sha256:896a37516c594805e34020c4a7546c8f8a234b679a7716a3f08197f38913e1a3"},
{file = "ruff-0.11.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab86d22d3d721a40dd3ecbb5e86ab03b2e053bc93c700dc68d1c3346b36ce835"},
{file = "ruff-0.11.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:258f3585057508d317610e8a412788cf726efeefa2fec4dba4001d9e6f90d46c"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727d01702f7c30baed3fc3a34901a640001a2828c793525043c29f7614994a8c"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3dca977cc4fc8f66e89900fa415ffe4dbc2e969da9d7a54bfca81a128c5ac219"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c657fa987d60b104d2be8b052d66da0a2a88f9bd1d66b2254333e84ea2720c7f"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f2e74b021d0de5eceb8bd32919f6ff8a9b40ee62ed97becd44993ae5b9949474"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b5ef39820abc0f2c62111f7045009e46b275f5b99d5e59dda113c39b7f4f38"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1dba3135ca503727aa4648152c0fa67c3b1385d3dc81c75cd8a229c4b2a1458"},
{file = "ruff-0.11.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f024d32e62faad0f76b2d6afd141b8c171515e4fb91ce9fd6464335c81244e5"},
{file = "ruff-0.11.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d365618d3ad747432e1ae50d61775b78c055fee5936d77fb4d92c6f559741948"},
{file = "ruff-0.11.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4d9aaa91035bdf612c8ee7266153bcf16005c7c7e2f5878406911c92a31633cb"},
{file = "ruff-0.11.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0eba551324733efc76116d9f3a0d52946bc2751f0cd30661564117d6fd60897c"},
{file = "ruff-0.11.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:161eb4cff5cfefdb6c9b8b3671d09f7def2f960cee33481dd898caf2bcd02304"},
{file = "ruff-0.11.8-py3-none-win32.whl", hash = "sha256:5b18caa297a786465cc511d7f8be19226acf9c0a1127e06e736cd4e1878c3ea2"},
{file = "ruff-0.11.8-py3-none-win_amd64.whl", hash = "sha256:6e70d11043bef637c5617297bdedec9632af15d53ac1e1ba29c448da9341b0c4"},
{file = "ruff-0.11.8-py3-none-win_arm64.whl", hash = "sha256:304432e4c4a792e3da85b7699feb3426a0908ab98bf29df22a31b0cdd098fac2"},
{file = "ruff-0.11.8.tar.gz", hash = "sha256:6d742d10626f9004b781f4558154bb226620a7242080e11caeffab1a40e99df8"},
{file = "ruff-0.14.2-py3-none-linux_armv6l.whl", hash = "sha256:7cbe4e593505bdec5884c2d0a4d791a90301bc23e49a6b1eb642dd85ef9c64f1"},
{file = "ruff-0.14.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8d54b561729cee92f8d89c316ad7a3f9705533f5903b042399b6ae0ddfc62e11"},
{file = "ruff-0.14.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c8753dfa44ebb2cde10ce5b4d2ef55a41fb9d9b16732a2c5df64620dbda44a3"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0bbeffb8d9f4fccf7b5198d566d0bad99a9cb622f1fc3467af96cb8773c9e3"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7047f0c5a713a401e43a88d36843d9c83a19c584e63d664474675620aaa634a8"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bf8d2f9aa1602599217d82e8e0af7fd33e5878c4d98f37906b7c93f46f9a839"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1c505b389e19c57a317cf4b42db824e2fca96ffb3d86766c1c9f8b96d32048a7"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a307fc45ebd887b3f26b36d9326bb70bf69b01561950cdcc6c0bdf7bb8e0f7cc"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61ae91a32c853172f832c2f40bd05fd69f491db7289fb85a9b941ebdd549781a"},
{file = "ruff-0.14.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1967e40286f63ee23c615e8e7e98098dedc7301568bd88991f6e544d8ae096"},
{file = "ruff-0.14.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2877f02119cdebf52a632d743a2e302dea422bfae152ebe2f193d3285a3a65df"},
{file = "ruff-0.14.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e681c5bc777de5af898decdcb6ba3321d0d466f4cb43c3e7cc2c3b4e7b843a05"},
{file = "ruff-0.14.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e21be42d72e224736f0c992cdb9959a2fa53c7e943b97ef5d081e13170e3ffc5"},
{file = "ruff-0.14.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8264016f6f209fac16262882dbebf3f8be1629777cf0f37e7aff071b3e9b92e"},
{file = "ruff-0.14.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ca36b4cb4db3067a3b24444463ceea5565ea78b95fe9a07ca7cb7fd16948770"},
{file = "ruff-0.14.2-py3-none-win32.whl", hash = "sha256:41775927d287685e08f48d8eb3f765625ab0b7042cc9377e20e64f4eb0056ee9"},
{file = "ruff-0.14.2-py3-none-win_amd64.whl", hash = "sha256:0df3424aa5c3c08b34ed8ce099df1021e3adaca6e90229273496b839e5a7e1af"},
{file = "ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a"},
{file = "ruff-0.14.2.tar.gz", hash = "sha256:98da787668f239313d9c902ca7c523fe11b8ec3f39345553a51b25abc4629c96"},
]
[[package]]
@ -1033,7 +1043,7 @@ name = "urllib3"
version = "2.2.1"
requires_python = ">=3.8"
summary = "HTTP library with thread-safe connection pooling, file post, and more."
groups = ["docs"]
groups = ["dev", "docs"]
files = [
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},

19
process-compose.yml Normal file
View File

@ -0,0 +1,19 @@
# Process compose for starting django
version: "0.5"
processes:
memcached:
command: memcached
availability:
restart: "always"
mailpit:
command: mailpit
availability:
restart: "always"
environment:
- "MP_MAX_MESSAGES=5000"
- "MP_SMTP_AUTH_ACCEPT_ANY=1"
- "MP_SMTP_AUTH_ALLOW_INSECURE=1"
- "MP_SMTP_TLS_CERT=docker/mailpit/dev-cert.pem"
- "MP_SMTP_TLS_KEY=docker/mailpit/dev-key.pem"

View File

@ -16,7 +16,7 @@ classifiers = [
dynamic = ["version", "README"]
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = []
[project.optional-dependencies]
@ -69,7 +69,7 @@ precision = 2
[tool.ruff]
target-version = "py38"
target-version = "py39"
line-length = 100
indent-width = 4
exclude = [
@ -107,7 +107,7 @@ unfixable = []
# ignore_missing_imports = true
[tool.mypy]
python_version = "3.8"
python_version = "3.9"
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_any_unimported = false
@ -162,4 +162,5 @@ dev = [
"ruff>=0.3.2",
"mock>=5.1.0",
"pypng>=0.20220715.0",
"requests>=2.31.0",
]

View File

@ -2,7 +2,7 @@
set -e
[ -z "$CEMENT_VERSION" ] && CEMENT_VERSION="3.0"
[ -z "$PYTHON_VERSIONS" ] && PYTHON_VERSIONS="3.8 3.9 3.10 3.11 3.12 3.13"
[ -z "$PYTHON_VERSIONS" ] && PYTHON_VERSIONS="3.9 3.10 3.11 3.12 3.13 3.14"
function smoke-test {
pyver=$1
@ -19,15 +19,15 @@ function smoke-test {
python:$pyver \
/bin/bash
docker exec -it cement-cli-smoke-test /bin/bash -c "cd /src ; pip install `ls dist/cement-*.tar.gz`[cli]"
tmp=$(docker exec cement-cli-smoke-test /bin/bash -c "mktemp -d")
docker exec cement-cli-smoke-test /bin/bash -c "cd /src ; pip install `ls dist/cement-*.tar.gz`[cli]"
tmp=$(docker exec -t cement-cli-smoke-test /bin/bash -c "mktemp -d")
### verify help output
res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement --version")
echo "$res" | grep "Cement Framework $CEMENT_VERSION.\d"
echo "$res" | grep "Python $pyver.\d"
echo "$res" | grep "Cement Framework $CEMENT_VERSION.[0-9]"
echo "$res" | grep "Python $pyver.[0-9]"
echo "$res" | grep "Platform Linux.*"
res=$(docker exec cement-cli-smoke-test /bin/bash -c "cement --help")
echo "$res" | grep "Cement Framework Developer Tools"
@ -45,37 +45,37 @@ function smoke-test {
echo "$res" | grep "destination directory path"
echo "$res" | grep -- "-D, --defaults"
### generate a project
docker exec cement-cli-smoke-test /bin/bash -c "cement generate project -D $tmp/myapp"
docker exec cement-cli-smoke-test /bin/bash -c "cd $tmp/myapp ; pip install -r requirements.txt"
docker exec cement-cli-smoke-test /bin/bash -c "cd $tmp/myapp ; pip install setuptools"
docker exec cement-cli-smoke-test /bin/bash -c "cd $tmp/myapp ; python setup.py install"
res=$(docker exec cement-cli-smoke-test /bin/bash -c "myapp --version")
echo "$res" | grep "Cement Framework $CEMENT_VERSION\.\d"
echo "$res" | grep "Python $pyver.\d"
res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "myapp --version")
echo "$res" | grep "Cement Framework $CEMENT_VERSION\.[0-9]"
echo "$res" | grep "Python $pyver.[0-9]"
echo "$res" | grep "Platform Linux.*"
### generate a script
docker exec cement-cli-smoke-test /bin/bash -c "cement generate script -D $tmp/myscript"
res=$(docker exec cement-cli-smoke-test /bin/bash -c "python $tmp/myscript/myscript.py --version")
res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "python $tmp/myscript/myscript.py --version")
echo "$res" | grep "myscript v0.0.1"
### generate an extension
docker exec cement-cli-smoke-test /bin/bash -c "cement generate extension -D $tmp/myapp/myapp/ext"
res=$(docker exec cement-cli-smoke-test /bin/bash -c "cat $tmp/myapp/myapp/ext/ext_myextension.py")
res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "cat $tmp/myapp/myapp/ext/ext_myextension.py")
echo "$res" | grep "myextension_pre_run_hook"
### generate a plugin
docker exec cement-cli-smoke-test /bin/bash -c "cement generate plugin -D $tmp/myapp/myapp/plugins"
res=$(docker exec cement-cli-smoke-test /bin/bash -c "cat $tmp/myapp/myapp/plugins/myplugin/controllers/myplugin.py")
res=$(docker exec -t cement-cli-smoke-test /bin/bash -c "cat $tmp/myapp/myapp/plugins/myplugin/controllers/myplugin.py")
echo "$res" | grep "class MyPlugin(Controller)"
### finish
@ -93,4 +93,4 @@ for pyver in $PYTHON_VERSIONS; do
echo -n "python $pyver . . . "
smoke-test $pyver 2>> tmp/cli-smoke-test.out 1>> tmp/cli-smoke-test.out
echo "ok"
done
done

View File

@ -6,6 +6,6 @@ def test_version():
# ensure that we bump things properly on version changes
assert backend.VERSION[0] == 3
assert backend.VERSION[1] == 0
assert backend.VERSION[2] == 14
assert backend.VERSION[2] == 15
assert backend.VERSION[3] == 'final'
assert backend.VERSION[4] == 0

View File

@ -77,24 +77,26 @@ def test_bogus_group(rando):
env.switch()
def _daemon_target(pid_file):
"""Target function for daemon test subprocess."""
with TestApp(argv=['--daemon'], extensions=['daemon']) as app:
app.config.set('daemon', 'pid_file', pid_file)
try:
# FIX ME: Can't daemonize, because nose/pytest lose sight of it
app.daemonize()
app.run()
finally:
app.close()
ext_daemon.cleanup(app)
def test_daemon(tmp):
os.remove(tmp.file)
from cement.utils import shell
# Test in a sub-process to avoid hangup
def target():
with TestApp(argv=['--daemon'], extensions=['daemon']) as app:
app.config.set('daemon', 'pid_file', tmp.file)
try:
# FIX ME: Can't daemonize, because nose/pytest lose sight of it
app.daemonize()
app.run()
finally:
app.close()
ext_daemon.cleanup(app)
p = shell.spawn_process(target)
p = shell.spawn_process(_daemon_target, args=(tmp.file,))
p.join()
assert p.exitcode == 0

View File

@ -64,8 +64,8 @@ def test_smtp_send(rando):
assert msg['To'][0]['Address'] == f'to-{rando}@localhost'
assert msg['Subject'] == f'UNIT TEST > {rando}'
assert msg['Attachments'] == 0
assert msg['Cc'] == []
assert msg['Bcc'] == []
assert msg['Cc'] in [None, []]
assert msg['Bcc'] in [None, []]
delete_msg(msg['ID'])

View File

@ -21,34 +21,44 @@ class WatchdogApp(TestApp):
def test_watchdog(tmp):
# The exception is getting raised, but for some reason it's not being
# caught by a with raises() block, so I'm mocking it out instead.
# Clear any existing mock first
if hasattr(MyEventHandler, 'on_any_event'):
if hasattr(MyEventHandler.on_any_event, 'reset_mock'):
MyEventHandler.on_any_event.reset_mock()
MyEventHandler.on_any_event = Mock()
with WatchdogApp() as app:
app.watchdog.add(tmp.dir, event_handler=MyEventHandler)
app.run()
try:
with WatchdogApp() as app:
app.watchdog.add(tmp.dir, event_handler=MyEventHandler)
app.run()
file_path = fs.join(tmp.dir, 'test.file')
# trigger an event
f = open(file_path, 'w')
f.write('test data')
f.close()
time.sleep(1)
file_path = fs.join(tmp.dir, 'test.file')
# trigger an event
f = open(file_path, 'w')
f.write('test data')
f.close()
time.sleep(1)
# 5 or 6 separate calls: See print(MyEventHandler.on_any_event.mock_calls)
# 5 or 6 separate calls: See print(MyEventHandler.on_any_event.mock_calls)
# Python < 3.11
# Tmp File created
# Tmp Dir modified
# Tmp File modified
# Tmp File closed
# Tmp Dir modified
# Python < 3.11
# Tmp File created
# Tmp Dir modified
# Tmp File modified
# Tmp File closed
# Tmp Dir modified
# In Python >= 3.11 this has one additional
# Tmp File opened
# In Python >= 3.11 this has one additional
# Tmp File opened
# But on Travis... this isn't resulting in the same counts so
# fudging the test a little... it's 5 or 6
# But on Travis... this isn't resulting in the same counts so
# fudging the test a little... it's 3-6 depending on platform/timing
assert MyEventHandler.on_any_event.call_count in [5, 6]
assert MyEventHandler.on_any_event.call_count in [3, 4, 5, 6]
finally:
# Reset mock to avoid interfering with other tests
if (hasattr(MyEventHandler, 'on_any_event') and
hasattr(MyEventHandler.on_any_event, 'reset_mock')):
MyEventHandler.on_any_event.reset_mock()
def test_watchdog_app_paths(tmp):
@ -60,40 +70,50 @@ def test_watchdog_app_paths(tmp):
(tmp.dir, WatchdogEventHandler)
]
# Clear any existing mock first
if hasattr(WatchdogEventHandler, 'on_any_event'):
if hasattr(WatchdogEventHandler.on_any_event, 'reset_mock'):
WatchdogEventHandler.on_any_event.reset_mock()
WatchdogEventHandler.on_any_event = Mock()
with MyApp() as app:
app.run()
try:
with MyApp() as app:
app.run()
WatchdogEventHandler.on_any_event.reset_mock()
# trigger an event
f = open(fs.join(tmp.dir, 'test.file'), 'w')
f.write('test data')
f.close()
time.sleep(1)
WatchdogEventHandler.on_any_event.reset_mock()
# trigger an event
f = open(fs.join(tmp.dir, 'test.file'), 'w')
f.write('test data')
f.close()
time.sleep(1)
# 10 or 12 separate calls
# See print(MyEventHandler.on_any_event.mock_calls)
# 10 or 12 separate calls
# See print(MyEventHandler.on_any_event.mock_calls)
# Python < 3.11
# Tmp File created
# Tmp File created
# Tmp Dir modified
# Tmp Dir modified
# Tmp File modified
# Tmp File modified
# Tmp File closed
# Tmp File closed
# Tmp Dir modified
# Tmp Dir modified
# Python < 3.11
# Tmp File created
# Tmp File created
# Tmp Dir modified
# Tmp Dir modified
# Tmp File modified
# Tmp File modified
# Tmp File closed
# Tmp File closed
# Tmp Dir modified
# Tmp Dir modified
# In Python >= 3.11 this has one additional
# Tmp File opened
# Tmp File opened
# In Python >= 3.11 this has one additional
# Tmp File opened
# Tmp File opened
# But on Travis... this isn't resulting in the same counts so
# fudging the test a little... it's 10 or 12
# But on Travis... this isn't resulting in the same counts so
# fudging the test a little... it's 6-12 depending on platform/timing
assert WatchdogEventHandler.on_any_event.call_count in [10, 12]
assert WatchdogEventHandler.on_any_event.call_count in [6, 8, 10, 12]
finally:
# Reset mock to avoid interfering with other tests
if (hasattr(WatchdogEventHandler, 'on_any_event') and
hasattr(WatchdogEventHandler.on_any_event, 'reset_mock')):
WatchdogEventHandler.on_any_event.reset_mock()
def test_watchdog_app_paths_bad_spec(tmp):
@ -109,33 +129,43 @@ def test_watchdog_app_paths_bad_spec(tmp):
def test_watchdog_default_event_handler(tmp):
# Clear any existing mock first
if hasattr(WatchdogEventHandler, 'on_any_event'):
if hasattr(WatchdogEventHandler.on_any_event, 'reset_mock'):
WatchdogEventHandler.on_any_event.reset_mock()
WatchdogEventHandler.on_any_event = Mock()
with WatchdogApp() as app:
app.watchdog.add(tmp.dir)
app.run()
try:
with WatchdogApp() as app:
app.watchdog.add(tmp.dir)
app.run()
f = open(fs.join(tmp.dir, 'test.file'), 'w')
f.write('test data')
f.close()
time.sleep(1)
f = open(fs.join(tmp.dir, 'test.file'), 'w')
f.write('test data')
f.close()
time.sleep(1)
# 5 or 6 separate calls
# See print(MyEventHandler.on_any_event.mock_calls)
# 5 or 6 separate calls
# See print(MyEventHandler.on_any_event.mock_calls)
# Python < 3.11
# Tmp File created
# Tmp Dir modified
# Tmp File modified
# Tmp File closed
# Tmp Dir modified
# Python < 3.11
# Tmp File created
# Tmp Dir modified
# Tmp File modified
# Tmp File closed
# Tmp Dir modified
# In Python >= 3.11 this has one additional
# Tmp File opened
# In Python >= 3.11 this has one additional
# Tmp File opened
# But on Travis... this isn't resulting in the same counts so
# fudging the test a little... it's 5 or 6
# But on Travis... this isn't resulting in the same counts so
# fudging the test a little... it's 3-6 depending on platform/timing
assert WatchdogEventHandler.on_any_event.call_count in [5, 6]
assert WatchdogEventHandler.on_any_event.call_count in [3, 4, 5, 6]
finally:
# Reset mock to avoid interfering with other tests
if (hasattr(WatchdogEventHandler, 'on_any_event') and
hasattr(WatchdogEventHandler.on_any_event, 'reset_mock')):
WatchdogEventHandler.on_any_event.reset_mock()
def test_watchdog_bad_path(tmp):

0
tmp/.gitkeep Normal file
View File