DockerでローカルのMySQL環境を構築する
Macのローカル環境にDockerでMySQL立てた時のメモ。
ディレクトリ構成は以下を想定。
docker-compose.yml docker - db - conf.d - my.cnf - data - initdb.d - 001_init.sql
まずはdocker-compose.ymlを用意。 Volumesでローカルのディレクトリ/ファイルをコンテナにマウントする。 /docker/db/data でデータを永続化できる データ消す場合はこのディレクトリ以下を削除する必要がある。
version: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: mydb MYSQL_USER: mydb MYSQL_PASSWORD: mydb TZ: "Asia/Tokyo" volumes: - ./docker/db/conf.d:/etc/mysql/conf.d - ./docker/db/initdb.d:/docker-entrypoint-initdb.d - ./docker/db/data:/var/lib/mysql ports: - "3306:3306"
続いてmy.cnfを用意。
[mysql] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 sql_mode = STRICT_TRANS_TABLES skip-character-set-client-handshake collation-server = utf8mb4_general_ci init-connect = SET NAMES utf8mb4 bind-address = 0.0.0.0
これだけあればとりあえずMySQLのコンテナを立ち上げられるので試す。
$ docker-compose up db
rootユーザでログインする。
$ mysql -uroot -p -h 127.0.0.1 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 2 Server version: 5.7.24 MySQL Community Server (GPL) Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mydb | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.02 sec)
docker-compose.ymlで指定したユーザmydbでログインする。
$ mysql -umydb -p -h 127.0.0.1 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.24 MySQL Community Server (GPL) Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mydb | +--------------------+ 2 rows in set (0.01 sec)
起動時に初期化したい場合は docker/db/initdb.d/
にシェルやSQLファイルを置くと実行される。
試しに初期化時に2つ目のテスト用のテーブルを作成する。
001-create.sql
CREATE DATABASE IF NOT EXISTS mydb; GRANT ALL ON *.* to 'mydb'@'%';
シェルにしたい場合。 001-create.sh
#!/bin/sh echo "CREATE DATABASE IF NOT EXISTS test_mydb;" echo "GRANT ALL ON *.* to 'mydb'@'%';"
環境変数を使って docker-compose.yml
を汎用的にする
.env
MYSQL_PORT=3306 MYSQL_DATABASE=mydb MYSQL_USER=mydb MYSQL_PASSWORD=mydb MYSQL_TEST_DATABASE=test_mydb
環境変数使った docker-compose.yml
version: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_TEST_DATABASE: ${MYSQL_TEST_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} TZ: "Asia/Tokyo" volumes: - ./docker/db/conf.d:/etc/mysql/conf.d - ./docker/db/initdb.d:/docker-entrypoint-initdb.d - ./docker/db/data:/var/lib/mysql ports: - "${MYSQL_PORT:-3306}:3306"
初期化用のシェルも環境変数使う場合。
#!/bin/sh # Django TEST DB if [ "$MYSQL_TEST_DATABASE" ]; then echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_TEST_DATABASE\` ;" | "${mysql[@]}" echo "GRANT ALL ON \`$MYSQL_TEST_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}" echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}" fi
プログラム側からMySQLに接続するように以下の環境変数も追加しておくと便利。 .env
MYSQL_HOST=127.0.0.1 DATABASE_URL=mysql://mydb:mydb@127.0.0.1:3306/mydb
Lambdaから他のLambda関数を呼び出す
Lambda使っていて、ふとLambdaから他のLambda関数って呼べないのかなと思って調べてみたらできたのでその時のメモ。
InvocationTypeで同期処理か非同期処理かの呼び出し方法を指定できる。 期処理がよければRequestResponseを指定、非同期ならEventを指定する。 非同期処理にすると最初のLambdaはすぐにレスポンスを返してくれるので、Lambda呼び出し先のタイムアウト等を気にする必要がなくなる。 呼び出した関数からのレスポンスを使いたいば同期処理にすればよい。
同期で関数を呼びたい場合
import os import json import boto3 import logging logger = logging.getLogger() logger.setLevel(logging.INFO) # 呼び出したい関数名 function_name = os.environ.get('LAMBDA_FUNCTION_NAME') def lambda_handler(event: dict, context): # ログ出力 logging.info(json.dumps(event)) client = boto3.client('lambda') response = client.invoke( FunctionName=function_name, InvocationType='RequestResponse', LogType='Tail', Payload=json.dumps(event) ) return { 'statusCode': 200, 'body': json.dumps('OK') }
非同期で関数を呼びたい場合
import os import json import boto3 import logging logger = logging.getLogger() logger.setLevel(logging.INFO) # 呼び出したい関数名 function_name = os.environ.get('LAMBDA_FUNCTION_NAME') def lambda_handler(event: dict, context): # ログ出力 logging.info(json.dumps(event)) client = boto3.client('lambda') client.invoke( FunctionName=function_name, InvocationType='Event', LogType='Tail', Payload=json.dumps(event) ) return { 'statusCode': 200, 'body': json.dumps('OK') }
やっていてハマったのが、非同期処理にしていても以下の用にレスポンスを変数で受け取るようにすると結果が帰ってくるまで最初のLambdaの実行が終わらない。 そのため、場合によっては呼び出し元がタイムアウトと勘違いをしてリトライしてくる可能性がある。 SlackからLambdaのAPIをたたいていて実際にあった。Slackは3秒以内にレスポンスが帰らなければリトライするようです。
response = client.invoke( FunctionName=function_name, InvocationType='Event', LogType='Tail', Payload=json.dumps(event) )
ちょっと長い処理だったり、条件によって処理をわけたい場合等に使うと力を発揮してくれそう。 個人的には非同期処理がLambdaだけでできるのは助かるな。
参考
AWS LambdaからLambdaを非同期で呼び出す(Python) - Qiita
DjangoのモデルでHaving句を使う
ユーザと趣味モデルを定義して、趣味が3つ以上ある既婚者を取得する例。
from django.db import models class Hobby(models.Model): """ 趣味モデル """ name = models.CharField(max_length=255, null=False, blank=False) class User(models.Model): """ ユーザモデル """ name = models.CharField(max_length=255, null=False, blank=False) is_married = models.BooleanField(default=False) hobbies = models.ManyToManyField( to=Hobby, )
# 既婚者で趣味が3つ以上あるユーザ取得 User.objects.annotate(hobby_count=Count('hobbies')).filter(is_married=True, hobby_count__gte=3)
参照
Pythonで電話番号をハイフン区切りに変換する
電話番号をハイフン区切りにできる便利なライブラリがないか探していたところ、GoogleがGithubで公開しているlibphonenumber
を見つけました!
libphonenumber
は国際電話番号を解析・変換・バリデーションすることができます。
だがしかし!Java
, C++
, JavaScript
の実装しかない…
諦めかけたその時、サードパーティが多言語に移植してくれている一覧を発見!C#
, Go
, Objective-c
, PHP
, PostgreSQL in-database types
, Python
, Ruby
…!!! Python
もありました!
名前は phonenumbers
になってますね。わからいやすいな。
早速こちらのライブラリを試して見ました。
まずはライブラリのインストール
$ pip install phonenumbers
Pythonコンソール起動して実行
$ python >>> import phonenumbers >>> tel = '0300000000' >>> tel_hyphen = phonenumbers.parse(tel, 'JP') >>> phonenumbers.format_number(tel_hyphen, phonenumbers.PhoneNumberFormat.NATIONAL) u'03-0000-0000'
ちゃんとハイフン区切りに変換されました!素晴らしい… 変換する電話番の形式がおかしかったりすると例外投げるので、入力ミスった値が来た時用に例外処理は入れといた方がよさそう。
libphonenumberの解説をしてくれているスライドあったので気になったら見てみよう。
Djangoのテンプレート内でリレーション関係の子データをソートする
リレーション組んで取得した子データの表示順がバラバラだったのでどうにかできないかと思ったらテンプレートの組み込みタグである dictsort
を使って解決できました!
dictsortを指定したキーで昇順にする
<ul> {% for user in users %} {% for hobby in user.hobbies.all|dictsort:"id"%} <li>{{ hobby.category.name }}: {{ hobby.name }}</li> {% endfor %} {% endfor %} </ul>
降順にしたい場合はdictsortreversedを指定する。てっきりorder_byみたいに-つければいいのかと思った...
<ul> {% for user in users %} {% for hobby in user.hobbies.all|dictsortreversed:"id"%} <li>{{ hobby.category.name }}: {{ hobby.name }}</li> {% endfor %} {% endfor %} </ul>
更にリレーション関係のあるキーでソートしたい場合は、カンマ区切りで指定できる。
<ul> {% for user in users %} {% for hobby in user.hobbies.all|dictsort:"category.id"%} <li>{{ hobby.category.name }}: {{ hobby.name }}</li> {% endfor %} {% endfor %} </ul>
参考
Pythonで数字を3桁区切りカンマにする
Integer型の場合
f"{num:,d}"
>>> num = 1000000 >>> print(f"{num:,d}") 1,000,000
Float型の場合
f"{num:,.2f}"
>>> num = 1234.5678 >>> print(f"{num:,.2f}") 1,234.57
参考
aliasで設定したコマンドをシェルスクリプト内で実行する
シェルスクリプトを実行したらaliasで追加したコマンドが無いよと怒られた...
./hoge.sh: 行 40: hoge: コマンドが見つかりません
普段コマンドラインで使っているコマンドだったので、???状態。
man bash
で見たらインタラクティブモードじゃないとaliasで登録したコマンドが使いえないらしい...
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt (see the description of shopt under SHELL BUILTIN COMMANDS below).
そこで、 /etc/bashrc
にaliasを登録して、環境変数を渡すことでシェルスクリプト内でもコマンドを実行できるようにした。
$ BASH_ENV=~/.bashrc bash -O expand_aliases hoge.sh