pythonでvenvを使ってみる

背景

以前から引き続きPythonの学習を進めています。

もともとRubyの方が知っている私にとって、bundlerでいうのようにプロジェクトごとに利用するモジュールをわける方法はなんだろう?という疑問が残っていました。

調べてみると以下のページが見つかります。

gem, bundler と pip, venv の比較

このページをみると、bundlerの–pathオプションにて設定する場合と同等のことがvenvを用いてできると書かれていました。

venvに俄然興味が出てきたので、venvについて調べたことを記していきたいと思います。

venvとは

公式サイトには以下のように記載されています。

1
2
3
4
venv モジュールは、軽量な "仮想環境" の作成のサポートを提供します。
仮想環境には、仮想環境ごとの site ディレクトリがあり、これはシステムの site ディレクトリから分離させることができます。
それぞれの仮想環境には、それ自身に (この仮想環境を作成するのに使ったバイナリのバージョンに合った) Python バイナリがあり、
仮想環境ごとの site ディレクトリに独立した Python パッケージ群をインストールできます。

ということなので、bundlerと同等の機能が提供されていそうです。早速インストールしてみます。

venvのインストール

venvのインストール方法は

1
python -m venv /path/to/new/virtual/environment

と書かれています。Djangoのプロジェクト配下にvenvというディレクトリを作成してそこで管理しようと考えていたので、インストールコマンドは以下のようになります。

1
2
$ cd django_project
$ python -m venv venv

実行すると、djangoプロジェクトの配下にvenvというディレクトリが作成されました。

venvディレクトリ配下のファイルを確認します

1
2
$ ls venv
bin include lib pyvenv.cfg

各ファイル、ディレクトリの役割を見ていきます

binディレクトリ

binディレクトリの中のファイルを確認します

1
2
3
4
5
6
7
8
9
10
11
12
$ ls venv/bin
total 72
-rw-r--r-- 1 user staff 8834 7 19 21:34 Activate.ps1
-rw-r--r-- 1 user staff 1909 7 19 21:34 activate
-rw-r--r-- 1 user staff 858 7 19 21:34 activate.csh
-rw-r--r-- 1 user staff 1998 7 19 21:34 activate.fish
-rwxr-xr-x 1 user staff 250 7 19 21:34 pip
-rwxr-xr-x 1 user staff 250 7 19 21:34 pip3
-rwxr-xr-x 1 user staff 250 7 19 21:34 pip3.9
lrwxr-xr-x 1 user staff 60 7 19 21:33 python -> /Users/user/.anyenv/envs/pyenv/versions/3.9.5/bin/python
lrwxr-xr-x 1 user staff 6 7 19 21:33 python3 -> python
lrwxr-xr-x 1 user staff 6 7 19 21:33 python3.9 -> python

venvを有効にするactivateスクリプトと、python、pipがあります。pythonはこのプロジェクトで利用しているpyenvのpythonへのリンクになっています。

includeディレクトリ

includeディレクトリは空でした。有効化した後に変化が起こるか見て見たいと思います。

lib

libディレクトリはこの仮想環境でモジュールがインストールされるディレクトリです。

1
2
3
4
5
6
7
8
9
$ ls -l venv/lib/python3.9/site-packages/
total 8
drwxr-xr-x 5 user staff 160 7 19 21:33 _distutils_hack
-rw-r--r-- 1 user staff 152 7 19 21:33 distutils-precedence.pth
drwxr-xr-x 8 user staff 256 7 19 21:33 pip
drwxr-xr-x 10 user staff 320 7 19 21:34 pip-21.1.1.dist-info
drwxr-xr-x 7 user staff 224 7 19 21:33 pkg_resources
drwxr-xr-x 41 user staff 1312 7 19 21:33 setuptools
drwxr-xr-x 11 user staff 352 7 19 21:33 setuptools-56.0.0.dist-info

pipなど標準でインストールされているモジュールが表示されていることがわかります。

pyvenv.cfg

1
2
3
home = /Users/user/.anyenv/envs/pyenv/versions/3.9.5/bin
include-system-site-packages = false
version = 3.9.5

pythonのディレクトリや、バージョンなどが記載されています。

venvの有効化

作成した仮想環境を有効にするにはbinディレクトリの下にあったactivateスクリプトを読み込みます。

1
2
$ source venv/bin/activate
(venv) $

プロンプトの先頭に(venv)が追加されました。仮想環境が有効になっていることがわかります。

無効にする場合はdeactivateコマンドを実行します。

1
2
(venv) $ deactivate
$

プロンプトの(venv)がなくなりました。

仮想環境へのパッケージのインストール

次にパッケージのインストールを行ってみます。

試しに仮想環境でないグローバルなpython3.9.5のインストール済パッケージ一覧を出力します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ pip list
Package Version
--------------- --------
awscli 1.18.169
botocore 1.19.9
colorama 0.4.3
docutils 0.15.2
jmespath 0.10.0
pip 21.1.1
pyasn1 0.4.8
python-dateutil 2.8.1
PyYAML 5.3.1
rsa 4.5
s3transfer 0.3.3
setuptools 56.0.0
six 1.15.0
urllib3 1.25.11
WARNING: You are using pip version 21.1.1; however, version 21.1.3 is available.
You should consider upgrading via the '/Users/user/.anyenv/envs/pyenv/versions/3.9.5/bin/python3.9 -m pip install --upgrade pip' command.

pipのバージョンが最新ではないという警告が出ていますが一旦置いておいて、わたしのグローバルな環境のパッケージの一覧を出してみました。

次に仮想環境を有効にしたあと、パッケージ一覧を出力してみます。

1
2
3
4
5
6
7
8
$ source venv/bin/activate
(venv) $ pip list
Package Version
---------- -------
pip 21.1.1
setuptools 56.0.0
WARNING: You are using pip version 21.1.1; however, version 21.1.3 is available.
You should consider upgrading via the '/Users/user/github/django/venv/bin/python -m pip install --upgrade pip' command.

同じ警告は出ていますが、インストール済パッケージの数が違います。

では、警告に出ているコマンドを実行して仮想環境のpipのバージョンを更新してみましょう。

1
2
3
4
5
6
7
8
9
10
(venv) $ python -m pip install --upgrade pip
Requirement already satisfied: pip in ./venv/lib/python3.9/site-packages (21.1.1)
Collecting pip
Using cached pip-21.1.3-py3-none-any.whl (1.5 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 21.1.1
Uninstalling pip-21.1.1:
Successfully uninstalled pip-21.1.1
Successfully installed pip-21.1.3

pip-21.1.3をインストールしました。警告が出なくなっていることを確認しましょう。

1
2
3
4
5
(venv) $ pip list
Package Version
---------- -------
pip 21.1.3
setuptools 56.0.0

グローバル環境の確認

ここで一旦仮想環境を止めてみます。

1
2
(venv) $ deactivate
$

pip listでインストール済パッケージ一覧を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ pip list
Package Version
--------------- --------
awscli 1.18.169
botocore 1.19.9
colorama 0.4.3
docutils 0.15.2
jmespath 0.10.0
pip 21.1.1
pyasn1 0.4.8
python-dateutil 2.8.1
PyYAML 5.3.1
rsa 4.5
s3transfer 0.3.3
setuptools 56.0.0
six 1.15.0
urllib3 1.25.11
WARNING: You are using pip version 21.1.1; however, version 21.1.3 is available.
You should consider upgrading via the '/Users/user/.anyenv/envs/pyenv/versions/3.9.5/bin/python3.9 -m pip install --upgrade pip' command.

グローバル環境はあいかわらず警告が出ています。先ほどpipをアップグレードしたのは仮想環境のみとなります。

仮想環境を有効にしてパッケージをインストールすれば仮想環境にのみインストールされるということがわかりました。

仮想環境の削除

仮想環境を削除するには作成した仮想環境のディレクトリ(今回はvenv)を削除すれば良さそうです。

1
$ rm -rf venv

git管理について

ここで一つ疑問に思ったのは、Gemfile、Gemfile.lockのようにgit管理にして、誰でも同じような環境を構築できるようにするにはどうしたらよいのか?ということです。

pyvenv.cfgにはpythonへのパスがフルパスで記載されているので、各利用者によって異なってしまいます。ですので、git管理には向いていなさそうです。

インストール済パッケージもファイル数としては膨大なのでgit管理には向いていません。

できることは、RubyでいうGemfile.lockを作ってそれをgit管理することでしょうか。

requirements.txtを作成する

RubyでいうGemfile.lockはrequirements.txtというファイルです。

requirements.txtを作成するにはpip freezeコマンドを利用します。

https://pip.pypa.io/en/stable/cli/pip_freeze/

では実際に仮想環境で作成してみます。

1
2
3
$ source venv/bin/activate
(venv) $ pip freeze
(venv) $

ん?なにも出力されません…と思ったら特に何もモジュールをインストールしていないからなにも出力されないのでした…

試しにDjangoをインストールしてみます。現時点で最新の3.2.5をインストールしてみます。

1
2
3
4
5
6
7
8
9
10
11
(venv) $ pip install django==3.2.5
Collecting django==3.2.5
Using cached Django-3.2.5-py3-none-any.whl (7.9 MB)
Collecting asgiref<4,>=3.3.2
Using cached asgiref-3.4.1-py3-none-any.whl (25 kB)
Collecting sqlparse>=0.2.2
Using cached sqlparse-0.4.1-py3-none-any.whl (42 kB)
Collecting pytz
Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
Installing collected packages: sqlparse, pytz, asgiref, django
Successfully installed asgiref-3.4.1 django-3.2.5 pytz-2021.1 sqlparse-0.4.1

インストールできました。この状態でpip freezeを実行してみます。

1
2
3
4
5
$ pip freeze
asgiref==3.4.1
Django==3.2.5
pytz==2021.1
sqlparse==0.4.1

この出力をrequirements.txtとして保存してgit管理するのがよいかと思います。

まとめ

Rubyとの違いに戸惑いながらなんとかPythonを学んでいっています。今回のvenvについては、Dockerを利用している場合はあまり関係ありません。Docker内のPythonにインストールされるためです。

ですが、Pythonのパッケージを追加するたびにイメージのビルドをしないといけないとするとそれはとても面倒ですね…Dockerでvenvを使ってそのディレクトリをDocker Volumeとしていくのが良いのでしょうか。

次回はこのあたりを考えていきたいと思います。

SQLで文字列を置き換える方法(MySQL)

背景

都道府県のIDをカンマ区切りで持つカラムがあり(それ自体がどうなのか?という観点は置いておいて)、IDではなく都道府県名を表示してほしいという依頼がありました。

都道府県IDを都道府県名に置き換えるために文字列を置換する関数を探してみました。

文字列の置換

MySQLにはreplaceという関数があります。
https://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_replace

replaceの引数

replace

1
replace(置換対象の文字列, 置換する文字列, 置換後の文字列)

となっています。

公式に書いてある例を手元で実行してみます。

1
2
3
4
5
6
7
mysql> SELECT REPLACE('www.mysql.com', 'w', 'Ww');
+-------------------------------------+
| REPLACE('www.mysql.com', 'w', 'Ww') |
+-------------------------------------+
| WwWwWw.mysql.com |
+-------------------------------------+
1 row in set (0.00 sec)

wは複数回現れますが、それを全て置換していることがわかります。

複数の文字列を置換するには

今回置換したい文字列は都道府県IDそれぞれを都道府県名に置換したいので、最大で47回置換する必要があります。

例えば以下の例は

1
2
3
4
5
6
7
mysql> select '1,11,12,13,14' as prefecture_ids;
+----------------+
| prefecture_ids |
+----------------+
| 1,11,12,13,14 |
+----------------+
1 row in set (0.00 sec)

置換後このようになっている必要があります。

1
北海道,埼玉県,千葉県,東京都,神奈川県

置換後の文字列を再度置換することで、実現できます。

1
2
3
4
5
6
7
mysql> select replace(replace(replace(replace(replace('1,11,12,13,14', '14', '神奈川県'), '13', '東京都'), '12', '千葉県'), '11', '埼玉県'), '1', '北海道') as prefecture_names;
+------------------------------------------------------+
| prefecture_names |
+------------------------------------------------------+
| 北海道,埼玉県,千葉県,東京都,神奈川県 |
+------------------------------------------------------+
1 row in set (0.00 sec)

47都道府県を行うのは大変ですが、見えやすいように入れ子で書けるといいですね。

対象文字列に置換文字列が複数現れる場合は全て変換されてしまうので、重複しないよう大きな数字から変換する必要があります。北海道の変換よりも埼玉県の変換を行わないと、埼玉県となるところが北海道北海道となってしまいます。

まとめ

今回はreplace関数を利用しました。マスターデータがシンプルな場合で、joinしづらいデータに関してはクエリで置換する処理を書いてもよいと思います。

参考図書

Railsでデプロイ中にデータベースマイグレーションが止まってしまう

背景

普段はRailsを利用していて、いつも通りステージングデプロイ後にプロダクションデプロイを行う予定でした。

デプロイツールはcapistrano3を利用しています。今回のデプロイではデータベースマイグレーションも含まれています。データベースマイグレーションが含まれているデプロイは通常のデプロイより少し緊張しますが、基本的にはなにも問題は起こりません。

ステージング環境ではなにごともなくデプロイが完了し、マイグレーションも終わっています。動作確認も終わったので本番にデプロイしました。すると、マイグレーションで止まってしまい、先に進みません…

原因

原因がわからず何度かデプロイを行ってみましたが変わりませんでした。

そういえばデプロイ前にデータ補正のためにMySQLクライアントでデータベースに接続していました。関係あるのかなぁ?と思ったのですが、通常、いろんなクライアントが接続しているため問題ないと思いました。

とりあえず接続しているMySQLクライアントを終了すると、止まっていたデプロイが一気に進み、エラーを大量に吐いて終了しました。

このときになんとなく原因がわかりました。データ補正のためにトランザクションをかけていたのでした

1
2
3
4
5
mysql> set autocommit = 0;
mysql> update data_table set name = 'xxxxxx';
mysql> commit;

この状態で放置していた…

今回マイグレーションではデータ補正していたテーブルに対してカラムを追加するという処理を行っていました。トランザクションをかけていたのが原因だったのです。

まとめ

マイグレーションを行うデプロイを行う際はトランザクションを利用しているセッションは切っておくこと。

2要素認証設定後のgitコマンドの認証について

背景

セキュリティの観点からGitHubでもMFAを利用しようと思い設定してみました。

設定方法についてはこちらを参考にしてスムーズに行うことができました。

サインアウト→サインインもできることを確認して、VSCodeからgit pullしようとしたところ、エラーが発生しました。

1
2
3
$ git pull
remote: Repository not found.
fatal: repository 'https://github.com/organization/repository' not found

同じようなことがなんども起こりそうなのでメモしておきます。

personal access tokenを取得する

こちらに記載があるように、コマンドラインの認証にはパスワードではなくPersonal access tokenが必要でした。まずはこちらを取得します。

先ほどのドキュメントのようにコマンドラインからリポジトリにアクセスするための権限のみ設定します(repoにチェックを入れるだけにします)

PATが作成されるので、どこかに書き留めておきます。

personal access tokenを利用する

コマンドラインから利用できるかを試してみます。利用するにはリモートのURLがHTTPSであること、URLにユーザー名、パスワードを設定することが必要となります。

設定のために.git/configを編集します。変更されているかどうかを確認します。

1
2
$ git config remote.origin.url
https://username:personal-access-token@github.com/organization/repository.git

変更されていることを確認しました。

試しにgit pullしてみます。

1
2
$ git pull
Already up to date.

無事認証されました。

おまけ

MFAを設定後、他の人が作成してくれたPullRequestをレビューしていてApproveしようとしたところ、以下のような画面が表示されました。

なんだろうと思って調べてみると、MFAの設定をきっかけにこうなる人が多いようです。

ぼくは別のブラウザ(Vivaldi)でApproveするとうまくいきました。

まとめ

今回はpersonal access tokenの設定を行なってみました。こちらにあるように、2021年8月13日からパスワード認証がサポートされなくなります。余裕を持って対応できてよかったです。

docker-composeでDjango+MySQLの環境を構築する

背景

前回の記事で使用するプログラミング言語としてPythonが選ばれやすいというお話をしました。

Pythonで利用できるWebフレームワークとして、二つの候補があります。flaskDjangoです。

軽量なフレームワークが良いか、フルスタックのフレームワークが良いか、という議論はあると思いますが、わたしはRailsに慣れているということ、また、10年くらい前にDjangoを利用していたこともあって、Djangoを採用したいと思います。

まずはローカル開発環境を整えようと思い、docker composeを利用してローカル開発環境を構築するところから始めようと思います。

docker composeを利用した例

Django docker composeと検索すると、クィックスタート: Compose と Djangoというページがヒットします。

これはありがたいと思って見てみると、データベースがpostgreSQLという点以外は探していた情報とマッチします。この情報を元にデータベースをpostgreSQLからMySQLに変更してみようと思います。

MySQLコンテナの設定

先ほどのページに載っていたdocker-compose.ymlは以下のようになっていました

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'

services:
db:
image: postgres
web:
build: .
command: python3 manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db

dbをMySQLに変更してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '3'

services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: 'django'
MYSQL_PASSWORD: 'django'
MYSQL_DATABASE: 'django'
ports:
- 3306:3306
volumes:
- mysql:/var/lib/mysql
web:
build: .
command: python3 manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
links:
- db

volumes:
mysql:
driver: local

MySQLコンテナの設定についてはdockerhubのサイトをご確認ください。

ここではdjangoユーザーを作成し、パスワードをdjangoに設定、djangoというデータベースを作成しました。

次にDjango側のデータベース接続設定を行います。

Djangoのデータベース接続設定

Djangoのデータベース接続設定を探します。今回はひながた生成の手順に沿って行っていきます。

プロジェクトの作成

以下のコマンドで作成します

1
docker-compose run web django-admin.py startproject composeexample .

composeexampleが作成できました。

データベース設定ファイルの確認

データベース設定ファイルはcomposeexample/settings.pyに記載されていました。

1
2
3
4
5
6
7
8
9
10
11
12
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'db',
'PORT': 5432,
}
}

postgreSQLの設定になっているので、MySQLの設定に修正します。

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django',
'USER': 'django',
'PASSWORD': 'django',
'HOST': 'db',
'PORT': 3306,
}
}

先ほどMySQLコンテナに設定した内容をsettings.pyに反映しました。

起動確認

docker-compose upで起動してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker-compose up
Starting django_db_1 ... done
Starting django_web_1 ... done
Attaching to django_db_1, django_web_1
(中略)
web_1 | Watching for file changes with StatReloader
web_1 | Performing system checks...
web_1 |
web_1 | System check identified no issues (0 silenced).
web_1 |
web_1 | You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
web_1 | Run 'python manage.py migrate' to apply them.
web_1 | June 24, 2021 - 12:27:25
web_1 | Django version 3.2.4, using settings 'composeexample.settings'
web_1 | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.

http://localhost:8000にアクセスします。

ローカル開発環境でのtopページ

無事確認できました。

まとめ

Django+MySQLのローカル開発環境をdocker composeで作成することができました。

Macで実行しているので、docker-syncを用いてローカルのソースコードの同期をスムーズに行ってみようと思います。

おまけ

Django起動時のメッセージをよく読むと

1
2
web_1  | You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
web_1 | Run 'python manage.py migrate' to apply them.

とありましたので、マイグレーションしてみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ docker-compose run --rm web python manage.py migrate
Starting django_db_1 ... done
Creating django_web_run ... done
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying sessions.0001_initial... OK

マイグレーションが完了しました。

docker-compose upしたときのメッセージも確認します

1
2
3
4
5
6
7
8
web_1  | Watching for file changes with StatReloader
web_1 | Performing system checks...
web_1 |
web_1 | System check identified no issues (0 silenced).
web_1 | June 24, 2021 - 12:59:28
web_1 | Django version 3.2.4, using settings 'composeexample.settings'
web_1 | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.

マイグレーションしてねのメッセージも消えていますね。

参考図書

まもなくDjangoの3.2LTS対応の本が発売されます!出たらすぐ買って読もうと思います!

pyenvを使ってpythonをインストールする

背景

Pythonは周りにかける人が多いので、新しくサービスを開始するときは候補にあがりやすいです。そして、新しくサービスを構築することになり、Python3を採用することになりました(WebフレームワークはDjangoを予定)。

Pythonは10年以上前にやっていましたが、その頃は2.x系で(確か2.6とか2.7だった)3.x系は全くわからない状態です。

とりあえずローカルにインストールしてみようと思います。

anyenvのアップデート

RubyやNode.jsでanyenvを利用していたので、引き続きanyenvを利用してpyenvをインストールしようと思います。

まずはanyenvをアップデートします

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ anyenv update
Updating 'anyenv'...
Updating 'anyenv/anyenv-git'...
Updating 'anyenv/anyenv-update'...
Updating 'nodenv'...
Updating 'nodenv/node-build'...
| From https://github.com/nodenv/node-build
| 79b3b885..03dc3d06 master -> origin/master
| * [new tag] v4.9.42 -> v4.9.42
| * [new tag] v4.9.39 -> v4.9.39
| * [new tag] v4.9.40 -> v4.9.40
| * [new tag] v4.9.41 -> v4.9.41
Updating 'nodenv/nodenv-vars'...
Updating 'pyenv'...
| From https://github.com/pyenv/pyenv
| 54e58dc7..f81bffc9 master -> origin/master
| * [new tag] v2.0.1 -> v2.0.1
| * [new tag] 1.2.27 -> 1.2.27
| * [new tag] v2.0.0 -> v2.0.0
| * [new tag] v2.0.0-rc1 -> v2.0.0-rc1
Skipping 'pyenv/python-build'; not git repo
Updating 'rbenv'...
| From https://github.com/rbenv/rbenv
| d604acb..585ed84 master -> origin/master
| * [new branch] rehash-speedup -> origin/rehash-speedup
Updating 'rbenv/ruby-build'...
| From https://github.com/rbenv/ruby-build
| 0bd64d3..19ed806 master -> origin/master
| * [new tag] v20210526 -> v20210526
| * [new tag] v20210510 -> v20210510
Updating 'anyenv manifest directory'...

こちらでanyenvを最新にできました。

Pythonのインストール

インストール可能な一覧を見てみます

1
2
3
4
5
6
7
8
9
10
11
12
13
$ pyenv install -l
Available versions:
2.1.3
2.2.3
2.3.7
(中略)
3.9.3
3.9.4
3.9.5
3.10.0b2
3.10-dev
3.11-dev
...

最新は3.9.5のようですね、こちらをインストールしましょう

1
2
3
4
5
6
7
8
9
10
11
12
$ pyenv install 3.9.5
Downloading openssl-1.1.1k.tar.gz...
-> https://www.openssl.org/source/openssl-1.1.1k.tar.gz
Installing openssl-1.1.1k...
Installed openssl-1.1.1k to /Users/user/.anyenv/envs/pyenv/versions/3.9.5

python-build: use readline from homebrew
Downloading Python-3.9.5.tar.xz...
-> https://www.python.org/ftp/python/3.9.5/Python-3.9.5.tar.xz
Installing Python-3.9.5...
python-build: use readline from homebrew
Installed Python-3.9.5 to /Users/user/.anyenv/envs/pyenv/versions/3.9.5

インストールできました

pyenvを利用するための設定

次に.bash_profileに以下の設定を追記します

1
2
3
export PYENV_ROOT="$HOME/.anyenv/envs/pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"

この設定を行わないと

1
2
WARNING: `pyenv init -` no longer sets PATH.
Run `pyenv init` to see the necessary changes to make to your configuration.

というエラーが出て、pyenvではないpython(/usr/bin/python)が呼ばれてしまいます…

詳しい設定についてはメッセージにある通りpyenv initを実行すると表示できます

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ pyenv init

# (The below instructions are intended for common
# shell setups. See the README for more guidance
# if they don't apply and/or don't work for you.)

# Add pyenv executable to PATH and
# enable shims by adding the following
# to ~/.profile:

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"

# If your ~/.profile sources ~/.bashrc,
# the lines need to be inserted before the part
# that does that. See the README for another option.

# If you have ~/.bash_profile, make sure that it
# also executes the above lines -- e.g. by
# copying them there or by sourcing ~/.profile

# Load pyenv into the shell by adding
# the following to ~/.bashrc:

eval "$(pyenv init -)"

# Make sure to restart your entire logon session
# for changes to profile files to take effect.

anyenvを利用しているためPATHが少し違いますので注意が必要です。

動作確認

早速動作確認をしてみます

1
2
3
4
5
6
7
$ python
Python 3.7.9 (default, Nov 16 2020, 18:18:20)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> $

想定しているバージョンでないPythonが起動しました…(しかも終了のコマンドを間違えています)

バージョンの指定をしてなかったことに気がつきました。このディレクトリでは3.9.5を利用するように設定します。

1
2
3
4
5
6
7
8
9
10
$ ls -a
. ..
$ pyenv local 3.9.5
$ ls -a
. .. .python-version
$ python
Python 3.9.5 (default, Jun 8 2021, 23:01:54)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

無事3.9.5が起動しました。

まとめ

anyenv経由でpyenvを利用しPython3をインストールしました。(venvというmのもあるみたいです)

これからはパッケージ管理システムpipを学び、その後dockerでローカル開発環境を構築していきます

※ Python3勉強用に入門 Python3 第2版を購入しました。

BigQueryのスキーマを変更する

背景

BigQueryでカラムを追加してほしいという依頼がきました。重要なカラムのようで、今までのデータは不要というレアなケースです。ですので、一度データセットを消しますが、スキーマ変更では一般的ではないと思いますので、後ほどデータを消さないスキーマ変更の方法についても調べてみようと思います。

スキーマの変更

既存スキーマの取得

まず既存のスキーマを取得します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ bq show --schema --format=prettyjson project_name:dataset_name.table_name
[
{
"mode": "REQUIRED",
"name": "id",
"type": "STRING"
},
{
"mode": "REQUIRED",
"name": "hostname",
"type": "STRING"
},
...
]

ファイル出力する場合はリダイレクトします。

1
$ bq show --schema --format=prettyjson project_name:dataset_name.table_name > schema.json

テーブルの削除

既存のスキーマを保存したのでテーブルを削除します

1
2
3
4
5
6
7
8
$ bq rm project_name:dataset_name.table_name


Updates are available for some Cloud SDK components. To install them,
please run:
$ gcloud components update

rm: remove table 'project_name:dataset_name.table_name' (y/N)

yを入力してエンターキーを押すと削除できます

スキーマの変更

先ほど保存したスキーマのファイルを変更します。追加したいカラムを追記します。

1
2
3
4
5
6
7
8
$ git diff schema.json
},
+ {
+ "mode": "NULLABLE",
+ "name": "additional_id",
+ "type": "STRING"
+ },
{

追記したらこのスキーマファイルを使ってテーブルを作成します。

作成していたテーブルは以前の記事のBigQueryで時間単位の列でのパーティション分割テーブルを作成するで作成したパーティション分割テーブルなので --time_partitioning_fieldオプションもつけます。

1
2
$ bq mk --table --schema schema.json --time_partitioning_field created_date project_name:dataset_name.table_name
Table 'project_name:dataset_name.table_name' successfully created.

テーブルが作成できました。

データを消さないスキーマ変更

データを消さない(通常の)スキーマ変更の方法はドキュメントに記載があります。

https://cloud.google.com/bigquery/docs/managing-table-schemas

bq updateというコマンドがあるんですね。

1
$ bq update project_id:dataset.table schema.json

schema.jsonを書き換えるだけでOKでした。

おまけ

先ほどテーブル削除時に

1
2
3
Updates are available for some Cloud SDK components.  To install them,
please run:
$ gcloud components update

とメッセージが出ていたので実行しました

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
$ gcloud components update


Your current Cloud SDK version is: 319.0.0
You will be upgraded to version: 343.0.0

┌─────────────────────────────────────────────────────────────────────────────┐
│ These components will be updated. │
├─────────────────────────────────────────────────────┬────────────┬──────────┤
│ Name │ Version │ Size │
├─────────────────────────────────────────────────────┼────────────┼──────────┤
│ BigQuery Command Line Tool │ 2.0.69 │ < 1 MiB │
│ BigQuery Command Line Tool (Platform Specific) │ 2.0.65 │ < 1 MiB │
│ Cloud SDK Core Libraries │ 2021.05.27 │ 18.8 MiB │
│ Cloud SDK Core Libraries (Platform Specific) │ 2021.03.12 │ < 1 MiB │
│ Cloud Storage Command Line Tool │ 4.62 │ 3.9 MiB │
│ Cloud Storage Command Line Tool (Platform Specific) │ 4.59 │ < 1 MiB │
│ anthoscli │ 0.2.16 │ 47.5 MiB │
│ gcloud cli dependencies │ 2021.05.27 │ 11.0 MiB │
│ gcloud cli dependencies │ 2021.04.16 │ < 1 MiB │
└─────────────────────────────────────────────────────┴────────────┴──────────┘

A lot has changed since your last upgrade. For the latest full release notes,
please visit:
https://cloud.google.com/sdk/release_notes

Do you want to continue (Y/n)? Y

╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: BigQuery Command Line Tool ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: BigQuery Command Line Tool (Platform Sp... ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud SDK Core Libraries ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud SDK Core Libraries (Platform Spec... ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud Storage Command Line Tool ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud Storage Command Line Tool (Platfo... ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: anthoscli ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: gcloud cli dependencies ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: gcloud cli dependencies ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: BigQuery Command Line Tool ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: BigQuery Command Line Tool (Platform Spec... ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud SDK Core Libraries ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud SDK Core Libraries (Platform Specific) ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud Storage Command Line Tool ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud Storage Command Line Tool (Platform... ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: anthoscli ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud cli dependencies ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud cli dependencies ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation ═╣
╚════════════════════════════════════════════════════════════╝

Performing post processing steps...done.

Update done!

To revert your SDK to the previously installed version, you may run:
$ gcloud components update --version 319.0.0



To take a quick anonymous survey, run:
$ gcloud survey

しばらく実行していなかったからか結構バージョン変わってますね。

アップデート後の最後の方に戻し方などが書かれています。便利ですね。

Rails Formオブジェクトまとめ

背景

久しぶりにFormオブジェクトを実装することになりました。いろいろ忘れてしまっていたので、実装に時間がかかってしまいました…次からスムーズにいくようここにまとめておきます。

実装方法

ファイルのパス

ファイルは今までの経験だと app/forms 配下に置くのが一般的となっています。autload_pathsに含まれていれば良いかなと思います。

autoload_pathsについてはこちらを参考にしてください。

必要なクラスのinclude

validationなどを実現するために、クラスをincludeします。
今回はActiveModel::Modelをincludeします。

こちらにあるように、ActiveModel::Modelをincludeすると以下のようなことができるようになります。

  • バリデーション
  • ハッシュでの初期化
  • form_forの利用

この3つはフォームを実装する上で必須の機能になります。

バリデーション

フォームで送信された値の妥当性をチェックするのに必要です。
valid?メソッドを呼び出すことによって、設定したバリデーションを呼び出すことができます。

ハッシュでの初期化

実装したフォームオブジェクトはコントローラで利用します。ハッシュで初期化できるということは、StrongParametersをそのまま渡すことができます。

こちらに記載の例を少し変更して

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class PeopleController < ActionController::Base
def new
@person = Person.new
end

def create
@person = Person.new(person_params)
if @person.valid?
@person.save
end
end

private
# 許可するパラメータはprivateメソッドでカプセル化します。
# これは非常によい手法であり、createとupdateの両方で使いまわすことで
# 同じ許可を与えることができます。また、許可する属性をユーザーごとにチェックするよう
# このメソッドを特殊化することもできます。
def person_params
params.require(:person).permit(:name, :age)
end
end

とすることができます。

form_forの利用

viewでフォームを作成する際はform_forを使うと便利です。上記の例を使うと

1
2
3
4
<% form_for @person, url: person_create_path do |f| %>
<%= f.text_field :name %>
<%= f.number_field :age %>
<% end %>

という感じになります。

ここまででほとんど実装できましたね。

翻訳

翻訳はactivemodelをキーにして定義します。

エラーメッセージなどは翻訳させないと項目名がそのまま表示されてしまいます。

まとめ

フォームオブジェクトの作り方をまとめました。ActiveModel::Modelモジュールがとても便利ということですね。

バリデーション、Strong Parameters、form_forを覚えておけばスムーズに実装できると思います。

余談ですが、利用規約の同意などでチェックボックスを使うことがあると思いますが、check_boxのバリデーションは acceptanceです。

validates :agreement, acceptance: { message: '利用規約に同意してください' }

という形式です。presenceではチェックしてなくてもバリデーション通ってしまいます…

※追記 こちらとてもよくまとまっています
https://thoughtbot.com/blog/activemodel-form-objects

remove_columnのrollbackでActiveRecord::IrreversibleMigrationエラーが発生したときの対応方法

背景

Create tableのマイグレーションで追加したカラムが必要なくなったので、remove_columnのマイグレーションを行いました。

マイグレーションファイルはこちら

1
2
3
4
5
class RemoveTitleFromExamples < ActiveRecord::Migration
def change
remove_column :examples, :title
end
end

マイグレーションは成功してtitleというカラムはなくなりました。

ところがやっぱりtitleというカラムが必要ということで、上記で実施したマイグレーションを元に戻そうとしました。すると以下のようにエラーが発生してしまいました…

1
2
3
4
5
6
7
8
9
10
11
$ ./bin/rails db:rollback
StandardError: An error has occurred, all later migrations canceled:

remove_column is only reversible if given a type.

(中略)

Caused by:
ActiveRecord::IrreversibleMigration:

remove_column is only reversible if given a type.

エラーメッセージを見れば対応方法は分かりそうですね

対応方法

エラーメッセージにある

1
remove_column is only reversible if given a type.

が答えですね。型を与えてあげればrollbackが可能ということです。ですので、先ほどのマイグレーションファイルを以下のように変更します。

1
2
3
4
5
class RemoveTitleFromExamples < ActiveRecord::Migration
def change
remove_column :examples, :title, :string
end
end

マイグレーションを実行してみましょう。

1
2
3
4
5
$ ./bin/rails db:rollback
== 20210523211330 RemoveTitleFromExamples: reverting - ===========
-- add_column(:examples, :title, :string)
-> 0.0812s
== 20210523211330 RemoveTitleFromExamples: reverted (0.0845s) - ==

無事追加されました!

ですが、元の位置には追加されておらず、最後に追加されてしまいます…
afterオプションが効くかどうか試してみます。

一旦マイグレーションします。

1
2
3
4
5
$ ./bin/rails db:migrate
== 20210523211330 RemoveTitleFromExamples: migrating - ===========
-- remove_column(:examples, :title, :string)
-> 0.0398s
== 20210523211330 RemoveTitleFromExamples: migrated (0.0399s) - ==

マイグレーションファイルを書き換えます

1
2
3
4
5
class RemoveTitleFromExamples < ActiveRecord::Migration
def change
remove_column :examples, :title, :string, after: :id
end
end

rollbackします

1
2
3
4
5
$ ./bin/rails db:rollback
== 20210523211330 RemoveTitleFromExamples: reverting - ===========
-- add_column(:examples, :title, :string, {:after=>:id})
-> 0.0489s
== 20210523211330 RemoveTitleFromExamples: reverted (0.0522s) - ==

テーブルを確認すると、ちゃんと指定したカラムの後に追加されています!

最後に、カラム削除用に作成したマイグレーションのファイルを削除して完了です。

Reactで 'this' is not allowed before 'super()'

背景

React.js&Next.js超入門第2版を読みながらサンプルを試していると、突然

1
2
3
4
5
6
7
8
9
10
11
12
13
14
src/Rect.js
Line 12:5: 'this' is not allowed before 'super()' no-this-before-super
Line 13:5: 'this' is not allowed before 'super()' no-this-before-super
Line 14:5: 'this' is not allowed before 'super()' no-this-before-super
Line 15:5: 'this' is not allowed before 'super()' no-this-before-super
Line 16:5: 'this' is not allowed before 'super()' no-this-before-super
Line 17:5: 'this' is not allowed before 'super()' no-this-before-super
Line 18:5: 'this' is not allowed before 'super()' no-this-before-super
Line 19:24: 'this' is not allowed before 'super()' no-this-before-super
Line 21:13: 'this' is not allowed before 'super()' no-this-before-super
Line 22:12: 'this' is not allowed before 'super()' no-this-before-super
Line 23:14: 'this' is not allowed before 'super()' no-this-before-super
Line 24:15: 'this' is not allowed before 'super()' no-this-before-super
Line 25:21: 'this' is not allowed before 'super()' no-this-before-super

とエラーが出てしまった。

エラーが起こったコード

エラーが起こったコードはこちら

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
constructor(props) {
this.x = props.x
this.y = props.y
this.width = props.w
this.height = props.h
this.color = props.c
this.radius = props.r
this.style = {
backgroundColor: this.color,
position: "absolute",
left: this.x + "px",
top: this.y + "px",
width: this.width + "px",
height: this.height + "px",
borderRadius: this.radius + "px"
}
}

原因

原因はエラーメッセージにある通り、super()を呼び出す前にthisを使ってはいけないということ。

修正後のコードはこちら

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
constructor(props) {
super(props)
this.x = props.x
this.y = props.y
this.width = props.w
this.height = props.h
this.color = props.c
this.radius = props.r
this.style = {
backgroundColor: this.color,
position: "absolute",
left: this.x + "px",
top: this.y + "px",
width: this.width + "px",
height: this.height + "px",
borderRadius: this.radius + "px"
}
}

thisを参照する前にsuperを呼べば問題ありません。

そして、Reactの話ではなく、ES6の話でした。
https://eslint.org/docs/rules/no-this-before-super

まとめ

ES6をちゃんと理解できていないので、問題の所在も怪しかった(React云々の話ではない)。ちゃんと学んでいきます。

参考図書

React.js&Next.js超入門第2版