Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/de/tutorials/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/explanation/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/explanation/design-philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/fr/tutorials/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 19 additions & 4 deletions docs/how-to/configure-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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")
```
3 changes: 3 additions & 0 deletions docs/how-to/new-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions docs/how-to/sqlalchemy-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
```
2 changes: 1 addition & 1 deletion docs/ja/explanation/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()` から順番に呼ぶ

## ミドルウェアスタック

Expand Down
2 changes: 1 addition & 1 deletion docs/ja/explanation/design-philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 層

Expand Down
9 changes: 4 additions & 5 deletions docs/ja/how-to/configure-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
```

### 動作
Expand All @@ -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"]
```

### 動作
Expand Down Expand Up @@ -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:
Expand All @@ -89,7 +88,7 @@ class JwtTokenVerifier:
`TokenIssuerProtocol` を実装して、JWT などのトークンを発行できます。

```python
from nene2.auth.interfaces import TokenIssuerProtocol
from nene2.auth import TokenIssuerProtocol
import jwt

class JwtTokenIssuer:
Expand Down
162 changes: 162 additions & 0 deletions docs/ja/how-to/new-project.md
Original file line number Diff line number Diff line change
@@ -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)))
```
5 changes: 4 additions & 1 deletion docs/ja/how-to/run-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 単体テスト
Expand Down
Loading
Loading