mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 18:16:53 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3527ade7b5 | ||
|
|
dad85d287a | ||
|
|
7c347abe43 | ||
|
|
bfb3b8c01b | ||
|
|
3ee6b5157b | ||
|
|
cc857e70a7 | ||
|
|
ac410db146 | ||
|
|
8b038170d8 | ||
|
|
23b9b95d93 | ||
|
|
9df6b3a3d3 | ||
|
|
bd0d5eb878 | ||
|
|
80da0029ed | ||
|
|
b46ce15833 | ||
|
|
41f2180976 | ||
|
|
8f5eaa817d | ||
|
|
2bc559a30d | ||
|
|
c314892fb3 | ||
|
|
822c22a1ff | ||
|
|
a7d004b82d | ||
|
|
ae763cf098 | ||
|
|
2fb2940e60 | ||
|
|
4669c7ad2e | ||
|
|
12e4e62fe9 | ||
|
|
9d51ed79b7 | ||
|
|
32fe2685f0 | ||
|
|
ac887016c4 | ||
|
|
a5a6a081f3 | ||
|
|
aeb9715247 | ||
|
|
67a1cd3030 | ||
|
|
5314d21a5f | ||
|
|
91953d07da | ||
|
|
d8bd90b925 | ||
|
|
a4ce9e760f |
14
.envrc
Normal file
14
.envrc
Normal 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
98
.github/workflows/build_and_test.yml
vendored
Normal 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
2
.gitignore
vendored
@ -52,7 +52,7 @@ pip-log.txt
|
||||
# Documentation
|
||||
doc/build
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.coverage*
|
||||
htmlcov
|
||||
coverage-report
|
||||
.tox
|
||||
|
||||
60
CHANGELOG.md
60
CHANGELOG.md
@ -1,5 +1,55 @@
|
||||
# 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:
|
||||
|
||||
- `[ext_jinja2]` Refactor hard-coded reference to `jinja2` template handler.
|
||||
- [Issue #749](https://github.com/datafolklabs/cement/issues/749)
|
||||
- `[ext_smtp]` Misc fixes and updates to better support content types.
|
||||
- [PR #742](https://github.com/datafolklabs/cement/pull/742)
|
||||
|
||||
Features:
|
||||
|
||||
- None
|
||||
|
||||
Refactoring:
|
||||
|
||||
- None
|
||||
|
||||
Misc:
|
||||
|
||||
- None
|
||||
|
||||
Deprecations:
|
||||
|
||||
- None
|
||||
|
||||
|
||||
## 3.0.12 - Nov 10, 2024
|
||||
|
||||
Bugs:
|
||||
@ -23,7 +73,7 @@ Refactoring:
|
||||
- `[dev]` Implement Ruff for Code Compliance (replaces Flake8)
|
||||
- [Issue #671](https://github.com/datafolklabs/cement/issues/671)
|
||||
- [PR #681](https://github.com/datafolklabs/cement/pull/681)
|
||||
- `[dev]` Remove Python 3.5, 3.6, 3.7 Docker Dev Targets
|
||||
- `[dev]` Remove Python 3.5, 3.6, 3.7 Docker Dev Targets
|
||||
- `[dev]` Added Python 3.13 Dev Target
|
||||
- `[dev]` Testing now requires typing compliance (`make test` -> `make comply-mypy`)
|
||||
- `[dev]` Type Annotations (related: [PR #628](https://github.com/datafolklabs/cement/pull/628))
|
||||
@ -71,7 +121,7 @@ Refactoring:
|
||||
|
||||
|
||||
|
||||
Misc:
|
||||
Misc:
|
||||
|
||||
- [cli] Move CLI dependencies to `cement[cli]` extras package, and remove included/nexted `contrib` sources. See note on 'Potential Upgrade Incompatibility'
|
||||
- [Issue #679](https://github.com/datafolklabs/cement/issues/679)
|
||||
@ -104,7 +154,7 @@ pip install cement[cli]
|
||||
|
||||
## 3.0.10 - Feb 28, 2024
|
||||
|
||||
Bugs:
|
||||
Bugs:
|
||||
|
||||
- `[ext.logging]` Support `logging.propagate` to avoid duplicate log entries
|
||||
- [Issue #310](https://github.com/datafolklabs/cement/issues/310)
|
||||
@ -126,7 +176,7 @@ Features:
|
||||
- [PR #669](https://github.com/datafolklabs/cement/pull/669)
|
||||
|
||||
|
||||
Refactoring:
|
||||
Refactoring:
|
||||
|
||||
- `[core.plugin]` Deprecate the use of `imp` in favor of `importlib`
|
||||
- [Issue #386](https://github.com/datafolklabs/cement/issues/386)
|
||||
@ -205,7 +255,7 @@ Bugs:
|
||||
|
||||
- `[ext.argparse]` Parser (`self._parser`) not accessible inside `_pre_argument_parsing` when `stacked_type = 'embedded'`
|
||||
- [Issue #569](https://github.com/datafolklabs/cement/issues/569)
|
||||
- `[ext.configparser]` Overriding config options with environment variables doesn't work correctly with surrounding underscore characters
|
||||
- `[ext.configparser]` Overriding config options with environment variables doesn't work correctly with surrounding underscore characters
|
||||
- [Issue #590](https://github.com/datafolklabs/cement/issues/590)
|
||||
- `[utils.fs]` Fix bug where trailing slash was not removed in `fs.backup()` of a directory.
|
||||
- [Issue #610](https://github.com/datafolklabs/cement/issues/610)
|
||||
|
||||
78
CLAUDE.md
Normal file
78
CLAUDE.md
Normal 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
|
||||
@ -22,3 +22,4 @@ documentation, or testing:
|
||||
- Mudassir Chapra (muddi900)
|
||||
- Christian Hengl (rednar)
|
||||
- sigma67
|
||||
- Blake Jameson (blakejameson)
|
||||
|
||||
@ -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"
|
||||
|
||||
2
Makefile
2
Makefile
@ -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
|
||||
|
||||
10
README.md
10
README.md
@ -5,9 +5,9 @@
|
||||
[](https://app.travis-ci.com/github/datafolklabs/cement/)
|
||||
|
||||
|
||||
Cement is an advanced Application Framework for Python, with a primary focus on Command Line Interfaces (CLI). Its goal is to introduce a standard, and feature-full platform for both simple and complex command line applications as well as support rapid development needs without sacrificing quality. Cement is flexible, and it's use cases span from the simplicity of a micro-framework to the complexity of a mega-framework. Whether it's a single file script, or a multi-tier application, Cement is the foundation you've been looking for.
|
||||
Cement is an advanced Application Framework for Python, with a primary focus on Command Line Interfaces (CLI). Its goal is to introduce a standard and feature-full platform for both simple and complex command line applications as well as support rapid development needs without sacrificing quality. Cement is flexible, and its use cases span from the simplicity of a micro-framework to the complexity of a mega-framework. Whether it's a single file script or a multi-tier application, Cement is the foundation you've been looking for.
|
||||
|
||||
The first commit to Git was on Dec 4, 2009. Since then, the framework has seen several iterations in design, and has continued to grow and improve since it's inception. Cement is the most stable, and complete framework for command line and backend application development.
|
||||
The first commit to Git was on Dec 4, 2009. Since then, the framework has seen several iterations in design and has continued to grow and improve since its inception. Cement is the most stable and complete framework for command line and backend application development.
|
||||
|
||||
## Installation
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
]
|
||||
@ -1,3 +1,3 @@
|
||||
"""Cement core backend module."""
|
||||
|
||||
VERSION = (3, 0, 12, 'final', 0) # pragma: nocover
|
||||
VERSION = (3, 0, 15, 'final', 0) # pragma: nocover
|
||||
|
||||
@ -116,7 +116,7 @@ class ExtensionHandler(ExtensionInterface, Handler):
|
||||
loaded.
|
||||
|
||||
"""
|
||||
# If its not a full module path then preppend our default path
|
||||
# If it's not a full module path then preppend our default path
|
||||
if ext_module.find('.') == -1:
|
||||
ext_module = f'cement.ext.ext_{ext_module}'
|
||||
|
||||
|
||||
@ -651,10 +651,10 @@ class App(meta.MetaMixin):
|
||||
extensions. Developers can optionally use the
|
||||
``App.__import__()`` method to import simple modules, and if
|
||||
that module exists in this mapping it will import the alternative
|
||||
library in it's place.
|
||||
library in its place.
|
||||
|
||||
This is a low-level feature, and may not produce the results you are
|
||||
expecting. It's purpose is to allow the developer to replace specific
|
||||
This is a low-level feature and may not produce the results you are
|
||||
expecting. Its purpose is to allow the developer to replace specific
|
||||
modules at a high level. Example: For an application wanting to use
|
||||
``ujson`` in place of ``json``, the developer could set the following:
|
||||
|
||||
|
||||
@ -599,7 +599,7 @@ class ArgparseController(ControllerHandler):
|
||||
|
||||
elif stacked_type == 'embedded':
|
||||
# if it's embedded, then just set it to use the same as the
|
||||
# controller its stacked on
|
||||
# controller it's stacked on
|
||||
parents[label] = parents[stacked_on]
|
||||
parsers[label] = parsers[stacked_on]
|
||||
contr._parser = parsers[stacked_on]
|
||||
@ -812,7 +812,7 @@ class ArgparseController(ControllerHandler):
|
||||
# if no __dispatch__ is set then that means we have hit a
|
||||
# controller with not sub-command (argparse doesn't support
|
||||
# default sub-command yet... so we rely on
|
||||
# __controller_namespace__ and it's default func
|
||||
# __controller_namespace__ and its default func
|
||||
|
||||
# We never get here on Python < 3 as Argparse would have already
|
||||
# complained about too few arguments
|
||||
|
||||
@ -126,7 +126,7 @@ class Environment(object):
|
||||
os._exit(os.EX_OK)
|
||||
except OSError as e:
|
||||
sys.stderr.write("Fork #1 failed: (%d) %s\n" %
|
||||
(e.errno, e.strerror))
|
||||
(e.errno, e.strerror)) # type: ignore
|
||||
sys.exit(1)
|
||||
|
||||
# Decouple from parent environment.
|
||||
@ -142,7 +142,7 @@ class Environment(object):
|
||||
os._exit(os.EX_OK)
|
||||
except OSError as e:
|
||||
sys.stderr.write("Fork #2 failed: (%d) %s\n" %
|
||||
(e.errno, e.strerror))
|
||||
(e.errno, e.strerror)) # type: ignore
|
||||
sys.exit(1)
|
||||
|
||||
# Redirect standard file descriptors.
|
||||
|
||||
@ -143,7 +143,7 @@ def setup_template_items(app: App) -> None:
|
||||
if os.path.exists(subpath) and subpath not in template_dirs:
|
||||
template_dirs.append(subpath)
|
||||
|
||||
# use app template module, find it's path on filesystem
|
||||
# use app template module, find its path on filesystem
|
||||
if app._meta.template_module is not None:
|
||||
mod_parts = app._meta.template_module.split('.')
|
||||
mod_name = mod_parts.pop()
|
||||
|
||||
@ -34,6 +34,9 @@ class Jinja2OutputHandler(OutputHandler):
|
||||
Please see the developer documentation on
|
||||
:cement:`Output Handling <dev/output>`.
|
||||
|
||||
This class has an assumed depency on it's associated Jinja2TemplateHandler.
|
||||
If sub-classing, you must also sub-class/implement the Jinja2TemplateHandler
|
||||
and give it the same label.
|
||||
"""
|
||||
|
||||
class Meta(OutputHandler.Meta):
|
||||
@ -48,7 +51,7 @@ class Jinja2OutputHandler(OutputHandler):
|
||||
|
||||
def _setup(self, app: App) -> None:
|
||||
super(Jinja2OutputHandler, self)._setup(app)
|
||||
self.templater = self.app.handler.resolve('template', 'jinja2', setup=True) # type: ignore
|
||||
self.templater = self.app.handler.resolve('template', self._meta.label, setup=True) # type: ignore
|
||||
|
||||
def render(self, data: Dict[str, Any], template: str = None, **kw: Any) -> str: # type: ignore
|
||||
"""
|
||||
|
||||
@ -218,7 +218,7 @@ class LoggingLogHandler(log.LogHandler):
|
||||
else:
|
||||
console_handler = NullHandler()
|
||||
|
||||
# FIXME: self._clear_loggers() should be preventing this but its not!
|
||||
# FIXME: self._clear_loggers() should be preventing this but it's not!
|
||||
for i in logging.getLogger(f"cement:app:{namespace}").handlers:
|
||||
if isinstance(i, logging.StreamHandler):
|
||||
self.backend.removeHandler(i)
|
||||
@ -259,7 +259,7 @@ class LoggingLogHandler(log.LogHandler):
|
||||
else:
|
||||
file_handler = NullHandler()
|
||||
|
||||
# FIXME: self._clear_loggers() should be preventing this but its not!
|
||||
# FIXME: self._clear_loggers() should be preventing this but it's not!
|
||||
for i in logging.getLogger(f"cement:app:{namespace}").handlers:
|
||||
if isinstance(i, file_handler.__class__): # pragma: nocover
|
||||
self.backend.removeHandler(i) # pragma: nocover
|
||||
|
||||
@ -58,7 +58,7 @@ class MemcachedCacheHandler(cache.CacheHandler):
|
||||
comma-separated string into a list). This function does not return
|
||||
anything, however it is expected to set the `hosts` value of the
|
||||
``[cache.memcached]`` section (which is what this extension reads for
|
||||
it's host configution).
|
||||
its host configution).
|
||||
|
||||
:returns: ``None``
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -92,7 +92,7 @@ class RedisCacheHandler(cache.CacheHandler):
|
||||
if res is None:
|
||||
return fallback
|
||||
else:
|
||||
return res.decode('utf-8') # type: ignore
|
||||
return res.decode('utf-8')
|
||||
|
||||
def set(self, key: str, value: Any, time: Optional[int] = None, **kw: Any) -> None:
|
||||
"""
|
||||
@ -128,7 +128,7 @@ class RedisCacheHandler(cache.CacheHandler):
|
||||
otherwise
|
||||
"""
|
||||
res = self.r.delete(key)
|
||||
return int(res) > 0 # type: ignore
|
||||
return int(res) > 0
|
||||
|
||||
def purge(self, **kw: Any) -> None:
|
||||
"""
|
||||
@ -139,7 +139,7 @@ class RedisCacheHandler(cache.CacheHandler):
|
||||
"""
|
||||
keys = self.r.keys('*')
|
||||
if keys:
|
||||
self.r.delete(*keys) # type: ignore
|
||||
self.r.delete(*keys)
|
||||
|
||||
|
||||
def load(app: App) -> None:
|
||||
|
||||
@ -4,13 +4,17 @@ Cement smtp extension module.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
import smtplib
|
||||
from email.header import Header
|
||||
from email.charset import Charset, BASE64, QP
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.image import MIMEImage
|
||||
from email import encoders
|
||||
from typing import Any, Dict, Union, Tuple, TYPE_CHECKING
|
||||
from email.utils import format_datetime, make_msgid
|
||||
from typing import Any, Optional, Dict, Union, Tuple, TYPE_CHECKING
|
||||
from ..core import mail
|
||||
from ..utils import fs
|
||||
from ..utils.misc import minimal_logger, is_true
|
||||
@ -54,6 +58,14 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
'username': None,
|
||||
'password': None,
|
||||
'files': None,
|
||||
# define controlling of mail encoding
|
||||
'charset': 'utf-8',
|
||||
'header_encoding': None,
|
||||
'body_encoding': None,
|
||||
'date_enforce': True,
|
||||
'msgid_enforce': True,
|
||||
'msgid_str': None,
|
||||
'msgid_domain': None,
|
||||
}
|
||||
|
||||
_meta: Meta # type: ignore
|
||||
@ -62,17 +74,33 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
params = dict()
|
||||
|
||||
# some keyword args override configuration defaults
|
||||
for item in ['to', 'from_addr', 'cc', 'bcc', 'subject',
|
||||
'subject_prefix', 'files']:
|
||||
for item in [
|
||||
'to', 'from_addr', 'cc', 'bcc', 'subject', 'subject_prefix', 'files',
|
||||
'charset', 'header_encoding', 'body_encoding',
|
||||
'date_enforce', 'msgid_enforce', 'msgid_str',
|
||||
]:
|
||||
config_item = self.app.config.get(self._meta.config_section, item)
|
||||
params[item] = kw.get(item, config_item)
|
||||
|
||||
# others don't
|
||||
other_params = ['ssl', 'tls', 'host', 'port', 'auth', 'username',
|
||||
'password', 'timeout']
|
||||
for item in other_params:
|
||||
params[item] = self.app.config.get(self._meta.config_section,
|
||||
item)
|
||||
for item in [
|
||||
'ssl', 'tls', 'host', 'port', 'auth', 'username', 'password',
|
||||
'timeout', 'msgid_domain'
|
||||
]:
|
||||
params[item] = self.app.config.get(self._meta.config_section, item)
|
||||
|
||||
# some are only set by message
|
||||
for item in ['date', 'message_id', 'return_path', 'reply_to']:
|
||||
value = kw.get(item, None)
|
||||
if value is not None and str.strip(f'{value}') != '':
|
||||
params[item] = kw.get(item, config_item)
|
||||
|
||||
# take all X-headers as is
|
||||
for item in kw.keys():
|
||||
if len(item) > 2 and item.startswith(('x-', 'X-', 'x_', 'X_')):
|
||||
value = kw.get(item, None)
|
||||
if value is not None:
|
||||
params[f'X-{item[2:]}'] = value
|
||||
|
||||
return params
|
||||
|
||||
@ -143,91 +171,231 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
if is_true(params['auth']):
|
||||
server.login(params['username'], params['password'])
|
||||
|
||||
res = self._send_message(server, body, **params)
|
||||
msg = self._make_message(body, **params)
|
||||
res = server.send_message(msg)
|
||||
|
||||
server.quit()
|
||||
return res
|
||||
|
||||
def _send_message(self,
|
||||
server: Union[smtplib.SMTP, smtplib.SMTP_SSL],
|
||||
body: Union[str, Tuple[str, str]],
|
||||
**params: Any) -> bool:
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg.set_charset('utf-8')
|
||||
# FIXME: should deprecate for 3.0 and change in 3.2
|
||||
# For smtplib this would be "senderrs" (dict), but for backward compat
|
||||
# we need to return bool
|
||||
# https://github.com/python/cpython/blob/3.13/Lib/smtplib.py#L899
|
||||
self.app.log.error(f"SMTPHandler Errors: {res}")
|
||||
if len(res) > 0:
|
||||
# this will be difficult to test with Mailpit as it accepts everything... no cover
|
||||
return False # pragma: nocover
|
||||
else:
|
||||
return True
|
||||
|
||||
msg['From'] = params['from_addr']
|
||||
def _header(self, value: Optional[str] = None, _charset: Optional[Charset] = None,
|
||||
**params: Dict[str, Any]) -> Header:
|
||||
header = Header(value, charset=_charset) if params['header_encoding'] else value
|
||||
return header # type: ignore
|
||||
|
||||
def _make_message(self, body: Union[str, Tuple[str, str]], **params: Dict[str, Any]) \
|
||||
-> MIMEMultipart:
|
||||
# use encoding for header parts
|
||||
cs_header = Charset(params['charset']) # type: ignore
|
||||
if params['header_encoding'] == 'base64':
|
||||
cs_header.header_encoding = BASE64
|
||||
elif params['header_encoding'] == 'qp' or params['body_encoding'] == 'quoted-printable':
|
||||
cs_header.header_encoding = QP
|
||||
|
||||
# use encoding for body parts
|
||||
cs_body = Charset(params['charset']) # type: ignore
|
||||
if params['body_encoding'] == 'base64':
|
||||
cs_body.body_encoding = BASE64
|
||||
elif params['body_encoding'] == 'qp' or params['body_encoding'] == 'quoted-printable':
|
||||
cs_body.body_encoding = QP
|
||||
|
||||
# setup body parts
|
||||
partText = None
|
||||
partHtml = None
|
||||
|
||||
if type(body) not in [str, tuple, dict]:
|
||||
error_msg = "Message body must be string, " \
|
||||
"tuple ('<text>', '<html>') or " \
|
||||
"dict {'text': '<text>', 'html': '<html>'}"
|
||||
raise TypeError(error_msg)
|
||||
|
||||
if isinstance(body, str):
|
||||
partText = MIMEText(body, 'plain', _charset=cs_body) # type: ignore
|
||||
elif isinstance(body, tuple):
|
||||
# handle plain text
|
||||
if len(body) >= 1 and body[0] and str.strip(body[0]) != '':
|
||||
partText = MIMEText(str.strip(body[0]),
|
||||
'plain',
|
||||
_charset=cs_body) # type: ignore
|
||||
# handle html
|
||||
if len(body) >= 2 and body[1] and str.strip(body[1]) != '':
|
||||
partHtml = MIMEText(str.strip(body[1]),
|
||||
'html',
|
||||
_charset=cs_body) # type: ignore
|
||||
elif isinstance(body, dict):
|
||||
# handle plain text
|
||||
if 'text' in body and str.strip(body['text']) != '':
|
||||
partText = MIMEText(str.strip(body['text']),
|
||||
'plain',
|
||||
_charset=cs_body)
|
||||
# handle html
|
||||
if 'html' in body and str.strip(body['html']) != '':
|
||||
partHtml = MIMEText(str.strip(body['html']), 'html', _charset=cs_body)
|
||||
|
||||
# To define the correct message content-type
|
||||
# we need to indentify the content of this mail.
|
||||
# If only "text" exists => text/plain, if only
|
||||
# "html" exists => text/html, if "text" and
|
||||
# "html" exists => multipart/alternative. In
|
||||
# any case that files exists => multipart/mixed.
|
||||
# Set message charset and encoding based on parts
|
||||
if params['files']:
|
||||
msg = MIMEMultipart('mixed')
|
||||
msg.set_charset(params['charset']) # type: ignore
|
||||
elif partText and partHtml:
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg.set_charset(params['charset']) # type: ignore
|
||||
elif partHtml:
|
||||
msg = MIMEBase('text', 'html') # type: ignore
|
||||
msg.set_charset(cs_body)
|
||||
else:
|
||||
msg = MIMEBase('text', 'plain') # type: ignore
|
||||
msg.set_charset(cs_body)
|
||||
|
||||
# create message
|
||||
msg['From'] = params['from_addr'] # type: ignore
|
||||
msg['To'] = ', '.join(params['to'])
|
||||
if params['cc']:
|
||||
msg['Cc'] = ', '.join(params['cc'])
|
||||
if params['bcc']:
|
||||
msg['Bcc'] = ', '.join(params['bcc'])
|
||||
if params['subject_prefix'] not in [None, '']:
|
||||
subject = f"{params['subject_prefix']} {params['subject']}"
|
||||
msg['Subject'] = self._header(f"{params['subject_prefix']} {params['subject']}",
|
||||
_charset=cs_header, **params) # type: ignore
|
||||
else:
|
||||
subject = params['subject']
|
||||
msg['Subject'] = Header(subject) # type: ignore
|
||||
msg['Subject'] = self._header(params['subject'], # type: ignore
|
||||
_charset=cs_header,
|
||||
**params)
|
||||
|
||||
# add body as text and/or as html
|
||||
partText = None
|
||||
partHtml = None
|
||||
# check for date
|
||||
if is_true(params['date_enforce']) and not params.get('date', None):
|
||||
params['date'] = format_datetime(datetime.now(timezone.utc)) # type: ignore
|
||||
# check for message-id
|
||||
if is_true(params['msgid_enforce']) and not params.get('message_id', None):
|
||||
params['message_id'] = make_msgid(params['msgid_str'], # type: ignore
|
||||
params['msgid_domain']) # type: ignore
|
||||
|
||||
if type(body) not in [str, tuple]:
|
||||
error_msg = "Message body must be string or tuple " \
|
||||
"('<text>', '<html>')"
|
||||
raise TypeError(error_msg)
|
||||
# check for message headers
|
||||
if params.get('date', None):
|
||||
msg['Date'] = params['date'] # type: ignore
|
||||
if params.get('message_id', None):
|
||||
msg['Message-Id'] = params['message_id'] # type: ignore
|
||||
if params.get('return_path', None):
|
||||
msg['Return-Path'] = params['return_path'] # type: ignore
|
||||
if params.get('reply_to', None):
|
||||
msg['Reply-To'] = params['reply_to'] # type: ignore
|
||||
|
||||
if isinstance(body, str):
|
||||
partText = MIMEText(body)
|
||||
elif isinstance(body, tuple):
|
||||
# handle plain text
|
||||
if len(body) >= 1:
|
||||
partText = MIMEText(body[0], 'plain')
|
||||
# check for X-headers
|
||||
for item in params.keys():
|
||||
if item.startswith('X-'):
|
||||
msg.add_header(item.title(),
|
||||
self._header(f'{params[item]}', # type: ignore
|
||||
_charset=cs_header, **params))
|
||||
|
||||
# handle html
|
||||
if len(body) >= 2:
|
||||
partHtml = MIMEText(body[1], 'html')
|
||||
|
||||
if partText:
|
||||
msg.attach(partText)
|
||||
if partHtml:
|
||||
msg.attach(partHtml)
|
||||
# append the body parts
|
||||
if params['files']:
|
||||
# multipart/mixed
|
||||
if partHtml:
|
||||
# when html exists, create always a related part to include
|
||||
# the body alternatives and eventually files as related
|
||||
# attachments (e.g. images).
|
||||
rel = MIMEMultipart('related')
|
||||
# create an alternative part to include bodies for text and html
|
||||
alt = MIMEMultipart('alternative')
|
||||
# body text and body html
|
||||
if partText:
|
||||
alt.attach(partText)
|
||||
alt.attach(partHtml)
|
||||
rel.attach(alt)
|
||||
msg.attach(rel)
|
||||
else:
|
||||
# only body text or no body
|
||||
if partText:
|
||||
msg.attach(partText)
|
||||
else:
|
||||
# no body no files = empty message = just headers
|
||||
pass # pragma: no cover
|
||||
else:
|
||||
# multipart/alternative
|
||||
if partText and partHtml:
|
||||
# plain/text and plain/html
|
||||
msg.attach(partText)
|
||||
msg.attach(partHtml)
|
||||
else:
|
||||
# plain/text or plain/html only so just append payload
|
||||
if partText:
|
||||
msg.set_payload(partText.get_payload(), charset=cs_body)
|
||||
elif partHtml:
|
||||
msg.set_payload(partHtml.get_payload(), charset=cs_body)
|
||||
else:
|
||||
# no body no files = empty message = just headers
|
||||
pass # pragma: no cover
|
||||
|
||||
# attach files
|
||||
if params['files']:
|
||||
for in_path in params['files']:
|
||||
part = MIMEBase('application', 'octet-stream')
|
||||
|
||||
# support for alternative file name if its tuple
|
||||
# like ('alt-name.ext', '/path/to/file.ext')
|
||||
# support for alternative file name if its tuple or dict
|
||||
# like [
|
||||
# 'path/simple.ext',
|
||||
# ('altname.ext', 'path/filename.ext'),
|
||||
# ('altname.ext', 'path/filename.ext', 'content_id'),
|
||||
# {'name': 'altname', 'path': 'path/filename.ext', cid: 'cidname'},
|
||||
# ]
|
||||
if isinstance(in_path, tuple):
|
||||
if in_path[0] == in_path[1]:
|
||||
# protect against the full path being passed in
|
||||
alt_name = os.path.basename(in_path[0])
|
||||
else:
|
||||
alt_name = in_path[0]
|
||||
altname = os.path.basename(in_path[0])
|
||||
path = in_path[1]
|
||||
cid = in_path[2] if len(in_path) >= 3 else None
|
||||
elif isinstance(in_path, dict):
|
||||
altname = os.path.basename(in_path.get('name', None))
|
||||
path = in_path.get('path')
|
||||
cid = in_path.get('cid', None)
|
||||
else:
|
||||
alt_name = os.path.basename(in_path)
|
||||
altname = None
|
||||
path = in_path
|
||||
cid = None
|
||||
|
||||
path = fs.abspath(path)
|
||||
if not altname:
|
||||
altname = os.path.basename(path)
|
||||
|
||||
# add attachment
|
||||
# add attachment payload from file
|
||||
with open(path, 'rb') as file:
|
||||
part.set_payload(file.read())
|
||||
# check for embedded image or regular attachments
|
||||
if cid:
|
||||
part = MIMEImage(file.read())
|
||||
else:
|
||||
part = MIMEBase('application', 'octet-stream')
|
||||
part.set_payload(file.read())
|
||||
|
||||
# encode and name
|
||||
# encoder
|
||||
encoders.encode_base64(part)
|
||||
part.add_header(
|
||||
'Content-Disposition',
|
||||
f'attachment; filename={alt_name}',
|
||||
)
|
||||
msg.attach(part)
|
||||
|
||||
server.send_message(msg)
|
||||
# embedded inline or attachment
|
||||
if cid:
|
||||
part.add_header(
|
||||
'Content-Disposition',
|
||||
f'inline; filename={altname}',
|
||||
)
|
||||
part.add_header('Content-ID', f'<{cid}>')
|
||||
msg.attach(part)
|
||||
else:
|
||||
# altname header
|
||||
part.add_header(
|
||||
'Content-Disposition',
|
||||
f'attachment; filename={altname}',
|
||||
)
|
||||
msg.attach(part)
|
||||
|
||||
# FIXME: how to check success? docs don't say return type
|
||||
# - `[ext.scrub]` [Issue #724](https://github.com/datafolklabs/cement/issues/724)
|
||||
return True
|
||||
return msg
|
||||
|
||||
|
||||
def load(app: App) -> None:
|
||||
|
||||
0
cement/py.typed
Normal file
0
cement/py.typed
Normal file
@ -34,7 +34,7 @@
|
||||
# in this file.
|
||||
|
||||
# Note: Nothing is covered here because this file is imported before nose and
|
||||
# coverage take over.. and so its a false positive that nothing is covered.
|
||||
# coverage take over.. and so it's a false positive that nothing is covered.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
1316
devbox.d/redis/redis.conf
Normal file
1316
devbox.d/redis/redis.conf
Normal file
File diff suppressed because it is too large
Load Diff
24
devbox.json
Normal file
24
devbox.json
Normal 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
402
devbox.lock
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM python:3.12.3-alpine
|
||||
FROM python:3.12-alpine
|
||||
LABEL MAINTAINER="BJ Dierkes <derks@datafolklabs.com>"
|
||||
ENV PS1="\[\e[0;33m\]|> cement-py312 <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
|
||||
ENV PATH="${PATH}:/root/.local/bin"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"]
|
||||
33
docker/compose-services-only.yml
Normal file
33
docker/compose-services-only.yml
Normal 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
|
||||
325
pdm.lock
325
pdm.lock
@ -3,9 +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"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:93bbcbfd6b1a08c9a37b42a42d1300b6f9ede3eb6e643a085a8152fb81a60d88"
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:88e91afb3db1d8d71ebff4abd3d4d3e29b3415592657bd9fe104948d542d1ee5"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = ">=3.9"
|
||||
|
||||
[[package]]
|
||||
name = "alabaster"
|
||||
@ -25,6 +28,9 @@ requires_python = ">=3.7"
|
||||
summary = "Timeout context manager for asyncio programs"
|
||||
groups = ["redis"]
|
||||
marker = "python_full_version < \"3.11.3\""
|
||||
dependencies = [
|
||||
"typing-extensions>=3.6.5; python_version < \"3.8\"",
|
||||
]
|
||||
files = [
|
||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||
@ -49,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"},
|
||||
@ -60,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"},
|
||||
@ -155,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"]
|
||||
@ -163,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]]
|
||||
@ -374,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"},
|
||||
@ -399,6 +405,7 @@ summary = "Read metadata from Python packages"
|
||||
groups = ["docs", "mustache"]
|
||||
marker = "python_version < \"3.10\""
|
||||
dependencies = [
|
||||
"typing-extensions>=3.6.4; python_version < \"3.8\"",
|
||||
"zipp>=0.5",
|
||||
]
|
||||
files = [
|
||||
@ -419,7 +426,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
version = "3.1.6"
|
||||
requires_python = ">=3.7"
|
||||
summary = "A very fast and expressive template engine."
|
||||
groups = ["cli", "docs", "jinja2"]
|
||||
@ -427,8 +434,8 @@ dependencies = [
|
||||
"MarkupSafe>=2.0",
|
||||
]
|
||||
files = [
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -493,18 +500,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mock"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Rolling backport of unittest.mock for all Pythons"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"},
|
||||
{file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"},
|
||||
{file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"},
|
||||
{file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.13.0"
|
||||
version = "1.14.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Optional static typing for Python"
|
||||
groups = ["dev"]
|
||||
@ -514,38 +521,44 @@ dependencies = [
|
||||
"typing-extensions>=4.6.0",
|
||||
]
|
||||
files = [
|
||||
{file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
|
||||
{file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
|
||||
{file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
|
||||
{file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
|
||||
{file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -631,9 +644,19 @@ files = [
|
||||
{file = "pylibmc-1.6.3.tar.gz", hash = "sha256:eefa46115537abad65fbe2e032acd1b3463d9bf9e335af4b0916df4e4d3206e0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pypng"
|
||||
version = "0.20220715.0"
|
||||
summary = "Pure Python library for saving and loading PNG images"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"},
|
||||
{file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pystache"
|
||||
version = "0.6.5"
|
||||
version = "0.6.8"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Mustache for Python"
|
||||
groups = ["mustache"]
|
||||
@ -641,13 +664,13 @@ dependencies = [
|
||||
"importlib-metadata>=4.6; python_version < \"3.10\"",
|
||||
]
|
||||
files = [
|
||||
{file = "pystache-0.6.5-py3-none-any.whl", hash = "sha256:84546278219431f1a2ecc86a627cc1b0fe3c83b5612f8a7d9c81ea0119ac8f49"},
|
||||
{file = "pystache-0.6.5.tar.gz", hash = "sha256:9f238d5a06f18843e0d491d8e4e292dc03fed6a54cb0a5c34be37a3faa973174"},
|
||||
{file = "pystache-0.6.8-py3-none-any.whl", hash = "sha256:7211e000974a6e06bce2d4d5cad8df03bcfffefd367209117376e4527a1c3cb8"},
|
||||
{file = "pystache-0.6.8.tar.gz", hash = "sha256:3707518e6a4d26dd189b07c10c669b1fc17df72684617c327bd3550e7075c72c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.3"
|
||||
version = "8.3.5"
|
||||
requires_python = ">=3.8"
|
||||
summary = "pytest: simple powerful testing with Python"
|
||||
groups = ["dev"]
|
||||
@ -660,8 +683,8 @@ dependencies = [
|
||||
"tomli>=1; python_version < \"3.11\"",
|
||||
]
|
||||
files = [
|
||||
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
||||
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
||||
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
|
||||
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -679,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 = ["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 = "5.2.0"
|
||||
version = "6.1.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Python client for Redis database and key-value store"
|
||||
groups = ["redis"]
|
||||
@ -762,8 +794,8 @@ dependencies = [
|
||||
"async-timeout>=4.0.3; python_full_version < \"3.11.3\"",
|
||||
]
|
||||
files = [
|
||||
{file = "redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897"},
|
||||
{file = "redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0"},
|
||||
{file = "redis-6.1.1-py3-none-any.whl", hash = "sha256:ed44d53d065bbe04ac6d76864e331cfe5c5353f86f6deccc095f8794fd15bb2e"},
|
||||
{file = "redis-6.1.1.tar.gz", hash = "sha256:88c689325b5b41cedcbdbdfd4d937ea86cf6dab2222a83e86d8a466e4b3d2600"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -771,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",
|
||||
@ -785,29 +817,30 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.7.3"
|
||||
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.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"},
|
||||
{file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"},
|
||||
{file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"},
|
||||
{file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"},
|
||||
{file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"},
|
||||
{file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"},
|
||||
{file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"},
|
||||
{file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"},
|
||||
{file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"},
|
||||
{file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"},
|
||||
{file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"},
|
||||
{file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"},
|
||||
{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]]
|
||||
@ -863,7 +896,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-rtd-theme"
|
||||
version = "3.0.1"
|
||||
version = "3.0.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Read the Docs theme for Sphinx"
|
||||
groups = ["docs"]
|
||||
@ -873,8 +906,8 @@ dependencies = [
|
||||
"sphinxcontrib-jquery<5,>=4",
|
||||
]
|
||||
files = [
|
||||
{file = "sphinx_rtd_theme-3.0.1-py2.py3-none-any.whl", hash = "sha256:921c0ece75e90633ee876bd7b148cfaad136b481907ad154ac3669b6fc957916"},
|
||||
{file = "sphinx_rtd_theme-3.0.1.tar.gz", hash = "sha256:a4c5745d1b06dfcb80b7704fe532eb765b44065a8fad9851e4258c8804140703"},
|
||||
{file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"},
|
||||
{file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1010,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
19
process-compose.yml
Normal 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"
|
||||
@ -16,7 +16,8 @@ classifiers = [
|
||||
|
||||
dynamic = ["version", "README"]
|
||||
|
||||
requires-python = ">=3.8"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
alarm = []
|
||||
@ -42,9 +43,6 @@ watchdog = ["watchdog"]
|
||||
yaml = ["pyYaml"]
|
||||
cli = ["cement[yaml,jinja2]"]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
cement = {call = "cement.cli.main:main"}
|
||||
|
||||
[project.scripts]
|
||||
cement = "cement.cli.main:main"
|
||||
|
||||
@ -70,32 +68,8 @@ python_files= "test_*.py"
|
||||
precision = 2
|
||||
|
||||
|
||||
[tool.pdm.build]
|
||||
package-dir = "."
|
||||
includes = [
|
||||
"cement/",
|
||||
"cement/cli/templates/generate/",
|
||||
"CONTRIBUTORS.md",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
excludes = ["tests/"]
|
||||
|
||||
[tool.pdm.version]
|
||||
source = "call"
|
||||
getter = "cement.utils.version:get_version"
|
||||
|
||||
[tool.pdm.dev-dependencies]
|
||||
dev = [
|
||||
"pytest>=4.3.1",
|
||||
"pytest-cov>=2.6.1",
|
||||
"coverage>=4.5.3",
|
||||
"mypy>=1.9.0",
|
||||
"ruff>=0.3.2",
|
||||
"mock>=5.1.0",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py38"
|
||||
target-version = "py39"
|
||||
line-length = 100
|
||||
indent-width = 4
|
||||
exclude = [
|
||||
@ -133,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
|
||||
@ -160,3 +134,33 @@ exclude = """(?x)(
|
||||
^.git/ |
|
||||
^tests
|
||||
)"""
|
||||
|
||||
[tool.pdm.scripts]
|
||||
cement = {call = "cement.cli.main:main"}
|
||||
|
||||
[tool.pdm.build]
|
||||
package-dir = "."
|
||||
includes = [
|
||||
"cement/",
|
||||
"cement/cli/templates/generate/",
|
||||
"CONTRIBUTORS.md",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
excludes = ["tests/"]
|
||||
|
||||
[tool.pdm.version]
|
||||
source = "call"
|
||||
getter = "cement.utils.version:get_version"
|
||||
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=4.3.1",
|
||||
"pytest-cov>=2.6.1",
|
||||
"coverage>=4.5.3",
|
||||
"mypy>=1.9.0",
|
||||
"ruff>=0.3.2",
|
||||
"mock>=5.1.0",
|
||||
"pypng>=0.20220715.0",
|
||||
"requests>=2.31.0",
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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] == 12
|
||||
assert backend.VERSION[2] == 15
|
||||
assert backend.VERSION[3] == 'final'
|
||||
assert backend.VERSION[4] == 0
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import os
|
||||
import mock
|
||||
import requests
|
||||
import json
|
||||
import png
|
||||
from time import sleep
|
||||
from pytest import raises
|
||||
from cement.utils.test import TestApp
|
||||
@ -63,8 +64,139 @@ 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'])
|
||||
|
||||
|
||||
def test_smtp_send_with_message_id(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost',
|
||||
message_id=f'message_id_{rando}')
|
||||
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
# FIXME: Mailpit doesn't support this??? See: PR #742
|
||||
# assert msg['Message-Id'] == f'message_id_{rando}'
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_send_with_return_path(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost',
|
||||
return_path=f'return_path_{rando}')
|
||||
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
# FIXME: Mailpit doesn't support this??? See: PR #742
|
||||
# assert msg['Return-Path'] == f'return_path_{rando}'
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_send_with_reply_to(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost',
|
||||
reply_to=f'reply_to_{rando}@localhost')
|
||||
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
assert msg['ReplyTo'][0]['Address'] == f'reply_to_{rando}@localhost'
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_send_with_x_headers(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost',
|
||||
X_Test_Header=f'x_header_{rando}')
|
||||
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
# FIXME: Mailpit doesn't support this??? See: PR #742
|
||||
# assert msg['X_Test_Header'] == f'x_header_{rando}'
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_send_with_base64_encoding(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost',
|
||||
header_encoding='base64',
|
||||
body_encoding='base64')
|
||||
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
# FIXME: Not sure how to test this? See: PR #742
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_send_with_qp_encoding(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost',
|
||||
header_encoding='qp',
|
||||
body_encoding='qp')
|
||||
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
# FIXME: Not sure how to test this? See: PR #742
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
@ -93,13 +225,65 @@ def test_smtp_html(rando):
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_dict_text_and_html(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
body = dict(
|
||||
text=rando,
|
||||
html=f"<body>{rando}</body>"
|
||||
)
|
||||
app.mail.send(body)
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
text_url = f"http://{smtp_host}:8025/view/{msg['ID']}.txt"
|
||||
res_text = requests.get(text_url)
|
||||
assert res_text.content.decode('utf-8') == rando
|
||||
|
||||
html_url = f"http://{smtp_host}:8025/view/{msg['ID']}.html"
|
||||
res_html = requests.get(html_url)
|
||||
assert res_html.content.decode('utf-8') == f"<body>{rando}</body>"
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_dict_html_only(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
body = dict(
|
||||
html=f"<body>{rando}</body>"
|
||||
)
|
||||
app.mail.send(body)
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
html_url = f"http://{smtp_host}:8025/view/{msg['ID']}.html"
|
||||
res_html = requests.get(html_url)
|
||||
assert res_html.content.decode('utf-8') == f"<body>{rando}</body>"
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_html_bad_body_type(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
error_msg = '(.*)Message body must be string or tuple(.*)'
|
||||
# ruff: noqa: E501
|
||||
error_msg = "(.*)Message body must be string, tuple(.*)"
|
||||
with raises(TypeError, match=error_msg):
|
||||
app.mail.send(['text', '<body>html</body>'])
|
||||
|
||||
@ -155,6 +339,35 @@ def test_smtp_files(rando, tmp):
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_dict_and_files(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
files = []
|
||||
for iter in [1, 2, 3]:
|
||||
_file = f"{tmp.file}-{iter}"
|
||||
with open(_file, 'w') as _open_file:
|
||||
_open_file.write(f"{rando}-{iter}")
|
||||
files.append(_file)
|
||||
|
||||
body = dict(
|
||||
text=rando,
|
||||
html=f"<body>{rando}</body>"
|
||||
)
|
||||
app.mail.send(body, files=files)
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
assert msg['Attachments'] == 3
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_files_alt_name(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
@ -177,6 +390,73 @@ def test_smtp_files_alt_name(rando, tmp):
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_image_files_as_dict(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
image_file = os.path.join(tmp.dir, 'gradient.png')
|
||||
# create a png (coverage)
|
||||
width = 255
|
||||
height = 255
|
||||
img = []
|
||||
for y in range(height):
|
||||
row = ()
|
||||
for x in range(width):
|
||||
row = row + (x, max(0, 255 - x - y), y)
|
||||
img.append(row)
|
||||
with open(image_file, 'wb') as f:
|
||||
w = png.Writer(width, height, greyscale=False)
|
||||
w.write(f, img)
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
files=[dict(name=f'alt-filename-{rando}', path=image_file)])
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
assert msg['Attachments'] == 1
|
||||
|
||||
res_full = requests.get(f"{mailpit_api}/message/{msg['ID']}")
|
||||
data = res_full.json()
|
||||
assert data['Attachments'][0]['FileName'] == f"alt-filename-{rando}"
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_image_files_as_dict_inline(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
image_file = os.path.join(tmp.dir, 'gradient.png')
|
||||
# create a png (coverage)
|
||||
width = 255
|
||||
height = 255
|
||||
img = []
|
||||
for y in range(height):
|
||||
row = ()
|
||||
for x in range(width):
|
||||
row = row + (x, max(0, 255 - x - y), y)
|
||||
img.append(row)
|
||||
with open(image_file, 'wb') as f:
|
||||
w = png.Writer(width, height, greyscale=False)
|
||||
w.write(f, img)
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
files=[dict(name=f'alt-filename-{rando}', path=image_file, cid=f'cid-{rando}')])
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_files_path_does_not_exist(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
|
||||
@ -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
0
tmp/.gitkeep
Normal file
Loading…
Reference in New Issue
Block a user