bitranox-template-py-cli 1.5.4__tar.gz → 1.6.1__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-1.5.4 → bitranox_template_py_cli-1.6.1}/.devcontainer/devcontainer.json +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.devcontainer/settings.json +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.env.example +0 -0
- bitranox_template_py_cli-1.6.1/.gitattributes +15 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/actions/extract-metadata/action.yml +16 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/dependabot.yml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/workflows/codeql.yml +1 -1
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/workflows/default_cicd_public.yml +59 -9
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.github/workflows/default_release_public.yml +23 -7
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.gitignore +15 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.qlty/qlty.toml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.snyk +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/CHANGELOG.md +42 -14
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/CONFIG.md +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/CONTRIBUTING.md +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/DEVELOPMENT.md +44 -28
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/INSTALL.md +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/LICENSE +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/Makefile +174 -20
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/PKG-INFO +20 -20
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/README.md +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/SECURITY.md +0 -0
- bitranox_template_py_cli-1.6.1/ai-stance.md +90 -0
- bitranox_template_py_cli-1.6.1/ai-transparency.md +117 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/codecov.yml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/docs/adr/0001-memory-adapters-in-src.md +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/docs/systemdesign/module_reference.md +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/notebooks/Quickstart.ipynb +1 -1
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/pyproject.toml +30 -21
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/__init__conf__.py +1 -1
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/__main__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/config.py +4 -1
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/_common.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_email.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_notification.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/info.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/commands/logging.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/constants.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/context.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/exit_codes.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/main.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/root.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/cli/typed_click.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/40-layered-config.toml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/50-mail.toml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/90-logging.toml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/defaultconfig.toml +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/deploy.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/display.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/loader.py +11 -1
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/overrides.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/permissions.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/config/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/config.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/sender.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/transport.py +9 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/email/validation.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/logging/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/logging/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/logging/setup.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/config.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/email.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/memory/logging.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/adapters/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/application/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/application/ports.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/application/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/composition/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/composition/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/__init__.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/behaviors.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/enums.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/errors.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/domain/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/entry.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/src/bitranox_template_py_cli/py.typed +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/conftest.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_behaviors.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cache_effectiveness.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_config.py +4 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_core.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_email.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_env_file.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_exit_codes.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_overrides.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_cli_validation.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_config_overrides.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_deploy_permissions.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_display.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_enums.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_errors.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_logging.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_mail.py +246 -150
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_metadata.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_metadata_sync.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_module_entry.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_ports.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_property_email.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/tests/test_property_overrides.py +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/rename.sh +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/rename_dry.sh +0 -0
- {bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/reset_git_history.sh +0 -0
{bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.devcontainer/devcontainer.json
RENAMED
|
File without changes
|
{bitranox_template_py_cli-1.5.4 → bitranox_template_py_cli-1.6.1}/.devcontainer/settings.json
RENAMED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Normalize every text file to LF in the repo AND the working tree, on every OS.
|
|
2
|
+
# `text=auto` auto-detects binary and leaves it untouched; `eol=lf` forces LF checkout
|
|
3
|
+
# so a Windows edit/save cannot reintroduce CRLF churn.
|
|
4
|
+
* text=auto eol=lf
|
|
5
|
+
|
|
6
|
+
# Runtime-critical: CRLF here breaks execution on Linux (^M in shebang / make recipes).
|
|
7
|
+
Makefile text eol=lf
|
|
8
|
+
*.sh text eol=lf
|
|
9
|
+
*.py text eol=lf
|
|
10
|
+
*.ps1 text eol=lf
|
|
11
|
+
*.toml text eol=lf
|
|
12
|
+
*.md text eol=lf
|
|
13
|
+
*.json text eol=lf
|
|
14
|
+
*.yml text eol=lf
|
|
15
|
+
*.yaml text eol=lf
|
|
@@ -29,6 +29,11 @@ outputs:
|
|
|
29
29
|
BANDIT_SKIP_FLAG:
|
|
30
30
|
description: Bandit skip flag string (e.g. "-s B101,B601") or empty
|
|
31
31
|
value: ${{ steps.meta.outputs.BANDIT_SKIP_FLAG }}
|
|
32
|
+
PYTEST_MARKER_EXPR:
|
|
33
|
+
description: >-
|
|
34
|
+
Marker expression for the CI test run (e.g. "not local_only and not integration"),
|
|
35
|
+
built from tool.ci.pytest-exclude-markers
|
|
36
|
+
value: ${{ steps.meta.outputs.PYTEST_MARKER_EXPR }}
|
|
32
37
|
|
|
33
38
|
runs:
|
|
34
39
|
using: composite
|
|
@@ -69,6 +74,15 @@ runs:
|
|
|
69
74
|
bandit_skips = data.get('tool', {}).get('bandit', {}).get('skips', [])
|
|
70
75
|
bandit_skip_flag = f"-s {','.join(bandit_skips)}" if bandit_skips else ""
|
|
71
76
|
|
|
77
|
+
# --- [tool.ci] section ---
|
|
78
|
+
# CI always skips local_only (those tests need a dev box). A project whose
|
|
79
|
+
# suite also carries markers CI cannot satisfy - integration tests needing a
|
|
80
|
+
# container that a separate workflow starts - names them here, rather than
|
|
81
|
+
# every such repo forking this workflow to edit one -m expression.
|
|
82
|
+
ci_config = data.get('tool', {}).get('ci', {})
|
|
83
|
+
exclude_markers = ['local_only', *ci_config.get('pytest-exclude-markers', [])]
|
|
84
|
+
pytest_marker_expr = ' and '.join(f'not {m}' for m in exclude_markers)
|
|
85
|
+
|
|
72
86
|
# Write to GITHUB_OUTPUT for composite action outputs
|
|
73
87
|
with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as out:
|
|
74
88
|
out.write(f"PROJECT_NAME={project}\n")
|
|
@@ -79,6 +93,7 @@ runs:
|
|
|
79
93
|
out.write(f"COV_REPORT_FILE={cov_report_file}\n")
|
|
80
94
|
out.write(f"COV_FAIL_UNDER={cov_fail_under}\n")
|
|
81
95
|
out.write(f"BANDIT_SKIP_FLAG={bandit_skip_flag}\n")
|
|
96
|
+
out.write(f"PYTEST_MARKER_EXPR={pytest_marker_expr}\n")
|
|
82
97
|
|
|
83
98
|
# Also write to GITHUB_ENV so subsequent steps can use ${{ env.VAR }}
|
|
84
99
|
with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
|
|
@@ -90,3 +105,4 @@ runs:
|
|
|
90
105
|
env.write(f"COV_REPORT_FILE={cov_report_file}\n")
|
|
91
106
|
env.write(f"COV_FAIL_UNDER={cov_fail_under}\n")
|
|
92
107
|
env.write(f"BANDIT_SKIP_FLAG={bandit_skip_flag}\n")
|
|
108
|
+
env.write(f"PYTEST_MARKER_EXPR={pytest_marker_expr}\n")
|
|
File without changes
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
python-latest: ${{ steps.metadata.outputs.python-latest }}
|
|
19
19
|
os-matrix: ${{ steps.metadata.outputs.os-matrix }}
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
22
|
- uses: actions/setup-python@v6
|
|
23
23
|
with:
|
|
24
24
|
python-version: "3.x"
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
UV_LINK_MODE: "copy"
|
|
67
67
|
UV_BREAK_SYSTEM_PACKAGES: "1"
|
|
68
68
|
steps:
|
|
69
|
-
- uses: actions/checkout@
|
|
69
|
+
- uses: actions/checkout@v7
|
|
70
70
|
- uses: actions/setup-python@v6
|
|
71
71
|
with:
|
|
72
72
|
python-version: ${{ matrix.python }}
|
|
@@ -169,7 +169,7 @@ jobs:
|
|
|
169
169
|
- name: Restore ruff cache
|
|
170
170
|
if: runner.environment == 'github-hosted'
|
|
171
171
|
id: ruff-cache
|
|
172
|
-
uses: actions/cache/restore@
|
|
172
|
+
uses: actions/cache/restore@v6
|
|
173
173
|
with:
|
|
174
174
|
path: .ruff_cache
|
|
175
175
|
key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
|
|
@@ -201,7 +201,7 @@ jobs:
|
|
|
201
201
|
# and stale journal files cause "database is locked" errors.
|
|
202
202
|
COVERAGE_FILE: "${{ runner.temp }}/.coverage"
|
|
203
203
|
run: >-
|
|
204
|
-
pytest -m "
|
|
204
|
+
pytest -m "${{ env.PYTEST_MARKER_EXPR }}"
|
|
205
205
|
--cov=${{ env.SRC_PATH }}/${{ env.PACKAGE_MODULE }}
|
|
206
206
|
--cov-report=term-missing
|
|
207
207
|
--cov-report=xml:${{ env.COV_REPORT_FILE }}
|
|
@@ -236,12 +236,62 @@ jobs:
|
|
|
236
236
|
continue-on-error: true
|
|
237
237
|
shell: python
|
|
238
238
|
run: |
|
|
239
|
-
import subprocess, sys
|
|
239
|
+
import subprocess, sys, tempfile
|
|
240
240
|
import rtoml
|
|
241
241
|
from pathlib import Path
|
|
242
|
+
|
|
243
|
+
# Audit the project's OWN resolved dependency tree, not the ambient
|
|
244
|
+
# environment. "Install dev deps" uses --system, so the interpreter
|
|
245
|
+
# being audited is the runner image's, and that image ships packages
|
|
246
|
+
# this project neither declares nor distributes (setuptools, pip,
|
|
247
|
+
# wheel). Auditing those reported CVEs no consumer of this project is
|
|
248
|
+
# exposed to, and a scan that is permanently yellow is a scan everyone
|
|
249
|
+
# learns to ignore. Resolving pyproject.toml here keeps the result
|
|
250
|
+
# identical on every OS/python and dependent only on what we declare.
|
|
242
251
|
data = rtoml.load(Path("pyproject.toml"))
|
|
252
|
+
|
|
253
|
+
# Not every project declares dev dependencies, and those that do may use
|
|
254
|
+
# either an extra or a PEP 735 group. Asking uv for one that does not
|
|
255
|
+
# exist is a hard error ("Requested extra not found"), which would leave
|
|
256
|
+
# the audit permanently broken here rather than merely noisy, so select
|
|
257
|
+
# the flag from what this project actually declares.
|
|
258
|
+
selector = []
|
|
259
|
+
if "dev" in data.get("project", {}).get("optional-dependencies", {}):
|
|
260
|
+
selector = ["--extra", "dev"]
|
|
261
|
+
elif "dev" in data.get("dependency-groups", {}):
|
|
262
|
+
selector = ["--group", "dev"]
|
|
263
|
+
|
|
264
|
+
tmp = Path(tempfile.mkdtemp())
|
|
265
|
+
req = tmp / "compiled-requirements.txt"
|
|
266
|
+
subprocess.check_call(
|
|
267
|
+
["uv", "pip", "compile", *selector, "pyproject.toml", "-o", str(req), "--quiet"]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Drop direct URL references ("pkg @ git+https://...") before auditing.
|
|
271
|
+
# pip-audit cannot audit those (they are not on PyPI) but, given a
|
|
272
|
+
# requirements file, first tries to `git clone` each one to resolve a
|
|
273
|
+
# version - a slow, credential-dependent fetch. No public project here
|
|
274
|
+
# uses one today, yet "allow-direct-references" is enabled, so this
|
|
275
|
+
# guards the day one appears and keeps this step identical to the
|
|
276
|
+
# default_cicd_private template's.
|
|
277
|
+
audit_req = tmp / "audit-requirements.txt"
|
|
278
|
+
kept, dropped = [], []
|
|
279
|
+
for line in req.read_text(encoding="utf-8").splitlines():
|
|
280
|
+
if " @ " in line and not line.lstrip().startswith("#"):
|
|
281
|
+
dropped.append(line.split(" @ ", 1)[0].strip())
|
|
282
|
+
else:
|
|
283
|
+
kept.append(line)
|
|
284
|
+
audit_req.write_text("\n".join(kept) + "\n", encoding="utf-8")
|
|
285
|
+
if dropped:
|
|
286
|
+
# Never drop coverage silently: name what is not being audited here.
|
|
287
|
+
print(f"::notice::pip-audit: not auditing {len(dropped)} direct-URL dependency/ies "
|
|
288
|
+
f"(audited in their own repo): {', '.join(sorted(dropped))}")
|
|
289
|
+
|
|
290
|
+
req = audit_req
|
|
243
291
|
vulns = data.get("tool", {}).get("pip-audit", {}).get("ignore-vulns", [])
|
|
244
|
-
|
|
292
|
+
# --no-deps: the compiled file is already a complete pinned resolution,
|
|
293
|
+
# so pip-audit must audit it verbatim rather than re-resolving it.
|
|
294
|
+
cmd = ["pip-audit", "--no-deps", "-r", str(req)]
|
|
245
295
|
for v in vulns:
|
|
246
296
|
cmd.extend(["--ignore-vuln", v])
|
|
247
297
|
sys.exit(subprocess.call(cmd))
|
|
@@ -293,7 +343,7 @@ jobs:
|
|
|
293
343
|
|
|
294
344
|
- name: Save ruff cache
|
|
295
345
|
if: always() && runner.environment == 'github-hosted' && steps.ruff-cache.outputs.cache-hit != 'true'
|
|
296
|
-
uses: actions/cache/save@
|
|
346
|
+
uses: actions/cache/save@v6
|
|
297
347
|
with:
|
|
298
348
|
path: .ruff_cache
|
|
299
349
|
key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
|
|
@@ -302,7 +352,7 @@ jobs:
|
|
|
302
352
|
name: pipx/uv verification (ubuntu)
|
|
303
353
|
runs-on: ubuntu-latest
|
|
304
354
|
steps:
|
|
305
|
-
- uses: actions/checkout@
|
|
355
|
+
- uses: actions/checkout@v7
|
|
306
356
|
- uses: actions/setup-python@v6
|
|
307
357
|
with:
|
|
308
358
|
python-version: "3.14"
|
|
@@ -341,7 +391,7 @@ jobs:
|
|
|
341
391
|
name: Execute notebooks (ubuntu, Python 3.14)
|
|
342
392
|
runs-on: ubuntu-latest
|
|
343
393
|
steps:
|
|
344
|
-
- uses: actions/checkout@
|
|
394
|
+
- uses: actions/checkout@v7
|
|
345
395
|
- uses: actions/setup-python@v6
|
|
346
396
|
with:
|
|
347
397
|
python-version: "3.14"
|
|
@@ -16,7 +16,7 @@ jobs:
|
|
|
16
16
|
env:
|
|
17
17
|
UV_LINK_MODE: "copy"
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
20
|
- uses: actions/setup-python@v6
|
|
21
21
|
with:
|
|
22
22
|
python-version: '3.14'
|
|
@@ -46,13 +46,21 @@ jobs:
|
|
|
46
46
|
environment: pypi
|
|
47
47
|
permissions:
|
|
48
48
|
contents: read
|
|
49
|
+
# id-token is required for OIDC / PyPI Trusted Publisher. It is harmless when
|
|
50
|
+
# a PYPI_API_TOKEN secret is configured, in which case the token path runs.
|
|
51
|
+
id-token: write
|
|
49
52
|
steps:
|
|
50
|
-
- name:
|
|
53
|
+
- name: Select PyPI auth method
|
|
54
|
+
id: auth
|
|
55
|
+
env:
|
|
56
|
+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
51
57
|
run: |
|
|
52
|
-
if [ -
|
|
53
|
-
echo "
|
|
54
|
-
echo "
|
|
55
|
-
|
|
58
|
+
if [ -n "$PYPI_API_TOKEN" ]; then
|
|
59
|
+
echo "PYPI_API_TOKEN is set; publishing with the API token."
|
|
60
|
+
echo "method=token" >> "$GITHUB_OUTPUT"
|
|
61
|
+
else
|
|
62
|
+
echo "No PYPI_API_TOKEN; publishing via OIDC Trusted Publisher."
|
|
63
|
+
echo "method=oidc" >> "$GITHUB_OUTPUT"
|
|
56
64
|
fi
|
|
57
65
|
|
|
58
66
|
- name: Download artifacts
|
|
@@ -61,9 +69,17 @@ jobs:
|
|
|
61
69
|
name: dist
|
|
62
70
|
path: dist/
|
|
63
71
|
|
|
64
|
-
- name: Publish to PyPI
|
|
72
|
+
- name: Publish to PyPI (API token)
|
|
73
|
+
if: steps.auth.outputs.method == 'token'
|
|
65
74
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
66
75
|
with:
|
|
67
76
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
68
77
|
attestations: false
|
|
69
78
|
skip-existing: true
|
|
79
|
+
|
|
80
|
+
- name: Publish to PyPI (Trusted Publisher / OIDC)
|
|
81
|
+
if: steps.auth.outputs.method == 'oidc'
|
|
82
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
83
|
+
with:
|
|
84
|
+
attestations: true
|
|
85
|
+
skip-existing: true
|
|
@@ -186,3 +186,18 @@ uv.lock
|
|
|
186
186
|
CLAUDE.md
|
|
187
187
|
prompts.md
|
|
188
188
|
.grimp_cache/
|
|
189
|
+
|
|
190
|
+
# Local, per-developer settings (Claude Code etc.) - never commit
|
|
191
|
+
*.local.json
|
|
192
|
+
# bitranox curated memory (local; CLAUDE.local.md @imports it)
|
|
193
|
+
CLAUDE.local.md
|
|
194
|
+
|
|
195
|
+
# Python virtual environments (managed by bmk)
|
|
196
|
+
.venv-win/
|
|
197
|
+
.venv-bmk/
|
|
198
|
+
|
|
199
|
+
# Python virtual environments (managed by bmk)
|
|
200
|
+
.venv*/
|
|
201
|
+
|
|
202
|
+
# Autonomous-execution decision log (never committed)
|
|
203
|
+
EXECUTION-USER-REVIEW.md
|
|
File without changes
|
|
File without changes
|
|
@@ -6,6 +6,34 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.6.1] - 2026-07-20
|
|
10
|
+
|
|
11
|
+
Re-release of 1.6.0. PyPI rejected the 1.6.0 upload because that filename was previously used by a file that has since been deleted, and PyPI never permits filename reuse. Package contents are identical to 1.6.0.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `send_email()` and `send_notification()` accept an optional `transport` argument, exposing btx_lib_mail's `Transport` port so a delivery double can be injected at a real seam instead of monkeypatching `smtplib`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Mail tests failed against btx_lib_mail 1.5.0, which streams the message over the wire protocol directly (MAIL/RCPT plus DATA or BDAT) instead of calling `smtp.sendmail()`. The suite now injects a recording transport and asserts delivery intent (envelope sender, recipients, host failover order, credentials, payload), so it no longer depends on the library's internals.
|
|
18
|
+
- `ConfMail.smtp_password` is a `SecretStr` as of btx_lib_mail 1.5.0 and is now unwrapped before comparison.
|
|
19
|
+
- `config-deploy` emits an ASCII marker so it cannot crash on a cp1252 Windows console.
|
|
20
|
+
- `validate_profile` normalised to raise `ValueError`.
|
|
21
|
+
- DEVELOPMENT.md referenced a `make test-slow` target that no longer exists (4 places); the target is `make testintegration`. It also documented the SMTP username environment variable as `EMAIL__SMTP_USER`, but the field is `smtp_username`, so the correct name is `EMAIL__SMTP_USERNAME`.
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
- DEVELOPMENT.md documents the SMTP test seam: inject a `transport` double instead of patching `smtplib`, and leave `transport` off the application ports.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Raised the `btx_lib_mail` floor to `>=1.5.0`, which the injected transport seam requires. Other dependency floors refreshed and the current bmk Makefile adopted.
|
|
28
|
+
- CI: configurable pytest marker exclusion, pip-audit scoped to the project dependency tree with direct-URL dependencies filtered out, `actions/checkout` v7 and `actions/cache` v6.
|
|
29
|
+
- `codecov-cli` disabled (it capped `click<8.3.0` and held click on CVE-2026-7246); the redundant PYSEC-2026-2132 ignore dropped.
|
|
30
|
+
- LF line endings pinned via `.gitattributes`; file modes normalised so the executable bit is set only on shebang scripts.
|
|
31
|
+
- Quickstart notebook installs the package with uv.
|
|
32
|
+
|
|
33
|
+
## [1.6.0] - 2026-07-20 [NOT PUBLISHED]
|
|
34
|
+
|
|
35
|
+
Tagged and released on GitHub, but never published to PyPI: the upload was rejected because the `1.6.0` filename had been used by a previously deleted file. Superseded by 1.6.1, which carries the same changes.
|
|
36
|
+
|
|
9
37
|
## [1.5.4] 2026-06-14
|
|
10
38
|
|
|
11
39
|
### Changed
|
|
@@ -84,13 +112,13 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
84
112
|
## [1.3.1] - 2026-02-13
|
|
85
113
|
|
|
86
114
|
### Fixed
|
|
87
|
-
- `tests/test_metadata_sync.py`: replaced `importlib.metadata` lookups with direct `pyproject.toml` reads
|
|
115
|
+
- `tests/test_metadata_sync.py`: replaced `importlib.metadata` lookups with direct `pyproject.toml` reads - tests no longer fail when the package is not installed in the test environment (uvx)
|
|
88
116
|
- Makefile `dev` target now correctly installs dev extras (`uv pip install -e ".[dev]"`)
|
|
89
117
|
|
|
90
118
|
### Changed
|
|
91
119
|
- CLAUDE.md: updated project structure trees to reflect actual codebase (added `entry.py`, `domain/errors.py`, `adapters/memory/`, `adapters/config/permissions.py`, `adapters/email/validation.py`; removed deleted `traceback.py`)
|
|
92
120
|
- CLAUDE.md: rewrote Make targets table to match new `bmk`-based Makefile (added aliases, new targets; removed obsolete `menu`, `test-slow`)
|
|
93
|
-
- CLAUDE.md: corrected versioning documentation
|
|
121
|
+
- CLAUDE.md: corrected versioning documentation - runtime metadata is served from `__init__conf__.py` constants, not `importlib.metadata`
|
|
94
122
|
- CLAUDE.md: replaced stale `scripts/` instrumentation section with `bmk` delegation note
|
|
95
123
|
- CLAUDE.md: updated `make test-slow` references to `make testintegration`
|
|
96
124
|
|
|
@@ -122,17 +150,17 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
122
150
|
- Profile name requirements documentation in CONFIG.md and README.md
|
|
123
151
|
|
|
124
152
|
### Removed
|
|
125
|
-
- Custom `_PROFILE_PATTERN` regex
|
|
153
|
+
- Custom `_PROFILE_PATTERN` regex - replaced by lib_layered_config's built-in validation
|
|
126
154
|
|
|
127
155
|
## [1.2.0] - 2026-01-30
|
|
128
156
|
|
|
129
157
|
### Added
|
|
130
158
|
- **Attachment security settings** for email configuration (`[email.attachments]` section in `50-mail.toml`)
|
|
131
|
-
- `allowed_extensions` / `blocked_extensions`
|
|
132
|
-
- `allowed_directories` / `blocked_directories`
|
|
133
|
-
- `max_size_bytes`
|
|
134
|
-
- `allow_symlinks`
|
|
135
|
-
- `raise_on_security_violation`
|
|
159
|
+
- `allowed_extensions` / `blocked_extensions` - whitelist/blacklist file extensions
|
|
160
|
+
- `allowed_directories` / `blocked_directories` - whitelist/blacklist attachment source directories
|
|
161
|
+
- `max_size_bytes` - maximum attachment file size (default 25 MiB, 0 to disable)
|
|
162
|
+
- `allow_symlinks` - whether symbolic links are permitted (default false)
|
|
163
|
+
- `raise_on_security_violation` - raise or skip on violations (default true)
|
|
136
164
|
- New `EmailConfig` fields for attachment security with Pydantic validators
|
|
137
165
|
- `load_email_config_from_dict()` now flattens nested `[email.attachments]` section
|
|
138
166
|
|
|
@@ -145,17 +173,17 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
145
173
|
- Coverage SQLite "database is locked" errors on Python 3.14 free-threaded builds and network mounts (SMB/NFS)
|
|
146
174
|
- Removed bogus `COVERAGE_NO_SQL=1` environment variable from `scripts/test.py` (not a real coverage.py setting)
|
|
147
175
|
- CI workflow now sets `COVERAGE_FILE` to `runner.temp` so coverage always writes to local disk
|
|
148
|
-
- **Import-linter was a silent no-op** in `make test` / `make push`
|
|
176
|
+
- **Import-linter was a silent no-op** in `make test` / `make push` - `python -m importlinter.cli lint` silently exits 0 without checking; replaced with `lint-imports` (the working console entry point)
|
|
149
177
|
- CI/local parameter mismatches: ruff now targets `.` (not hardcoded `src tests notebooks`), pytest uses `python -m pytest` with `--cov=src/$PACKAGE_MODULE`, `--cov-fail-under=90`, and `-vv` matching local runs
|
|
150
178
|
- `scripts/test.py` bandit source path now reads `src-path` from `[tool.scripts.test]` instead of hardcoding `Path("src")`
|
|
151
179
|
- `scripts/test.py` module-level `_default_env` now rebuilt with configured `src_path` before running checks
|
|
152
180
|
- `run_slow_tests()` now reads pytest verbosity from `[tool.scripts.test].pytest-verbosity` instead of hardcoding `"-vv"`
|
|
153
181
|
|
|
154
182
|
### Changed
|
|
155
|
-
- **pyproject.toml as single source of truth**: CI workflow extracts all tool configuration (src-path, pytest-verbosity, coverage-report-file, fail_under, bandit skips) from `pyproject.toml` via metadata step
|
|
183
|
+
- **pyproject.toml as single source of truth**: CI workflow extracts all tool configuration (src-path, pytest-verbosity, coverage-report-file, fail_under, bandit skips) from `pyproject.toml` via metadata step - workflow is portable across projects without editing
|
|
156
184
|
- `scripts/test.py` removed module-level `PACKAGE_SRC` constant; bandit source path computed from `config.src_path` inside the functions that need it
|
|
157
185
|
- `make push` now accepts an unquoted message as trailing words (e.g. `make push fix typo in readme`); commit message format is `<version> - <message>`, defaulting to `<version> - chores` when no message is given
|
|
158
|
-
- Removed interactive commit-message prompt from `push.py`
|
|
186
|
+
- Removed interactive commit-message prompt from `push.py` - message is either provided via CLI args / `COMMIT_MESSAGE` env var, or defaults to `"chores"`
|
|
159
187
|
|
|
160
188
|
### Added
|
|
161
189
|
- `pytest_configure` hook in `tests/conftest.py` that redirects coverage data to `tempfile.gettempdir()` and purges stale SQLite journal files before each run
|
|
@@ -165,8 +193,8 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
165
193
|
### Fixed
|
|
166
194
|
- CLAUDE.md: replaced stale package name `bitranox_template_cli_app_config_log_mail` with `bitranox_template_py_cli` throughout
|
|
167
195
|
- Brittle SMTP mock assertions in `test_cli.py` now use structured `call_args` attributes instead of `str()` coercion
|
|
168
|
-
- Stale docstring in `__init__conf__.py` claiming "adapters/platform layer"
|
|
169
|
-
- Weak OR assertion in `test_cli.py` for SMTP host display
|
|
196
|
+
- Stale docstring in `__init__conf__.py` claiming "adapters/platform layer" - corrected to "Package-level metadata module"
|
|
197
|
+
- Weak OR assertion in `test_cli.py` for SMTP host display - replaced with two independent assertions
|
|
170
198
|
- Removed stale `# type: ignore[reportUnknownVariableType]` from `sender.py` (`btx_lib_mail.ConfMail` now has proper type annotations)
|
|
171
199
|
- Late function-body imports in `adapters/cli/commands/config.py` moved to module-level for consistency
|
|
172
200
|
|
|
@@ -219,7 +247,7 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
219
247
|
- Precompile all regex patterns in `scripts/` as module-level constants for consistent compilation
|
|
220
248
|
- **LIBRARIES**: Replace custom redaction/validation with `lib_layered_config` redaction API and `btx_lib_mail` validators; bump both libraries
|
|
221
249
|
- **LIBRARIES**: Replace stdlib `json` with `orjson`; replace `urllib` with `httpx` in scripts
|
|
222
|
-
- **ARCHITECTURE**: Purified domain layer
|
|
250
|
+
- **ARCHITECTURE**: Purified domain layer - `emit_greeting()` renamed to `build_greeting()` (returns `str`, no I/O); decoupled `display.py` from Click
|
|
223
251
|
- **DATA ARCHITECTURE**: Consolidated `EmailConfig` into single Pydantic `BaseModel` (eliminated dataclass conversion chain)
|
|
224
252
|
|
|
225
253
|
## [1.0.0] - 2026-01-15
|
|
File without changes
|
|
File without changes
|
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Make Targets
|
|
4
4
|
|
|
5
|
-
| Target
|
|
6
|
-
|
|
7
|
-
| `help`
|
|
8
|
-
| `install`
|
|
9
|
-
| `dev`
|
|
10
|
-
| `test`
|
|
11
|
-
| `run`
|
|
12
|
-
| `version-current`
|
|
13
|
-
| `bump`
|
|
14
|
-
| `bump-patch`
|
|
15
|
-
| `bump-minor`
|
|
16
|
-
| `bump-major`
|
|
17
|
-
| `clean`
|
|
18
|
-
| `push`
|
|
19
|
-
| `build`
|
|
20
|
-
| `coverage`
|
|
21
|
-
| `
|
|
22
|
-
| `dependencies`
|
|
23
|
-
| `dependencies-update` | Update dependencies to latest versions
|
|
24
|
-
| `menu`
|
|
5
|
+
| Target | Description |
|
|
6
|
+
|-----------------------|--------------------------------------------------------------------------------------------|
|
|
7
|
+
| `help` | Show help |
|
|
8
|
+
| `install` | Install package editable |
|
|
9
|
+
| `dev` | Install package with dev extras |
|
|
10
|
+
| `test` | Lint, type-check, run tests with coverage, upload to Codecov |
|
|
11
|
+
| `run` | Run module CLI (requires dev install or src on PYTHONPATH) |
|
|
12
|
+
| `version-current` | Print current version from pyproject.toml |
|
|
13
|
+
| `bump` | Bump version (updates pyproject.toml and CHANGELOG.md) |
|
|
14
|
+
| `bump-patch` | Bump patch version (X.Y.Z -> X.Y.(Z+1)) |
|
|
15
|
+
| `bump-minor` | Bump minor version (X.Y.Z -> X.(Y+1).0) |
|
|
16
|
+
| `bump-major` | Bump major version ((X+1).0.0) |
|
|
17
|
+
| `clean` | Remove caches, build artifacts, and coverage |
|
|
18
|
+
| `push` | Run tests, prompt for/accept a commit message, create (allow-empty) commit, push to remote |
|
|
19
|
+
| `build` | Build wheel/sdist artifacts via `python -m build` |
|
|
20
|
+
| `coverage` | Generate coverage reports |
|
|
21
|
+
| `testintegration` | Run integration tests only (SMTP, external resources) [aliases: `testi`, `ti`] |
|
|
22
|
+
| `dependencies` | Check and list project dependencies |
|
|
23
|
+
| `dependencies-update` | Update dependencies to latest versions |
|
|
24
|
+
| `menu` | Interactive TUI to run targets and edit parameters (requires dev dep: textual) |
|
|
25
25
|
|
|
26
26
|
### Target Parameters (env vars)
|
|
27
27
|
|
|
@@ -99,11 +99,11 @@ Some tests require external resources (SMTP servers, databases) and are excluded
|
|
|
99
99
|
|
|
100
100
|
### Quick Reference
|
|
101
101
|
|
|
102
|
-
| Command
|
|
103
|
-
|
|
104
|
-
| `make test`
|
|
105
|
-
| `make
|
|
106
|
-
| `pytest tests/`
|
|
102
|
+
| Command | What it runs |
|
|
103
|
+
|------------------------|------------------------------------------------|
|
|
104
|
+
| `make test` | All tests EXCEPT `local_only` (default for CI) |
|
|
105
|
+
| `make testintegration` | ONLY `local_only` integration tests |
|
|
106
|
+
| `pytest tests/` | ALL tests (no marker filter) |
|
|
107
107
|
|
|
108
108
|
### Email Integration Tests
|
|
109
109
|
|
|
@@ -116,7 +116,7 @@ To run email tests that actually send messages:
|
|
|
116
116
|
EMAIL__SMTP_HOSTS=smtp.example.com:587
|
|
117
117
|
EMAIL__FROM_ADDRESS=sender@example.com
|
|
118
118
|
EMAIL__RECIPIENTS=recipient@example.com
|
|
119
|
-
|
|
119
|
+
EMAIL__SMTP_USERNAME=your_username
|
|
120
120
|
EMAIL__SMTP_PASSWORD=your_password
|
|
121
121
|
```
|
|
122
122
|
|
|
@@ -129,7 +129,7 @@ bitranox-template-py-cli --env-file /path/to/my/.env send-notification --subject
|
|
|
129
129
|
2. **Run the integration tests**:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
make
|
|
132
|
+
make testintegration
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
3. **Or run specific email tests**:
|
|
@@ -150,7 +150,23 @@ def test_real_external_service(...):
|
|
|
150
150
|
...
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
These tests will be skipped in CI but run with `make
|
|
153
|
+
These tests will be skipped in CI but run with `make testintegration`.
|
|
154
|
+
|
|
155
|
+
### SMTP Test Seam
|
|
156
|
+
|
|
157
|
+
`send_email()` and `send_notification()` accept an optional `transport` argument, forwarded
|
|
158
|
+
to `btx_lib_mail`'s `Transport` port. Unit tests inject a double (`RecordingTransport` in
|
|
159
|
+
`tests/test_mail.py`) rather than patching `smtplib.SMTP`, and assert on delivery intent:
|
|
160
|
+
envelope sender, recipients, host failover order, credentials, and the composed payload.
|
|
161
|
+
|
|
162
|
+
Do not go back to monkeypatching `smtplib`. btx_lib_mail streams the message over the wire
|
|
163
|
+
protocol itself (MAIL/RCPT plus DATA or BDAT), so a `MagicMock` does not honour the contract
|
|
164
|
+
and breaks whenever the library changes delivery strategy.
|
|
165
|
+
|
|
166
|
+
The `SendEmail` / `SendNotification` ports in `application/ports.py` deliberately omit
|
|
167
|
+
`transport`: a delivery mechanism is an adapter concern, not something the application layer
|
|
168
|
+
should see. The adapter still satisfies the Protocol because an extra keyword parameter with
|
|
169
|
+
a default is structurally compatible. Do not add `transport` to the port.
|
|
154
170
|
|
|
155
171
|
## Development Workflow
|
|
156
172
|
|
|
File without changes
|
|
File without changes
|