skydum

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

dockerのCentOS7でtelnetdを使えるようにする

dockerでCentOS7 +telnetdを起動してホストから接続する

  • 検証用にdockerで起動したCentOS7にホストからtelnetdで接続したいので作った
  • 検証用なのでコンテナイメージをなるべく小さくするなどの対処は行わない

事前準備

  • CentOS7のdockerイメージそのままではtelnetdは使えないのでDockerfileを作ってイメージを作成する
  • 任意の場所に以下のDockerfileを作る
  • 作成するコンテナイメージでtelnetで接続するユーザー
    • ID: root, password: rootとする
    • ID: user, password: userとする
# syntax=docker/dockerfile:1-labs
FROM centos:7

RUN yum update -y
RUN yum install -y xinetd
RUN yum install -y telnet-server

# rootで同時に接続したいユーサー数の数だけ書く
# 一般ユーザーの場合は記載する必要なし
RUN echo "pts/0" >> /etc/securetty
RUN echo "pts/1" >> /etc/securetty
RUN echo "pts/2" >> /etc/securetty

RUN echo "root:root" | chpasswd

RUN adduser user
RUN echo "user:user" | chpasswd

COPY <<EOF /etc/xinetd.d/telnet
service telnet
{
    flags = REUSE
    socket_type = stream
    wait = no
    user = root
    server = /usr/sbin/in.telnetd
    log_on_failure += USERID
    disable = no
}
EOF

COPY <<EOF /docker-entrypoint.sh
#!/bin/bash
xinetd -dontfork -stayalive
EOF

RUN chmod 777 docker-entrypoint.sh

ENTRYPOINT ["/docker-entrypoint.sh"]

※ Dockerfile中にヒアドキュメントがつかえるようになっていたので使ってみた
ヒアドキュメントを使うとホスト側にファイルを用意してdocker内へファイルをコピーするという手間が減るので便利

telnetdが使える様にしたイメージの作成

  • 以下のコマンドでssh-serverの名称でイメージを作成する
  • 作成するコンテナイメージの名前はcentos7-sshd:latest
# ヒアドキュメントを使う場合はdocker buildではなくて、docker buildxを利用する
# コンテナをビルドしているときの表示も少し変わる
$ docker buildx build --tag centos-telnetd .

$ docker images
REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
centos-telnetd   latest    fd4dfbbb65ad   27 minutes ago   796MB

centos-telnetdのイメージを起動する

  • 起動するサーバのホスト名: telned
  • 起動するサーバのコンテナ名: telnet-server
  • 起動するサーバのポート番号: 10023
$ docker run -it --hostname telentd --name telnet-server -p 10023:23/tcp -d centos-telnetd

# 以下のように--rmをつけてイメージを起動するとコンテナが停止した時に自動的にコンテナが削除される
# $ docker run --rm -it --hostname telentd --name telnet-server -p 10023:23/tcp -d centos-telnetd


$ $ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                                     NAMES
a663e68cffa3   centos-telnetd   "/docker-entrypoint.…"   3 seconds ago   Up 2 seconds   0.0.0.0:10023->23/tcp, :::10023->23/tcp   telnet-server

起動したtelnet-serverにtelnetでホストから接続する

$ telnet localhost 10023
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Kernel 5.4.0-109-generic on an x86_64
telentd login: root
Password: 
[root@telentd ~]# 

telnet-serverを停止してイメージを削除

$ docker ps -a    # 停止しているコンテナも含めて表示する
CONTAINER ID   IMAGE            COMMAND                  CREATED              STATUS              PORTS                                     NAMES
a663e68cffa3   centos-telnetd   "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:10023->23/tcp, :::10023->23/tcp   telnet-server

$ docker stop telnet-server
$ docker rm a663e68cffa3    # docker rm telnet-serverでも可

$ docker images
REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
centos-telnetd   latest    fd4dfbbb65ad   33 minutes ago   796MB

$ docker rmi fd4dfbbb65ad    # docker rmi centos7-telnetd:latestでも可

$ docker ps -a    # 停止しているコンテナも含めて表示する
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

$ docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

dockerのCentOS7でsshdを使えるようにする

dockerでCentOS7 + sshdを起動してホストから接続する

  • 検証用にdockerで起動したCentOS7にホストからsshdで接続したいので作った
  • 検証用なのでコンテナイメージをなるべく小さくするなどの対処は行わない

事前準備

  • CentOS7のdockerイメージそのままではsshdは使えないのでDockerfileを作ってイメージを作成する
  • 任意の場所に以下のDockerfileを作る
  • 作成するコンテナイメージでsshで接続するユーザー
    • ID: root, password: rootとする
    • ID: user, password: userとする
FROM centos:7

RUN yum update -y
RUN yum install -y openssh-server

RUN ssh-keygen -A
RUN echo "root:root" | chpasswd

RUN adduser user
RUN echo "user:user" | chpasswd

CMD ["/usr/sbin/sshd", "-D"]

はてなブログはDockerfileはシンタックスハイライトが効かない

sshdが使える様にしたイメージの作成

  • 以下のコマンドでssh-serverの名称でイメージを作成する
  • 作成するコンテナイメージの名前はcentos7-sshd:latest
$ docker build --tag centos7-sshd .

$ docker images
REPOSITORY     TAG       IMAGE ID       CREATED              SIZE
centos7-sshd   latest    4b6b1ef383b0   33 seconds ago       673MB
<none>         <none>    9d2878d51761   About a minute ago   885MB
centos         7         eeb6ee3f44bd   7 months ago         204MB

sshd-serverのイメージを起動する

  • 起動するサーバのホスト名: server
  • 起動するサーバのコンテナ名: sshd-server
  • 起動するサーバのポート番号: 10022
$ docker run -it --hostname server --name sshd-server -p 10022:22/tcp -d centos7-sshd

$ docker ps
CONTAINER ID   IMAGE          COMMAND               CREATED         STATUS         PORTS                                     NAMES
d94cb10cd66f   centos7-sshd   "/usr/sbin/sshd -D"   8 seconds ago   Up 7 seconds   0.0.0.0:10022->22/tcp, :::10022->22/tcp   sshd-server

起動したsshd-serverにsshでホストから接続する

$ ssh -p 10022 root@localhost
The authenticity of host '[localhost]:10022 ([127.0.0.1]:10022)' can't be established.
ECDSA key fingerprint is SHA256:Z3I2gQ3w7wo1MNJ/8fuTBE1Wnd169RR0qgZ5IyhBHpk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:10022' (ECDSA) to the list of known hosts.
root@localhost's password: 
[root@server ~]#

sshで接続した時にエラーが出た場合

$ ssh -p 10022 root@localhost
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:/W9DGeoHg56m1FvGj8UWp9SBJ+ZNcRVhDdVZDI52XaA.
Please contact your system administrator.
Add correct host key in /home/username/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/username/.ssh/known_hosts:1
  remove with:
  ssh-keygen -f "/home/username/.ssh/known_hosts" -R "[localhost]:10022"
ECDSA host key for [localhost]:10022 has changed and you have requested strict checking.
Host key verification failed.
  • エラーが出た場合~/.ssh/known_hostsに書かれている接続先のホストの公開鍵と現在の接続先のホストの公開鍵が一致しない為エラーが出ている(中間者攻撃対策のため)
    以下のコマンドを使って古いホストの情報を削除して、もう一度接続する
$ ssh-keygen -R [localhost]:10022
# Host [localhost]:10022 found: line 1
/home/username/.ssh/known_hosts updated.

$ ssh -p 10022 root@localhost
The authenticity of host '[localhost]:10022 ([127.0.0.1]:10022)' can't be established.
ECDSA key fingerprint is SHA256:/W9DGeoHg56m1FvGj8UWp9SBJ+ZNcRVhDdVZDI52XaA.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:10022' (ECDSA) to the list of known hosts.
root@localhost's password: 
[root@server ~]#

sshd-serverを停止してイメージを削除

$ docker ps -a    # 停止しているコンテナも含めて表示する
CONTAINER ID   IMAGE          COMMAND               CREATED          STATUS                      PORTS     NAMES
3ca739888943   centos7-sshd   "/usr/sbin/sshd -D"   21 seconds ago   Exited (0) 12 seconds ago             sshd-server

$ docker stop sshd-server
$ docker rm 3ca739888943    # docker rm sshd-serverでも可

$ docker images
REPOSITORY     TAG       IMAGE ID       CREATED         SIZE
centos7-sshd   latest    5c352901945b   4 seconds ago   673MB
centos         7         eeb6ee3f44bd   7 months ago    204MB

$ docker rmi 5c352901945b    # docker rmi centos7-sshd:latestでも可
$ docker rmi eeb6ee3f44bd    # docker rmi centos:7でも可

$ docker ps -a    # 停止しているコンテナも含めて表示する
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

$ docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

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);
            },
        },
    },
});