Fakerの拡張プロバイダを自作して親子関係のあるデータを生成するテクニック

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/46fa189e-b9ad-ff35-920c-ad1a377d621d.png)


データ生成ライブラリとして有名な [Faker](https://faker.readthedocs.io/en/master/) を活用して、**親子関係のあるデータ** を自由自在に生成する方法を紹介します。

例えば、以下のようなケースに対応できます。

- **顧客 (Parent) と 注文 (Child)**  
  - `customer_id` をキーとして、 `orders` に `customer_id` を持たせる
- **商品カテゴリ (Parent) と サブカテゴリ (Child)**  
  - `category_id` をキーとして、 `sub_categories` に `parent_category_id` を設定
- **ブログのユーザ (Parent) と 記事 (Child)**  
  - `user_id` をキーとして、 `posts` に `user_id` を持たせる

Faker の標準機能だけでは対応しきれないため、**カスタムプロバイダ** を作成する方法を解説します。

---

## 環境設定

- Python 3.x (この記事では 3.9 以上を想定)
- [Faker](https://pypi.org/project/Faker/) ライブラリ
- Google Colab やローカルの Python 環境

```bash
pip install Faker
```

Google Colab では、ノートブックの最初のセルで上記コマンドを実行してください。

---

## 方法 1: 親子をセットで生成する

**親1レコードと子Nレコードをまとめて生成** するカスタムプロバイダを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/92f86969-5fe3-5db1-4a1b-3d3b816d1c90.png)

### 実装

```python
from faker import Faker
from faker.providers import BaseProvider
import random
from datetime import datetime

fake = Faker()
fake.seed_instance(1234)

class ParentAndChildDataProvider(BaseProvider):
    def create_parent_with_children(self, num_children=3):
        """
        親1件、子N件を生成し、親のIDを子に紐づける
        """
        parent_id = f"CUST-{random.randint(1000,9999)}"
        parent_data = {
            "customer_id": parent_id,
            "customer_name": fake.name(),
            "registered_date": fake.date_between('-1y', 'today')
        }

        children_data = []
        for _ in range(num_children):
            order_id = f"ORD-{random.randint(10000,99999)}"
            order_date = fake.date_between(start_date=parent_data['registered_date'], end_date='today')
            total_amount = random.randint(1000, 20000)

            child = {
                "order_id": order_id,
                "customer_id": parent_id,
                "order_date": order_date,
                "total_amount": total_amount,
            }
            children_data.append(child)

        return {
            "parent": parent_data,
            "children": children_data
        }

# プロバイダ追加
fake.add_provider(ParentAndChildDataProvider)

# 実行例
print(fake.create_parent_with_children(num_children=3))
```

---

## 方法 2: 先に親を生成し、子を後で割り当てる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/f420972f-be25-cc47-784a-e7fedd1c5bac.png)

データベースのように **親リストを先に作成** し、後で **子データを親に紐づける** 方法です。

### 実装

```python
class ParentChildSeparateProvider(BaseProvider):
    def create_parents(self, n=5):
        """ 親データを n 件生成 """
        parent_list = []
        for _ in range(n):
            parent_id = f"CUST-{random.randint(1000,9999)}"
            parent = {
                "customer_id": parent_id,
                "customer_name": fake.name(),
                "registered_date": fake.date_between('-1y', 'today')
            }
            parent_list.append(parent)
        return parent_list

    def create_children(self, parents, num_children=3):
        """ 既存の親データを参照しながら、子データを生成 """
        children_list = []
        for _ in range(num_children):
            parent = random.choice(parents)
            order_id = f"ORD-{random.randint(10000,99999)}"
            order_date = fake.date_between(start_date=parent['registered_date'], end_date='today')
            total_amount = random.randint(1000, 20000)

            child = {
                "order_id": order_id,
                "customer_id": parent["customer_id"],
                "order_date": order_date,
                "total_amount": total_amount,
            }
            children_list.append(child)
        return children_list

# プロバイダ追加
fake.add_provider(ParentChildSeparateProvider)

# 親データを10件生成
parents_data = fake.create_parents(n=10)
print(f"--- Parents ---\n{parents_data}\n")

# 子データを20件生成し、親IDをランダムに割り当て
children_data = fake.create_children(parents=parents_data, num_children=20)
print(f"--- Children ---")
for c in children_data:
    print(c)
```

---

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b9acedb1-90c7-60b5-b306-74dc161c1bc2.png)

- Faker のカスタムプロバイダを利用して、親子関係のあるデータを簡単に生成できる
- **親と子をセットで生成する方法** と **親リストを先に作って子に紐づける方法** の2パターンを紹介
- 実データに近い **外部キーのような制約** を持ったモックデータを作れる

これを活用すれば、テストデータや開発時のモックデータ生成がスムーズになります。ぜひ活用してみてください！