From 9cd2de4ffafa5aaa26079df93cc526aa0ad9b1ea Mon Sep 17 00:00:00 2001 From: hideyukiMORI Date: Wed, 20 May 2026 00:10:53 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=85=A8=E4=BD=93=E3=81=AE=E7=9B=A3=E6=9F=BB?= =?UTF-8?q?=E3=83=BB=E5=A4=9A=E8=A7=92=E7=9A=84=E4=BF=AE=E6=AD=A3=EF=BC=88?= =?UTF-8?q?3=E3=83=A9=E3=82=A6=E3=83=B3=E3=83=89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 1 — 致命的な誤りと古いコマンド: - configure-auth.md (EN/JA): BEARER_TOKENS/API_KEYS をJSON配列形式に修正 (カンマ区切りはJSONDecodeErrorになる) - ja/reference/configuration.md: .env例のCORS_ORIGINS/BEARER_TOKENSをJSON配列形式に修正 - design-philosophy.md (EN/JA): 削除済みの `uv run export-openapi` を `uv run python src/scripts/export_openapi.py` に修正 - 全言語のテスト数を167+に統一(EN:165+, JA/DE/FR/ZH/PT-BR:135+ がすべて古かった) - sqlalchemy-repository.md (EN): セクション番号の飛び(4→6)を修正(→5)、末尾の孤立```を削除 - todo/current.md: v0.2.0作業中のTODOリスト(全タスク未完のまま放置)をv1.x完了後の状態に更新 Round 2 — 抜けと不整合: - new-project.md (EN): BearerTokenMiddleware/ApiKeyAuthMiddleware/CORSMiddlewareのimport文を追加 - configure-auth.md (EN/JA): TokenIssuerProtocol セクションをEN版に追加しJA版と整合 - configure-auth.md (EN/JA): importパスを nene2.auth トップレベルに統一 - architecture.md (EN/JA): schema.pyの説明に「新規プロジェクトはドメイン別ensure_schema」の注記追加 - ja/how-to/run-tests.md: テスト構造のサブディレクトリをEN版に合わせて列挙 - ja/how-to/sqlalchemy-repository.md: Section 5「transactional()を使った原子的マルチライト」を追加 (EN版には既に存在したがJA版に欠落していた) Round 3 — JA版の欠けているページ作成: - docs/ja/how-to/new-project.md: 新規作成(EN版 new-project.md の完全日本語翻訳) Co-Authored-By: Claude Sonnet 4.6 --- docs/de/tutorials/getting-started.md | 2 +- docs/explanation/architecture.md | 2 +- docs/explanation/design-philosophy.md | 2 +- docs/fr/tutorials/getting-started.md | 2 +- docs/how-to/configure-auth.md | 23 +++- docs/how-to/new-project.md | 3 + docs/how-to/sqlalchemy-repository.md | 3 +- docs/ja/explanation/architecture.md | 2 +- docs/ja/explanation/design-philosophy.md | 2 +- docs/ja/how-to/configure-auth.md | 9 +- docs/ja/how-to/new-project.md | 162 +++++++++++++++++++++++ docs/ja/how-to/run-tests.md | 5 +- docs/ja/how-to/sqlalchemy-repository.md | 115 ++++++++++++++++ docs/ja/reference/configuration.md | 4 +- docs/ja/tutorials/getting-started.md | 2 +- docs/pt-br/tutorials/getting-started.md | 2 +- docs/todo/current.md | 117 +++------------- docs/tutorials/getting-started.md | 2 +- docs/zh/tutorials/getting-started.md | 2 +- 19 files changed, 339 insertions(+), 122 deletions(-) create mode 100644 docs/ja/how-to/new-project.md diff --git a/docs/de/tutorials/getting-started.md b/docs/de/tutorials/getting-started.md index 1e12dbd..1f1513c 100644 --- a/docs/de/tutorials/getting-started.md +++ b/docs/de/tutorials/getting-started.md @@ -47,7 +47,7 @@ curl http://localhost:8080/notes uv run pytest ``` -Mehr als 135 Tests sollten erfolgreich sein. +Mehr als 167 Tests sollten erfolgreich sein. ## Nächste Schritte diff --git a/docs/explanation/architecture.md b/docs/explanation/architecture.md index ab79868..8637080 100644 --- a/docs/explanation/architecture.md +++ b/docs/explanation/architecture.md @@ -55,7 +55,7 @@ async def create_note(body: CreateNoteBody) -> JSONResponse: - SQLAlchemy Core (no ORM) with parameterised queries - Queries are executed via `SqlAlchemyQueryExecutor` -- Table schema is managed centrally in `src/example/schema.py` +- Table schema: the example app uses a central `src/example/schema.py`; for new projects, define `ensure_schema()` in each domain's `sqlalchemy_repository.py` and call each from `create_app()` ## Middleware stack diff --git a/docs/explanation/design-philosophy.md b/docs/explanation/design-philosophy.md index 38780c7..39db826 100644 --- a/docs/explanation/design-philosophy.md +++ b/docs/explanation/design-philosophy.md @@ -6,7 +6,7 @@ nene2-python shares the same design philosophy as PHP NENE2. ### API First -The JSON API contract and OpenAPI schema are defined before the database schema. Use `uv run export-openapi` to export a static `openapi.yaml` at any time. +The JSON API contract and OpenAPI schema are defined before the database schema. Use `uv run python src/scripts/export_openapi.py` to export a static `openapi.yaml` at any time. ### Thin HTTP layer diff --git a/docs/fr/tutorials/getting-started.md b/docs/fr/tutorials/getting-started.md index e44010c..bb0afcf 100644 --- a/docs/fr/tutorials/getting-started.md +++ b/docs/fr/tutorials/getting-started.md @@ -47,7 +47,7 @@ curl http://localhost:8080/notes uv run pytest ``` -Plus de 135 tests doivent tous réussir. +Plus de 167 tests doivent tous réussir. ## Étapes suivantes diff --git a/docs/how-to/configure-auth.md b/docs/how-to/configure-auth.md index a97c1c9..f917a43 100644 --- a/docs/how-to/configure-auth.md +++ b/docs/how-to/configure-auth.md @@ -10,7 +10,7 @@ Add to your `.env` file: ```dotenv BEARER_TOKEN_ENABLED=true -BEARER_TOKENS=token1,token2,token3 +BEARER_TOKENS=["token1","token2","token3"] ``` ### Behaviour @@ -31,7 +31,7 @@ curl -H "Authorization: Bearer token1" http://localhost:8080/notes ```dotenv API_KEY_ENABLED=true -API_KEYS=key1,key2 +API_KEYS=["key1","key2"] ``` ### Behaviour @@ -64,8 +64,7 @@ client = TestClient(create_app(AppSettings(bearer_token_enabled=False))) Implement `TokenVerifierProtocol` and raise `TokenVerificationException` on failure. ```python -from nene2.auth import TokenVerificationException -from nene2.auth.interfaces import TokenVerifierProtocol +from nene2.auth import TokenVerificationException, TokenVerifierProtocol import jwt class JwtTokenVerifier: @@ -81,3 +80,19 @@ class JwtTokenVerifier: ``` Pass your verifier directly to `BearerTokenMiddleware`. + +## Custom TokenIssuer (e.g. JWT) + +Implement `TokenIssuerProtocol` to issue tokens (e.g. for a login endpoint). + +```python +from nene2.auth import TokenIssuerProtocol +import jwt + +class JwtTokenIssuer: + def __init__(self, secret: str) -> None: + self._secret = secret + + def issue(self, claims: dict[str, object]) -> str: + return jwt.encode(claims, self._secret, algorithm="HS256") +``` diff --git a/docs/how-to/new-project.md b/docs/how-to/new-project.md index b1be4c9..745a568 100644 --- a/docs/how-to/new-project.md +++ b/docs/how-to/new-project.md @@ -55,6 +55,9 @@ Create `src/app.py`: from fastapi import FastAPI from fastapi.exceptions import RequestValidationError +from fastapi.middleware.cors import CORSMiddleware + +from nene2.auth import ApiKeyAuthMiddleware, BearerTokenMiddleware, LocalTokenVerifier from nene2.config import AppSettings from nene2.log import setup_logging from nene2.middleware import ErrorHandlerMiddleware diff --git a/docs/how-to/sqlalchemy-repository.md b/docs/how-to/sqlalchemy-repository.md index 9d1f89b..f35a564 100644 --- a/docs/how-to/sqlalchemy-repository.md +++ b/docs/how-to/sqlalchemy-repository.md @@ -232,7 +232,7 @@ def _make_repo() -> SqlAlchemyBookRepository: --- -## 6. Atomic multi-write operations with `transactional()` +## 5. Atomic multi-write operations with `transactional()` When a UseCase needs to write to multiple tables atomically, use `SqlAlchemyTransactionManager.transactional()` together with `_in_tx` repository methods. @@ -344,4 +344,3 @@ class InMemoryTransactionManager(DatabaseTransactionManagerInterface): ``` > **Rollback on exception**: `SqlAlchemyTransactionManager.transactional()` uses `engine.begin()` — any exception inside the callback triggers an automatic rollback. Domain exceptions (`AccountNotFoundException`, etc.) propagate normally after rollback. -``` diff --git a/docs/ja/explanation/architecture.md b/docs/ja/explanation/architecture.md index b75e099..c09aebd 100644 --- a/docs/ja/explanation/architecture.md +++ b/docs/ja/explanation/architecture.md @@ -54,7 +54,7 @@ async def create_note(body: CreateNoteBody) -> JSONResponse: - SQLAlchemy Core(ORM なし)でパラメータ化クエリを実行 - `SqlAlchemyQueryExecutor` でクエリを抽象化 -- テーブルスキーマは `src/example/schema.py` で一元管理 +- テーブルスキーマ: example アプリは `src/example/schema.py` で一元管理。新規プロジェクトでは各ドメインの `sqlalchemy_repository.py` に `ensure_schema()` を定義し `create_app()` から順番に呼ぶ ## ミドルウェアスタック diff --git a/docs/ja/explanation/design-philosophy.md b/docs/ja/explanation/design-philosophy.md index 4bcb0f1..5ec9c41 100644 --- a/docs/ja/explanation/design-philosophy.md +++ b/docs/ja/explanation/design-philosophy.md @@ -6,7 +6,7 @@ nene2-python は PHP 版 NENE2 と同一の設計思想を持ちます。 ### API First -JSON API と OpenAPI 契約を中心に据えます。DB 設計より先に API の形を定義し、スキーマを `uv run export-openapi` で生成します。 +JSON API と OpenAPI 契約を中心に据えます。DB 設計より先に API の形を定義し、スキーマを `uv run python src/scripts/export_openapi.py` で生成します。 ### 薄い HTTP 層 diff --git a/docs/ja/how-to/configure-auth.md b/docs/ja/how-to/configure-auth.md index 7d99b8f..eb5ab4e 100644 --- a/docs/ja/how-to/configure-auth.md +++ b/docs/ja/how-to/configure-auth.md @@ -11,7 +11,7 @@ nene2-python は Bearer Token 認証と API Key 認証の 2 種類をサポー ```dotenv BEARER_TOKEN_ENABLED=true -BEARER_TOKENS=token1,token2,token3 +BEARER_TOKENS=["token1","token2","token3"] ``` ### 動作 @@ -32,7 +32,7 @@ curl -H "Authorization: Bearer token1" http://localhost:8080/notes ```dotenv API_KEY_ENABLED=true -API_KEYS=key1,key2 +API_KEYS=["key1","key2"] ``` ### 動作 @@ -66,8 +66,7 @@ client = TestClient(create_app(AppSettings(bearer_token_enabled=False))) `TokenVerifierProtocol` を実装することで、JWT や外部サービスによる検証を追加できます。 ```python -from nene2.auth.interfaces import TokenVerifierProtocol -from nene2.auth.exceptions import TokenVerificationException +from nene2.auth import TokenVerificationException, TokenVerifierProtocol import jwt class JwtTokenVerifier: @@ -89,7 +88,7 @@ class JwtTokenVerifier: `TokenIssuerProtocol` を実装して、JWT などのトークンを発行できます。 ```python -from nene2.auth.interfaces import TokenIssuerProtocol +from nene2.auth import TokenIssuerProtocol import jwt class JwtTokenIssuer: diff --git a/docs/ja/how-to/new-project.md b/docs/ja/how-to/new-project.md new file mode 100644 index 0000000..a1b148d --- /dev/null +++ b/docs/ja/how-to/new-project.md @@ -0,0 +1,162 @@ +# nene2 を使った新しいプロジェクトを作る + +このガイドは、このリポジトリを clone するのではなく、nene2 を依存関係として使う新しいプロジェクトを作成する手順を説明します。 + +## 前提条件 + +- Python 3.12+ +- [uv](https://docs.astral.sh/uv/) がインストール済み + +## 1. プロジェクトを初期化する + +```bash +mkdir my-api && cd my-api +uv init --name my-api --no-workspace +``` + +## 2. nene2 を依存関係として追加する + +GitHub からインストールします(最新の安定版): + +```bash +uv add "nene2-python @ git+https://github.com/hideyukiMORI/nene2-python.git" +``` + +## 3. プロジェクト構成 + +`src/` 以下にソースを配置します: + +``` +my-api/ + src/ + myapp/ + __init__.py + entity.py + repository.py + exceptions.py + use_case.py + handler.py + sqlalchemy_repository.py # 任意 — InMemory のみの場合は省略可 + app.py # FastAPI アプリケーションファクトリ + .env + pyproject.toml +``` + +## 4. ドメインを作る + +[新しいドメインを実装する](../tutorials/first-domain.md) チュートリアルに従ってください。 +開発中は `InMemoryXxxRepository` を使い、永続化が必要になったら `SqlAlchemyXxxRepository` に切り替えます。 + +## 5. アプリケーションを配線する + +`src/app.py` を作成します: + +```python +from fastapi import FastAPI +from fastapi.exceptions import RequestValidationError +from fastapi.middleware.cors import CORSMiddleware + +from nene2.auth import ApiKeyAuthMiddleware, BearerTokenMiddleware, LocalTokenVerifier +from nene2.config import AppSettings +from nene2.log import setup_logging +from nene2.middleware import ErrorHandlerMiddleware +from nene2.middleware.error_handler import request_validation_error_handler +from nene2.middleware.request_id import RequestIdMiddleware +from nene2.middleware.request_logging import RequestLoggingMiddleware +from nene2.middleware.request_size_limit import RequestSizeLimitMiddleware +from nene2.middleware.security_headers import SecurityHeadersMiddleware +from nene2.middleware.throttle import ThrottleMiddleware + +from myapp.exceptions import MyEntityNotFoundExceptionHandler +from myapp.handler import make_my_router +from myapp.repository import InMemoryMyRepository +from myapp.use_case import CreateMyUseCase, DeleteMyUseCase, GetMyUseCase, ListMyUseCase, UpdateMyUseCase + + +def create_app(settings: AppSettings | None = None) -> FastAPI: + if settings is None: + settings = AppSettings() + + setup_logging(app_env=settings.app_env) + + app = FastAPI(title="my-api", version="0.1.0") + + repo = InMemoryMyRepository() + app.include_router(make_my_router( + list_use_case=ListMyUseCase(repo), + get_use_case=GetMyUseCase(repo), + create_use_case=CreateMyUseCase(repo), + update_use_case=UpdateMyUseCase(repo), + delete_use_case=DeleteMyUseCase(repo), + )) + + # ミドルウェアは登録の逆順に適用されます。 + # 最内側(エラーハンドラー)を最初に、最外側(スロットル)を最後に登録します。 + app.add_middleware( + ErrorHandlerMiddleware, + debug=settings.app_debug, + domain_handlers=[MyEntityNotFoundExceptionHandler()], + ) + app.add_middleware(SecurityHeadersMiddleware) + app.add_middleware(RequestIdMiddleware) + app.add_middleware(RequestLoggingMiddleware) + app.add_middleware(RequestSizeLimitMiddleware, max_bytes=settings.max_body_size) + if settings.throttle_enabled: + app.add_middleware( + ThrottleMiddleware, + limit=settings.throttle_limit, + window=settings.throttle_window, + ) + # Auth ミドルウェア — CORS より前に登録して CORS レイヤーの内側に配置する + if settings.bearer_token_enabled: + app.add_middleware(BearerTokenMiddleware, verifier=LocalTokenVerifier(settings.bearer_tokens)) + if settings.api_key_enabled: + app.add_middleware(ApiKeyAuthMiddleware, verifier=LocalTokenVerifier(settings.api_keys)) + # CORS は最外側に配置 — 必ず最後に登録する。 + # OPTIONS preflight リクエストは Auth チェックの前に CORSMiddleware に到達しなければならない。 + # CORSMiddleware を Auth より前に登録すると、Auth が最外側になり preflight が 401 になる。 + if settings.cors_enabled: + app.add_middleware( + CORSMiddleware, + allow_origins=settings.cors_origins, + allow_credentials=settings.cors_allow_credentials, + allow_methods=settings.cors_allow_methods, + allow_headers=settings.cors_allow_headers, + ) + + # Pydantic BaseModel の検証エラーを RFC 9457 Problem Details に変換 + app.add_exception_handler(RequestValidationError, request_validation_error_handler) # type: ignore[arg-type] + + return app + + +app = create_app() +``` + +> **ミドルウェア登録順の注意:** Starlette の `add_middleware` は逆順に適用されます — 最後に登録したものが最外側のレイヤーになります。`ErrorHandlerMiddleware` を最初に登録することですべての例外をキャッチします。 + +> **CORS + Auth ルール**: `CORSMiddleware` は Auth ミドルウェアの*後に*必ず登録してください。Starlette の逆順ルールにより「最後に登録 = 最外側」となり、CORS が Auth をラップします。これによりブラウザの preflight(`OPTIONS`)リクエストが認証前に処理されます。 + +## 6. 開発サーバーを起動する + +```bash +PYTHONPATH=src uv run uvicorn app:app --reload --port 8080 +``` + +`http://localhost:8080/docs` で Swagger UI が開きます。 + +## 7. テストを実行する + +```bash +PYTHONPATH=src uv run pytest +``` + +テストのフィクスチャでは `AppSettings(throttle_enabled=False)` を使ってレートリミットを無効化します: + +```python +from fastapi.testclient import TestClient +from nene2.config import AppSettings +from app import create_app + +client = TestClient(create_app(AppSettings(throttle_enabled=False))) +``` diff --git a/docs/ja/how-to/run-tests.md b/docs/ja/how-to/run-tests.md index a079759..b1a49de 100644 --- a/docs/ja/how-to/run-tests.md +++ b/docs/ja/how-to/run-tests.md @@ -23,7 +23,10 @@ uv run pytest --cov=src --cov-report=html tests/ nene2/ フレームワークコアの単体テスト use_case/ UseCaseProtocol 準拠テスト - ... + auth/ 認証ミドルウェアとベリファイアー + database/ TransactionManager テスト + mcp/ McpHttpClient テスト + middleware/ 各ミドルウェアの単体テスト example/ note/ Note ドメインテスト test_list_notes.py UseCase 単体テスト diff --git a/docs/ja/how-to/sqlalchemy-repository.md b/docs/ja/how-to/sqlalchemy-repository.md index 5cd144d..a0ff313 100644 --- a/docs/ja/how-to/sqlalchemy-repository.md +++ b/docs/ja/how-to/sqlalchemy-repository.md @@ -222,3 +222,118 @@ def _make_repo() -> SqlAlchemyBookRepository: ensure_schema(executor) return SqlAlchemyBookRepository(executor) ``` + +--- + +## 5. `transactional()` を使った原子的マルチライト + +複数テーブルへの書き込みを原子的に行う UseCase では、`SqlAlchemyTransactionManager.transactional()` と `_in_tx` リポジトリメソッドを組み合わせます。 + +### インターフェースに `_in_tx` メソッドを定義する + +`transactional()` コールバック内からのみ呼ぶ専用メソッドを用意し、明示的な `executor` を受け取ります。 + +```python +from nene2.database import DatabaseQueryExecutorInterface +from abc import ABC, abstractmethod + +class AccountRepositoryInterface(ABC): + @abstractmethod + def find_by_id(self, account_id: int) -> Account | None: ... + + # _in_tx バリアント — transactional() コールバック内からのみ呼ぶ + @abstractmethod + def find_by_id_in_tx( + self, executor: DatabaseQueryExecutorInterface, account_id: int + ) -> Account | None: ... + + @abstractmethod + def update_balance_in_tx( + self, executor: DatabaseQueryExecutorInterface, account_id: int, delta_cents: int + ) -> None: ... +``` + +### SQLAlchemy リポジトリで `_in_tx` を実装する + +`_in_tx` メソッドは `self._executor` の代わりに渡された `executor` を使うため、同じトランザクションに参加します。 + +```python +class SqlAlchemyAccountRepository(AccountRepositoryInterface): + def __init__(self, executor: SqlAlchemyQueryExecutor) -> None: + self._executor = executor + + def find_by_id(self, account_id: int) -> Account | None: + row = self._executor.fetch_one( + "SELECT id, name, balance_cents FROM accounts WHERE id = :id", + {"id": account_id}, + ) + return self._to_entity(row) if row else None + + def find_by_id_in_tx( + self, executor: DatabaseQueryExecutorInterface, account_id: int + ) -> Account | None: + row = executor.fetch_one( + "SELECT id, name, balance_cents FROM accounts WHERE id = :id", + {"id": account_id}, + ) + return self._to_entity(row) if row else None + + def update_balance_in_tx( + self, executor: DatabaseQueryExecutorInterface, account_id: int, delta_cents: int + ) -> None: + executor.write( + "UPDATE accounts SET balance_cents = balance_cents + :delta WHERE id = :id", + {"delta": delta_cents, "id": account_id}, + ) +``` + +### UseCase に `SqlAlchemyTransactionManager` を配線する + +```python +from nene2.database import SqlAlchemyTransactionManager + +engine = create_engine(cfg.db_url, connect_args={"check_same_thread": False}) +transaction_manager = SqlAlchemyTransactionManager(engine) + +transfer_use_case = TransferUseCase(transaction_manager, account_repo, transfer_repo) +``` + +### InMemory 版での単体テスト + +InMemory 実装は executor を無視してインメモリストアに直接書き込みます。`InMemoryTransactionManager` はコールバックを no-op executor で即座に呼び出します。 + +```python +from nene2.database import DatabaseQueryExecutorInterface, DatabaseTransactionManagerInterface +from collections.abc import Callable + +class InMemoryAccountRepository(AccountRepositoryInterface): + def find_by_id_in_tx( + self, executor: DatabaseQueryExecutorInterface, account_id: int + ) -> Account | None: + return self._accounts.get(account_id) + + def update_balance_in_tx( + self, executor: DatabaseQueryExecutorInterface, account_id: int, delta_cents: int + ) -> None: + account = self._accounts[account_id] + self._accounts[account_id] = Account( + id=account.id, name=account.name, balance_cents=account.balance_cents + delta_cents + ) + +class _NoOpExecutor(DatabaseQueryExecutorInterface): + def fetch_all(self, sql: str, params: dict[str, object] | None = None) -> list[dict[str, object]]: + return [] + def fetch_one(self, sql: str, params: dict[str, object] | None = None) -> dict[str, object] | None: + return None + def write(self, sql: str, params: dict[str, object] | None = None) -> int: + return 0 + +class InMemoryTransactionManager(DatabaseTransactionManagerInterface): + def transactional[T](self, callback: Callable[[DatabaseQueryExecutorInterface], T]) -> T: + return callback(_NoOpExecutor()) + def begin(self) -> None: pass + def commit(self) -> None: pass + def rollback(self) -> None: pass +``` + +> **ロールバックのタイミング**: `SqlAlchemyTransactionManager.transactional()` は `engine.begin()` を使用します。コールバック内で例外が発生すると自動的にロールバックされます。ドメイン例外(`AccountNotFoundException` 等)はロールバック後に正常に伝播します。 diff --git a/docs/ja/reference/configuration.md b/docs/ja/reference/configuration.md index a7dea27..7117558 100644 --- a/docs/ja/reference/configuration.md +++ b/docs/ja/reference/configuration.md @@ -103,10 +103,10 @@ THROTTLE_LIMIT=100 THROTTLE_WINDOW=60 CORS_ENABLED=true -CORS_ORIGINS=https://example.com,https://app.example.com +CORS_ORIGINS=["https://example.com","https://app.example.com"] BEARER_TOKEN_ENABLED=true -BEARER_TOKENS=secret-token-1,secret-token-2 +BEARER_TOKENS=["secret-token-1","secret-token-2"] DB_ADAPTER=mysql DB_HOST=db.example.com diff --git a/docs/ja/tutorials/getting-started.md b/docs/ja/tutorials/getting-started.md index ce903a2..02462b4 100644 --- a/docs/ja/tutorials/getting-started.md +++ b/docs/ja/tutorials/getting-started.md @@ -47,7 +47,7 @@ curl http://localhost:8080/notes uv run pytest ``` -135 件以上のテストがすべて通ることを確認してください。 +167 件以上のテストがすべて通ることを確認してください。 ## 次のステップ diff --git a/docs/pt-br/tutorials/getting-started.md b/docs/pt-br/tutorials/getting-started.md index b71f052..88a5911 100644 --- a/docs/pt-br/tutorials/getting-started.md +++ b/docs/pt-br/tutorials/getting-started.md @@ -47,7 +47,7 @@ curl http://localhost:8080/notes uv run pytest ``` -Mais de 135 testes devem passar com sucesso. +Mais de 167 testes devem passar com sucesso. ## Próximos passos diff --git a/docs/todo/current.md b/docs/todo/current.md index 4931144..b1c541d 100644 --- a/docs/todo/current.md +++ b/docs/todo/current.md @@ -1,111 +1,32 @@ -# TODO — current (v0.2.0 作業) +# TODO — current -最終更新: 2026-05-19 -対象マイルストーン: v0.2.0 — Write Operations & Domain Exceptions +最終更新: 2026-05-20 +現状: **v1.x 完了済み** --- -## 優先順位ルール +## 状態サマリー -1. **ブロッカー**: CI がなければ他の作業が不安定 → GitHub Actions を最初に立てる -2. **Note フル CRUD**: 既存コードの拡張なので最小コスト -3. **ドメイン例外パターン**: Note と Tag で共通パターンを確立してから Tag に横展開 -4. **Tag**: Note の構造をコピーして展開 +v0.1.0〜v1.x のすべてのマイルストーンが完了しています。 +ロードマップの詳細は [roadmap.md](../roadmap.md) を参照してください。 --- -## v0.2.0 タスク一覧 +## 検討中の次のステップ -### 🔴 最優先(ブロッカー) - -- [ ] **GitHub Actions CI** (`feat/issue-1-github-actions-ci`) - - `.github/workflows/ci.yml` - - jobs: pytest, mypy, ruff check, ruff format --check, pip-audit - - Python matrix: 3.12 - - uv でキャッシュ - -- [ ] **`.env.example`** (`feat/issue-2-env-example`) - - 全環境変数のキー一覧(値は空またはデフォルト値) - - `.gitignore` に `.env` を追加 - ---- - -### 🟠 Note フル CRUD - -- [ ] **`NoteNotFoundException`** + `DomainExceptionHandlerProtocol` - - `src/nene2/error/domain_exception_handler.py` に Protocol 定義 - - `src/example/note/exceptions.py` に `NoteNotFoundException` - - `ErrorHandlerMiddleware` が `DomainExceptionHandlerProtocol` のリストを受け取る設計に拡張 - -- [ ] **`UpdateNoteUseCase`** + **`UpdateNoteHandler`** - - `UpdateNoteInput(note_id: int, title: str, body: str)` - - `NoteRepositoryInterface.update()` メソッドを追加 - - `InMemoryNoteRepository.update()` 実装 - - Handler: `PUT /notes/{note_id}` → 200 / 404 - -- [ ] **`DeleteNoteUseCase`** + **`DeleteNoteHandler`** - - `DeleteNoteInput(note_id: int)` - - `NoteRepositoryInterface.delete()` メソッドを追加 - - `InMemoryNoteRepository.delete()` 実装 - - Handler: `DELETE /notes/{note_id}` → 204 / 404 - ---- - -### 🟡 Tag フル CRUD - -Note の実装完了後に着手すること。構造は Note の完全な同型コピーから始める。 - -- [ ] **`Tag` Entity** + `TagRepositoryInterface` + `InMemoryTagRepository` - - `src/example/tag/entity.py`, `repository.py` - -- [ ] **`TagNotFoundException`** - -- [ ] **Tag UseCases** (5本) - - `ListTagsUseCase`, `GetTagUseCase`, `CreateTagUseCase`, `UpdateTagUseCase`, `DeleteTagUseCase` - -- [ ] **Tag Handlers** (5本) - - `GET /tags`, `GET /tags/{tag_id}`, `POST /tags`, `PUT /tags/{tag_id}`, `DELETE /tags/{tag_id}` - -- [ ] **Tag をアプリに配線** (`src/example/app.py`) - ---- - -### 🟢 Health Check - -- [ ] **`HealthCheckProtocol`** in `src/nene2/http/health.py` - - `check() -> HealthStatus` (dataclass: `status: str`, `checks: dict[str, str]`) - -- [ ] **`GET /health`** エンドポイント - - 200 `{"status": "ok"}` / 503 `{"status": "degraded", "checks": {...}}` +- **Field Trial 7**: 親子リソース / MySQL・PostgreSQL / PyPI 公開フロー(検討中) +- **WebSocket サポート**: 検討中 +- **PyPI 公開**: パッケージメタデータ整備済み(v1.x 完了後に実施予定) --- -## 作業ルール - -- 各タスクは GitHub Issue を立ててから着手 -- ブランチ名: `feat/issue-{N}-{kebab-summary}` -- PR 前に全チェック通過: `uv run pytest && uv run mypy src/ && uv run ruff check src/ tests/ && uv run ruff format --check src/ tests/ && uv run pip-audit` -- テスト: 新しい UseCase・Handler には必ず pytest を書く -- 1 PR = 1 タスク(混ぜない) - ---- - -## 完了定義(v0.2.0) - -以下が全て満たされること: - -```bash -# CI が green -uv run pytest && uv run mypy src/ && uv run ruff check src/ tests/ && uv run pip-audit +## 直近のフィールドトライアル -# 全エンドポイントが動作 -curl -X GET http://localhost:8080/notes -curl -X POST http://localhost:8080/notes -d '{"title":"T","body":"B"}' -curl -X PUT http://localhost:8080/notes/1 -d '{"title":"T2","body":"B2"}' -curl -X DELETE http://localhost:8080/notes/1 -curl -X GET http://localhost:8080/tags -curl -X POST http://localhost:8080/tags -d '{"name":"python"}' -curl -X PUT http://localhost:8080/tags/1 -d '{"name":"py"}' -curl -X DELETE http://localhost:8080/tags/1 -curl -X GET http://localhost:8080/health -``` +| FT | テーマ | 結果 | +|---|---|---| +| FT1 | InMemory CRUD + git+ インストール | 完了 ✅ | +| FT2 | SQLite 永続化 | 完了 ✅ | +| FT3 | Bearer Token 認証 + MCP stdio | 完了 ✅ | +| FT4 | MCP + SQLite 共有 / ApiKey / CORS | 完了 ✅ | +| FT5 | transactional() DX(ウォレット送金 API)| 完了 ✅ | +| FT6 | AsyncUseCaseProtocol DX(天気ダッシュボード)| 完了 ✅ | diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 9c19657..ccfd86a 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -47,7 +47,7 @@ curl http://localhost:8080/notes uv run pytest ``` -All 165+ tests should pass. +All 167+ tests should pass. ## Next steps diff --git a/docs/zh/tutorials/getting-started.md b/docs/zh/tutorials/getting-started.md index 95ff48c..963efa8 100644 --- a/docs/zh/tutorials/getting-started.md +++ b/docs/zh/tutorials/getting-started.md @@ -47,7 +47,7 @@ curl http://localhost:8080/notes uv run pytest ``` -135 个以上的测试应全部通过。 +167 个以上的测试应全部通过。 ## 下一步