skydum

個人的な作業記録とか備忘録代わりのメモ

dockerで起動に失敗したコンテナに接続する

dockerで起動に失敗してExited (1) 17 seconds agoの様になったコンテナに接続する

やり方をよく忘れるので忘れないようにメモ

例えば以下のようにdockerでmysqlを起動しようとすると起動に失敗する

$ docker run --name docker-mysql -p 13306:3306/tcp -d mysql:5.7.38

この時に起動に失敗したコンテナに

$ docker exec -it docker-mysql /bin/bash

としても既にコンテナが停止していて接続することができないので、一回コンテナをコミットしてから再度コンテナを作ると接続できる

$ docker commit docker-mysql commit-mysql
$ docker run --rm -it commit-mysql /bin/bash

dockerのphpMyAdminで複数のMYSQLサーバを管理する

dockerのphpMyAdminMYSQLのサーバ複数を管理する

  • phpMyAdminには1個のphpMyAdminで複数のサーバを管理する機能があるがdockerで複数管理は行ったことがなかったので調べてみた
  • MYSQLのサーバ毎に個別のID, PASSを使いたい場合は以下の方法ではできないので、config.user.inc.phpを使った方法を使わないといけない気がする。

利用するdockerのイメージ

設定方法

  • docker hubのphpMyAdminのページを見るとdocker-composeを使って任意のサーバに対してphpMyAdminを使う方法のサンプルの記載がある

    Usage with docker-compose and arbitrary server

    This will run phpMyAdmin with the arbitrary server option - allowing you to specify any MySQL/MariaDB server on the login page.

    version: '3.1'
    
    services:
      db:
        image: mariadb:10.3
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: notSecureChangeMe
    
      phpmyadmin:
        image: phpmyadmin
        restart: always
        ports:
          - 8080:80
        environment:   ← ここの環境変数に値を追加すれば任意のホストに接続できるようになる
          - PMA_ARBITRARY=1
    
  • docker hubの上記の記載のもう少し下にdocker-compose.ymlで渡せる環境変数の一覧(Environment variables summary)が書かれており、その中にPMA_HOSTS - define comma separated list of address/host names of the MySQL serversの記載がある。

  • 接続したいmysqlのサーバをmysql1,mysql2、ID: root PASS: passwordとした場合、docker-compose.ymlを以下のように書き換える
version: "3"

services:
  phpmyadmin:
    image: phpmyadmin/phpmyadmin:5.1.3
    restart: always
    environment:
      - PMA_ARBITRARY=-1
      - PMA_HOSTS=mysql1,mysql2
      - PMA_USER=root
      - PMA_PASSWORD=password
    ports:
      - 8080:80

テスト用サンプルdocker-compose.ymlの使い方

  • docker-compose..ymlと同じディレクトリにmainのディレクトリを作成してその中に*.sqlのファイルを置くとmysql-mainにデータがインポートされてから起動する
  • サンプルデータを利用しない場合は- ./main:/docker-entrypoint-initdb.d:roの行をコメントアウトするか削除してください
  • 準備ができたらdocker-compose.ymlのファイルのある所でdocker-compose up -dをすると起動します
  • 終わらせる時はdocker-compose.ymlのファイルのある所でdocker-compose down -vをするとupした時にできたボリュームのデータも一緒に消えます。

ディレクトリ構成

.
├── docker-compose.yml
└── main
    └── world.sql

docker-compose.yml

version: "3"

services:
  mysql-main:
    image: mysql:5.7.38
    restart: always
    ports:
      - "13306:3306"
    volumes:
      - main_db_data:/var/lib/mysql:rw
      - ./main:/docker-entrypoint-initdb.d:ro
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_USER: master
      MYSQL_PASSWORD: password

  mysql-sub:
    image: mysql:5.7.38
    restart: always
    ports:
      - "23306:3306"
    volumes:
      - sub_db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_USER: sub
      MYSQL_PASSWORD: password

  phpmyadmin:
    image: phpmyadmin/phpmyadmin:5.1.3
    restart: always
    environment:
      - PMA_ARBITRARY=-1
      - PMA_HOSTS=mysql-main,mysql-sub
      - PMA_USER=root
      - PMA_PASSWORD=secret
    ports:
      - 18080:80
    depends_on:
      - mysql-main
      - mysql-sub

volumes:
  main_db_data:
  sub_db_data:

pydanticで_(アンダースコア)から始まる変数を利用する方法

pydanticで_から始まるクラス変数を利用できないので、利用できるようにする

通常の問題がないパターン

from pydantic import BaseModel, Field


class Users(BaseModel):
    id: str = Field(None)
    name: str = Field(None)
    data: str = Field(None)


u = Users(id="a", name="b", data="data")

# id='a' name='b' data='data'
print(u)

# {'id': 'a', 'name': 'b', 'data': 'data'}
print(u.dict())

問題があるパターン

  • _idの様に変数名が_から始まる場合、pydanticでは値を入れられるが取得ができなくなる
  • private変数として扱われて外部から参照できなくなる?
from pydantic import BaseModel, Field


class Users(BaseModel):
    _id: str = Field(None)
    name: str = Field(None)
    data: str = Field(None)


u = Users(_id="a", name="b", data="data")

# name='b' data='data'
print(u)

# {'name': 'b', 'data': 'data'}
print(u.dict())

_から始まる変数を利用できるようにする

方法1. Field(alias)を利用する

  • pydanticで標準で用意されているFieldにaliasを割り当てる方法で変数を定義し、dictに変換する時にaliasに指定した値で変換する
from pydantic import BaseModel, Field


class Users(BaseModel):
    id: str = Field(None, alias="_id")
    name: str = Field(None)
    data: str = Field(None)


u = Users(_id="a", name="b", data="data")

# id='a' name='b' data='data'
print(u)

# {'_id': 'a', 'name': 'b', 'data': 'data'}
print(u.dict(by_alias=True))

方法2. pydanticのdefにモンキーパッチを当てて問題を回避する

# monkey patch to get underscore fields
def is_valid_field(name:str):
    if not name.startswith('__'):
        return True
    elif name == '__root__':
        return True

pydantic.main.is_valid_field = is_valid_field

モンキーパッチを当てたソース

  • これでField(alias)を使わなくても問題なく_から始まる変数名を利用できるようになる
  • こだわりがないのなら方法1のField(alias)を使った方がいいと思う
import pydantic
from pydantic import BaseModel, Field


def is_valid_field(name: str):
    if not name.startswith("__"):
        return True
    elif name == "__root__":
        return True


pydantic.main.is_valid_field = is_valid_field


class Users(BaseModel):
    _id: str = Field(None)
    name: str = Field(None)
    data: str = Field(None)


u = Users(_id="a", name="b", data="data")

# _id='a' name='b' data='data'
print(u)

# {'_id': 'a', 'name': 'b', 'data': 'data'}
print(u.dict())

pydanticの使い方

pydanticでネストされたモデルに値を入れる方法、ネストされたクラスを取得する方法、サブクラスの取得方法

  • 親 → 子供 → 孫の様に定義された(ネストされた)クラスに値を入れる必要があって調べた
  • 親に定義された子供クラスの情報を取得したかったため、親のクラスに定義されている情報から子供クラスの取得方法についても調べた

データの入れ方(Field aliasなし)

クラスの定義

  • 以下のように定義されているBaseModelに対してParentクラスからGrandchildクラスに定義されている変数全てに値を入れたい
class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default")


class Child(BaseModel):
    child_value: str = Field("child_default")
    grandchild: Grandchild = Field(None)


class Parent(BaseModel):
    parent_value: str = Field("parent_default")
    child: Child = Field(None)

データの入れ方

  • BaseModelに定義されている通りにdict形式で値を定義してキーワード引数を展開して渡すと一括で値を入れることができる
    data = {
        "parent_value": "parent_data",
        "child": {"child_value": "child_data", "grandchild": {"grandchild_value": "grandchild_data"}},
    }

    # 成功パターン
    ok_model = Parent(**data)

    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_data', 'grandchild': {'grandchild_value': 'grandchild_data'}}}
    print(ok_model.dict())

ソース全文

aliasなしのパターン

from pydantic import BaseModel, Field


class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default")


class Child(BaseModel):
    child_value: str = Field("child_default")
    grandchild: Grandchild = Field(None)


class Parent(BaseModel):
    parent_value: str = Field("parent_default")
    child: Child = Field(None)


def main():
    """親→子供→孫のようにネストされたpydanticのモデルにdict形式で定義したデータを一括で入れる"""
    data = {
        "parent_value": "parent_data",
        "child": {"child_value": "child_data", "grandchild": {"grandchild_value": "grandchild_data"}},
    }

    # 成功パターン
    ok_model = Parent(**data)

    # こちらの場合dict変換したときのキー名はクラスに設定された変数名が使われる
    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_data', 'grandchild': {'grandchild_value': 'grandchild_data'}}}
    print(ok_model.dict())


if __name__ == "__main__":
    main()

データの入れ方(Field aliasあり)

クラスの定義

class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default", alias="孫の値")


class Child(BaseModel):
    child_value: str = Field("child_default", alias="子供の値")
    grandchild: Grandchild = Field(None, alias="孫")


class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")

データの入れ方

    data = {
        "parent_value": "parent_data",
        "child": {"child_value": "child_data", "grandchild": {"grandchild_value": "grandchild_data"}},
    }

    alias_data = {
        "親の値": "parent_data",
        "子供": {"子供の値": "child_data", "孫": {"孫の値": "grandchild_data"}},
    }

    # 成功パターン
    # Field aliasが設定されている時はdictで渡すキー名にaliasで指定した値を指定する必要がある
    ok_model = Parent(**alias_data)

    # dict変換したときのキー名はクラスに設定された変数名が使われる
    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_data', 'grandchild': {'grandchild_value': 'grandchild_data'}}}
    print(ok_model.dict())

    # Field aliasなしで利用したdataでは値が代入されない
    ng_model = Parent(**data)

    # {'parent_value': 'parent_default', 'child': None}
    print(ng_model.dict())


    # Parent.Configに定義されているConfiの定義を変更するとaliasが設定されている場合でも値を入れることができる
    # https://pydantic-docs.helpmanual.io/usage/model_config/
    Parent.Config.allow_population_by_field_name = True
    ng_model = Parent(**data)
    
    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_data', 'grandchild': {'grandchild_value': 'grandchild_data'}}}
    print(ng_model.dict())
class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")

    class Config:
        # デフォルトでFalseで設定されている
        # Fieldにaliasが設定されている場合、値の代入にクラスの変数名を優先して利用するかどうか
        # デフォルトではFalseになっているのでFiledのaliasが優先して利用される
        # Trueにするとクラスに設定されているキー名が優先して利用される
        # ParentクラスでTrueに設定した場合Child, GrandchildについてもTrueが設定されているのと同じ様に動作する
        allow_population_by_field_name = False

ソース全文

aliasありのパターン

from pydantic import BaseModel, Field


class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default", alias="孫の値")


class Child(BaseModel):
    child_value: str = Field("child_default", alias="子供の値")
    grandchild: Grandchild = Field(None, alias="孫")


class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")

    class Config:
        # デフォルトでFalseで設定されている
        # Fieldにaliasが設定されている場合、値の代入にクラスの変数名を優先して利用するかどうか
        # デフォルトではFalseになっているのでFiledのaliasが優先して利用される
        allow_population_by_field_name = False


def main():
    """親→子供→孫のようにネストされたpydanticのモデルにdict形式で定義したデータを一括で入れる"""
    data = {
        "parent_value": "parent_data",
        "child": {"child_value": "child_data", "grandchild": {"grandchild_value": "grandchild_data"}},
    }

    alias_data = {
        "親の値": "parent_data",
        "子供": {"子供の値": "child_data", "孫": {"孫の値": "grandchild_data"}},
    }

    # 成功パターン
    ok_model = Parent(**alias_data)

    # こちらの場合dict変換したときのキー名はクラスに設定された変数名が使われる
    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_data', 'grandchild': {'grandchild_value': 'grandchild_data'}}}
    print(ok_model.dict())

    # Fieldにaliasが設定されている時はaliasに指定した値で設定する必要がある
    # こちらは値が代入されない
    ng_model = Parent(**data)
    print(ng_model.dict())


if __name__ == "__main__":
    main()

Dict変換(Field aliasあり)

  • BaseModelをdict変換した時にFiledに設定したaliasを利用することができる

クラス定義

class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default", alias="孫の値")


class Child(BaseModel):
    child_value: str = Field("child_default", alias="子供の値")
    grandchild: Grandchild = Field(None, alias="孫")


class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")

    class Config:
        # デフォルトでFalseで設定されている
        # Fieldにaliasが設定されている場合、値の代入にクラスの変数名を優先して利用するかどうか
        # デフォルトではFalseになっているのでFiledのaliasが優先して利用される
        allow_population_by_field_name = False

dict変換のやり方

    data = {
        "parent_value": "parent_data",
        "child": {"child_value": "child_data", "grandchild": {"grandchild_value": "grandchild_data"}},
    }

    alias_data = {
        "親の値": "parent_data",
        "子供": {"子供の値": "child_data", "孫": {"孫の値": "grandchild_data"}},
    }

    # 成功パターン
    ok_model = Parent(**alias_data)

    # dictする際にby_alias=Trueにした場合キー名はFieldに設定されたaliasが使われる
    # {'親の値': 'parent_data', '子供': {'子供の値': 'child_data', '孫': {'孫の値': 'grandchild_data'}}}
    print(ok_model.dict(by_alias=True))

    # Parent.Configに定義されているConfiの定義を変更するとaliasが設定されている場合でも値を入れることができる
    Parent.Config.allow_population_by_field_name = True
    ng_model = Parent(**data)

    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_default', 'grandchild': None}}
    print(ng_model.dict())

    # dictする際にby_alias=Trueにした場合キー名はFieldに設定されたaliasが使われる
    # {'親の値': 'parent_data', '子供': {'子供の値': 'child_default', '孫': None}}
    print(ng_model.dict(by_alias=True))

ソース全文

dict変換のやり方

from pydantic import BaseModel, Field


class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default", alias="孫の値")


class Child(BaseModel):
    child_value: str = Field("child_default", alias="子供の値")
    grandchild: Grandchild = Field(None, alias="孫")


class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")

    class Config:
        # デフォルトでFalseで設定されている
        # Fieldにaliasが設定されている場合、値の代入にクラスの変数名を優先して利用するかどうか
        # デフォルトではFalseになっているのでFiledのaliasが優先して利用される
        allow_population_by_field_name = False


def main():
    """親→子供→孫のようにネストされたpydanticのモデルにdict形式で定義したデータを一括で入れる"""
    data = {
        "parent_value": "parent_data",
        "child": {"child_value": "child_data", "grandchild": {"grandchild_value": "grandchild_data"}},
    }

    alias_data = {
        "親の値": "parent_data",
        "子供": {"子供の値": "child_data", "孫": {"孫の値": "grandchild_data"}},
    }

    # 成功パターン
    ok_model = Parent(**alias_data)

    # dictする際にby_alias=Trueにした場合キー名はFieldに設定されたaliasが使われる
    # {'親の値': 'parent_data', '子供': {'子供の値': 'child_data', '孫': {'孫の値': 'grandchild_data'}}}
    print(ok_model.dict(by_alias=True))

    # Parent.Configに定義されているConfiの定義を変更するとaliasが設定されている場合でも値を入れることができる
    Parent.Config.allow_population_by_field_name = True
    ng_model = Parent(**data)

    # {'parent_value': 'parent_data', 'child': {'child_value': 'child_default', 'grandchild': None}}
    print(ng_model.dict())

    # dictする際にby_alias=Trueにした場合キー名はFieldに設定されたaliasが使われる
    # {'親の値': 'parent_data', '子供': {'子供の値': 'child_default', '孫': None}}
    print(ng_model.dict(by_alias=True))

if __name__ == "__main__":
    main()

BaseModelで定義されたネストしたクラスの変数からサブのクラスを取得とFiledに定義されたalias名の取得

クラス定義

class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default", alias="孫の値")


class Child(BaseModel):
    child_value: str = Field("child_default", alias="子供の値")
    grandchild: Grandchild = Field(None, alias="孫")


class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")

サブクラスの取得方法とクラスに定義されたalias名の取得

    # 親クラスに定義されているparent_valueのFieldに設定されているaliasを取得する
    # 親の値
    print(Parent.__fields__["parent_value"].field_info.alias)

    sub_class = Parent.__fields__["child"].type_

    # <class '__main__.Child'>
    print(sub_class)

    sub_sub_class = sub_class.__fields__["grandchild"].type_

    # <class '__main__.Grandchild'>
    print(sub_sub_class)

ソース全文

サブクラスの取得とalias名の取得

from pydantic import BaseModel, Field


class Grandchild(BaseModel):
    grandchild_value: str = Field("grandchild_default", alias="孫の値")


class Child(BaseModel):
    child_value: str = Field("child_default", alias="子供の値")
    grandchild: Grandchild = Field(None, alias="孫")


class Parent(BaseModel):
    parent_value: str = Field("parent_default", alias="親の値")
    child: Child = Field(None, alias="子供")


def get_subclass():
    """親→子供→孫のようにネストされたpydanticのモデルからサブクラスを取得する
    親(Parent)→childに定義されたクラスを取得する
    親に定義されたparent_valueに定義されたaliasを取得する
    """

    # 親クラスに定義されているparent_valueのFieldに設定されているaliasを取得する
    # 親の値
    print(Parent.__fields__["parent_value"].field_info.alias)

    sub_class = Parent.__fields__["child"].type_

    # <class '__main__.Child'>
    print(sub_class)

    sub_sub_class = sub_class.__fields__["grandchild"].type_

    # <class '__main__.Grandchild'>
    print(sub_sub_class)


if __name__ == "__main__":
    get_subclass()

Vue 2でコンポーネント間のデータのやり取りを行う

Vue 2でコンポーネント間のデータのやり取りを行う

  • 普段Vueをそれほど頻繁には使わないのでv-model、.syncを使った方法をよく忘れるので備忘録として記載する
  • 記載してあるソース全文についてはindex.htlmの様にコピペしてローカルでそのまま動きます。(VueCLIは不要)

  • Vue.js でコンポーネント間のデータのやり取りを行うにはいくつかの方法がある。

    1. props を利用する
    2. v-modelを利用する
    3. .syncを利用する
  • コンポーネント間のやり取りを行うために以下のルールがある
    1. 親のコンポーネントから子供のコンポーネントへ渡したデータは子供側で変更を行ってはいけない
    2. 子供から親へデータを渡すには$emitを使って渡す

props を使ったコンポーネント間のデータのやり取り

  • 渡す値が少数ならこの方法でも特に問題はない

ソース全文

props パターンのソース

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <title>Vue.js props</title>

    <style>
        .red {
            border: solid 2px red;
            padding: 5px;
        }

        .blue {
            border: solid 2px blue;
            padding: 5px;
        }

        .purple {
            border: solid 2px black;
            padding: 5px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="red">
            <div>テスト</div>
            <div class="blue">親コンポーネント
                <input v-model="parentInputData">
            </div>
            <div class="red" style="margin-top: 5px;">
                <div>コンポーネント
                    <!-- prop-childコンポーネントにparentDataの変数名でparentInputDataの値を渡す -->
                    <!-- porp-childコンポーネントのemitで指定するメソッド名をreveive-dataとして登録 -->
                    <!-- 親側で実際に実行されるメソッド名をparentReceiveDataとして登録 -->
                    <props-child :parent-data="parentInputData" @receive-data="parentReceiveData" class="purple">
                    </props-child>
                </div>
            </div>
        </div>
    </div>

    <script>
        Vue.component("props-child", {
            template: `
                <div>
                    <div>子供コンポーネント
                        初期値:{{ childData2 }} / 更新: {{ parentData }} 
                        <div>
                            親コンポーネントと同期: <input v-model="childData"></input>
                        </div>
                    </div>
                </div>
            `,
            props: ["parentData"],
            data() {
                return {
                    childData2: this.parentData,
                };
            },

            computed: {
                childData: {
                    get() {
                        return this.parentData;
                    },
                    set(data) {
                        // "receive-data"が親側のコンポーネントで指定した@receive-data
                        // data親側のreceiveData(value)の所に入る
                        this.$emit("receive-data", data);
                    },
                },
            },
        });

        new Vue({
            el: "#app",
            data: {
                parentInputData: "あ"
            },
            methods: {
                parentReceiveData(value) {
                    // 子供側のコンポーネントの$emitで呼び出されるメソッド
                    this.parentInputData = value;
                }
            },
        })
    </script>
</body>

</html>


データの流れ

  1. テキストボックスに文字を入れると parentInputData が更新される
  2. 子供のコンポーネントの parentData が更新され、parentData が更新されると子供側の computed の childData が更新される
  3. 子供側のテキストボックスの内容が親側と同じ内容で表示される
  4. 子供側のテキストボックスに文字を入れると computed 経由で$emit が実行されて親側の parentReciveData メソッドが実行される
  5. 親側の parentReviceData が実行されると親側の parentData が子供側で更新された内容に更新される

f:id:Phoca:20220328220046p:plain
propsの場合

ソース

  • html
<div id="app">
    <div class="red">
        <div>テスト</div>
        <div class="blue">
            親コンポーネント
            <input v-model="parentInputData" />
        </div>
        <div class="red" style="margin-top: 5px;">
            <div>
                コンポーネント
                <props-child
                    :parent-data="parentInputData"
                    @receive-data="parentReceiveData"
                    class="purple"
                >
                </props-child>
            </div>
        </div>
    </div>
</div>
// 親側のコンポーネント(vueのインスタンス)
new Vue({
    el: "#app",
    data: {
        parentInputData: "あ",
    },
    methods: {
        parentReceiveData(value) {
            // 子供側のコンポーネントの$emitで呼び出されるメソッド
            this.parentInputData = value;
        },
    },
});
// 子供のコンポーネント
Vue.component("props-child", {
    template: `
                <div>
                    <div>子供コンポーネント
                        初期値:{{ childData2 }} / 更新: {{ parentData }} 
                        <div>
                            親コンポーネントと同期: <input v-model="childData"></input>
                        </div>
                    </div>
                </div>
            `,
    props: ["parentData"],
    data() {
        return {
            childData2: this.parentData,
        };
    },

    computed: {
        childData: {
            get() {
                return this.parentData;
            },
            set(data) {
                // "receive-data"が親側のコンポーネントで指定した@receive-data
                // data親側のreceiveData(value)の所に入る
                this.$emit("receive-data", data);
            },
        },
    },
});

v-modelを使ったコンポーネント間のデータのやり取り

  • propよりも若干書きやすい
  • 親側はスッキリかけるけれども子供側のcomputedにはget, setをたくさん書く必要がある
  • v-modelは使った親 → 子供への値の受け渡しにつかえるのは1個だけ(複数個のv-modelを指定することはできない)

ソース全文

v-model パターンのソース

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <title>Vue.js v-model</title>

    <style>
        .red {
            border: solid 2px red;
            padding: 5px;
        }

        .blue {
            border: solid 2px blue;
            padding: 5px;
        }

        .purple {
            border: solid 2px black;
            padding: 5px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="red">
            <div>テスト</div>
            <div class="blue">親コンポーネント
                <input v-model="parentInputData">
            </div>
            <div class="red" style="margin-top: 5px;">
                <div>コンポーネント
                    <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す -->
                    <!-- prop-childコンポーネント側ではv-modelで渡された値はvalueとして受け取る(vue.jsの仕様) -->
                    <props-child v-model="parentInputData" class="purple"></props-child>
                    <!-- 上記は以下のように書いたのと同等となる -->
                    <!-- 
                    <props-child :value="parentInputData" @input="parentInputData = $event" />
                    -->
                    <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html -->
                    </props-child>
                </div>
            </div>
        </div>
    </div>

    <script>
Vue.component("props-child", {
    template: `
                <div>
                    <div>子供コンポーネント
                        初期値:{{ childData2 }} / 更新: {{ childData }} 
                        <div>
                            親コンポーネントと同期: <input v-model="childData"></input>
                        </div>
                    </div>
                </div>
            `,
    // 親側のv-modelで渡された値は子供側ではvalueとして受け取る
    props: ["value"],
    data() {
        return {
            childData2: this.value,
        };
    },

    computed: {
        childData: {
            get() {
                return this.value;
            },
            set(data) {
                // childDataの値が更新されたら親のコンポーネントのinputを$emitで呼び出す
                // v-modelで親から子供に値が渡るときはvalueで渡され
                // 親側に返すときはvue.jsの仕様でinputイベントで返すと決まっている
                this.$emit("input", data);
            },
        },
    },
});

        new Vue({
            el: "#app",
            data: {
                parentInputData: "あ"
            },
            methods: {}
        })
    </script>
</body>

</html>


データの流れ

  1. テキストボックスに文字を入れると parentInputData が更新される
  2. v-model経由で子供のコンポーネントのprops: ["value"]へ 値が渡され、value が更新されると子供側の computed の childData が更新される
  3. 子供側のテキストボックスの内容が親側と同じ内容で表示される
  4. 子供側のテキストボックスに文字を入れると computed 経由で$emit で親側のinputメソッドが実行される
  5. 親側の inputメソッド が実行されると親側の parentData が子供側で更新された内容に更新される

f:id:Phoca:20220329222225p:plain
v-model

v-modelとprops: ["value"]と$emit("input")の関係

f:id:Phoca:20220329214142p:plain
v-model糖衣構文

<props-child v-model="parentInputData" />
上記は下記の糖衣構文
<props-child :value="parentInputData" @input="parentInputData = $event" />
  • 以下のようにv-modelを複数個記載することはできない
<props-child v-model="parentInputData" v-model="parentInputData2" />

ソース

  • html
<div id="app">
    <div class="red">
        <div>テスト</div>
        <div class="blue">親コンポーネント
            <input v-model="parentInputData">
        </div>
        <div class="red" style="margin-top: 5px;">
            <div>コンポーネント
                <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す -->
                <!-- prop-childコンポーネント側ではv-modelで渡された値はvalueとして受け取る(vue.jsの仕様) -->
                <props-child v-model="parentInputData" class="purple"></props-child>
                <!-- 上記は以下のように書いたのと同等となる -->
                <!--
                <props-child :value="parentInputData" @input="parentInputData = $event" />
                 -->
                <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html -->
                </props-child>
            </div>
        </div>
    </div>
</div>
new Vue({
    el: "#app",
    data: {
        parentInputData: "あ"
    },
    methods: {}
})
Vue.component("props-child", {
    template: `
                <div>
                    <div>子供コンポーネント
                        初期値:{{ childData2 }} / 更新: {{ childData }} 
                        <div>
                            親コンポーネントと同期: <input v-model="childData"></input>
                        </div>
                    </div>
                </div>
            `,
    // 親側のv-modelで渡された値は子供側ではvalueとして受け取る
    props: ["value"],
    data() {
        return {
            childData2: this.value,
        };
    },

    computed: {
        childData: {
            get() {
                return this.value;
            },
            set(data) {
                // childDataの値が更新されたら親のコンポーネントのinputを$emitで呼び出す
                // v-modelで親から子供に値が渡るときはvalueで渡され
                // 親側に返すときはvue.jsの仕様でinputイベントで返すと決まっている
                this.$emit("input", data);
            },
        },
    },
});

.syncを使ったコンポーネント間のデータのやり取り

  • v-modelと同じ様な感じでデータのやり取りが可能
  • v-modelはコンポーネントに対して1個しか利用できなかったが、こちらには制限がない
  • v-modelのパターンと同じく子供側のcomputedにはget, setをたくさん書く必要がある

ソース全文

.sync propsが1個のパターンのソース

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <title>Vue.js .sync</title>

    <style>
        .red {
            border: solid 2px red;
            padding: 5px;
        }

        .blue {
            border: solid 2px blue;
            padding: 5px;
        }

        .purple {
            border: solid 2px black;
            padding: 5px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="red">
            <div>テスト</div>
            <div class="blue">親コンポーネント
                <input v-model="parentInputData">
            </div>
            <div class="red" style="margin-top: 5px;">
                <div>コンポーネント
                    <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す -->
                    <props-child :parent-data.sync="parentInputData" />
                    <!-- 上記は以下のように書いたのと同等となる -->
                    <!--
                    <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" />
                    -->
                    <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html -->
                </div>
            </div>
        </div>
    </div>

    <script>
        Vue.component("props-child", {
            template: `
<div>
    <div>子供コンポーネント
        初期値:{{ childData2 }} / 更新: {{ childData }} 
        <div>
            親コンポーネントと同期: <input v-model="childData"></input>
        </div>
    </div>
</div>
`,
            // 親側の.syncで渡された値は子供側ではpropsの場合と同じ様に親側で指定した変数名で受け取る
            props: ["parentData"],
            data() {
                return {
                    childData2: this.value,
                };
            },

            computed: {
                childData: {
                    get() {
                        return this.parentData;
                    },
                    set(data) {
                        // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す
                        // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている
                        this.$emit("update:parent-data", data);
                    },
                },
            },
        });

        new Vue({
            el: "#app",
            data: {
                parentInputData: "あ"
            },
            methods: {}
        })
    </script>
</body>

</html>

.sync propsが2個のパターンのソース

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <title>Vue.js .sync</title>

    <style>
        .red {
            border: solid 2px red;
            padding: 5px;
        }

        .blue {
            border: solid 2px blue;
            padding: 5px;
        }

        .purple {
            border: solid 2px black;
            padding: 5px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="red">
            <div>テスト</div>
            <div class="blue">親コンポーネント1
                <input v-model="parentInputData">
            </div>
            <div class="blue">親コンポーネント2
                <input v-model="parentInputData2">
            </div>
            <div class="red" style="margin-top: 5px;">
                <div>コンポーネント
                    <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す -->
                    <props-child :parent-data.sync="parentInputData" :parent-data2.sync="parentInputData2" />
                    <!-- 上記は以下のように書いたのと同等となる -->
                    <!--
                    <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event"
                        :parent-data2.sync="parentInputData2" @update:parent-data2="parentInputData2 = $event" />
                    -->
                </div>
            </div>
        </div>
    </div>

    <script>
        Vue.component("props-child", {
            template: `
                <div>
                    <div>子供コンポーネント1
                        初期値1:{{ childDataA }} / 更新1: {{ childData }}
                        <div>
                            親コンポーネントと同期: <input v-model="childData"></input>
                        </div>
                    </div>
                    <div>子供コンポーネント2
                        初期値2:{{ childDataB }} / 更新2: {{ childData2 }}
                        <div>
                            親コンポーネントと同期: <input v-model="childData2"></input>
                        </div>
                    </div>
                </div>
            `,
            // 親側の.syncで渡された値は子供側ではpropsの場合と同じ様に親側で指定した変数名で受け取る
            props: ["parentData", "parentData2"],
            data() {
                return {
                    childDataA: this.parentData,
                    childDataB: this.parentData2,
                };
            },

            computed: {
                childData: {
                    get() {
                        return this.parentData;
                    },
                    set(data) {
                        // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す
                        // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている
                        this.$emit("update:parent-data", data);
                    },
                },
                childData2: {
                    get() {
                        return this.parentData2;
                    },
                    set(data) {
                        // childDataの値が更新されたら親のコンポーネントのupdate:parentData2を$emitで呼び出す
                        // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている
                        this.$emit("update:parent-data2", data);
                    },
                },
            },
        });

        new Vue({
            el: "#app",
            data: {
                parentInputData: "あ",
                parentInputData2: "い"
            },
            methods: {}
        })
    </script>
</body>

</html>

データの流れ

  1. テキストボックスに文字を入れると parentInputData が更新される
  2. :parent-data.sync経由で子供のコンポーネントのprops: ["parentData"]へ 値が渡され、parentDataが更新されると子供側の computed の childData が更新される
  3. 子供側のテキストボックスの内容が親側と同じ内容で表示される
  4. 子供側のテキストボックスに文字を入れると computed 経由で$emit で親側のupdate:parent-dataメソッドが実行される
  5. 親側の inputメソッド が実行されると親側の parentData が子供側で更新された内容に更新される

f:id:Phoca:20220329222416p:plain
sync

:parent-data.syncとupdate:parent-dataの関係

f:id:Phoca:20220402211050p:plain
sync-syntax-sugar

<props-child :parent-data.sync="parentInputData" />
上記は下記の糖衣構文
<props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" />
  • 複数個書くことができる(2個書くときは以下のようにする)
親側
<props-child :parent-data.sync="parentInputData" :parent-data2.sync="parentInputData2" />
子供側computedの所だけ抜粋
    computed: {
        childData: {
            get() {
                return this.parentData;
            },
            set(data) {
                // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す
                // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている
                this.$emit("update:parent-data", data);
            },
        },
        childData2: {
            get() {
                return this.parentData2;
            },
            set(data) {
                // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す
                // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている
                this.$emit("update:parent-data2", data);
            },
        },
    },

.syncが1個のパターンのソース

  • html
<div id="app">
    <div class="red">
        <div>テスト</div>
        <div class="blue">親コンポーネント
            <input v-model="parentInputData">
        </div>
        <div class="red" style="margin-top: 5px;">
            <div>コンポーネント
                <!-- pro-childコンポーネントにv-modelでparentInputDataの値を渡す -->
                <props-child :parent-data.sync="parentInputData" />
                <!-- 上記は以下のように書いたのと同等となる -->
                <props-child :parent-data.sync="parentInputData" @update:parent-data="parentInputData = $event" />
                <!-- https://v3.ja.vuejs.org/guide/migration/v-model.html -->
            </div>
        </div>
    </div>
</div>
new Vue({
    el: "#app",
    data: {
        parentInputData: "あ"
    },
    methods: {}
})
Vue.component("props-child", {
    template: `
<div>
    <div>子供コンポーネント
        初期値:{{ childData2 }} / 更新: {{ childData }} 
        <div>
            親コンポーネントと同期: <input v-model="childData"></input>
        </div>
    </div>
</div>
`,
    // 親側の.syncで渡された値は子供側ではpropsの場合と同じ様に親側で指定した変数名で受け取る
    props: ["parentData"],
    data() {
        return {
            childData2: this.value,
        };
    },

    computed: {
        childData: {
            get() {
                return this.parentData;
            },
            set(data) {
                // childDataの値が更新されたら親のコンポーネントのupdate:parentDataを$emitで呼び出す
                // 親側に返すときはvue.jsの仕様でupdateイベントで返すと決まっている
                this.$emit("update:parent-data", data);
            },
        },
    },
});

vue.jsのdataに定義された変数を文字列で指定したい

vue.jsのdataに定義された変数を文字列で指定したい

  • 以下のように定義されているとする
  • この時にthis.firstValueを"this.firstValue"の様に文字で指定してthis.firstValueに値を入れたいとする
    <script>
        new Vue({
            el: "#app",
            vuetify: new Vuetify(),
            components: {},
            data: {
                firstValue: null,
                secondValue: null,
                insertData: [{ "key": "firstValue", "value": "foo" }, { "key": "secondValue", "value": "bar" }]
            },
            created() {
                this.insertData.forEach(element => {
                    const key = "this." + element.key
                    let v = eval(key);
                    v = element.value
                });
            },
            methods: {},
            computed: {},
            watch: {}
        })
    </script>

this.変数名はthis[変数名]で参照ができる

  • この様な場合vueでは以下のように書くことができる
  • this["firstValue]とするとthis.firstValueに値を入れることができる
    <script>
        new Vue({
            el: "#app",
            vuetify: new Vuetify(),
            components: {},
            data: {
                firstValue: null,
                secondValue: null,
                insertData: [{ "key": "firstValue", "value": "foo" }, { "key": "secondValue", "value": "bar" }]
            },
            created() {
                this.insertData.forEach(element => {
                    this[element.key] = element.value;
                });
            },
            methods: {},
            computed: {},
            watch: {}
        })
    </script>

ソース全文

ソース全文

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <v-app>
            <v-main>
                <v-container>
                    <v-card>
                        <v-card-title>dataの中の変数をthis+文字列で値を入れる</v-card-title>
                        <v-card-text>{{ firstValue }}</v-card-text>
                        <v-card-text>{{ secondValue }}</v-card-text>
                    </v-card>

                </v-container>
            </v-main>
            </v-main>
        </v-app>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-router@3.5.3/dist/vue-router.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/http-vue-loader"></script>
    <script>
        new Vue({
            el: "#app",
            vuetify: new Vuetify(),
            components: {},
            data: {
                firstValue: null,
                secondValue: null,
                insertData: [{ "key": "firstValue", "value": "foo" }, { "key": "secondValue", "value": "bar" }]
            },
            created() {
                this.insertData.forEach(element => {
                    this[element.key] = element.value;
                });
            },
            methods: {},
            computed: {},
            watch: {}
        })
    </script>
</body>

</html>

SQLAlchemyで外部制約キーを複数持ったリレーションシップの設定

SQLAlchemyを使って外部制約キーを複数持ったリレーションシップを設定する

リレーションシップの基本

構築したいDBのER図

ER図

生成したいSQL

CREATE TABLE persons (
        id VARCHAR(4) NOT NULL, 
        name VARCHAR(30) NOT NULL, 
        PRIMARY KEY (id)
)

CREATE TABLE tickets (
        person_id VARCHAR(4) NOT NULL, 
        ticket_id VARCHAR(8) NOT NULL, 
        room_name VARCHAR(30) NOT NULL, 
        PRIMARY KEY (person_id, ticket_id), 
        FOREIGN KEY(person_id) REFERENCES persons (id)
)

CREATE TABLE bookings (
        person_id VARCHAR(4) NOT NULL, 
        ticket_id VARCHAR(8) NOT NULL, 
        booking_id VARCHAR(12) NOT NULL, 
        comment VARCHAR(30), 
        PRIMARY KEY (person_id, ticket_id, booking_id), 
        FOREIGN KEY(person_id, ticket_id) REFERENCES tickets (person_id, ticket_id)
)

環境

  • Python 3.8.10
  • SQLAlchemy == 1.4.27

基本的なリレーションシップのパターン

ER図

ER図基本パターン

python

  • Personクラスからrelation_shipでTicketクラスにリレーションを貼る
  • Person側のrelation_shipにback_refがついているのでTicket側にはrelation_shinpの設定をしなくてもリレーションが貼られる
  • Personのtickets = relationship("Ticket", backref="persons")の様にする

pythonのソース(全文)

from sqlalchemy import VARCHAR, Column, ForeignKey, MetaData, UniqueConstraint, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker

url = "sqlite:///"
engine = create_engine(url, echo=False)
Session = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
Base = declarative_base()


class Person(Base):
    __tablename__ = "persons"

    id = Column(VARCHAR(4), autoincrement=False, primary_key=True)
    name = Column(VARCHAR(30), nullable=False)

    tickets = relationship("Ticket", backref="persons")

    def dict(self):
        tickets = [ticket.dict() for ticket in self.tickets]

        res = {"id": self.id, "name": self.name, "tickets": tickets}
        return res


class Ticket(Base):
    __tablename__ = "tickets"

    person_id = Column(VARCHAR(4), ForeignKey("persons.id"), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    room_name = Column(VARCHAR(30), nullable=False)

    def dict(self):
        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "room_name": self.room_name,
        }
        return res


metadata = MetaData()


Base.metadata.create_all(bind=engine, checkfirst=True)


def insert_data():
    with Session() as session:
        person = Person(id=1, name="hoge")
        tickets = [
            Ticket(person_id="1000", ticket_id="10001000", room_name="hoge"),
            Ticket(person_id="1000", ticket_id="10001001", room_name="hage"),
        ]

        person.tickets.extend(tickets)

        session.add(person)
        session.commit()


def query():
    import json

    with Session() as session:
        session = Session()
        res = session.query(Person).one()

        print(json.dumps(res.dict(), indent=2))

        res = session.query(Ticket).filter(Ticket.ticket_id == "10001000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.persons.dict(), indent=2))


if __name__ == "__main__":
    insert_data()
    query()

実行結果

# print(json.dumps(res.dict(), indent=2))
{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge"
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage"
    }
  ]
}
# print(json.dumps(res.dict(), indent=2))
{
  "person_id": "1",
  "ticket_id": "10001000",
  "room_name": "hoge"
}
# リレーションを参照して親(Person)側から再度データを取得している
# print(json.dumps(res.persons.dict(), indent=2))
{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge"
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage"
    }
  ]
}

拡張したパターン

ER図(最初に貼ったのと同じ)

ER図(最初と同じ)

python

  • ticketテーブルのときは外部制約に利用するperson_idにはForeignkeyをつけたけれどもbookinsの様に外部制約キーが2個ある場合はテーブルのColumnの方にはForeignKeyをつけずに table_args = (ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),)の様にする

bookingとticketの双方向のリレーションを構築する

tikect → booking
  bookings = relationship(
        "Booking", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="ticket_back_populates"
    )
booking → ticket
  __table_args__ = (ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),)

  ticket_back_populates = relationship(
        "Ticket", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="bookings"
    )

ソースコード

pythonのソース(全文)

from sqlalchemy import VARCHAR, Column, ForeignKey, ForeignKeyConstraint, MetaData, UniqueConstraint, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker

url = "sqlite:///"
engine = create_engine(url, echo=False)
Session = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
Base = declarative_base()


class Person(Base):
    __tablename__ = "persons"

    id = Column(VARCHAR(4), autoincrement=False, primary_key=True)
    name = Column(VARCHAR(30), nullable=False)

    tickets = relationship("Ticket", backref="persons")

    def dict(self):
        tickets = [ticket.dict() for ticket in self.tickets]

        res = {"id": self.id, "name": self.name, "tickets": tickets}
        return res


class Ticket(Base):
    __tablename__ = "tickets"

    person_id = Column(VARCHAR(4), ForeignKey("persons.id"), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    room_name = Column(VARCHAR(30), nullable=False)
    bookings = relationship(
        "Booking", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="ticket_back_populates"
    )

    def dict(self):
        bookings = [booking.dict() for booking in self.bookings]

        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "room_name": self.room_name,
            "bookings": bookings,
        }
        return res


class Booking(Base):
    __tablename__ = "bookings"
    __table_args__ = (ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),)

    person_id = Column(VARCHAR(4), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    booking_id = Column(VARCHAR(12), primary_key=True, autoincrement=False)
    comment = Column(VARCHAR(30), nullable=True)

    ticket_back_populates = relationship(
        "Ticket", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="bookings"
    )

    def dict(self):
        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "booking_id": self.booking_id,
            "comment": self.comment,
        }
        return res


metadata = MetaData()
metadata.reflect(engine)


Base.metadata.create_all(bind=engine, checkfirst=True)


def insert_data():
    with Session() as session:
        person = Person(id=1, name="hoge")
        tickets = [
            Ticket(person_id="1000", ticket_id="10001000", room_name="hoge"),
            Ticket(person_id="1000", ticket_id="10001001", room_name="hage"),
        ]
        booking1 = Booking(person_id="1000", ticket_id="10001000", booking_id="100010001000", comment="first ticket")
        tickets[0].bookings.append(booking1)

        booking2 = [
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011000", comment="first ticket"),
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011001", comment="second ticket"),
        ]
        tickets[1].bookings.extend(booking2)

        person.tickets.extend(tickets)

        session.add(person)
        session.commit()


def query():
    import json

    with Session() as session:
        session = Session()
        res = session.query(Person).one()

        print(json.dumps(res.dict(), indent=2))

        res = session.query(Ticket).filter(Ticket.ticket_id == "10001000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.persons.dict(), indent=2))

        res = session.query(Booking).filter(Booking.booking_id == "100010011000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.persons.dict(), indent=2))


if __name__ == "__main__":
    insert_data()
    query()

実行結果

# print(json.dumps(res.dict(), indent=2))

{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001000",
          "booking_id": "100010001000",
          "comment": "first ticket"
        }
      ]
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011000",
          "comment": "first ticket"
        },
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011001",
          "comment": "second ticket"
        }
      ]
    }
  ]
}
# print(json.dumps(res.dict(), indent=2))

{
  "person_id": "1",
  "ticket_id": "10001000",
  "room_name": "hoge",
  "bookings": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "booking_id": "100010001000",
      "comment": "first ticket"
    }
  ]
}
# print(json.dumps(res.persons.dict(), indent=2))

{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001000",
          "booking_id": "100010001000",
          "comment": "first ticket"
        }
      ]
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011000",
          "comment": "first ticket"
        },
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011001",
          "comment": "second ticket"
        }
      ]
    }
  ]
}
# print(json.dumps(res.dict(), indent=2))

{
  "person_id": "1",
  "ticket_id": "10001001",
  "booking_id": "100010011000",
  "comment": "first ticket"
}
# print(json.dumps(res.ticket_back_populates.dict(), indent=2))

{
  "person_id": "1",
  "ticket_id": "10001001",
  "room_name": "hage",
  "bookings": [
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "booking_id": "100010011000",
      "comment": "first ticket"
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "booking_id": "100010011001",
      "comment": "second ticket"
    }
  ]
}
#  print(json.dumps(res.ticket_back_populates.persons.dict(), indent=2))

{
  "id": "1",
  "name": "hoge",
  "tickets": [
    {
      "person_id": "1",
      "ticket_id": "10001000",
      "room_name": "hoge",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001000",
          "booking_id": "100010001000",
          "comment": "first ticket"
        }
      ]
    },
    {
      "person_id": "1",
      "ticket_id": "10001001",
      "room_name": "hage",
      "bookings": [
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011000",
          "comment": "first ticket"
        },
        {
          "person_id": "1",
          "ticket_id": "10001001",
          "booking_id": "100010011001",
          "comment": "second ticket"
        }
      ]
    }
  ]
}

おまけ

  • 複数のカラムにuniq制約をつける

  • sqlalchemyのテーブル定義tips qiita.com

構築したいDBのサンプルにuniqを追加したパターン

python(全文)

from sqlalchemy import VARCHAR, Column, ForeignKey, ForeignKeyConstraint, MetaData, UniqueConstraint, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker

url = "sqlite:///"
engine = create_engine(url, echo=False)
Session = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
Base = declarative_base()


class Person(Base):
    __tablename__ = "persons"

    id = Column(VARCHAR(4), autoincrement=False, primary_key=True)
    name = Column(VARCHAR(30), nullable=False)

    tickets = relationship("Ticket", backref="persons")

    def dict(self):
        tickets = [ticket.dict() for ticket in self.tickets]

        res = {"id": self.id, "name": self.name, "tickets": tickets}
        return res


class Ticket(Base):
    __tablename__ = "tickets"
    __table_args__ = (UniqueConstraint("person_id", "ticket_id", name="uniq_person_id_ticket_id"),)

    person_id = Column(VARCHAR(4), ForeignKey("persons.id"), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    room_name = Column(VARCHAR(30), nullable=False)
    bookings = relationship(
        "Booking", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="ticket_back_populates"
    )

    def dict(self):
        bookings = [booking.dict() for booking in self.bookings]

        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "room_name": self.room_name,
            "bookings": bookings,
        }
        return res


class Booking(Base):
    __tablename__ = "bookings"
    __table_args__ = (
        UniqueConstraint("person_id", "ticket_id", "booking_id", name="uniq_person_id_ticket_id_booking_id"),
        ForeignKeyConstraint(["person_id", "ticket_id"], ["tickets.person_id", "tickets.ticket_id"]),
    )

    person_id = Column(VARCHAR(4), primary_key=True, autoincrement=False)
    ticket_id = Column(VARCHAR(8), primary_key=True, autoincrement=False)
    booking_id = Column(VARCHAR(12), primary_key=True, autoincrement=False)
    comment = Column(VARCHAR(30), nullable=True)

    ticket_back_populates = relationship(
        "Ticket", foreign_keys="[Booking.person_id, Booking.ticket_id]", back_populates="bookings"
    )

    def dict(self):
        res = {
            "person_id": self.person_id,
            "ticket_id": self.ticket_id,
            "booking_id": self.booking_id,
            "comment": self.comment,
        }
        return res


metadata = MetaData()
metadata.reflect(engine)


Base.metadata.create_all(bind=engine, checkfirst=True)


def insert_data():
    with Session() as session:
        person = Person(id=1, name="hoge")
        tickets = [
            Ticket(person_id="1000", ticket_id="10001000", room_name="hoge"),
            Ticket(person_id="1000", ticket_id="10001001", room_name="hage"),
        ]
        booking1 = Booking(person_id="1000", ticket_id="10001000", booking_id="100010001000", comment="first ticket")
        tickets[0].bookings.append(booking1)

        booking2 = [
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011000", comment="first ticket"),
            Booking(person_id="1000", ticket_id="10001001", booking_id="100010011001", comment="second ticket"),
        ]
        tickets[1].bookings.extend(booking2)

        person.tickets.extend(tickets)

        session.add(person)
        session.commit()


def query():
    import json

    with Session() as session:
        session = Session()
        res = session.query(Person).one()

        print(json.dumps(res.dict(), indent=2))

        res = session.query(Ticket).filter(Ticket.ticket_id == "10001000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.persons.dict(), indent=2))

        res = session.query(Booking).filter(Booking.booking_id == "100010011000").one()
        print(json.dumps(res.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.dict(), indent=2))
        print(json.dumps(res.ticket_back_populates.persons.dict(), indent=2))


if __name__ == "__main__":
    insert_data()
    query()