nene2-python 1.8.163__tar.gz → 1.8.164__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.163 → nene2_python-1.8.164}/.github/workflows/ci.yml +48 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/CHANGELOG.md +21 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/PKG-INFO +1 -1
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/release-and-publish.md +11 -10
- nene2_python-1.8.164/docs/how-to/run-integration-tests.md +51 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/release-and-publish.md +9 -10
- nene2_python-1.8.164/docs/ja/how-to/run-integration-tests.md +48 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/roadmap.md +4 -4
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/todo/current.md +23 -22
- {nene2_python-1.8.163 → nene2_python-1.8.164}/pyproject.toml +2 -1
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/sqlalchemy_executor.py +24 -4
- nene2_python-1.8.164/tests/integration/conftest.py +58 -0
- nene2_python-1.8.164/tests/integration/test_repository_real_db.py +65 -0
- nene2_python-1.8.164/tests/scripts/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/uv.lock +12 -1
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.env.example +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.github/workflows/docs.yml +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.github/workflows/publish.yml +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.gitignore +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.vitepress/config.mts +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.vitepress/theme/custom.css +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/.vitepress/theme/index.ts +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/AGENTS.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/CLAUDE.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/Dockerfile +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/LICENSE +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/README.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/README +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/env.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/script.py.mako +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/alembic.ini +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/compose.yaml +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0001-toolchain.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0002-clean-architecture.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0003-security-first.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0004-ai-first-design.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0005-logging.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0006-rate-limiting.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0009-mcp-design.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0010-async-use-case.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/de/index.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/de/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/explanation/architecture.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/explanation/field-trial-methodology.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-1.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-10.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-100.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-101.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-102.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-103.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-104.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-105.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-106.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-107.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-108.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-109.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-11.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-110.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-111.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-112.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-113.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-114.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-115.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-116.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-117.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-118.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-119.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-12.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-120.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-121.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-122.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-123.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-124.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-125.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-126.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-127.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-128.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-129.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-13.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-130.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-131.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-132.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-133.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-134.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-135.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-136.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-137.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-138.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-139.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-14.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-140.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-141.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-142.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-143.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-144.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-145.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-146.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-147.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-148.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-149.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-15.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-150.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-151.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-152.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-153.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-154.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-155.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-156.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-157.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-158.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-159.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-16.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-160.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-161.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-162.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-163.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-164.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-165.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-166.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-167.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-168.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-169.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-17.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-170.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-171.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-172.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-173.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-174.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-175.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-176.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-177.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-178.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-179.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-18.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-180.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-181.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-182.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-183.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-184.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-185.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-186.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-187.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-188.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-189.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-19.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-190.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-191.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-192.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-193.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-194.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-195.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-196.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-197.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-198.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-199.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-2.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-20.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-200.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-201.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-202.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-203.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-204.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-205.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-206.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-207.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-208.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-209.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-21.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-210.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-211.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-212.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-213.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-214.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-215.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-216.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-217.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-218.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-219.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-22.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-220.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-221.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-222.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-223.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-224.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-225.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-226.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-227.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-228.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-229.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-23.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-230.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-231.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-232.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-233.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-234.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-235.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-236.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-237.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-238.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-239.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-24.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-240.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-241.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-242.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-243.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-244.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-245.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-246.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-247.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-248.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-249.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-25.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-250.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-251.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-252.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-253.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-254.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-255.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-256.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-257.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-258.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-259.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-26.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-260.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-261.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-262.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-263.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-264.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-265.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-266.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-267.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-268.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-269.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-27.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-270.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-271.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-272.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-273.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-274.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-275.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-276.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-277.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-278.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-279.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-28.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-280.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-281.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-282.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-29.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-3.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-30.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-31.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-32.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-33.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-34.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-35.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-36.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-37.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-38.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-39.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-4.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-40.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-41.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-42.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-43.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-44.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-45.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-46.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-47.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-48.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-49.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-5.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-50.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-51.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-52.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-53.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-54.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-55.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-56.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-57.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-58.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-59.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-6.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-60.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-61.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-62.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-63.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-64.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-65.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-66.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-67.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-68.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-69.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-7.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-70.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-71.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-72.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-73.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-74.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-75.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-76.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-77.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-78.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-79.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-8.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-80.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-81.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-82.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-83.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-84.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-85.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-86.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-87.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-88.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-89.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-9.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-90.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-91.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-92.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-93.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-94.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-95.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-96.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-97.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-98.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/2026-05-field-trial-99.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/field-trials/INDEX.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/fr/index.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/fr/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/api-versioning.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/async-use-case.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/background-tasks.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/concurrency-patterns.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/cors.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/custom-auth-middleware.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/decimal-unicode-input.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/dependency-injection.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/domain-events.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/email-address-parsing.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/file-upload.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/lifespan-and-app-state.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/middleware-stack.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/new-project.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/problem-details.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/response-patterns.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/run-tests.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/soft-delete.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/streaming.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/structured-logging.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/validation.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/how-to/webhook.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/index.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/explanation/architecture.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/explanation/design-philosophy.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/explanation/field-trial-methodology.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/add-new-domain.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/configure-auth.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/new-project.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/run-tests.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/howto/mcp-setup.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/index.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/reference/api.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/reference/configuration.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/reference/framework-modules.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/ja/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/pt-br/index.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/pt-br/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/reference/api.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/reference/configuration.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/reference/framework-modules.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/review/2026-05-22.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/review/2026-05-23.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/templates/field-trial-report.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/tutorials/first-domain.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/zh/index.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/docs/zh/tutorials/getting-started.md +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/package-lock.json +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/package.json +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/__main__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/app.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/entity.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/handler.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/comment/use_case.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/mcp.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/async_use_case.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/entity.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/handler.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/note/use_case.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/schema.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/entity.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/handler.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/sqlalchemy_repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/example/tag/use_case.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/api_key.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/bearer_token.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/composite.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/deps.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/interfaces.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/local_bearer_jwt.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/local_issuer.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/auth/local_verifier.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/cache/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/cache/ttl.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/config/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/config/settings.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/health.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/interfaces.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/database/utils.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/context.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/etag.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/health.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/pagination.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/problem_details.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/http/query.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/log/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/log/setup.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/mcp/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/mcp/http_client.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/mcp/server.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/domain_exception.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/error_handler.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/request_id.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/request_logging.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/request_size_limit.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/security_headers.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/setup.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/middleware/throttle.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/py.typed +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/security/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/security/webhook.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/use_case/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/use_case/protocols.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/validation/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/nene2/validation/exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/scripts/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/src/scripts/export_openapi.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/conftest.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/test_comment_http.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/test_comment_repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/comment/test_comment_use_case.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/conftest.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/test_async_note_use_case.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/test_list_notes.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/note/test_note_repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/tag/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/tag/test_tag_repository.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/tag/test_tags.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_cors.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_examples_protected.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_local_throttle_default.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_mcp.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/example/test_system_routes.py +0 -0
- {nene2_python-1.8.163/tests/nene2 → nene2_python-1.8.164/tests/integration}/__init__.py +0 -0
- {nene2_python-1.8.163/tests/nene2/auth → nene2_python-1.8.164/tests/nene2}/__init__.py +0 -0
- {nene2_python-1.8.163/tests/nene2/cache → nene2_python-1.8.164/tests/nene2/auth}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_api_key.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_bearer_token.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_composite_auth.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_local_bearer_jwt.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_local_issuer.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_make_require_auth.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/auth/test_token_issuer.py +0 -0
- {nene2_python-1.8.163/tests/nene2/config → nene2_python-1.8.164/tests/nene2/cache}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/cache/test_ttl.py +0 -0
- {nene2_python-1.8.163/tests/nene2/database → nene2_python-1.8.164/tests/nene2/config}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/config/test_settings.py +0 -0
- {nene2_python-1.8.163/tests/nene2/http → nene2_python-1.8.164/tests/nene2/database}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/database/test_transaction.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/database/test_utils.py +0 -0
- {nene2_python-1.8.163/tests/nene2/log → nene2_python-1.8.164/tests/nene2/http}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_context.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_etag.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_health.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_pagination.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_problem_details.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/http/test_query.py +0 -0
- {nene2_python-1.8.163/tests/nene2/mcp → nene2_python-1.8.164/tests/nene2/log}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/log/test_setup.py +0 -0
- {nene2_python-1.8.163/tests/nene2/middleware → nene2_python-1.8.164/tests/nene2/mcp}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/mcp/test_http_client.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/mcp/test_server.py +0 -0
- {nene2_python-1.8.163/tests/nene2/security → nene2_python-1.8.164/tests/nene2/middleware}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_error_handler.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_request_id.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_request_logging.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_request_size_limit.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_security_headers.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/middleware/test_throttle.py +0 -0
- {nene2_python-1.8.163/tests/nene2/use_case → nene2_python-1.8.164/tests/nene2/security}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/security/test_webhook.py +0 -0
- {nene2_python-1.8.163/tests/nene2/validation → nene2_python-1.8.164/tests/nene2/use_case}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/use_case/test_protocols.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
- {nene2_python-1.8.163/tests/scripts → nene2_python-1.8.164/tests/nene2/validation}/__init__.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/nene2/validation/test_exceptions.py +0 -0
- {nene2_python-1.8.163 → nene2_python-1.8.164}/tests/scripts/test_export_openapi.py +0 -0
|
@@ -51,6 +51,54 @@ jobs:
|
|
|
51
51
|
# Transitive via mcp>=1.0. Re-evaluate when pyjwt releases a fix. (#280)
|
|
52
52
|
run: uv run pip-audit --ignore-vuln PYSEC-2025-183
|
|
53
53
|
|
|
54
|
+
integration-db:
|
|
55
|
+
name: Real-DB integration tests
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
# 実 PostgreSQL / MySQL に対してリポジトリ層を検証する(#747)。
|
|
58
|
+
# SQLite だけでは露見しなかった lastrowid 由来の採番バグを CI で恒常的に防ぐ。
|
|
59
|
+
services:
|
|
60
|
+
postgres:
|
|
61
|
+
image: postgres:16-alpine
|
|
62
|
+
env:
|
|
63
|
+
POSTGRES_PASSWORD: nene2
|
|
64
|
+
POSTGRES_DB: nene2_test
|
|
65
|
+
ports:
|
|
66
|
+
- 5432:5432
|
|
67
|
+
options: >-
|
|
68
|
+
--health-cmd "pg_isready -U postgres"
|
|
69
|
+
--health-interval 5s --health-timeout 5s --health-retries 10
|
|
70
|
+
mysql:
|
|
71
|
+
image: mysql:8
|
|
72
|
+
env:
|
|
73
|
+
MYSQL_ROOT_PASSWORD: nene2
|
|
74
|
+
MYSQL_DATABASE: nene2_test
|
|
75
|
+
ports:
|
|
76
|
+
- 3306:3306
|
|
77
|
+
options: >-
|
|
78
|
+
--health-cmd "mysqladmin ping -uroot -pnene2"
|
|
79
|
+
--health-interval 5s --health-timeout 5s --health-retries 20
|
|
80
|
+
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/checkout@v4
|
|
83
|
+
|
|
84
|
+
- name: Install uv
|
|
85
|
+
uses: astral-sh/setup-uv@v5
|
|
86
|
+
with:
|
|
87
|
+
version: "latest"
|
|
88
|
+
enable-cache: true
|
|
89
|
+
|
|
90
|
+
- name: Set up Python
|
|
91
|
+
run: uv python install 3.14
|
|
92
|
+
|
|
93
|
+
- name: Install dependencies
|
|
94
|
+
run: uv sync --all-extras
|
|
95
|
+
|
|
96
|
+
- name: integration tests (PostgreSQL + MySQL)
|
|
97
|
+
env:
|
|
98
|
+
NENE2_TEST_POSTGRES_URL: postgresql+psycopg2://postgres:nene2@127.0.0.1:5432/nene2_test
|
|
99
|
+
NENE2_TEST_MYSQL_URL: mysql+pymysql://root:nene2@127.0.0.1:3306/nene2_test
|
|
100
|
+
run: uv run pytest tests/integration/ -v --no-cov
|
|
101
|
+
|
|
54
102
|
package-build:
|
|
55
103
|
name: Package build verification
|
|
56
104
|
runs-on: ubuntu-latest
|
|
@@ -8,6 +8,27 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [1.8.164] — 2026-05-29
|
|
12
|
+
|
|
13
|
+
実データベース(PostgreSQL / MySQL)統合テストを追加し、マルチDB対応を実測で検証 (#747)。
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `SqlAlchemyQueryExecutor.write()` / `_BoundQueryExecutor.write()` の INSERT 採番を
|
|
17
|
+
全方言で正しく返すよう修正。psycopg2 は `lastrowid` 非対応のため、PostgreSQL では
|
|
18
|
+
`lastval()` フォールバックを使う(従来は `rowcount` を返し、`save()` が常に `1` を
|
|
19
|
+
返す重大バグだった)。SQLite / MySQL は従来どおり `lastrowid`。
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- 実DB統合テスト `tests/integration/`(PostgreSQL / MySQL)。環境変数
|
|
23
|
+
`NENE2_TEST_POSTGRES_URL` / `NENE2_TEST_MYSQL_URL` 設定時のみ実行、未設定ならスキップ。
|
|
24
|
+
スキーマは SQLAlchemy `Table` から方言非依存に生成。
|
|
25
|
+
- CI に `integration-db` ジョブ(postgres:16 / mysql:8 service container)を追加。
|
|
26
|
+
- how-to [`run-integration-tests.md`](docs/how-to/run-integration-tests.md)(EN/JA)を追加。
|
|
27
|
+
- `pymysql` を dev 依存に追加(`mysql+pymysql://` ドライバが未導入だった)。
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- リリース手順 how-to を「公開済み」の現実に合わせて訂正(#541 の事後整合)。
|
|
31
|
+
|
|
11
32
|
## [1.8.163] — 2026-05-29
|
|
12
33
|
|
|
13
34
|
v1.8.35〜v1.8.163 の集約リリース。フィールドトライアル網羅スイープの完了と、
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nene2-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.164
|
|
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
|
|
@@ -5,21 +5,22 @@ automated publish (TestPyPI → PyPI → GitHub Release)**. The automation lives
|
|
|
5
5
|
[`.github/workflows/publish.yml`](../../.github/workflows/publish.yml) and triggers
|
|
6
6
|
on `v*` tags via **PyPI Trusted Publishing** (OIDC — no long-lived tokens).
|
|
7
7
|
|
|
8
|
-
## One-time setup (maintainer
|
|
8
|
+
## One-time setup (maintainer — already done)
|
|
9
9
|
|
|
10
|
-
The publish workflow uses Trusted Publishing and GitHub Environments.
|
|
11
|
-
configured
|
|
10
|
+
The publish workflow uses Trusted Publishing and GitHub Environments. This is
|
|
11
|
+
**already configured** and operational; it is recorded here for reference and
|
|
12
|
+
recovery:
|
|
12
13
|
|
|
13
14
|
1. **PyPI / TestPyPI — Trusted Publisher** for `nene2-python`:
|
|
14
15
|
- Owner: `hideyukiMORI`, Repository: `nene2-python`
|
|
15
16
|
- Workflow: `publish.yml`
|
|
16
17
|
- Environment: `pypi` (on pypi.org) and `testpypi` (on test.pypi.org)
|
|
17
|
-
-
|
|
18
|
-
2. **GitHub repo → Settings → Environments**:
|
|
19
|
-
(
|
|
18
|
+
- Registered at https://pypi.org/manage/account/publishing/ and the TestPyPI equivalent.
|
|
19
|
+
2. **GitHub repo → Settings → Environments**: `testpypi` and `pypi` exist
|
|
20
|
+
(add required reviewers on `pypi` for a manual approval gate if desired).
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
The package is **published** — `pip install nene2-python` installs the latest
|
|
23
|
+
release (v1.8.163+) directly from PyPI.
|
|
23
24
|
|
|
24
25
|
## Release procedure (per release)
|
|
25
26
|
|
|
@@ -67,5 +68,5 @@ excluded by `[tool.hatch.build.targets.wheel] packages = ["src/nene2"]`).
|
|
|
67
68
|
[`docs/todo/current.md`](../todo/current.md) milestone table; `CHANGELOG.md`
|
|
68
69
|
records release-grained aggregated entries.
|
|
69
70
|
- This procedure corresponds to the **FT7-class "publish flow" trial** (#541): the
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
flow is fully operational — v1.8.163 was published to PyPI through it and is
|
|
72
|
+
installable via `pip install nene2-python`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# How to run the real-database integration tests
|
|
2
|
+
|
|
3
|
+
The default `uv run pytest` runs against SQLite / in-memory only — fast and
|
|
4
|
+
dependency-free. A separate suite under `tests/integration/` exercises the
|
|
5
|
+
repository layer against **real PostgreSQL and MySQL** servers, the dialects the
|
|
6
|
+
framework claims to support. These tests **skip** unless the corresponding URL
|
|
7
|
+
env var is set, so they never slow down the default run.
|
|
8
|
+
|
|
9
|
+
## Run locally with Docker
|
|
10
|
+
|
|
11
|
+
Start throwaway databases:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
docker run -d --name nene2-pg -e POSTGRES_PASSWORD=nene2 -e POSTGRES_DB=nene2_test \
|
|
15
|
+
-p 5432:5432 postgres:16-alpine
|
|
16
|
+
docker run -d --name nene2-mysql -e MYSQL_ROOT_PASSWORD=nene2 -e MYSQL_DATABASE=nene2_test \
|
|
17
|
+
-p 3306:3306 mysql:8
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Point the suite at them and run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export NENE2_TEST_POSTGRES_URL="postgresql+psycopg2://postgres:nene2@127.0.0.1:5432/nene2_test"
|
|
24
|
+
export NENE2_TEST_MYSQL_URL="mysql+pymysql://root:nene2@127.0.0.1:3306/nene2_test"
|
|
25
|
+
uv run pytest tests/integration/ -v --no-cov
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Set only one variable to test a single backend. With neither set, the suite is
|
|
29
|
+
skipped.
|
|
30
|
+
|
|
31
|
+
Tear down when finished:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
docker rm -f nene2-pg nene2-mysql
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## In CI
|
|
38
|
+
|
|
39
|
+
The `integration-db` job in [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml)
|
|
40
|
+
provides PostgreSQL and MySQL as service containers and runs this suite on every
|
|
41
|
+
push and PR — so dialect-specific regressions (e.g. PostgreSQL's lack of
|
|
42
|
+
`lastrowid`, which once made `save()` return the wrong PK, #747) are caught
|
|
43
|
+
automatically.
|
|
44
|
+
|
|
45
|
+
## How the schema is created
|
|
46
|
+
|
|
47
|
+
The fixture builds the schema from a SQLAlchemy `Table` definition
|
|
48
|
+
(`tests/integration/conftest.py`), so the same `Integer` autoincrement primary
|
|
49
|
+
key becomes `SERIAL` on PostgreSQL, `AUTO_INCREMENT` on MySQL, and `AUTOINCREMENT`
|
|
50
|
+
on SQLite — no hand-written per-dialect DDL. Each test gets a freshly created and
|
|
51
|
+
dropped table.
|
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
[`.github/workflows/publish.yml`](../../../.github/workflows/publish.yml) にあり、
|
|
6
6
|
`v*` タグを契機に **PyPI Trusted Publishing**(OIDC・長期トークン不要)で動く。
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 一度だけの設定(メンテナ作業・設定済み)
|
|
9
9
|
|
|
10
|
-
publish ワークフローは Trusted Publishing と GitHub Environments
|
|
11
|
-
|
|
10
|
+
publish ワークフローは Trusted Publishing と GitHub Environments を使う。これは
|
|
11
|
+
**既に構成済み・稼働中**で、参照・復旧用に記録する:
|
|
12
12
|
|
|
13
13
|
1. **PyPI / TestPyPI の Trusted Publisher**(`nene2-python`):
|
|
14
14
|
- Owner: `hideyukiMORI`、Repository: `nene2-python`
|
|
15
15
|
- Workflow: `publish.yml`
|
|
16
16
|
- Environment: `pypi`(pypi.org)と `testpypi`(test.pypi.org)
|
|
17
|
-
- https://pypi.org/manage/account/publishing/ と TestPyPI
|
|
18
|
-
2. **GitHub repo → Settings → Environments**: `testpypi` と `pypi`
|
|
17
|
+
- https://pypi.org/manage/account/publishing/ と TestPyPI の同等画面で登録済み。
|
|
18
|
+
2. **GitHub repo → Settings → Environments**: `testpypi` と `pypi` 作成済み
|
|
19
19
|
(`pypi` に必須レビュアーを設定すれば手動承認ゲートになる)。
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
パッケージは**公開済み**で、`pip install nene2-python` で最新リリース(v1.8.163 以降)
|
|
22
|
+
を PyPI から直接取得できる。
|
|
23
23
|
|
|
24
24
|
## リリース手順(毎回)
|
|
25
25
|
|
|
@@ -64,6 +64,5 @@ import 可能、配布物は `src/nene2` のみ(`example`/`tests` は
|
|
|
64
64
|
- **CHANGELOG の粒度**: 版ごとの一行サマリーは
|
|
65
65
|
[`docs/todo/current.md`](../../todo/current.md) のマイルストーン表に、
|
|
66
66
|
`CHANGELOG.md` にはリリース粒度の集約エントリを記録する。
|
|
67
|
-
- 本手順は **FT7 相当の「公開フロー」トライアル**(#541
|
|
68
|
-
|
|
69
|
-
最初の `v*` タグ作成のみ。
|
|
67
|
+
- 本手順は **FT7 相当の「公開フロー」トライアル**(#541)に対応する。フローは完全稼働済みで、
|
|
68
|
+
v1.8.163 を本フローで PyPI に公開済み(`pip install nene2-python` で取得可能)。
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 実データベース統合テストの実行方法
|
|
2
|
+
|
|
3
|
+
デフォルトの `uv run pytest` は SQLite / インメモリのみを対象とし、高速で外部依存が
|
|
4
|
+
ない。`tests/integration/` 配下の別スイートは、フレームワークが対応を謳う方言である
|
|
5
|
+
**実 PostgreSQL / MySQL** に対してリポジトリ層を検証する。これらは対応する URL の
|
|
6
|
+
環境変数が設定されている時だけ実行され、未設定なら**スキップ**される — デフォルトの
|
|
7
|
+
実行を遅くしない。
|
|
8
|
+
|
|
9
|
+
## Docker でローカル実行
|
|
10
|
+
|
|
11
|
+
捨てDBを起動:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
docker run -d --name nene2-pg -e POSTGRES_PASSWORD=nene2 -e POSTGRES_DB=nene2_test \
|
|
15
|
+
-p 5432:5432 postgres:16-alpine
|
|
16
|
+
docker run -d --name nene2-mysql -e MYSQL_ROOT_PASSWORD=nene2 -e MYSQL_DATABASE=nene2_test \
|
|
17
|
+
-p 3306:3306 mysql:8
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
スイートを向けて実行:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export NENE2_TEST_POSTGRES_URL="postgresql+psycopg2://postgres:nene2@127.0.0.1:5432/nene2_test"
|
|
24
|
+
export NENE2_TEST_MYSQL_URL="mysql+pymysql://root:nene2@127.0.0.1:3306/nene2_test"
|
|
25
|
+
uv run pytest tests/integration/ -v --no-cov
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
片方だけ設定すれば単一バックエンドのみ検証。どちらも未設定ならスキップ。
|
|
29
|
+
|
|
30
|
+
後始末:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
docker rm -f nene2-pg nene2-mysql
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## CI での実行
|
|
37
|
+
|
|
38
|
+
[`.github/workflows/ci.yml`](../../../.github/workflows/ci.yml) の `integration-db`
|
|
39
|
+
ジョブが PostgreSQL / MySQL を service container として提供し、push / PR ごとに本
|
|
40
|
+
スイートを実行する。これにより方言固有のリグレッション(例: PostgreSQL は `lastrowid`
|
|
41
|
+
を持たず、かつて `save()` が誤ったPKを返していた #747)が自動で検出される。
|
|
42
|
+
|
|
43
|
+
## スキーマの作り方
|
|
44
|
+
|
|
45
|
+
フィクスチャは SQLAlchemy の `Table` 定義(`tests/integration/conftest.py`)から
|
|
46
|
+
スキーマを生成するため、同じ `Integer` autoincrement 主キーが PostgreSQL では
|
|
47
|
+
`SERIAL`、MySQL では `AUTO_INCREMENT`、SQLite では `AUTOINCREMENT` になる — 方言別の
|
|
48
|
+
手書きDDLは不要。各テストはテーブルを作成→破棄したクリーンな状態で走る。
|
|
@@ -35,11 +35,11 @@ PHP 版 NENE2 との機能同等性を達成し、さらに Python 固有の強
|
|
|
35
35
|
- Git タグ `v1.8.N` は選択的リリース時に付与(最新タグは [GitHub Releases](https://github.com/hideyukiMORI/nene2-python/releases) 参照)。FT ループ中は pyproject が先行することがある。
|
|
36
36
|
|
|
37
37
|
### 残課題(オープン Issue 含む)
|
|
38
|
-
- ~~[#541](https://github.com/hideyukiMORI/nene2-python/issues/541) PyPI
|
|
38
|
+
- ~~[#541](https://github.com/hideyukiMORI/nene2-python/issues/541) PyPI 公開フロー~~ — ✅ 稼働中。**v1.8.163 を PyPI 公開**(`pip install nene2-python`)。[手順](how-to/release-and-publish.md)
|
|
39
39
|
- ~~[#553](https://github.com/hideyukiMORI/nene2-python/issues/553) example app に `/examples/ping`・`/examples/notes` を追加(parity)~~ — ✅ #578 で実装済み(既存確認の上 close)
|
|
40
40
|
- ~~[#539](https://github.com/hideyukiMORI/nene2-python/issues/539) handler の `response_model` 統一~~ — ✅ v1.8.161 で解消
|
|
41
41
|
- ~~[#540](https://github.com/hideyukiMORI/nene2-python/issues/540) FT ループの目的・終着点の明文化~~ — ✅ [field-trial-methodology.md](explanation/field-trial-methodology.md)
|
|
42
|
-
- PostgreSQL / MySQL 実 DB
|
|
42
|
+
- ~~[#747](https://github.com/hideyukiMORI/nene2-python/issues/747) PostgreSQL / MySQL 実 DB 統合テスト~~ — ✅ v1.8.164。CI service container で検証。psycopg2 lastrowid 非対応の採番バグを発見・修正。[手順](how-to/run-integration-tests.md)
|
|
43
43
|
- 並行系 how-to — [concurrency-patterns.md](how-to/concurrency-patterns.md)(FT188–192 知見)
|
|
44
44
|
- WebSocket サポート(検討中)
|
|
45
45
|
- PyJWT 推移的 CVE(PYSEC-2025-183、mcp 経由)— CI で ignore、修正待ち
|
|
@@ -132,8 +132,8 @@ PHP 版追跡・Python 固有の強化:
|
|
|
132
132
|
- [x] Diátaxis 構造のドキュメント整備(tutorial / howto / explanation / reference)(#43)
|
|
133
133
|
- [x] Field Trial 1〜6: InMemory / SQLite / 認証 / MCP / トランザクション / AsyncUseCase 検証完了
|
|
134
134
|
- [x] Field Trial 7〜282: stdlib フルカバレッジ + セキュリティ深掘りスイープ完了 → 保守 + オンデマンドへ([方法論](explanation/field-trial-methodology.md))
|
|
135
|
-
- [x] PyPI
|
|
136
|
-
- [
|
|
135
|
+
- [x] PyPI 公開(#541)— フロー稼働中・**v1.8.163 公開済み**(`pip install nene2-python`)。[手順](how-to/release-and-publish.md)
|
|
136
|
+
- [x] PostgreSQL / MySQL 実 DB 統合テスト(CI Docker service ジョブ、#747・v1.8.164)— [手順](how-to/run-integration-tests.md)
|
|
137
137
|
- [ ] WebSocket サポート検討
|
|
138
138
|
- [ ] 並行系 how-to ガイド(threading / asyncio / concurrent.futures 比較・選択指針)
|
|
139
139
|
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
# TODO — current
|
|
2
2
|
|
|
3
3
|
最終更新: 2026-05-29
|
|
4
|
-
現状: **v1.8.
|
|
4
|
+
現状: **v1.8.164 / 実DB統合テスト導入・マルチDB対応を実測検証 / CI グリーン**
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## 状態サマリー
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
`
|
|
14
|
-
|
|
15
|
-
- `
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
#747 に対応(v1.8.164)。「`sqlite/mysql/pgsql` 対応」という未検証の約束を実測で検証し、
|
|
11
|
+
潜んでいたマルチDBバグを修正した:
|
|
12
|
+
- **発見・修正**: `SqlAlchemyQueryExecutor.write()` の INSERT 採番が `result.lastrowid`
|
|
13
|
+
依存で、psycopg2(PostgreSQL)は lastrowid 非対応のため `rowcount`(=1) にフォールバック。
|
|
14
|
+
`save()` が常に `id=1` を返す重大バグだった → PostgreSQL では `lastval()` フォールバックに修正
|
|
15
|
+
- **実DB統合テスト** `tests/integration/`(PostgreSQL/MySQL、各9ケース)を追加。環境変数
|
|
16
|
+
未設定ならskip(デフォルト `uv run pytest` は SQLite のまま高速:466 passed, 9 skipped)
|
|
17
|
+
- **CI** に `integration-db` ジョブ(postgres:16 / mysql:8 service container)追加
|
|
18
|
+
- `pymysql` を dev 依存に追加(`mysql+pymysql://` ドライバが未導入だった)
|
|
19
|
+
- how-to [`run-integration-tests.md`](../how-to/run-integration-tests.md)(EN/JA)追加
|
|
20
|
+
- ついでに #541 のリリース how-to の「未公開・メンテナ待ち」誤記(EN/JA)を訂正
|
|
21
|
+
|
|
22
|
+
直前の対応: #541(PyPI 公開フロー解消・v1.8.163 を PyPI 公開)、#540(FT ループ目的・
|
|
23
|
+
終着点明文化・v1.8.162)、#539(response_model 統一・
|
|
22
24
|
v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキーピング(サンドボックス
|
|
23
|
-
5.1G→79M 整理・orphan ブランチ 8 本削除)。**フレームワーク本体 466 tests
|
|
25
|
+
5.1G→79M 整理・orphan ブランチ 8 本削除)。**フレームワーク本体 466 tests 据え置き。
|
|
26
|
+
オープン Issue ゼロ。**
|
|
24
27
|
|
|
25
28
|
---
|
|
26
29
|
|
|
@@ -32,11 +35,8 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
|
|
|
32
35
|
|
|
33
36
|
## オープン Issue
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | handler の response_model を統一して CLAUDE.md ポリシーに準拠 | 中 | enhancement |
|
|
38
|
-
| [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | FT ループの目的と終着点を明文化する | 中 | docs |
|
|
39
|
-
| [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | PyPI 公開フロー検証(uv publish ワークフロー) | 中 | enhancement |
|
|
38
|
+
なし(#539 / #540 / #541 / #553 はすべて 2026-05-29 に解消・close)。
|
|
39
|
+
提案 PR #545(nene-style governance)のみ未マージで残置。
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -44,6 +44,7 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
|
|
|
44
44
|
|
|
45
45
|
| バージョン | 主な内容 |
|
|
46
46
|
|---|---|
|
|
47
|
+
| v1.8.164 | feat: 実DB統合テスト(#747)— PostgreSQL/MySQL を CI service container で検証。psycopg2 lastrowid 非対応による INSERT 採番バグ(`save()` が常に id=1)を発見・修正。pymysql 追加 |
|
|
47
48
|
| v1.8.163 | feat: PyPI 公開フロー検証(#541)— ビルド検証・CI package-build ジョブ・CHANGELOG 連携・リリース how-to |
|
|
48
49
|
| v1.8.162 | docs: FT ループの目的・終着点を明文化(#540)— field-trial-methodology.md(EN/JA) |
|
|
49
50
|
| v1.8.161 | fix: handler の response_model 統一(#539)— Note/Tag/Comment に Response モデル定義・OpenAPI スキーマ出力 |
|
|
@@ -153,9 +154,8 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
|
|
|
153
154
|
|
|
154
155
|
| 優先度 | Issue | タスク | 種別 |
|
|
155
156
|
|---|---|---|---|
|
|
156
|
-
| 低 | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | 実 PyPI 公開(Trusted Publisher 設定 + v* タグ)— メンテナ作業 | enhancement |
|
|
157
157
|
| — | — | FT は保守 + オンデマンド(4 トリガー時のみ。[方法論](../explanation/field-trial-methodology.md)) | FT |
|
|
158
|
-
|
|
|
158
|
+
| — | — | リリース時は `v*` タグ push → publish.yml が自動公開([手順](../how-to/release-and-publish.md)) | infra |
|
|
159
159
|
| 低 | — | PyJWT 推移的 CVE(PYSEC-2025-183)— mcp 修正待ち | 保留 |
|
|
160
160
|
|
|
161
161
|
---
|
|
@@ -166,7 +166,8 @@ v1.8.161)、#553(#578 で実装済みを確認し close)、ハウスキー
|
|
|
166
166
|
|---|---|---|---|
|
|
167
167
|
| ~~handler response_model 未使用~~ | — | [#539](https://github.com/hideyukiMORI/nene2-python/issues/539) | ✅ 2026-05-29 解消(v1.8.161)。Note/Tag/Comment 全ハンドラーに `XxxResponse`/`XxxListResponse` を定義し `response_model` 明示。OpenAPI に 6 レスポンススキーマが出力されることを確認。466 tests 据え置き |
|
|
168
168
|
| ~~FT ループ目的の明文化~~ | — | [#540](https://github.com/hideyukiMORI/nene2-python/issues/540) | ✅ 2026-05-29 解消(v1.8.162)。explanation/field-trial-methodology.md(EN/JA)に目的・3 フェーズ・終着点(網羅スイープ完了→保守+オンデマンド)を明文化。INDEX/roadmap からリンク |
|
|
169
|
-
| PyPI
|
|
169
|
+
| ~~PyPI 公開フロー検証~~ | — | [#541](https://github.com/hideyukiMORI/nene2-python/issues/541) | ✅ 2026-05-29 解消(v1.8.163)。公開フローは既に稼働中で **v1.8.163 を PyPI へ公開**(pip install で取得可能)。CI package-build ジョブ・CHANGELOG 連携・how-to 整備 |
|
|
170
|
+
| ~~マルチDB対応が未検証~~ | — | [#747](https://github.com/hideyukiMORI/nene2-python/issues/747) | ✅ 2026-05-29 解消(v1.8.164)。実DB統合テスト(PostgreSQL/MySQL・CI service container)を追加。psycopg2 lastrowid 非対応で `save()` が常に id=1 を返す重大バグを発見・修正(lastval フォールバック)。pymysql 追加・how-to(EN/JA) |
|
|
170
171
|
| ~~古い FT サンドボックス肥大化~~ | — | — | ✅ 2026-05-29 整理(5.1G→79M)。`ft-status.sh --clean-sandbox` を追加(.venv/キャッシュ削除・ソース保持・uv sync で再生可)。`--clean` は dist/ のみで誤記だった |
|
|
171
172
|
| ~~マージ済み orphan リモートブランチ~~ | — | — | ✅ 2026-05-29 削除(merged 8 本)。未マージの提案 PR #545 のブランチのみ残置 |
|
|
172
173
|
| ログ秘匿 Filter は形式依存の best-effort | 低 | — | FT220 D3。主防御は `SecretStr`。how-to に「秘匿は多層防御の保険」を明記予定 |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nene2-python"
|
|
3
|
-
version = "1.8.
|
|
3
|
+
version = "1.8.164"
|
|
4
4
|
description = "NENE2 Python — minimal API framework following NENE2's design philosophy"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
@@ -62,6 +62,7 @@ dev = [
|
|
|
62
62
|
"ruff>=0.9",
|
|
63
63
|
"pip-audit>=2.7",
|
|
64
64
|
"psycopg2-binary>=2.9.12",
|
|
65
|
+
"pymysql>=1.1",
|
|
65
66
|
]
|
|
66
67
|
|
|
67
68
|
# ---------------------------------------------------------------------------
|
|
@@ -6,13 +6,33 @@ Supports SQLite, MySQL, and PostgreSQL via SQLAlchemy's engine URL.
|
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from sqlalchemy import Connection, Engine, text
|
|
10
|
-
from sqlalchemy.exc import IntegrityError, OperationalError
|
|
9
|
+
from sqlalchemy import Connection, CursorResult, Engine, text
|
|
10
|
+
from sqlalchemy.exc import IntegrityError, OperationalError, ProgrammingError
|
|
11
11
|
|
|
12
12
|
from .exceptions import DatabaseConnectionException, DatabaseIntegrityException
|
|
13
13
|
from .interfaces import DatabaseQueryExecutorInterface, DatabaseTransactionManagerInterface
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def _insert_id(conn: Connection, result: CursorResult[Any]) -> int:
|
|
17
|
+
"""Return the new row's PK after an INSERT, portably across dialects.
|
|
18
|
+
|
|
19
|
+
SQLite and MySQL expose the value via DBAPI ``lastrowid``. PostgreSQL
|
|
20
|
+
(psycopg2) does not, so fall back to ``lastval()`` on the same connection
|
|
21
|
+
(valid for SERIAL/IDENTITY columns within the current transaction). If no
|
|
22
|
+
sequence was touched, fall back to ``rowcount``.
|
|
23
|
+
"""
|
|
24
|
+
if result.lastrowid:
|
|
25
|
+
return result.lastrowid
|
|
26
|
+
if conn.dialect.name == "postgresql":
|
|
27
|
+
try:
|
|
28
|
+
value = conn.execute(text("SELECT lastval()")).scalar()
|
|
29
|
+
except (OperationalError, ProgrammingError):
|
|
30
|
+
return result.rowcount
|
|
31
|
+
if value is not None:
|
|
32
|
+
return int(value)
|
|
33
|
+
return result.rowcount
|
|
34
|
+
|
|
35
|
+
|
|
16
36
|
class SqlAlchemyQueryExecutor(DatabaseQueryExecutorInterface):
|
|
17
37
|
"""Execute queries using SQLAlchemy Core (connection-per-call, no ORM).
|
|
18
38
|
|
|
@@ -75,7 +95,7 @@ class SqlAlchemyQueryExecutor(DatabaseQueryExecutorInterface):
|
|
|
75
95
|
with self._engine.begin() as conn:
|
|
76
96
|
result = conn.execute(text(sql), params or {})
|
|
77
97
|
if sql.strip().upper().startswith("INSERT"):
|
|
78
|
-
return
|
|
98
|
+
return _insert_id(conn, result)
|
|
79
99
|
return result.rowcount
|
|
80
100
|
except IntegrityError as exc:
|
|
81
101
|
raise DatabaseIntegrityException(str(exc)) from exc
|
|
@@ -112,7 +132,7 @@ class _BoundQueryExecutor(DatabaseQueryExecutorInterface):
|
|
|
112
132
|
except OperationalError as exc:
|
|
113
133
|
raise DatabaseConnectionException(str(exc)) from exc
|
|
114
134
|
if sql.strip().upper().startswith("INSERT"):
|
|
115
|
-
return
|
|
135
|
+
return _insert_id(self._conn, result)
|
|
116
136
|
return result.rowcount
|
|
117
137
|
|
|
118
138
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Fixtures for real-database integration tests.
|
|
2
|
+
|
|
3
|
+
These tests exercise SqlAlchemyQueryExecutor against actual PostgreSQL / MySQL
|
|
4
|
+
servers — the dialects the framework claims to support but only ever tested on
|
|
5
|
+
SQLite. They run only when the corresponding URL env var is set, so the default
|
|
6
|
+
``uv run pytest`` stays SQLite-only and fast. CI sets the env vars via service
|
|
7
|
+
containers (see .github/workflows/ci.yml).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
from sqlalchemy import Column, Integer, MetaData, Table, Text, create_engine
|
|
15
|
+
|
|
16
|
+
from nene2.database import SqlAlchemyQueryExecutor
|
|
17
|
+
|
|
18
|
+
# backend name -> env var holding its SQLAlchemy URL
|
|
19
|
+
_BACKEND_ENV: dict[str, str] = {
|
|
20
|
+
"postgresql": "NENE2_TEST_POSTGRES_URL",
|
|
21
|
+
"mysql": "NENE2_TEST_MYSQL_URL",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Dialect-portable schema. SQLAlchemy emits SERIAL (PostgreSQL) / AUTO_INCREMENT
|
|
25
|
+
# (MySQL) from the same Integer autoincrement PK — no hand-written per-dialect DDL.
|
|
26
|
+
_metadata = MetaData()
|
|
27
|
+
Table(
|
|
28
|
+
"notes",
|
|
29
|
+
_metadata,
|
|
30
|
+
Column("id", Integer, primary_key=True, autoincrement=True),
|
|
31
|
+
Column("title", Text, nullable=False),
|
|
32
|
+
Column("body", Text, nullable=False),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _configured_backends() -> list[str]:
|
|
37
|
+
return [name for name, env in _BACKEND_ENV.items() if os.environ.get(env)]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _params() -> list[object]:
|
|
41
|
+
backends = _configured_backends()
|
|
42
|
+
if backends:
|
|
43
|
+
return list(backends)
|
|
44
|
+
reason = "no real DB configured (set NENE2_TEST_POSTGRES_URL / NENE2_TEST_MYSQL_URL)"
|
|
45
|
+
return [pytest.param(None, marks=pytest.mark.skip(reason=reason))]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture(params=_params())
|
|
49
|
+
def real_db_executor(request: pytest.FixtureRequest) -> Iterator[SqlAlchemyQueryExecutor]:
|
|
50
|
+
backend: str = request.param
|
|
51
|
+
engine = create_engine(os.environ[_BACKEND_ENV[backend]])
|
|
52
|
+
_metadata.drop_all(engine)
|
|
53
|
+
_metadata.create_all(engine)
|
|
54
|
+
try:
|
|
55
|
+
yield SqlAlchemyQueryExecutor(engine)
|
|
56
|
+
finally:
|
|
57
|
+
_metadata.drop_all(engine)
|
|
58
|
+
engine.dispose()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Note repository contract verified against real PostgreSQL / MySQL servers.
|
|
2
|
+
|
|
3
|
+
Mirrors the SQLite contract in tests/example/note/test_note_repository.py, plus a
|
|
4
|
+
regression for the multi-DB INSERT-PK bug: psycopg2 exposes no ``lastrowid``, so
|
|
5
|
+
``save()`` must derive the new PK another way (lastval) rather than returning
|
|
6
|
+
``rowcount`` (#747).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from nene2.database import SqlAlchemyQueryExecutor
|
|
12
|
+
from src.example.note.entity import Note
|
|
13
|
+
from src.example.note.sqlalchemy_repository import SqlAlchemyNoteRepository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def repo(real_db_executor: SqlAlchemyQueryExecutor) -> SqlAlchemyNoteRepository:
|
|
18
|
+
return SqlAlchemyNoteRepository(real_db_executor)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_save_returns_sequential_pks(repo: SqlAlchemyNoteRepository) -> None:
|
|
22
|
+
first = repo.save("A", "a")
|
|
23
|
+
second = repo.save("B", "b")
|
|
24
|
+
assert (first.id, second.id) == (1, 2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_save_and_find_round_trip(repo: SqlAlchemyNoteRepository) -> None:
|
|
28
|
+
note = repo.save("Hello", "World")
|
|
29
|
+
assert repo.find_by_id(note.id) == note
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_find_by_id_returns_none_when_missing(repo: SqlAlchemyNoteRepository) -> None:
|
|
33
|
+
assert repo.find_by_id(9999) is None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_find_all_respects_limit_and_offset(repo: SqlAlchemyNoteRepository) -> None:
|
|
37
|
+
for i in range(5):
|
|
38
|
+
repo.save(f"Note {i}", "body")
|
|
39
|
+
page = repo.find_all(limit=2, offset=2)
|
|
40
|
+
assert [n.title for n in page] == ["Note 2", "Note 3"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_count_reflects_saved_notes(repo: SqlAlchemyNoteRepository) -> None:
|
|
44
|
+
repo.save("T", "B")
|
|
45
|
+
repo.save("T2", "B2")
|
|
46
|
+
assert repo.count() == 2
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_update_changes_title_and_body(repo: SqlAlchemyNoteRepository) -> None:
|
|
50
|
+
note = repo.save("Old", "old body")
|
|
51
|
+
assert repo.update(note.id, "New", "new body") == Note(id=note.id, title="New", body="new body")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_update_returns_none_for_missing_id(repo: SqlAlchemyNoteRepository) -> None:
|
|
55
|
+
assert repo.update(9999, "T", "B") is None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_delete_removes_note(repo: SqlAlchemyNoteRepository) -> None:
|
|
59
|
+
note = repo.save("T", "B")
|
|
60
|
+
repo.delete(note.id)
|
|
61
|
+
assert repo.find_by_id(note.id) is None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_delete_returns_false_for_missing_id(repo: SqlAlchemyNoteRepository) -> None:
|
|
65
|
+
assert repo.delete(9999) is False
|
|
File without changes
|
|
@@ -925,7 +925,7 @@ wheels = [
|
|
|
925
925
|
|
|
926
926
|
[[package]]
|
|
927
927
|
name = "nene2-python"
|
|
928
|
-
version = "1.8.
|
|
928
|
+
version = "1.8.164"
|
|
929
929
|
source = { editable = "." }
|
|
930
930
|
dependencies = [
|
|
931
931
|
{ name = "alembic" },
|
|
@@ -958,6 +958,7 @@ dev = [
|
|
|
958
958
|
{ name = "mypy" },
|
|
959
959
|
{ name = "pip-audit" },
|
|
960
960
|
{ name = "psycopg2-binary" },
|
|
961
|
+
{ name = "pymysql" },
|
|
961
962
|
{ name = "pytest" },
|
|
962
963
|
{ name = "pytest-asyncio" },
|
|
963
964
|
{ name = "pytest-cov" },
|
|
@@ -993,6 +994,7 @@ dev = [
|
|
|
993
994
|
{ name = "mypy", specifier = ">=1.13" },
|
|
994
995
|
{ name = "pip-audit", specifier = ">=2.7" },
|
|
995
996
|
{ name = "psycopg2-binary", specifier = ">=2.9.12" },
|
|
997
|
+
{ name = "pymysql", specifier = ">=1.1" },
|
|
996
998
|
{ name = "pytest", specifier = ">=8.3" },
|
|
997
999
|
{ name = "pytest-asyncio", specifier = ">=0.24" },
|
|
998
1000
|
{ name = "pytest-cov", specifier = ">=6.0" },
|
|
@@ -1288,6 +1290,15 @@ crypto = [
|
|
|
1288
1290
|
{ name = "cryptography" },
|
|
1289
1291
|
]
|
|
1290
1292
|
|
|
1293
|
+
[[package]]
|
|
1294
|
+
name = "pymysql"
|
|
1295
|
+
version = "1.2.0"
|
|
1296
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1297
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/bc/1c6a92f385940f727daeecf3bacaf186e03875dff57197801046c583bcf0/pymysql-1.2.0.tar.gz", hash = "sha256:6c7b17ca686988104d7426c27895b455cdeea3e9d3ceb1270f0c3704fead8c33", size = 49021, upload-time = "2026-05-19T08:26:22.302Z" }
|
|
1298
|
+
wheels = [
|
|
1299
|
+
{ url = "https://files.pythonhosted.org/packages/c4/bd/2534e130295c8cfd4f0a2e31623baab7502278f1e97bcfe61db75656a77f/pymysql-1.2.0-py3-none-any.whl", hash = "sha256:62169ce6d5510f08e140c5e7990ee884a9764024e4a9a27b2cc11f1099322ae0", size = 45716, upload-time = "2026-05-19T08:26:20.974Z" },
|
|
1300
|
+
]
|
|
1301
|
+
|
|
1291
1302
|
[[package]]
|
|
1292
1303
|
name = "pyparsing"
|
|
1293
1304
|
version = "3.3.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|