nene2-python 1.8.45__tar.gz → 1.8.46__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.
- {nene2_python-1.8.45 → nene2_python-1.8.46}/PKG-INFO +1 -1
- nene2_python-1.8.46/docs/field-trials/2026-05-field-trial-175.md +271 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/pyproject.toml +1 -1
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.env.example +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.github/workflows/ci.yml +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.gitignore +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/AGENTS.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/CHANGELOG.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/CLAUDE.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/Dockerfile +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/LICENSE +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/README.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/alembic/README +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/alembic/env.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/alembic.ini +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/compose.yaml +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/de/index.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/fr/index.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/index.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/index.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/reference/api.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/roadmap.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/todo/current.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/zh/index.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/package-lock.json +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/package.json +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/__main__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/app.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/mcp.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/schema.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/database/sqlalchemy_executor.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/conftest.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/tests/scripts/test_export_openapi.py +0 -0
- {nene2_python-1.8.45 → nene2_python-1.8.46}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.46
|
|
4
4
|
Summary: NENE2 Python — minimal API framework following NENE2's design philosophy
|
|
5
5
|
Project-URL: Homepage, https://github.com/hideyukiMORI/nene2-python
|
|
6
6
|
Project-URL: Repository, https://github.com/hideyukiMORI/nene2-python
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# FT175: logging モジュール
|
|
2
|
+
|
|
3
|
+
**日付**: 2026-05-21
|
|
4
|
+
**テーマ**: `logging` モジュール — センシティブデータマスキング・LoggerAdapter・dictConfig
|
|
5
|
+
**セキュリティ診断**: なし(FT176 で実施)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
Python 標準ライブラリの `logging` モジュールを検証する。
|
|
12
|
+
CLAUDE.md で「`print()` 禁止・`logging` モジュールのみ使用」と明示されており、
|
|
13
|
+
nene2-python の根幹ポリシーに直結するモジュールである。
|
|
14
|
+
|
|
15
|
+
このFTで確認する点:
|
|
16
|
+
- `logging.Filter` によるセンシティブデータのマスキング(パスワード・トークン・カード番号)
|
|
17
|
+
- `logging.LoggerAdapter` によるリクエストIDの全ログへの付与
|
|
18
|
+
- `setup_logger()` パターン(テスト用 StringIO へのキャプチャ)
|
|
19
|
+
- `parse_log_level()` によるログレベルの安全な変換
|
|
20
|
+
- `logging.config.dictConfig` による宣言的ログ設定
|
|
21
|
+
- `capture_logs()` / `release_capture()` テストユーティリティ
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 実装したサンプルアプリ
|
|
26
|
+
|
|
27
|
+
**場所**: `/home/xi/docker/nene2-python-FT/ft175-logging/`
|
|
28
|
+
|
|
29
|
+
### 主要機能
|
|
30
|
+
|
|
31
|
+
| 関数/クラス | 概要 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `mask_sensitive(message)` | パスワード・トークン・API鍵・カード番号をマスク |
|
|
34
|
+
| `SensitiveFilter` | `logging.Filter` サブクラス — LogRecord のメッセージを自動マスク |
|
|
35
|
+
| `RequestIdAdapter` | `LoggerAdapter[Logger]` — 全ログに `[request_id]` を付与 |
|
|
36
|
+
| `setup_logger(name, level, stream)` | テスト用 StringIO 出力ロガーを作成 |
|
|
37
|
+
| `parse_log_level(level_str)` | 文字列 → `logging.DEBUG` 等の定数、不明は `INFO` |
|
|
38
|
+
| `log_event(logger, level, message, extra)` | 構造化ログエントリ(辞書)を記録して返す |
|
|
39
|
+
| `is_level_enabled(logger, level)` | ログレベルが有効かを `bool` で返す |
|
|
40
|
+
| `effective_level_name(logger)` | 有効ログレベル名を文字列で返す |
|
|
41
|
+
| `LOGGING_CONFIG` | `dictConfig` 用設定辞書(SensitiveFilter 組み込み) |
|
|
42
|
+
| `apply_logging_config()` | dictConfig を適用する |
|
|
43
|
+
| `capture_logs(logger)` | テスト用キャプチャハンドラーを追加して `(StringIO, handler)` を返す |
|
|
44
|
+
| `release_capture(logger, handler)` | キャプチャハンドラーを解放する |
|
|
45
|
+
|
|
46
|
+
マスキングパターン:
|
|
47
|
+
|
|
48
|
+
| パターン | 置換後 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `password=<4文字以上>` | `password=***` |
|
|
51
|
+
| `token: <4文字以上>` | `token: ***` |
|
|
52
|
+
| `api_key=<4文字以上>` | `api_key=***` |
|
|
53
|
+
| `\b\d{13,19}\b`(カード番号) | `****-****-****-****` |
|
|
54
|
+
|
|
55
|
+
### HTTP エンドポイント
|
|
56
|
+
|
|
57
|
+
| メソッド | パス | 概要 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| GET | `/logging/mask` | センシティブデータをマスクした文字列を返す |
|
|
60
|
+
| GET | `/logging/level` | ロガーのレベル有効判定 |
|
|
61
|
+
| POST | `/logging/event` | イベントを記録(センシティブマスク付き) |
|
|
62
|
+
| GET | `/logging/events` | 記録済みイベント一覧 |
|
|
63
|
+
| DELETE | `/logging/events` | イベント一覧をクリア |
|
|
64
|
+
| GET | `/logging/parse-level` | 文字列をログレベル数値に変換 |
|
|
65
|
+
| GET | `/logging/buffer` | インメモリバッファのログ行一覧 |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## テスト結果
|
|
70
|
+
|
|
71
|
+
**32 passed**
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
32 passed in 0.34s
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 摩擦ポイント
|
|
80
|
+
|
|
81
|
+
### F-1: `logging.StreamHandler` のジェネリック型注釈(深刻度: 低)
|
|
82
|
+
|
|
83
|
+
**事象**: `logging.StreamHandler` は `StreamHandler[StringIO]` とジェネリック型注釈できるが、
|
|
84
|
+
mypy が `# type: ignore[type-arg]` なしでは警告を出すケースがある。
|
|
85
|
+
|
|
86
|
+
**原因**: `StreamHandler` は Python 3.12 でジェネリック対応済みだが、
|
|
87
|
+
`StreamHandler` の型スタブが `Generic[TextIO]` として定義されているため
|
|
88
|
+
`StreamHandler` 単独だと型引数省略警告が出ることがある。
|
|
89
|
+
|
|
90
|
+
**対応**: 関数シグネチャに `# type: ignore[type-arg]` を追記。
|
|
91
|
+
これは mypy の型スタブ側の制約であり実装ミスではないため、CLAUDE.md 規約に従いコード添付コメントで理由を明記した。
|
|
92
|
+
|
|
93
|
+
### F-2: `LoggerAdapter.extra` の型(深刻度: 低)
|
|
94
|
+
|
|
95
|
+
**事象**: `LoggerAdapter[Logger]` の `self.extra` の型が `Mapping[str, Any] | None` なので
|
|
96
|
+
`self.extra.get("request_id")` の前に `if self.extra` のガードが必要。
|
|
97
|
+
|
|
98
|
+
**原因**: `LoggerAdapter.__init__` の `extra` パラメーターが `Mapping[str, Any] | None` で
|
|
99
|
+
初期化できるため、mypy がnullチェックを要求する。
|
|
100
|
+
|
|
101
|
+
**対応**: `request_id = self.extra.get("request_id", "-") if self.extra else "-"` で対応。
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 観察点
|
|
106
|
+
|
|
107
|
+
### 観察1: `SensitiveFilter` によるパイプライン的マスキング
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
class SensitiveFilter(logging.Filter):
|
|
111
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
112
|
+
record.msg = mask_sensitive(str(record.msg))
|
|
113
|
+
if record.args:
|
|
114
|
+
if isinstance(record.args, dict):
|
|
115
|
+
record.args = {k: mask_sensitive(str(v)) for k, v in record.args.items()}
|
|
116
|
+
elif isinstance(record.args, tuple):
|
|
117
|
+
record.args = tuple(mask_sensitive(str(a)) for a in record.args)
|
|
118
|
+
return True
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`logging.Logger.info("user %s password=%s", username, password)` のように
|
|
122
|
+
フォーマット引数を分離して渡すケースでは `record.args` をマスクする必要がある。
|
|
123
|
+
`record.msg` だけマスクしても `%s` 置換後の最終文字列に平文が現れるためである。
|
|
124
|
+
|
|
125
|
+
### 観察2: `RequestIdAdapter` でコンテキストを注入する
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
class RequestIdAdapter(logging.LoggerAdapter[logging.Logger]):
|
|
129
|
+
def process(self, msg: str, kwargs: Any) -> tuple[str, Any]:
|
|
130
|
+
request_id = self.extra.get("request_id", "-") if self.extra else "-"
|
|
131
|
+
return f"[{request_id}] {msg}", kwargs
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
FastAPI のリクエストスコープで `RequestIdAdapter` インスタンスを生成し、
|
|
135
|
+
同一リクエスト内の全ログに `[req-xxxxxxxx]` プレフィックスを付与できる。
|
|
136
|
+
`logging.getLogger()` グローバルシングルトンと異なり、スコープごとに別インスタンスを作れる。
|
|
137
|
+
|
|
138
|
+
### 観察3: `parse_log_level` の安全な変換
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
def parse_log_level(level_str: str) -> int:
|
|
142
|
+
level = logging.getLevelName(level_str.upper())
|
|
143
|
+
if not isinstance(level, int): # 不明な文字列は int ではなく文字列を返す
|
|
144
|
+
return logging.INFO
|
|
145
|
+
return level
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`logging.getLevelName("UNKNOWN")` は `"Level UNKNOWN"` という文字列を返す(None ではない)。
|
|
149
|
+
`isinstance(level, int)` チェックでフォールバックを実装する必要がある。
|
|
150
|
+
|
|
151
|
+
### 観察4: `dictConfig` で `SensitiveFilter` を `()` 形式でインスタンス化
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
"filters": {
|
|
155
|
+
"sensitive": {
|
|
156
|
+
"()": SensitiveFilter, # クラスを直接参照してインスタンス化
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`logging.config.dictConfig` では `"()"` キーを使ってカスタムクラスのコンストラクターを呼べる。
|
|
162
|
+
`class` キーは `logging` 組み込みクラスのみに使われ、カスタムクラスには `()` を使う。
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## nene2-python フレームワークとの統合
|
|
167
|
+
|
|
168
|
+
- nene2-python の `nene2.log` には `structlog` ベースの設定が既に存在する。
|
|
169
|
+
`logging` モジュールの `SensitiveFilter` パターンは `structlog` の `processor` と
|
|
170
|
+
役割が対応する(`structlog` では `ProcessorFormatter` で同様のマスキングが可能)
|
|
171
|
+
- CLAUDE.md の「`logging` モジュールのみ使用(`print()` 禁止)」を実証する FT となった
|
|
172
|
+
- `RequestIdAdapter` と nene2 の `RequestIdMiddleware` が生成する `x-request-id` を
|
|
173
|
+
連動させることで、ログとHTTPレスポンスの追跡IDを統一できる
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Developer Experience (DX) Review
|
|
178
|
+
|
|
179
|
+
### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
|
|
180
|
+
|
|
181
|
+
`logging.basicConfig(level=logging.DEBUG)` から入る人には、
|
|
182
|
+
`logging.Filter` のサブクラス化は最初のハードルになる。
|
|
183
|
+
|
|
184
|
+
**ドキュメント理解**: `filter()` が `True` を返すとログ通過、`False` で破棄という
|
|
185
|
+
Python の慣習は直感と逆に感じる場合がある(True = 「フィルターを通す」という意味)。
|
|
186
|
+
**事故リスク**: 高。`record.args` のマスクを忘れてフォーマット引数に平文パスワードが
|
|
187
|
+
残るパターンは気づきにくい。`SensitiveFilter` のような共通フィルターを強制するのが安全。
|
|
188
|
+
**規約の使いやすさ**: `setup_logger()` ファクトリ関数でテスト・本番の切り替えができる。
|
|
189
|
+
|
|
190
|
+
### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
|
|
191
|
+
|
|
192
|
+
`print()` から `logging` への移行は「なぜ面倒なことをするのか」と感じやすい層。
|
|
193
|
+
センシティブデータの事故事例を見せることが動機付けに有効。
|
|
194
|
+
|
|
195
|
+
**コピペ可能性**: `setup_logger()` はそのままコピーして使える。
|
|
196
|
+
**拡張時の罠**: `logger.propagate = False` を忘れると親ロガーにも出力され、
|
|
197
|
+
二重ログ・センシティブデータ漏洩につながる。
|
|
198
|
+
**セキュリティ的な事故リスク**: 高。パスワードをログに書いて CloudWatch / Datadog に流れた
|
|
199
|
+
インシデントは実際に多数報告されている。`SensitiveFilter` は必須。
|
|
200
|
+
|
|
201
|
+
### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
|
|
202
|
+
|
|
203
|
+
Node.js の `winston` / `pino` と比較すると Python の `logging` は低レベル API に感じるが、
|
|
204
|
+
`dictConfig` による宣言的設定で同様の構成が可能。
|
|
205
|
+
|
|
206
|
+
**エラーレスポンスの質**: `/logging/mask` エンドポイントで
|
|
207
|
+
「original_length(入力長)+ masked(マスク後)」を返す設計でクライアント側が動作確認しやすい。
|
|
208
|
+
**Python 固有概念の学習コスト**: `LogRecord` の `msg` と `args` の分離は
|
|
209
|
+
Python %-style フォーマットの知識が必要で、JS 開発者には説明が必要。
|
|
210
|
+
**事故リスク**: 中。HTTP API として使う分には Pydantic が入力を保護している。
|
|
211
|
+
|
|
212
|
+
### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
|
|
213
|
+
|
|
214
|
+
Django の `LOGGING` 設定辞書と `dictConfig` はほぼ同じ形式なので即理解できる。
|
|
215
|
+
`SensitiveFilter` の `record.args` マスクは見落としがちなポイントで評価が高い。
|
|
216
|
+
|
|
217
|
+
**他フレームワークとの差異**: Django の `LOGGING` は `dictConfig` のラッパー。
|
|
218
|
+
FastAPI では自前で `logging.config.dictConfig()` を `lifespan` で呼ぶ必要がある。
|
|
219
|
+
**nene2-python の薄さへの評価**: `structlog` との共存方法を明確にするドキュメントがほしい。
|
|
220
|
+
**本番投入可能性**: `SensitiveFilter` + `RequestIdAdapter` のペアは本番環境でそのまま使える品質。
|
|
221
|
+
|
|
222
|
+
### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
|
|
223
|
+
|
|
224
|
+
`record.args` のマスク実装が適切。ただし `%` フォーマット以外(`{}`形式)への対応が未検討。
|
|
225
|
+
|
|
226
|
+
**コードレビューチェックポイント**:
|
|
227
|
+
- [x] `SensitiveFilter.filter()` が `record.args` も処理しているか — OK
|
|
228
|
+
- [x] `mask_sensitive()` の正規表現が ReDoS リスクを持っていないか — `[^\s,\"'&;]{4,}` は量指定子が単純で爆発しない ✅
|
|
229
|
+
- [ ] `logging.LogRecord.args` が `dict` / `tuple` 以外の型(例: `int`)のとき `isinstance` チェックが抜ける — `else` ブロックなし(稀なケースで問題発生する可能性)
|
|
230
|
+
- [ ] `_LOGGED_EVENTS: list[dict]` がモジュールレベルのグローバル変数 — 複数 `create_app()` 呼び出し時に状態が混在する
|
|
231
|
+
|
|
232
|
+
**チームでの安全な共有パターン**: `SensitiveFilter` を `nene2.log` に組み込み、
|
|
233
|
+
デフォルトで全ロガーに適用される設計が理想。
|
|
234
|
+
**ツール追加の必要性**: `ruff S314` (print statement) は `print()` を禁止するが、
|
|
235
|
+
Python 3 では `print` は関数なので別アプローチ(プリコミットフック等)が必要。
|
|
236
|
+
|
|
237
|
+
### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
|
|
238
|
+
|
|
239
|
+
CLAUDE.md の「`logging` モジュールのみ使用」ポリシーを、実際の使用パターンとして実証した。
|
|
240
|
+
|
|
241
|
+
**ポリシー達成度**: 高
|
|
242
|
+
**「初心者でも安全な API」達成度**: 中(`record.args` マスク忘れのリスクがある)
|
|
243
|
+
**設計上の負債・ドキュメント不足**:
|
|
244
|
+
- `nene2.log` の `structlog` 設定と標準 `logging` の共存について ADR が必要
|
|
245
|
+
- `SensitiveFilter` を `nene2-python` フレームワーク本体に組み込む価値がある
|
|
246
|
+
- `LOGGING_CONFIG` 辞書は `nene2.log` の設定と統一すべき
|
|
247
|
+
|
|
248
|
+
**Follow-up Issue 候補**: `SensitiveFilter` を `nene2.middleware` または `nene2.log` に追加
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Follow-up Issues
|
|
253
|
+
|
|
254
|
+
| 優先度 | タイトル | 種別 |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| 中 | `SensitiveFilter` を `nene2.log` または `nene2.middleware` に追加 | feat |
|
|
257
|
+
| 中 | `structlog` と標準 `logging` の共存 ADR を作成 | docs |
|
|
258
|
+
| 低 | `SensitiveFilter.filter()` の `record.args` が dict/tuple 以外のとき `str` として処理する | fix |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## まとめ
|
|
263
|
+
|
|
264
|
+
FT175 では `logging` モジュールの実践的パターンを実証した。
|
|
265
|
+
`SensitiveFilter` による `record.args` を含む完全マスキング、
|
|
266
|
+
`RequestIdAdapter` によるリクエストスコープのコンテキスト注入、
|
|
267
|
+
`dictConfig` による宣言的設定が核心。
|
|
268
|
+
`record.args` のマスク忘れは実際の本番事故につながるため、
|
|
269
|
+
`SensitiveFilter` を nene2-python フレームワーク本体に取り込むことを検討すべき。
|
|
270
|
+
|
|
271
|
+
次の FT176 は 176 % 3 = 2 → セキュリティ診断なし。176 % 4 = 0 → クラッカーペンテスト実施。
|
|
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
|
{nene2_python-1.8.45 → nene2_python-1.8.46}/alembic/versions/001_create_notes_and_tags_tables.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
|
|
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
|
|
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
|