Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e206ad4
Initial plan
Copilot Mar 5, 2026
cee6458
Add class-based schema notation support with InstanceValidator
Copilot Mar 5, 2026
edb2744
Extend class schema: Optional/List/Dict typing support, docs, bug fix
Copilot Mar 5, 2026
686c53e
Address review: implement coerce(), use public API, clean imports, ad…
Copilot Mar 5, 2026
14ef031
Fix Union passthrough/empty class schema/docstring; add 12 regression…
Copilot Mar 5, 2026
9c386d7
TDD: fix _is_class_schema() over-broad detection; document Union Type…
Copilot Mar 5, 2026
66a1b12
TDD cycle 2: fix Union error message, exception chaining, own-dict an…
Copilot Mar 5, 2026
0513245
TDD cycle 3: add PEP 604 union syntax (T | None) support — 13 new tes…
Copilot Mar 5, 2026
cefcb54
TDD cycle 4: Python 3.9 compat — module-level _UnionType guard + skip…
Copilot Mar 5, 2026
2417529
TDD cycle 5: Fix _UnionType annotation to Optional[type] (not type | …
Copilot Mar 5, 2026
6e91dd1
Merge main, fix _BASIC_TYPES reuse, sync docs/changelog, and align Sc…
Copilot Mar 7, 2026
d245ce0
Merge latest main docs updates and re-sync class-schema documentation
Copilot Mar 7, 2026
66959a2
Resolve latest PR conflicts and preserve class-schema docs
Copilot Mar 7, 2026
e679669
Resolve latest main conflicts and re-sync docs/runtime
Copilot Mar 7, 2026
21e36f6
Finalize main merge and clarify unsupported union docs
Copilot Mar 7, 2026
78c8c77
Merge origin/main to resolve PR conflicts
Copilot Mar 7, 2026
1197f31
Add class schema examples to example.py and improve docs/index.md
Copilot Mar 8, 2026
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
15 changes: 13 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@

## Unreleased

- 現在、未整理の変更はありません。
### Added
- クラス記法スキーマ (`class Config: ...`) を追加し、型アノテーションと `Validator` クラス属性を既存の dict スキーマ検証経路へ変換できるようになりました。
- `v.instance(type_cls)` / `InstanceValidator` を追加し、任意クラスに対する `isinstance` ベースの検証と `.coerce()` をサポートしました。

### Changed
- `Schema(...)` は実行時に dict スキーマだけでなく class 記法スキーマも直接ラップできるようになりました。
- README / `docs/index.md` / 回帰テストをクラス記法スキーマと Python 3.9+ 型ヒント対応に合わせて更新しました。

### Fixed
- クラス記法スキーマで `Optional[T]` / `Union[T, None]` が必須扱いになっていた問題を修正しました。
- `typing.Union` / PEP 604 (`T | None`) のうち、`None` 以外の複数メンバーを持つ型がサイレントにパススルーされる問題を修正し、明示的に `TypeError` を送出するようにしました。
- Python 3.9 で `types.UnionType` や `_UnionType: type | None` に起因する import / 実行時エラーが発生しないよう互換性を改善しました。
- `InstanceValidator.coerce()` が元例外を失っていた問題を修正し、例外チェーンを保持するようにしました。

## [1.2.1] - 2026-03-07

Expand All @@ -28,7 +40,6 @@
- `Schema.generate_sample()` が生成候補を各バリデータで再検証するようになり、`regex()` や `custom()` を満たせない不正なサンプルを返さず `ValueError` を送出するようになりました。

## [1.2.0] - 2026-02-27

### Added
- すべてのバリデータに `.default(value)` を追加しました。欠損キーを自動補完し、設定したフィールドは自動的に optional として扱われます。
- すべてのバリデータに `.examples(list)` を追加しました。サンプル生成やドキュメント補助に使える例を保持できます。
Expand Down
123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ ValidKit は、<strong>「直感的なスキーマ定義」と「日本語キー
* [クイックスタート](#クイックスタート)
* [API 例](#api-例)
* [高度な使い方](#高度な使い方)
* [クラス記法によるスキーマ定義](#クラス記法によるスキーマ定義)
* [IDE 補完を効かせる(TypedDict + Schema)](#ide-補完を効かせるtypeddict--schema)
* [スキーマを既存データから逆生成する(v.auto_infer)](#auto-infer)
* [部分更新とデフォルト値のマージ](#部分更新とデフォルト値のマージ)
* [マイグレーション](#マイグレーション)
* [品質管理・セキュリティ](#品質管理セキュリティ)
* [変更履歴](#変更履歴)
* [貢献ガイドライン](#貢献ガイドライン)
Expand Down Expand Up @@ -102,6 +107,7 @@ except ValidationError as e:
## 特徴

* 📝 **直感的なチェインメソッド** — `v.int().range(1, 10).optional()` のように流れるように記述。
* 🏷️ **クラス記法対応** — Python クラスの型アノテーションをそのままスキーマとして使えます。`Optional[T]`・`List[T]`・`Dict[K,V]`・カスタム型にも対応。
* 🌏 **日本語キー対応** — 日本語のキー名をそのまま扱えるため、仕様書に近いコードが書けます。
* 🧠 **型補完に強い** — `Schema[T]` と TypedDict を組み合わせると、IDE / 型チェッカーが戻り値の形を推論できます。
* 🔄 **強力な変換・マイグレーション** — 旧形式から新形式へのキー名変換や、値の動的変換を検証時に同時に行えます。
Expand All @@ -120,6 +126,7 @@ except ValidationError as e:
* `v.bool()`: 真偽値
* `v.list(schema)`: リスト(要素のスキーマを指定)
* `v.dict(key_type, value_schema)`: 辞書
* `v.instance(type_cls)`: 任意のクラスの isinstance チェック

### 修飾メソッド
* `.optional()`: 必須でないフィールドにする
Expand All @@ -135,6 +142,122 @@ except ValidationError as e:

## 高度な使い方

### クラス記法によるスキーマ定義

辞書スキーマに加えて、Python のクラス型アノテーションをそのままスキーマとして使えます。

#### 基本的なアノテーション

```python
from validkit import v, validate

class UserProfile:
name: str
age: int
score: float
active: bool

data = {"name": "Alice", "age": 30, "score": 9.5, "active": True}
result = validate(data, UserProfile)
# -> {"name": "Alice", "age": 30, "score": 9.5, "active": True}
```

#### `Optional` / `List` / `Dict` など typing モジュールの型

```python
from typing import Dict, List, Optional
from validkit import validate

class ServerConfig:
host: str
port: int
tags: Optional[List[str]] # 省略可能なリスト
metadata: Dict[str, int] # 辞書

# tags は省略してもエラーにならない
result = validate(
{"host": "db.local", "port": 5432, "metadata": {"connections": 10}},
ServerConfig,
)
# -> {"host": "db.local", "port": 5432, "metadata": {"connections": 10}}
```

Python 3.9 以降では `list[T]` / `dict[K, V]` の組み込みジェネリクス記法も使えます。

```python
class Report:
scores: list[int]
labels: dict[str, str]
```

#### カスタム型 (オリジナルクラス)

任意のクラスをアノテーションに使うと、`isinstance` チェックが自動的に行われます。

```python
from validkit import validate

class Timezone:
def __init__(self, name: str) -> None:
self.name = name

UTC = Timezone("UTC")

class Config:
name: str
age: int
timezone: Timezone # カスタム型 → isinstance チェック

result = validate({"name": "server1", "age": 5, "timezone": UTC}, Config)
```

`v.instance()` を使うと辞書スキーマ内でも同じチェックができます。

```python
from validkit import v, validate

schema = {
"name": v.str(),
"timezone": v.instance(Timezone).default(UTC),
}
result = validate({"name": "server1"}, schema)
# timezone は省略時に UTC が補完される
```

#### クラス属性によるデフォルト値と Validator の組み合わせ

クラス属性に具体的な値を設定するとデフォルト値として機能します。
Validator インスタンスを直接クラス属性にすることで、詳細な制約も記述できます。

```python
from typing import Optional
from validkit import v, validate

class Config:
host: str # 必須
port: int = 5432 # デフォルト値 5432
ssl: bool = False # デフォルト値 False
timeout: Optional[int] = 30 # オプション + デフォルト
role = v.str().default("worker") # Validator で詳細に定義

result = validate({"host": "db.local"}, Config)
# -> {"host": "db.local", "port": 5432, "ssl": False, "timeout": 30, "role": "worker"}
```

#### 対応型ヒント一覧

| アノテーション | 動作 |
|---|---|
| `str` / `int` / `float` / `bool` | 型チェック |
| `Optional[T]` / `Union[T, None]` | 内部型をチェック・省略可能 |
| `List[T]` / `list[T]` | リスト要素の型チェック |
| `Dict[K, V]` / `dict[K, V]` | 辞書の値の型チェック |
| 任意のクラス | `isinstance` チェック |
| `Any` / 不明な型 | チェックなし(パススルー) |
Comment on lines +249 to +256
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README lists Optional[T] / Union[T, None] as supported, but it doesn’t mention that non-optional Union[T1, T2, ...] annotations now raise TypeError. Adding a short note/row about unsupported non-optional unions (and the recommended alternatives) would prevent surprises for users.

Copilot uses AI. Check for mistakes.
| `Validator` インスタンス | ValidKit の完全なバリデーション |

> **注意**: `Union[int, str]` / `Union[int, str, None]` や `int | str` / `int | str | None` のように、`None` 以外の複数型を持つ Union は現在サポートしていません。`validate()` の呼び出し時(スキーマ変換フェーズ)に `TypeError` が送出されます。代わりに `Optional[T]`(= `Union[T, None]`)か具体的な単一型を使用してください。複数の型を受け付けたい場合は `v.instance(MyBaseClass)` などを検討してください。

### IDE 補完を効かせる(TypedDict + Schema)

`Schema[T]` クラスを使うと、IDE(PyCharm / VS Code)での型補完が有効になります。
Expand Down
92 changes: 92 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ ValidKit の API リファレンスおよび高度な使用方法について詳

型情報を持つスキーマのラッパーです。

`Schema({...})` による従来の dict スキーマに加えて、実行時には `Schema(MyClass)` のように
クラス記法スキーマもそのままラップできます。

```python
from validkit import Schema, validate

class Profile:
name: str
age: int

schema = Schema(Profile)
result = validate({"name": "Alice", "age": 30}, schema)
```

#### `.generate_sample()`
スキーマ定義からサンプルデータの辞書を生成します。生成後の値は各バリデータで再検証されるため、制約を満たさないサンプルは返されません。

Expand All @@ -43,6 +57,83 @@ sample = SCHEMA.generate_sample()

`regex()` や `custom()` などでこれらの候補が制約を満たせない場合は、`ValueError` を送出します。その場合は `.default(...)` または `.examples([...])` で妥当なサンプル候補を与えてください。

### クラス記法スキーマ

辞書スキーマに加えて、Python クラスの型アノテーションや `Validator` クラス属性をそのまま
スキーマとして扱えます。

#### 基本的な使い方

```python
from typing import Dict, List, Optional
from validkit import v, validate, ValidationError

class Config:
host: str
port: int = 5432 # クラス属性 → デフォルト値
tags: Optional[List[str]] # 省略可能なリスト
metadata: Dict[str, int]
role = v.str().default("worker") # Validator クラス属性

# 必須フィールドのみ指定 — デフォルト値が自動補完される
result = validate(
{"host": "db.local", "metadata": {"connections": 10}},
Config,
)
# -> {"host": "db.local", "port": 5432, "metadata": {"connections": 10}, "role": "worker"}
# tags は Optional なので省略可能

# 型が合わない場合は ValidationError
try:
validate({"host": 123, "metadata": {}}, Config)
except ValidationError as e:
print(e.path, e.message) # "host" / "Expected str, got int"
```

#### カスタム型 (オリジナルクラス)

```python
from validkit import v, validate

class Timezone:
def __init__(self, name: str) -> None:
self.name = name

UTC = Timezone("UTC")

class ServerConfig:
name: str
timezone: Timezone # → isinstance(value, Timezone) で検証

result = validate({"name": "server1", "timezone": UTC}, ServerConfig)
# -> {"name": "server1", "timezone": <Timezone "UTC">}

# 辞書スキーマで同じことをするには v.instance() を使う
schema = {
"name": v.str(),
"timezone": v.instance(Timezone).default(UTC),
}
result = validate({"name": "server1"}, schema)
# -> {"name": "server1", "timezone": <Timezone "UTC">} (デフォルト補完)
```

対応する型ヒント:

| アノテーション | 動作 |
|---|---|
| `str` / `int` / `float` / `bool` | 型チェック |
| `Optional[T]` / `Union[T, None]` | 内部型をチェックし、省略可能 |
| `List[T]` / `list[T]` | 要素ごとの型チェック |
| `Dict[K, V]` / `dict[K, V]` | 値の型チェック |
| 任意のクラス | `isinstance` チェック |
| `Validator` クラス属性 | その Validator をそのまま使用 |

`v.instance(MyType)` を使うと、辞書スキーマでも同じ `isinstance` チェックを明示的に書けます。

> **注意**: `Union[int, str]` / `Union[int, str, None]` や `int | str` / `int | str | None` のように、`None` 以外の複数型を持つ Union は現在サポートしていません。スキーマ変換時に `TypeError` を送出します。代わりに `Optional[T]`、単一型、または `v.instance(...)` を使用してください。

---

### スキーマ自動生成

#### `v.auto_infer(data, type_map=None, schema_overrides=None)`
Expand Down Expand Up @@ -125,6 +216,7 @@ schema = v.auto_infer(
- `v.list(item_schema)`: 各要素が `item_schema` に適合するリストであることを検証。
- `v.dict(key_type, value_schema)`: キーが `key_type` であり、値が `value_schema` に適合する辞書であることを検証。
- `v.oneof(choices)`: 値が `choices` リストのいずれかであることを検証。
- `v.instance(type_cls)`: 任意クラスに対する `isinstance` チェックを行います。

### 修飾メソッド(チェーンメソッド)
すべてのバリデータで使用可能なメソッド:
Expand Down
Loading
Loading