bitranox-template-py-cli 2.0.1__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.env.example +14 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/CHANGELOG.md +21 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/PKG-INFO +5 -4
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/README.md +3 -2
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/docs/systemdesign/module_reference.md +6 -3
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/pyproject.toml +2 -2
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/__init__conf__.py +1 -1
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/50-mail.toml +28 -3
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/email/config.py +10 -14
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_email_config_translation.py +31 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.devcontainer/devcontainer.json +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.devcontainer/settings.json +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.gitattributes +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.github/actions/extract-metadata/action.yml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.github/dependabot.yml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.github/workflows/codeql.yml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.github/workflows/default_cicd_public.yml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.github/workflows/default_release_public.yml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.gitignore +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.qlty/qlty.toml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.snyk +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/CONFIG.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/CONTRIBUTING.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/DEVELOPMENT.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/INSTALL.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/LICENSE +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/Makefile +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/SECURITY.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/ai-stance.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/ai-transparency.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/codecov.yml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/docs/adr/0001-memory-adapters-in-src.md +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/notebooks/Quickstart.ipynb +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/rename.sh +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/rename_dry.sh +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/reset_git_history.sh +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/__main__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/config.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/email/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/email/_common.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_email.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_notification.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/info.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/commands/logging.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/config_load.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/constants.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/context.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/exit_codes.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/main.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/root.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/safe_console.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/cli/typed_click.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/40-layered-config.toml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/90-logging.toml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/defaultconfig.toml +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/deploy.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/display.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/loader.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/overrides.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/config/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/email/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/email/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/email/sender.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/email/transport.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/email/validation.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/logging/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/logging/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/logging/setup.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/memory/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/memory/config.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/memory/email.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/memory/logging.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/adapters/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/application/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/application/ports.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/application/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/composition/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/composition/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/domain/__init__.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/domain/behaviors.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/domain/enums.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/domain/errors.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/domain/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/entry.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/src/bitranox_template_py_cli/py.typed +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/conftest.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_behaviors.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cache_effectiveness.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_config.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_config_errors.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_core.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_email.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_email_config_errors.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_env_file.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_exit_codes.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_main_exit.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_overrides.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_validation.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_config_overrides.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_declared_dependencies.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_deploy_mode_safety.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_deploy_permissions.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_display.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_email_attachment_lists.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_email_password_secrecy.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_email_shipped_defaults.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_enums.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_errors.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_logging.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_logging_dotenv_isolation.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_mail.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_memory_logging.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_metadata.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_metadata_sync.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_module_entry.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_module_reference_sync.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_permission_defaults.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_ports.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_property_email.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_property_overrides.py +0 -0
- {bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_safe_console.py +0 -0
|
@@ -101,6 +101,20 @@ GITHUB_TOKEN=
|
|
|
101
101
|
# .env: EMAIL__LOCAL_HOSTNAME=mail.example.com
|
|
102
102
|
# EMAIL__LOCAL_HOSTNAME=mail.example.com
|
|
103
103
|
|
|
104
|
+
# Purpose: Upper bound in seconds for one SMTP session (connect to QUIT); 0 for none
|
|
105
|
+
# Type: float
|
|
106
|
+
# Default: 0 (none)
|
|
107
|
+
# Environment Variable: BITRANOX_TEMPLATE_PY_CLI___EMAIL__DELIVERY_DEADLINE=300
|
|
108
|
+
# .env: EMAIL__DELIVERY_DEADLINE=300
|
|
109
|
+
# EMAIL__DELIVERY_DEADLINE=300
|
|
110
|
+
|
|
111
|
+
# Purpose: Most recipients one send accepts; 0 for no limit
|
|
112
|
+
# Type: integer
|
|
113
|
+
# Default: 1000
|
|
114
|
+
# Environment Variable: BITRANOX_TEMPLATE_PY_CLI___EMAIL__RECIPIENT_MAX_COUNT=1000
|
|
115
|
+
# .env: EMAIL__RECIPIENT_MAX_COUNT=1000
|
|
116
|
+
# EMAIL__RECIPIENT_MAX_COUNT=1000
|
|
117
|
+
|
|
104
118
|
# Purpose: Raise FileNotFoundError when attachment files are missing
|
|
105
119
|
# Type: boolean (true/false)
|
|
106
120
|
# Default: true
|
|
@@ -6,6 +6,27 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.1.0] 2026-10-06 15:37:27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Three email limits from btx_lib_mail 4.0.0 are configurable**: `[email] delivery_deadline`
|
|
13
|
+
(an upper bound in seconds for one SMTP session; the field is `smtp_delivery_deadline`),
|
|
14
|
+
`[email] recipient_max_count` (default 1000) and `[email.attachments] max_count` (default
|
|
15
|
+
100). A value of 0 means no limit, as `max_size_bytes = 0` already did. Shipped in
|
|
16
|
+
`50-mail.toml`, `.env.example` and the module reference.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Requires `btx_lib_mail>=4.0.0`.** Effects a user of the email commands can see: the
|
|
20
|
+
Windows dangerous extensions (`.exe`, `.bat`, `.ps1`, ...) are refused on every platform by
|
|
21
|
+
default and the POSIX ones on Windows; a sender or recipient longer than RFC 5321 allows is
|
|
22
|
+
refused; a subject with a control character or over 4096 characters is refused; an
|
|
23
|
+
attachment swapped after its checks is refused; a delivery error is a `DeliveryError` (still
|
|
24
|
+
a `RuntimeError`). See the btx_lib_mail 4.0.0 changelog for the full list.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
- **`EmailConfig`'s own SMTP host validator.** `ConfMail` checks every host's syntax (port
|
|
28
|
+
range, IPv6 brackets) itself since btx_lib_mail 3.0.1; the messages are unchanged.
|
|
29
|
+
|
|
9
30
|
## [2.0.1] 2026-10-06 13:09:50
|
|
10
31
|
|
|
11
32
|
Re-release of 2.0.0, which was tagged but never published: PyPI rejected the upload because
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: bitranox_template_py_cli
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Template CLI application with configuration management and structured logging
|
|
5
5
|
Project-URL: Homepage, https://github.com/bitranox/bitranox_template_py_cli
|
|
6
6
|
Project-URL: Repository, https://github.com/bitranox/bitranox_template_py_cli.git
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
|
-
Requires-Dist: btx-lib-mail>=
|
|
24
|
+
Requires-Dist: btx-lib-mail>=4.0.0
|
|
25
25
|
Requires-Dist: click>=8.5.0
|
|
26
26
|
Requires-Dist: lib-cli-exit-tools>=2.4.0
|
|
27
27
|
Requires-Dist: lib-layered-config>=7.0.1
|
|
@@ -286,6 +286,7 @@ use_starttls = true
|
|
|
286
286
|
starttls_verify = true # false accepts any certificate: only for a known internal relay
|
|
287
287
|
timeout = 60.0
|
|
288
288
|
# local_hostname = "mail.example.com" # EHLO name; set it where reverse DNS is slow
|
|
289
|
+
# delivery_deadline = 300 # upper bound in seconds for one SMTP session; 0 = none
|
|
289
290
|
```
|
|
290
291
|
|
|
291
292
|
A key `[email]` or `[email.attachments]` does not know is refused (exit 78,
|
|
@@ -378,8 +379,8 @@ from bitranox_template_py_cli.composition import send_email, send_notification
|
|
|
378
379
|
|
|
379
380
|
# Configure email. EmailConfig is a btx_lib_mail ConfMail, so in Python the fields use the
|
|
380
381
|
# library's names (smtphosts, smtp_use_starttls, smtp_timeout, smtp_starttls_verify,
|
|
381
|
-
# smtp_local_hostname); configuration files keep their keys (smtp_hosts,
|
|
382
|
-
# starttls_verify, local_hostname). smtp_password is a SecretStr: it prints as
|
|
382
|
+
# smtp_local_hostname, smtp_delivery_deadline); configuration files keep their keys (smtp_hosts,
|
|
383
|
+
# use_starttls, timeout, starttls_verify, local_hostname, delivery_deadline). smtp_password is a SecretStr: it prints as
|
|
383
384
|
# '**********' in logs, reprs and dumps, and config.smtp_password.get_secret_value() returns
|
|
384
385
|
# the plain text.
|
|
385
386
|
config = EmailConfig(
|
|
@@ -231,6 +231,7 @@ use_starttls = true
|
|
|
231
231
|
starttls_verify = true # false accepts any certificate: only for a known internal relay
|
|
232
232
|
timeout = 60.0
|
|
233
233
|
# local_hostname = "mail.example.com" # EHLO name; set it where reverse DNS is slow
|
|
234
|
+
# delivery_deadline = 300 # upper bound in seconds for one SMTP session; 0 = none
|
|
234
235
|
```
|
|
235
236
|
|
|
236
237
|
A key `[email]` or `[email.attachments]` does not know is refused (exit 78,
|
|
@@ -323,8 +324,8 @@ from bitranox_template_py_cli.composition import send_email, send_notification
|
|
|
323
324
|
|
|
324
325
|
# Configure email. EmailConfig is a btx_lib_mail ConfMail, so in Python the fields use the
|
|
325
326
|
# library's names (smtphosts, smtp_use_starttls, smtp_timeout, smtp_starttls_verify,
|
|
326
|
-
# smtp_local_hostname); configuration files keep their keys (smtp_hosts,
|
|
327
|
-
# starttls_verify, local_hostname). smtp_password is a SecretStr: it prints as
|
|
327
|
+
# smtp_local_hostname, smtp_delivery_deadline); configuration files keep their keys (smtp_hosts,
|
|
328
|
+
# use_starttls, timeout, starttls_verify, local_hostname, delivery_deadline). smtp_password is a SecretStr: it prints as
|
|
328
329
|
# '**********' in logs, reprs and dumps, and config.smtp_password.get_secret_value() returns
|
|
329
330
|
# the plain text.
|
|
330
331
|
config = EmailConfig(
|
|
@@ -337,6 +337,8 @@ column).
|
|
|
337
337
|
| `smtp_starttls_verify` | `bool` | `True` | `starttls_verify` | Verify the server certificate after STARTTLS |
|
|
338
338
|
| `smtp_timeout` | `float` | `30.0` | `timeout` | Socket timeout in seconds |
|
|
339
339
|
| `smtp_local_hostname` | `str \| None` | `None` | `local_hostname` | Host name announced in EHLO; `None` looks it up once per process |
|
|
340
|
+
| `smtp_delivery_deadline` | `float \| None` | `None` | `delivery_deadline` | Upper bound in seconds for one SMTP session; `None` for none |
|
|
341
|
+
| `recipient_max_count` | `int \| None` | `1000` | `recipient_max_count` | Most recipients one send accepts; `None` lifts the limit |
|
|
340
342
|
| `raise_on_missing_attachments` | `bool` | `True` | `raise_on_missing_attachments` | Raise on missing attachment files |
|
|
341
343
|
| `raise_on_invalid_recipient` | `bool` | `True` | `raise_on_invalid_recipient` | Raise on invalid recipient addresses |
|
|
342
344
|
|
|
@@ -345,10 +347,11 @@ column).
|
|
|
345
347
|
| Field | Type | Default | File key (`[email.attachments]`) | Description |
|
|
346
348
|
|------------------------------------------|---------------------------|--------------|----------------------------------|---------------------------------------------------------|
|
|
347
349
|
| `attachment_allowed_extensions` | `frozenset[str] \| None` | `None` | `allowed_extensions` | Whitelist of allowed extensions |
|
|
348
|
-
| `attachment_blocked_extensions` | `frozenset[str]` |
|
|
350
|
+
| `attachment_blocked_extensions` | `frozenset[str]` | POSIX + Win | `blocked_extensions` | Blacklist of blocked extensions (both lists, every OS) |
|
|
349
351
|
| `attachment_allowed_directories` | `frozenset[Path] \| None` | `None` | `allowed_directories` | Whitelist of allowed source directories |
|
|
350
352
|
| `attachment_blocked_directories` | `frozenset[Path]` | OS defaults | `blocked_directories` | Blacklist of blocked directories |
|
|
351
353
|
| `attachment_max_size_bytes` | `int \| None` | `26_214_400` | `max_size_bytes` | Maximum file size (25 MiB), `None` to disable |
|
|
354
|
+
| `attachment_max_count` | `int \| None` | `100` | `max_count` | Most attachments one send accepts, `None` to disable |
|
|
352
355
|
| `attachment_allow_symlinks` | `bool` | `False` | `allow_symlinks` | Whether symlinks are permitted |
|
|
353
356
|
| `attachment_raise_on_security_violation` | `bool` | `True` | `raise_on_security_violation` | Raise or skip on security violation |
|
|
354
357
|
| `attachment_allow_empty_blocklists` | `bool` | `False` | not exposed | Allow an empty blocked set (blocks nothing) from Python |
|
|
@@ -359,13 +362,13 @@ lib_layered_config is the only reader of configuration: it merges every layer (t
|
|
|
359
362
|
defaults, the app, host and user files, `.env`, the environment, `--set`) into one mapping.
|
|
360
363
|
`load_email_config_from_dict()` turns that mapping's `[email]` section into an `EmailConfig`:
|
|
361
364
|
|
|
362
|
-
- the
|
|
365
|
+
- the six keys in the third column that differ from the field names are mapped;
|
|
363
366
|
- `[email.attachments]` keys become `attachment_<key>`;
|
|
364
367
|
- a key that is not listed is refused (exit 78, `email.<key>: unknown key`), whatever layer it came from;
|
|
365
368
|
- blank text means "not configured"; a single host or address string is a one-entry list;
|
|
366
369
|
- an empty attachment list (`[]` or a blank string) means the library's defaults, and any other
|
|
367
370
|
value that is not a list, such as a comma-separated string, is refused, as is a blank entry;
|
|
368
|
-
- `max_size_bytes
|
|
371
|
+
- a limit of 0 (`max_size_bytes`, `max_count`, `recipient_max_count`, `delivery_deadline`) means no limit.
|
|
369
372
|
|
|
370
373
|
`describe_validation_error()` renders a refusal as one `email.<file key>: <reason>` line per
|
|
371
374
|
problem, never with the refused value.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bitranox_template_py_cli"
|
|
3
|
-
version = "2.0
|
|
3
|
+
version = "2.1.0"
|
|
4
4
|
description = "Template CLI application with configuration management and structured logging"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
"lib_log_rich>=6.3.9",
|
|
13
13
|
"python-dotenv>=1.2.4",
|
|
14
14
|
"lib_layered_config>=7.0.1",
|
|
15
|
-
"btx_lib_mail>=
|
|
15
|
+
"btx_lib_mail>=4.0.0",
|
|
16
16
|
"pydantic>=2.13.5",
|
|
17
17
|
"orjson>=3.12.0",
|
|
18
18
|
]
|
|
@@ -40,7 +40,7 @@ name = "bitranox_template_py_cli"
|
|
|
40
40
|
#: Human-readable summary shown in CLI help output.
|
|
41
41
|
title = "Template CLI application with configuration management and structured logging"
|
|
42
42
|
#: Current release version pulled from ``pyproject.toml`` by automation.
|
|
43
|
-
version = "2.0
|
|
43
|
+
version = "2.1.0"
|
|
44
44
|
#: Repository homepage presented to users.
|
|
45
45
|
homepage = "https://github.com/bitranox/bitranox_template_py_cli"
|
|
46
46
|
#: Author attribution surfaced in CLI output.
|
|
@@ -110,6 +110,23 @@ starttls_verify = true
|
|
|
110
110
|
# qualified name here.
|
|
111
111
|
local_hostname = ""
|
|
112
112
|
|
|
113
|
+
# Purpose: Upper bound in seconds for one SMTP session, from the connect to QUIT
|
|
114
|
+
# Type: float, or 0 for no deadline
|
|
115
|
+
# Default: 0 (none; only the socket timeout bounds each read or write)
|
|
116
|
+
# Environment Variable: BITRANOX_TEMPLATE_PY_CLI___EMAIL__DELIVERY_DEADLINE=300
|
|
117
|
+
# .env: EMAIL__DELIVERY_DEADLINE=300
|
|
118
|
+
# Note: The socket timeout restarts with every byte, so a server answering one byte at a
|
|
119
|
+
# time can hold a session open indefinitely; past the deadline that host counts as failed.
|
|
120
|
+
delivery_deadline = 0
|
|
121
|
+
|
|
122
|
+
# Purpose: Most recipients one send accepts (counted after duplicates are dropped)
|
|
123
|
+
# Type: integer, or 0 for no limit
|
|
124
|
+
# Default: 1000
|
|
125
|
+
# Environment Variable: BITRANOX_TEMPLATE_PY_CLI___EMAIL__RECIPIENT_MAX_COUNT=1000
|
|
126
|
+
# .env: EMAIL__RECIPIENT_MAX_COUNT=1000
|
|
127
|
+
# Note: More recipients are refused before anything is delivered
|
|
128
|
+
recipient_max_count = 1000
|
|
129
|
+
|
|
113
130
|
# Purpose: Behavior when attachment files are missing
|
|
114
131
|
# Type: boolean
|
|
115
132
|
# Default: true (raise FileNotFoundError)
|
|
@@ -152,9 +169,9 @@ allowed_extensions = []
|
|
|
152
169
|
|
|
153
170
|
# Purpose: Blacklist of blocked file extensions (ignored when allowed_extensions is set)
|
|
154
171
|
# Type: array of strings
|
|
155
|
-
# Default: [] (the library's defaults apply: btx_lib_mail DANGEROUS_EXTENSIONS_POSIX
|
|
156
|
-
# DANGEROUS_EXTENSIONS_WINDOWS; paths matching SENSITIVE_PATH_PATTERNS such
|
|
157
|
-
# always refused)
|
|
172
|
+
# Default: [] (the library's defaults apply: btx_lib_mail DANGEROUS_EXTENSIONS_POSIX plus
|
|
173
|
+
# DANGEROUS_EXTENSIONS_WINDOWS on every platform; paths matching SENSITIVE_PATH_PATTERNS such
|
|
174
|
+
# as /.ssh/ are always refused)
|
|
158
175
|
# Environment Variable: BITRANOX_TEMPLATE_PY_CLI___EMAIL__ATTACHMENTS__BLOCKED_EXTENSIONS='[".exe",".bat",".cmd"]'
|
|
159
176
|
# .env: EMAIL__ATTACHMENTS__BLOCKED_EXTENSIONS=[".exe",".bat",".cmd"] (unquoted)
|
|
160
177
|
# A comma-separated value is NOT a list: it arrives as one string and is refused
|
|
@@ -197,6 +214,14 @@ blocked_directories = []
|
|
|
197
214
|
# Note: Set to 0 to disable size checking entirely
|
|
198
215
|
max_size_bytes = 26214400
|
|
199
216
|
|
|
217
|
+
# Purpose: Most attachments one send accepts
|
|
218
|
+
# Type: integer, or 0 for no limit
|
|
219
|
+
# Default: 100
|
|
220
|
+
# Environment Variable: BITRANOX_TEMPLATE_PY_CLI___EMAIL__ATTACHMENTS__MAX_COUNT=100
|
|
221
|
+
# .env: EMAIL__ATTACHMENTS__MAX_COUNT=100
|
|
222
|
+
# Note: More attachments are refused before any file is checked or anything is delivered
|
|
223
|
+
max_count = 100
|
|
224
|
+
|
|
200
225
|
# Purpose: Whether to allow symbolic links as attachments
|
|
201
226
|
# Type: boolean
|
|
202
227
|
# Default: false (symlinks are rejected)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
lib_layered_config is the only reader of configuration. It merges every layer (the shipped
|
|
4
4
|
defaults, the app, host and user files, ``.env``, the environment and ``--set``) into one
|
|
5
5
|
mapping, and :func:`load_email_config_from_dict` turns that mapping's ``[email]`` section into
|
|
6
|
-
:class:`EmailConfig`. Operators keep writing the file keys they always wrote;
|
|
6
|
+
:class:`EmailConfig`. Operators keep writing the file keys they always wrote; six of them
|
|
7
7
|
differ from the field names code reads (``FILE_KEY_TO_FIELD``). A key that is not a file key
|
|
8
8
|
is refused, so a typo cannot leave a setting silently at its default.
|
|
9
9
|
"""
|
|
@@ -14,7 +14,7 @@ import re
|
|
|
14
14
|
from collections.abc import Mapping
|
|
15
15
|
from typing import TYPE_CHECKING, Any, cast
|
|
16
16
|
|
|
17
|
-
from btx_lib_mail import ConfMail, validate_email_address
|
|
17
|
+
from btx_lib_mail import ConfMail, validate_email_address
|
|
18
18
|
from pydantic import ConfigDict, Field, SecretStr, ValidationError, field_validator
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
@@ -34,6 +34,7 @@ FILE_KEY_TO_FIELD: dict[str, str] = {
|
|
|
34
34
|
"timeout": "smtp_timeout",
|
|
35
35
|
"starttls_verify": "smtp_starttls_verify",
|
|
36
36
|
"local_hostname": "smtp_local_hostname",
|
|
37
|
+
"delivery_deadline": "smtp_delivery_deadline",
|
|
37
38
|
}
|
|
38
39
|
_FIELD_TO_FILE_KEY: dict[str, str] = {field: key for key, field in FILE_KEY_TO_FIELD.items()}
|
|
39
40
|
|
|
@@ -47,6 +48,7 @@ SECTION_KEYS = frozenset(
|
|
|
47
48
|
"smtp_password",
|
|
48
49
|
"raise_on_missing_attachments",
|
|
49
50
|
"raise_on_invalid_recipient",
|
|
51
|
+
"recipient_max_count",
|
|
50
52
|
_ATTACHMENTS,
|
|
51
53
|
}
|
|
52
54
|
)
|
|
@@ -59,6 +61,7 @@ ATTACHMENT_KEYS = frozenset(
|
|
|
59
61
|
"allowed_directories",
|
|
60
62
|
"blocked_directories",
|
|
61
63
|
"max_size_bytes",
|
|
64
|
+
"max_count",
|
|
62
65
|
"allow_symlinks",
|
|
63
66
|
"raise_on_security_violation",
|
|
64
67
|
}
|
|
@@ -74,6 +77,9 @@ _BLANK_TEXT_MEANS_UNSET = frozenset(
|
|
|
74
77
|
_EMPTY_LIST_MEANS_DEFAULT = frozenset(
|
|
75
78
|
{"allowed_extensions", "blocked_extensions", "allowed_directories", "blocked_directories"}
|
|
76
79
|
)
|
|
80
|
+
#: Limits whose 0 means "no limit". ConfMail refuses 0 and lifts a limit with None, which a
|
|
81
|
+
#: TOML file or an environment variable cannot write.
|
|
82
|
+
_ZERO_MEANS_NO_LIMIT = frozenset({"max_size_bytes", "max_count", "recipient_max_count", "delivery_deadline"})
|
|
77
83
|
#: The refusal of a blank entry in an attachment list.
|
|
78
84
|
_BLANK_ENTRY = ValueError("an entry is blank; remove it")
|
|
79
85
|
#: The form an attachment list must take, named by the refusal of any other form. A
|
|
@@ -146,16 +152,6 @@ class EmailConfig(ConfMail):
|
|
|
146
152
|
return [value] if value.strip() else []
|
|
147
153
|
return value
|
|
148
154
|
|
|
149
|
-
@field_validator("smtphosts")
|
|
150
|
-
@classmethod
|
|
151
|
-
def _check_hosts(cls, value: list[str]) -> list[str]:
|
|
152
|
-
# ConfMail refuses userinfo, a path and control characters; the port range and the
|
|
153
|
-
# IPv6 brackets are checked only by validate_smtp_host, so a typo surfaces at load
|
|
154
|
-
# time rather than at the first delivery.
|
|
155
|
-
for host in value:
|
|
156
|
-
validate_smtp_host(host)
|
|
157
|
-
return value
|
|
158
|
-
|
|
159
155
|
@field_validator("from_address")
|
|
160
156
|
@classmethod
|
|
161
157
|
def _check_from_address(cls, value: str | None) -> str | None:
|
|
@@ -273,10 +269,10 @@ def _means_unset(key: str, value: object) -> bool:
|
|
|
273
269
|
|
|
274
270
|
|
|
275
271
|
def _field_value(key: str, value: object) -> object:
|
|
276
|
-
"""A lone host or address becomes a one-entry list; a
|
|
272
|
+
"""A lone host or address becomes a one-entry list; a limit of 0 means no limit."""
|
|
277
273
|
if key in _ONE_OR_MANY and isinstance(value, str):
|
|
278
274
|
return [value]
|
|
279
|
-
if key
|
|
275
|
+
if key in _ZERO_MEANS_NO_LIMIT and type(value) in (int, float) and value == 0:
|
|
280
276
|
return None
|
|
281
277
|
return value
|
|
282
278
|
|
|
@@ -108,6 +108,37 @@ def test_a_size_limit_of_zero_means_no_limit() -> None:
|
|
|
108
108
|
assert _load({"attachments": {"max_size_bytes": 0}}).attachment_max_size_bytes is None
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
@pytest.mark.os_agnostic
|
|
112
|
+
@pytest.mark.parametrize(
|
|
113
|
+
("email", "field"),
|
|
114
|
+
[
|
|
115
|
+
({"recipient_max_count": 0}, "recipient_max_count"),
|
|
116
|
+
({"delivery_deadline": 0}, "smtp_delivery_deadline"),
|
|
117
|
+
({"delivery_deadline": 0.0}, "smtp_delivery_deadline"),
|
|
118
|
+
({"attachments": {"max_count": 0}}, "attachment_max_count"),
|
|
119
|
+
],
|
|
120
|
+
ids=["recipient_max_count", "delivery_deadline", "delivery_deadline_float", "attachments.max_count"],
|
|
121
|
+
)
|
|
122
|
+
def test_every_other_limit_of_zero_means_no_limit(email: dict[str, Any], field: str) -> None:
|
|
123
|
+
assert getattr(_load(email), field) is None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.os_agnostic
|
|
127
|
+
def test_the_limits_reach_their_fields() -> None:
|
|
128
|
+
config = _load({"recipient_max_count": 5, "delivery_deadline": 120, "attachments": {"max_count": 3}})
|
|
129
|
+
|
|
130
|
+
assert (config.recipient_max_count, config.smtp_delivery_deadline, config.attachment_max_count) == (5, 120.0, 3)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.mark.os_agnostic
|
|
134
|
+
def test_a_negative_deadline_is_named_by_its_file_key() -> None:
|
|
135
|
+
(line,) = _problems({"delivery_deadline": -1})
|
|
136
|
+
|
|
137
|
+
assert line.startswith("email.delivery_deadline: ")
|
|
138
|
+
assert "smtp_delivery_deadline" not in line
|
|
139
|
+
assert "unknown key" not in line
|
|
140
|
+
|
|
141
|
+
|
|
111
142
|
@pytest.mark.os_agnostic
|
|
112
143
|
@pytest.mark.parametrize(
|
|
113
144
|
("email", "line"),
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.devcontainer/devcontainer.json
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.devcontainer/settings.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/.github/workflows/codeql.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/notebooks/Quickstart.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cache_effectiveness.py
RENAMED
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_config_errors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_env_file.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_exit_codes.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_main_exit.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_overrides.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_cli_validation.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_config_overrides.py
RENAMED
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_deploy_mode_safety.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_deploy_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_memory_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_metadata_sync.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_module_entry.py
RENAMED
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_permission_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_property_email.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_property_overrides.py
RENAMED
|
File without changes
|
{bitranox_template_py_cli-2.0.1 → bitranox_template_py_cli-2.1.0}/tests/test_safe_console.py
RENAMED
|
File without changes
|