GPD PocketでBluetoothトラックボールを使うと途中で使用できなくなる現象

参考

当たった症状

Digio2のBluetoothトラックボールを使っていたところ、唐突に使用できなくなる現象にあたった。 そのとき、Windowsの設定画面「Bluetoothとその他のデバイス」を開いたところ、 当該トラックボールは「接続済み」と表示されていた。 デバイスの削除と再度追加、ドライバの更新(結局最新版だった)を試みるも現象は収まらなかった。

対処

関連情報を含むブログ記事 で記載の通り、デバイスマネージャから節電関係の機能を オフにすることで現象は解消したように見える。 おおむね上記ブログのとおりだが、一部デバイス名に差分があったので、 手元で試した方法を記載する。

  • Windowsのランチャーからデバイスマネージャを開く
  • 「ヒューマン インターフェイス デバイス」-> 「Bluetooth HID デバイス」のプロパティを開く。
    • 「電源の管理」を開き、「電力の節電のために…(省略)」のチェックボックスを外す
    • なお、手元の環境では当該デバイスは2件表示されたので両方とも対応した
  • おなじくデバイスマネージャ上で「bluetooth」->「Bluetooth 無線」のプロパティを開く。
    • 「電源の管理」を開き、「電力の節電のために…(省略)」のチェックボックスを外す

備考:ブログとの差分

なお、関連情報を含むブログ記事では、「Bluetooth HID デバイス」のところが 「Bluetooth 低エネルギーGTT 対応 HID デバイス」となっていたようだ。

共有

tmuxでctrl + lを押したときに改行の挙動がおかしくなる現象

参考

メモ

tmux-1419 に記載の通り、~/.tmux.confに以下のように記載すると、 強制的に改行されるようになるようだ。

1
set -as terminal-overrides ',*:indn@'
共有

Basic of SQLAlchemy

参考

簡単な動作確認

エンジンの定義。(今回はSQLiteのインメモリモードを使用)

1
2
from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True)

テーブルの作成。今回は2種類のテーブルを作成。 ユーザのIDをプライマリキーとして使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey
metadata = MetaData()
users = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
Column('fullname', String),
)

addresses = Table('addresses', metadata,
Column('id', Integer, primary_key=True),
Column('user_id', None, ForeignKey('users.id')),
Column('email_address', String, nullable=False)
)

実際にSQLite内でテーブルを定義する。このとき、テーブルの存在を確認するので複数回実行してもよい。(べき等である)

1
metadata.create_all(engine)

挿入のクエリ

まずは空のクエリを定義。

1
ins = users.insert()

strを使うと、SQLを確認できる。

1
2
3
str(ins)
--
'INSERT INTO users (id, name, fullname) VALUES (:id, :name, :fullname)'

今度は値を入れてみる。

1
2
3
4
ins = users.insert().values(name='jack', fullname='Jack Jones')
str(ins)
--
'INSERT INTO users (name, fullname) VALUES (:name, :fullname)'

接続の取得と実行

接続の取得

1
conn = engine.connect()

実行

1
2
3
4
5
result = conn.execute(ins)
--
2018-12-24 14:08:34,031 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname) VALUES (?, ?)
2018-12-24 14:08:34,031 INFO sqlalchemy.engine.base.Engine ('jack', 'Jack Jones')
2018-12-24 14:08:34,032 INFO sqlalchemy.engine.base.Engine COMMIT

挿入されたレコードのプライマリキーの確認。

1
2
3
>>> result.inserted_primary_key
--
[1]

複数ステートメントの実行

先の例と同じ1個ずつ挿入する場合。

1
2
3
4
5
6
7
ins = users.insert()
conn.execute(ins, id=2, name='wendy', fullname='Wendy Williams')
--
2018-12-24 14:12:55,688 INFO sqlalchemy.engine.base.Engine INSERT INTO users (id, name, fullname) VALUES (?, ?, ?)
2018-12-24 14:12:55,688 INFO sqlalchemy.engine.base.Engine (2, 'wendy', 'Wendy Williams')
2018-12-24 14:12:55,688 INFO sqlalchemy.engine.base.Engine COMMIT
<sqlalchemy.engine.result.ResultProxy object at 0x7f63c8ca7940>

複数ステートメントを一度に実行する場合。

1
2
3
4
5
6
7
8
9
10
11
conn.execute(addresses.insert(), [
{'user_id': 1, 'email_address' : 'jack@yahoo.com'},
{'user_id': 1, 'email_address' : 'jack@msn.com'},
{'user_id': 2, 'email_address' : 'www@www.org'},
{'user_id': 2, 'email_address' : 'wendy@aol.com'},
])
--
2018-12-24 14:13:42,077 INFO sqlalchemy.engine.base.Engine INSERT INTO addresses (user_id, email_address) VALUES (?, ?)
2018-12-24 14:13:42,078 INFO sqlalchemy.engine.base.Engine ((1, 'jack@yahoo.com'), (1, 'jack@msn.com'), (2, 'www@www.org'), (2, 'wendy@aol.com'))
2018-12-24 14:13:42,078 INFO sqlalchemy.engine.base.Engine COMMIT
<sqlalchemy.engine.result.ResultProxy object at 0x7f63c8ca7c18>

SELECT

まずはクエリを実行。

1
2
3
4
5
6
7
from sqlalchemy.sql import select
s = select([users])
result = conn.execute(s)
--
2018-12-24 14:16:11,363 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname
FROM users
2018-12-24 14:16:11,363 INFO sqlalchemy.engine.base.Engine ()

結果のパース。タプルライクに取得。

1
2
3
4
5
for row in result:
print(row)
--
(1, 'jack', 'Jack Jones')
(2, 'wendy', 'Wendy Williams')

辞書ライクに取得。

1
2
3
4
5
result = conn.execute(s)
row = result.fetchone()
print("name:", row['name'], "; fullname:", row['fullname'])
--
name: jack ; fullname: Jack Jones

conn.execute()の結果はイテレータっぽく扱えるので以下のような書き方ができる。 今回は2個のテーブルのカルテシアン積を求める例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for row in conn.execute(select([users, addresses])):
print(row)
--
2018-12-24 14:20:14,164 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname, addresses.id, addresses.user_id, addresses.email_address
FROM users, addresses
2018-12-24 14:20:14,164 INFO sqlalchemy.engine.base.Engine ()
(1, 'jack', 'Jack Jones', 1, 1, 'jack@yahoo.com')
(1, 'jack', 'Jack Jones', 2, 1, 'jack@msn.com')
(1, 'jack', 'Jack Jones', 3, 2, 'www@www.org')
(1, 'jack', 'Jack Jones', 4, 2, 'wendy@aol.com')
(2, 'wendy', 'Wendy Williams', 1, 1, 'jack@yahoo.com')
(2, 'wendy', 'Wendy Williams', 2, 1, 'jack@msn.com')
(2, 'wendy', 'Wendy Williams', 3, 2, 'www@www.org')
(2, 'wendy', 'Wendy Williams', 4, 2, 'wendy@aol.com')

どちらかというと、こちらの方がイメージに近いか。

1
2
3
4
5
6
7
8
9
10
11
for row in conn.execute(select([users, addresses]).where(users.c.id == addresses.c.user_id)):
print(row)
--
2018-12-24 14:22:46,769 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname, addresses.id, addresses.user_id, addresses.email_address
FROM users, addresses
WHERE users.id = addresses.user_id
2018-12-24 14:22:46,769 INFO sqlalchemy.engine.base.Engine ()
(1, 'jack', 'Jack Jones', 1, 1, 'jack@yahoo.com')
(1, 'jack', 'Jack Jones', 2, 1, 'jack@msn.com')
(2, 'wendy', 'Wendy Williams', 3, 2, 'www@www.org')
(2, 'wendy', 'Wendy Williams', 4, 2, 'wendy@aol.com')

オペレータの結合

and_などを用いる例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sqlalchemy.sql import and_, or_, not_
s = select([(users.c.fullname +
", " + addresses.c.email_address).
label('title')]).\
where(
and_(
users.c.id == addresses.c.user_id,
users.c.name.between('m', 'z'),
or_(
addresses.c.email_address.like('%@aol.com'),
addresses.c.email_address.like('%@msn.com')
)
)
)
conn.execute(s).fetchall()

Pythonの演算子を用いる例。

1
2
3
4
5
6
7
8
9
10
11
12
s = select([(users.c.fullname +
", " + addresses.c.email_address).
label('title')]).\
where(
(users.c.id == addresses.c.user_id) &
(users.c.name.between('m', 'z')) &
(
addresses.c.email_address.like('%@aol.com') | \
addresses.c.email_address.like('%@msn.com')
)
)
conn.execute(s).fetchall()

and_はwhere句を並べることでも実現できる。

1
2
3
4
5
6
7
8
9
10
11
12
s = select([(users.c.fullname +
", " + addresses.c.email_address).
label('title')]).\
where(users.c.id == addresses.c.user_id).\
where(users.c.name.between('m', 'z')).\
where(
or_(
addresses.c.email_address.like('%@aol.com'),
addresses.c.email_address.like('%@msn.com')
)
)
conn.execute(s).fetchall()

テキストで実行

基本的な使い方

1
2
3
4
5
6
7
8
9
from sqlalchemy.sql import text
s = text(
"SELECT users.fullname || ', ' || addresses.email_address AS title "
"FROM users, addresses "
"WHERE users.id = addresses.user_id "
"AND users.name BETWEEN :x AND :y "
"AND (addresses.email_address LIKE :e1 "
"OR addresses.email_address LIKE :e2)")
conn.execute(s, x='m', y='z', e1='%@aol.com', e2='%@msn.com').fetchall()

予めバインドされた値を予め定義することもできる。

1
2
3
4
5
6
7
stmt = text("SELECT * FROM users WHERE users.name BETWEEN :x AND :y")
stmt = stmt.bindparams(x="m", y="z")
# stmt = stmt.bindparams(bindparam("x", type_=String), bindparam("y", type_=String))
result = conn.execute(stmt, {"x": "m", "y": "z"})
result.fetchall()
--
[(2, 'wendy', 'Wendy Williams')]

ステートメントの中でSQLテキストを用いることもできる。

1
2
3
4
5
6
7
8
9
10
11
12
13
s = select([
text("users.fullname || ', ' || addresses.email_address AS title")
]).\
where(
and_(
text("users.id = addresses.user_id"),
text("users.name BETWEEN 'm' AND 'z'"),
text(
"(addresses.email_address LIKE :x "
"OR addresses.email_address LIKE :y)")
)
).select_from(text('users, addresses'))
conn.execute(s, x='%@aol.com', y='%@msn.com').fetchall()

グループ

単純な例

1
2
3
4
5
6
7
from sqlalchemy import func
stmt = select([
addresses.c.user_id,
func.count(addresses.c.id).label('num_addresses')]).\
group_by("user_id").order_by("user_id", "num_addresses")

conn.execute(stmt).fetchall()

結合

単純な例

1
print(users.join(addresses))

いろいろな表現を用いることができる。

1
2
3
4
print(users.join(addresses,
addresses.c.email_address.like(users.c.name + '%')
)
)

実施に実行

1
2
3
4
5
6
7
8
9
10
s = select([users.c.fullname]).select_from(
users.join(addresses,
addresses.c.email_address.like(users.c.name + '%'))
)
conn.execute(s).fetchall()
--
2018-12-25 12:45:29,919 INFO sqlalchemy.engine.base.Engine SELECT users.fullname
FROM users JOIN addresses ON addresses.email_address LIKE users.name || ?
2018-12-25 12:45:29,919 INFO sqlalchemy.engine.base.Engine ('%',)
[('Jack Jones',), ('Jack Jones',), ('Wendy Williams',)]

outer join

1
2
3
4
print(select([users.c.fullname]).select_from(users.outerjoin(addresses)))
--
SELECT users.fullname
FROM users LEFT OUTER JOIN addresses ON users.id = addresses.user_id

変数バインド

単純な例

1
2
3
4
5
6
7
8
from sqlalchemy.sql import bindparam
s = users.select(users.c.name.like(bindparam('username', type_=String) + text("'%'")))
conn.execute(s, username='wendy').fetchall()
--
2018-12-25 13:34:19,392 INFO sqlalchemy.engine.base.Engine SELECT users.fullname
FROM users LEFT OUTER JOIN addresses ON users.id = addresses.user_id
2018-12-25 13:34:19,392 INFO sqlalchemy.engine.base.Engine ()
[('Jack Jones',), ('Jack Jones',), ('Wendy Williams',), ('Wendy Williams',)]

バインドした変数は複数回使用できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s = select([users, addresses]).\
where(
or_(
users.c.name.like(
bindparam('name', type_=String) + text("'%'")),
addresses.c.email_address.like(
bindparam('name', type_=String) + text("'@%'"))
)
).\
select_from(users.outerjoin(addresses)).\
order_by(addresses.c.id)
conn.execute(s, name='jack').fetchall()
--
2018-12-25 13:43:43,880 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname, addresses.id, addresses.user_id, addresses.email_address
FROM users LEFT OUTER JOIN addresses ON users.id = addresses.user_id
WHERE users.name LIKE ? || '%' OR addresses.email_address LIKE ? || '@%' ORDER BY addresses.id
2018-12-25 13:43:43,880 INFO sqlalchemy.engine.base.Engine ('jack', 'jack')
[(1, 'jack', 'Jack Jones', 1, 1, 'jack@yahoo.com'), (1, 'jack', 'Jack Jones', 2, 1, 'jack@msn.com')]

その他の機能

  • ファンクション
  • ウィンドウファンクション
  • union、except_
  • Label
  • Ordering, Limiting, ...

Update

1
2
3
4
5
stmt = users.update().\
where(users.c.name == 'jack').\
values(name='ed')

conn.execute(stmt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
stmt = users.update().\
where(users.c.name == bindparam('oldname')).\
values(name=bindparam('newname'))
conn.execute(stmt, [
{'oldname':'jack', 'newname':'ed'},
{'oldname':'wendy', 'newname':'mary'},
{'oldname':'jim', 'newname':'jake'},
])
conn.execute(select([users])).fetchall()
--
2018-12-25 15:11:08,628 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname
FROM users
2018-12-25 15:11:08,628 INFO sqlalchemy.engine.base.Engine ()
[(1, 'ed', 'Jack Jones'), (2, 'mary', 'Wendy Williams')]
共有

Flask SQLAlchemy

参考

シェルで動作確認

Dockerコンテナのビルド

Flask on Dockerのブログ と同様にDockerfileを作り、ビルドする。

ファイルを作成

1
2
3
4
5
6
7
8
9
10
$ mkdir -p  ~/Sources/docker_flask/sqlalchemy
$ cd ~/Sources/docker_flask/sqlalchemy
$ cat << EOF > Dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y

RUN pip3 install flask flask_sqlalchemy
EOF

ビルド

1
$ sudo docker build . -t dobachi/flask_sqlalchemy:1.0

アプリ作成と実行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)

def __repr__(self):
return '<User %r>' % self.username
EOF

シェルを起動する。

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask_sqlalchemy:1.0 /bin/bash

シェル内で動作確認。テーブルを作ってレコードを生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> from app import db
/usr/local/lib/python3.6/dist-packages/flask_sqlalchemy/__init__.py:794: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
>>> db.create_all()
>>> from app import User
>>> admin = User(username='admin', email='admin@example.com')
>>> guest = User(username='guest', email='guest@example.com')
>>> db.session.add(admin)
>>> db.session.add(guest)
>>> db.session.commit()
>>> User.query.all()
[<User 'admin'>, <User 'guest'>]
>>> User.query.filter_by(username='admin').first()
<User 'admin'>

sqlite3コマンドで接続して内容を確認。

1
2
3
4
5
6
7
8
9
root@a5d5ee81df78:/usr/local/apps# apt install sqlite3
root@a5d5ee81df78:/usr/local/apps# sqlite3 /tmp/test.db
sqlite> .databases
main: /tmp/test.db
sqlite> .tables
user
sqlite> select * from user;
1|admin|admin@example.com
2|guest|guest@example.com

アプリから動作確認

上記と同様の流れをアプリから動作確認する。 Dockerイメージは上記で作ったものをベースとする。

アプリ作成

ここでは/registにアクセスすると、登録画面が出て、 登録するとリスト(/list)が表示されるアプリを作成する。

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
$ mkdir -p  ~/Sources/docker_flask/sqlalchemy_app
$ cd ~/Sources/docker_flask/sqlalchemy_app
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask, url_for, request, render_template, redirect
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)

def __repr__(self):
return '<User %r>' % self.username

db.create_all()

@app.route('/regist', methods=['POST', 'GET'])
def regist():
if request.method == 'POST':
user = User(username=request.form['username'], email=request.form['email'])
db.session.add(user)
db.session.commit()
return redirect(url_for('list'))
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('regist.html')

@app.route('/list')
def list():
users = User.query.order_by(User.username).all()
return render_template('list.html', users=users)
EOF
$ mkdir -p apps/templates
$ cat << EOF > apps/templates/regist.html
<!doctype html>
<title>Registration</title>
<form action="/regist" method="post">
<div>username: <input type="text" name="username"></div>
<div>email: <input type="text" name="email"></div>
<input type="submit" value="send">
<input type="reset" value="reset">
</form>

<a href="/list">list</a>
EOF
$ cat << EOF > apps/templates/list.html
<!doctype html>
<title>List ordered by username</title>
<table>
<thead>
<tr>
<th>id</th>
<th>username</th>
<th>email</th>
</tr>
</thead>
<tbody>
{% for item in users %}
<tr>
<td>
{{ item.id }}
</td>
<td>
{{ item.username }}
</td>
<td>
{{ item.email }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/regist">registration</a>
EOF

実行

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask_sqlalchemy:1.0 flask run --host 0.0.0.0

なお、上記の実装はエラーハンドリング等を一切実施していないので 例えば同じユーザ名、メールアドレスで登録しようとすると例外が生じる。

共有

tmux_rectangle_select

参考

方法

tmuxを使ってコピーするとき、矩形選択したいときがある。 矩形選択にも言及のあるブログ にも記載あるが、 <prefix> + [ でコピーモードに入った後、 該当箇所まで移動し、スペースキーを押して選択開始、その後 v を押すと矩形選択になる。 (初期状態では、行選択になっているはず)

共有

Gitのgitignore

参考

手順

後からまとめてignoreする方法 の通りで問題ない。

gitignoreファイルのダウンロード

GitHubのgitignoreファイルの雛形 からダウンロード。

Pythonの例

1
2
$ cd <レポジトリ>
$ wget https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore

必要に応じて既存の.gitignoreとマージしたり、mvして.gitignoreとしたり。

1
$ mv Python.gitignore .gitignore

コミット。

1
2
$ git add .gitignore
$ git commit -m "Add ignore pattern"

すでにコミットしてしまったファイルがあればまとめて削除。

1
$ git rm --cached `git ls-files --full-name -i --exclude-from=.gitignore`
共有

Flask extension

参考

flask_adminを試す

flask-adminのドキュメント を見ながら進める。 またFlask on Dockerのブログ で作成したDockerイメージを使って環境を作る。

Dockerfileを作成してビルド

1
2
3
4
5
6
7
8
9
10
$ mkdir -p  ~/Sources/docker_flask/docker_admin
$ cd ~/Sources/docker_flask/docker_admin
$ cat << EOF > Dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y

RUN pip3 install flask flask_admin
EOF
1
$ sudo docker build . -t dobachi/flask_admin:1.0

アプリ作成と実行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask
from flask_admin import Admin

app = Flask(__name__)

# set optional bootswatch theme
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean'

admin = Admin(app, name='microblog', template_mode='bootstrap3')

@app.route('/')
def index():
return "Hello world!!"
EOF
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask_admin:1.0 flask run --host 0.0.0.0
共有

Check global IP address

参考

手順

1
$ curl globalip.me
共有

Flask on Docker

参考

自前のイメージを作って動かす手順

基本的には Flask込みのDockerイメージを作る方法のブログ で記載された内容で問題ない。 自身の環境向けにアレンジは必要。

Dockerfileとビルド

Dockerfileは Flask込みのDockerイメージを作る方法のブログ の通り。

1
2
3
4
5
6
7
8
9
10
$ mkdir -p  ~/Sources/docker_flask/simple
$ cd ~/Sources/docker_flask/simple
$ cat << EOF > Dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y

RUN pip3 install flask
EOF

ビルドコマンドは自分の環境に合わせた。

1
$ sudo docker build . -t dobachi/flask:1.0

インタラクティブにつなげる例も自分の環境に合わせた。

1
$ docker run -it --rm dobachi/flask:1.0 /bin/bash

アプリ作成と実行

自分の環境に合わせた。 Flask込みのDockerイメージを作る方法のブログ では、 /bin/bashを起動してから、python3 <アプリ >としていたが、 ここでは直接指定して実行することにした。

1
2
3
4
5
6
7
8
9
10
11
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
return "Hello world!!"
EOF
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0

なお、もともとの Flask込みのDockerイメージを作る方法のブログ では、以下のようにPythonコマンドから直接実行するように記載されていたが、 今回は Flask公式 に従い、FlaskのCLIを用いることとした。

1
sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps dobachi/flask:1.0 python3 /usr/local/apps/app.py

その際、手元の環境ではLANG等の事情により、いくつか環境変数を設定しないとエラーを吐いたので、 上記のようなコマンドになった。

公式手順に則ったシンプルな例

上記で作ったDockerイメージを流用する。

準備

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
$ mkdir -p  ~/Sources/docker_flask/getting_started
$ cd ~/Sources/docker_flask/getting_started
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask, url_for, request, render_template

app = Flask(__name__)

@app.route('/')
def index():
return 'index'

@app.route('/user/<username>')
def profile(username):
return '{}\'s profile'.format(username)

@app.route('/post/<int:post_id>')
def post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id

@app.route('/path/<path:subpath>')
def subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % subpath

@app.route('/projects/')
def projects():
return 'The project page'

@app.route('/about')
def about():
return 'The about page'

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()

def do_the_login():
return 'Do log in'

def show_the_login_form():
return 'This is a login form'

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)

# Test for URLs
with app.test_request_context():
print(url_for('index'))
print(url_for('login'))
print(url_for('login', next='/'))
print(url_for('profile', username='John Doe'))
EOF
$ mkdir -p apps/templates
$ cat << EOF > apps/templates/hello.html
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
EOF

実行

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0

フォームを使う例

上記で作ったDockerイメージを流用する。

準備

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
$ mkdir -p  ~/Sources/docker_flask/form
$ cd ~/Sources/docker_flask/form
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask, url_for, request, render_template, redirect, session

app = Flask(__name__)

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
session['username'] = request.form['username']
return redirect(url_for('welcome'))
else:
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)

@app.route('/welcome')
def welcome():
if 'username' in session:
return render_template('welcome.html', username=session['username'])
else:
return redirect(url_for('login'))

def valid_login(username, password):
if username == 'hoge' and password == 'fuga':
return True
else:
return False
EOF
$ mkdir -p apps/templates
$ cat << EOF > apps/templates/welcome.html
<!doctype html>
<title>Hello from Flask</title>
<h1>Hello {{ username }}!</h1>
EOF
$ cat << EOF > apps/templates/login.html
<!doctype html>
<title>Question from Flask</title>
{% if error %}
<h1>Please use valid username and password</h1>
{% endif %}
<form action="/login" method="post">
<div>username: <input type="text" name="username"></div>
<div>password: <input type="password" name="password"></div>
<input type="submit" value="Send">
<input type="reset" value="Reset">
</form>
EOF

実行

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0

ファイルのアップロード

上記で作ったDockerイメージを流用する。

準備

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
$ mkdir -p  ~/Sources/docker_flask/file_upload
$ cd ~/Sources/docker_flask/file_upload
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask, request, render_template, redirect, session, url_for
from werkzeug.utils import secure_filename

app = Flask(__name__)

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
session['filename'] = secure_filename(f.filename)
f.save('/tmp/' + session['filename'])
return redirect(url_for('done'))
elif request.method == 'GET':
return render_template('upload.html')

@app.route('/done')
def done():
return render_template('done.html')

EOF
$ mkdir -p apps/templates
$ cat << EOF > apps/templates/upload.html
<!doctype html>
<title>Fileuploader by Flask</title>
<form action="/upload" method="post" enctype="multipart/form-data">
<div>file: <input type="file" name="the_file"></div>
<input type="submit" value="Send">
<input type="reset" value="Reset">
</form>
EOF
$ cat << EOF > apps/templates/done.html
<!doctype html>
<title>Fileuploader by Flask</title>
<h1>The file: {{ filename }} was uploaded</h1>
<a href="/upload">Got to the first page</a>
EOF

実行

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0

クッキー

上記で作ったDockerイメージを流用する。

準備

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
82
$ mkdir -p  ~/Sources/docker_flask/cookie
$ cd ~/Sources/docker_flask/cookie
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask, url_for, request, render_template, redirect, make_response

app = Flask(__name__)

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
username = request.form['username']
resp = make_response(render_template('success.html'))
resp.set_cookie('username', username)
return resp
else:
error = 'Invalid username/password'
elif request.method == 'GET':
if 'username' in request.cookies:
return redirect(url_for('welcome'))
else:
return render_template('login.html', error=error)

@app.route('/logout')
def logout():
resp = make_response(render_template('logout.html'))
resp.delete_cookie('username')
return resp

@app.route('/welcome')
def welcome():
if 'username' in request.cookies:
return render_template('welcome.html', username=request.cookies.get('username'))
else:
return redirect(url_for('login'))

def valid_login(username, password):
if username == 'hoge' and password == 'fuga':
return True
else:
return False
EOF
$ mkdir -p apps/templates
$ cat << EOF > apps/templates/welcome.html
<!doctype html>
<title>Hello from Flask</title>
<h1>Hello {{ username }}!</h1>
<a href="/logout">logout</a>
EOF
$ cat << EOF > apps/templates/success.html
<!doctype html>
<title>Login Success</title>
<h1>Login!</h1>
<META http-equiv="Refresh" content="3;URL=/welcome">
<p>Jump in 3 seconds</p>
EOF
$ cat << EOF > apps/templates/logout.html
<!doctype html>
<title>Logout Success</title>
<h1>Logout!</h1>
<META http-equiv="Refresh" content="3;URL=/login">
<p>Jump in 3 seconds</p>
EOF
$ cat << EOF > apps/templates/login.html
<!doctype html>
<title>Question from Flask</title>
{% if error %}
<h1>Please use valid username and password</h1>
{% endif %}
<form action="/login" method="post">
<div>username: <input type="text" name="username"></div>
<div>password: <input type="password" name="password"></div>
<input type="submit" value="Send">
<input type="reset" value="Reset">
</form>
EOF

実行

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0

ロギング

上記で作ったDockerイメージを流用する。

準備

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
$ mkdir -p  ~/Sources/docker_flask/logging
$ cd ~/Sources/docker_flask/logging
$ mkdir apps
$ cat << EOF > apps/app.py
from flask import Flask, url_for, request, render_template, redirect, session

app = Flask(__name__)

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/login', methods=['POST', 'GET'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'],
request.form['password']):
session['username'] = request.form['username']
return redirect(url_for('welcome'))
else:
app.logger.warning('Failed to login as:' + request.form['username'])
error = 'Invalid username/password'
# the code below is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)

@app.route('/welcome')
def welcome():
if 'username' in session:
return render_template('welcome.html', username=session['username'])
else:
return redirect(url_for('login'))

def valid_login(username, password):
if username == 'hoge' and password == 'fuga':
return True
else:
return False
EOF
$ mkdir -p apps/templates
$ cat << EOF > apps/templates/welcome.html
<!doctype html>
<title>Hello from Flask</title>
<h1>Hello {{ username }}!</h1>
EOF
$ cat << EOF > apps/templates/login.html
<!doctype html>
<title>Question from Flask</title>
{% if error %}
<h1>Please use valid username and password</h1>
{% endif %}
<form action="/login" method="post">
<div>username: <input type="text" name="username"></div>
<div>password: <input type="password" name="password"></div>
<input type="submit" value="Send">
<input type="reset" value="Reset">
</form>
EOF

実行

1
$ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0

JSON形式でやり取りする例

基本的には、 DockerコンテナでFlaskを起動し, JSONデータのPOSTとGET の通り。

Dockerfile

dockerfileを作る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ mkdir -p  ~/Sources/docker_flask/post_json
$ cd ~/Sources/docker_flask/post_json
$ cat << EOF > Dockerfile
fROM python:3.6

aRG project_dir=/app/

# ADD requirements.txt \$project_dir
aDD reply.py \$project_dir

wORKDIR \$project_dir

rUN pip install flask
# RUN pip install -r requirements.txt

cMD ["python", "reply.py"]
eOF

アプリ

アプリを作る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ cat << EOF > reply.py
from flask import Flask, jsonify, request
import json
app = Flask(__name__)

@app.route("/", methods=['GET'])
def hello():
return "Hello World!"

@app.route('/reply', methods=['POST'])
def reply():
data = json.loads(request.data)
answer = "Yes, it is %s!\n" % data["keyword"]
result = {
"Content-Type": "application/json",
"Answer":{"Text": answer}
}
# return answer
return jsonify(result)

if __name__ == "__main__":
app.run(host='0.0.0.0',port=5000,debug=True)
eOF

ビルドする。

1
$ sudo docker build . -t dobachi/flask_json:1.0

動作確認

ポート5000で起動する。

1
$ sudo docker run --rm -p 5000:5000 -it dobachi/flaskjson:1.0

動作確認する。

1
$ curl http://localhost:5000/reply -X POST -H "Content-Type: application/json" -d '{"keyword": "Keffia"}'

nginxと組み合わせて動かす

gitHub: uwsgi-nginx-flask-docker を参考にする。 gitHub: uwsgi-nginx-flask-dockerのクイックスタート を見ながら試す。

Dockerfile、アプリなど

1
2
$ mkdir -p  ~/Sources/docker_flask/nginx
$ cd ~/Sources/docker_flask/nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat << EOF > Dockerfile
fROM tiangolo/uwsgi-nginx-flask:python3.7

cOPY ./app /app
eOF
$ mkdir app
$ cat << EOF > app/main.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World from Flask"

if __name__ == "__main__":
# Only for debugging while developing
app.run(host='0.0.0.0', debug=True, port=80)
eOF

ビルド

1
$ sudo docker build -t dobachi/nginx-flask:1.0 .

実行

1
$ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask:1.0

nginxと組あわせてSPA

gitHub: uwsgi-nginx-flask-dockerのSPA を参考に進める。

Dockerfileとアプリ

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
$ mkdir -p  ~/Sources/docker_flask/nginx_spa
$ cd ~/Sources/docker_flask/nginx_spa
$ cat << EOF > Dockerfile
fROM tiangolo/uwsgi-nginx-flask:python3.7

eNV STATIC_INDEX 1

cOPY ./app /app
eOF
$ mkdir app
$ cat << EOF > app/main.py
import os
from flask import Flask, send_file
app = Flask(__name__)


@app.route("/hello")
def hello():
return "Hello World from Flask"


@app.route("/")
def main():
index_path = os.path.join(app.static_folder, 'index.html')
return send_file(index_path)


# Everything not declared before (not a Flask route / API endpoint)...
@app.route('/<path:path>')
def route_frontend(path):
# ...could be a static file needed by the front end that
# doesn't use the `static` path (like in `<script src="bundle.js">`)
file_path = os.path.join(app.static_folder, path)
if os.path.isfile(file_path):
return send_file(file_path)
# ...or should be handled by the SPA's "router" in front end
else:
index_path = os.path.join(app.static_folder, 'index.html')
return send_file(index_path)


if __name__ == "__main__":
# Only for debugging while developing
app.run(host='0.0.0.0', debug=True, port=80)
eOF
1
2
3
4
5
6
7
8
9
10
11
12
13
$ mkdir app/static
$ cat << EOF > app/static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1>Hello World from HTML</h1>
</body>
</html>
eOF

ビルド

1
$ sudo docker build -t dobachi/nginx-flask-spa:1.0 .

実行

1
$ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-spa:1.0

構造的なプロジェクト

gitHub: 構造的なプロジェクトの例 に記載の例を示す。 example-flask-package-python3.7.zip をダウンロードしてビルドしてみる。

アーカイブをダウンロード

1
2
3
4
$ mkdir -p  ~/Sources/docker_flask/
$ wget https://github.com/tiangolo/uwsgi-nginx-flask-docker/releases/download/v0.3.10/example-flask-package-python3.7.zip
$ unzip example-flask-package-python3.7.zip
$ cd example-flask-package-python3.7

ビルド

1
$ sudo docker build -t dobachi/nginx-flask-package:1.0 .

実行

1
$ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-package:1.0

一部修正してみる

apiを追加する。

ファイルを修正

1
2
3
$ cat example-flask-package-python3.7/app/app/api/api.py 
from .endpoints import user
from .endpoints import get_ip

ファイルを追加

1
2
3
4
5
6
7
8
9
10
11
$ cat example-flask-package-python3.7/app/app/api/endpoints/get_ip.py 
from flask import request
from flask import jsonify

from ..utils import senseless_print

from ...main import app

@app.route("/get_ip", methods=["GET"])
def get_ip():
return jsonify({'ip': request.remote_addr}), 200

ビルドし、実行して、/get_ipにアクセスすると、接続元のIPアドレスが表示される。

flask + NginxでSSLを使用(ここがうまく動かない)

アドホックな対応

flask + NginxでのSSL対応に関する記事 に記載された方法で試す。 また上記で使ったexample-flask-package-python3.7の例を用いてみる。

まずDockerfileを以下のように変更した。

1
2
3
4
5
6
7
$ cat Dockerfile 
froM tiangolo/uwsgi-nginx-flask:python3.7

run apt-get update
run pip install pyopenssl

copY ./app /app

main.pyを以下のように修正し、アドホックなSSL対応を試せるようにする。

1
2
3
4
5
6
7
8
9
10
11
12
$ cat app/app/main.py 
from flask import Flask

app = Flask(__name__)

from .core import app_setup


if __name__ == "__main__":
# Only for debugging while developing
# app.run(host='0.0.0.0', debug=True, port=80)
app.run(host='0.0.0.0', debug=True, ssl_context='adhoc')

以上の修正を加えた上で、Dockerイメージをビルドし実行した。

1
2
$ sudo docker build -t dobachi/nginx-flask-package:1.0 .
$ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-package:1.0 /bin/bash

gitHub: uwsgi-nginx-flask-dockerのアーキテクチャ

gitHub: uwsgi-nginx-flask-dockerのテクニカル詳細 で書かれている通り。 以下の役割分担。

  • ウェブサーバ:Nginx
  • wSGIのアプリサーバ:uWSGI

またアプリはFlaskで書かれていることを前提とし、 プロセスはSupervisordで管理される。 アプリはコンテナ内の/app以下に配備されることを前提とし、その中にとりあえずは動くuwsgi.iniも含まれる。 そのため、Dockerfile内でuwsgi.iniを上書きさせるようにして用いることを想定しているようだ。

Supervisord化について

以下のように/bin/bashを起動して確認した。

1
$ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-package:1.0  /bin/bash

/etc/supervisor/conf.d/supervisord.confを確認する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cat /etc/supervisor/conf.d/supervisord.conf 
[supervisord]
nodaemon=true

[program:uwsgi]
command=/usr/local/bin/uwsgi --ini /etc/uwsgi/uwsgi.ini --die-on-term --need-app
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
# graceful stop, see http://nginx.org/en/docs/control.html
stopsignal=QUIT

uwsgiとnginxを起動させていることがわかる。

続いて、nginxの設定において、uwsgiと連携しているのを設定している箇所を確認。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# cat /etc/nginx/conf.d/nginx.conf 
server {
listen 80;
location / {
try_files $uri @app;
}
location @app {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
location /static {
alias /app/static;
}
}

uwsgiでは、/appディレクトリ以下のuwsgi.iniと/etc/uwsgi/uwsgi.iniの両方を確認するようだ。

1
2
3
4
5
6
7
# cat /etc/uwsgi/uwsgi.ini 
[uwsgi]
socket = /tmp/uwsgi.sock
chown-socket = nginx:nginx
chmod-socket = 664
# graceful shutdown on SIGTERM, see https://github.com/unbit/uwsgi/issues/849#issuecomment-118869386
hook-master-start = unix_signal:15 gracefully_kill_them_all
1
2
3
4
# cat /app/uwsgi.ini 
[uwsgi]
module = app.main
callable = app
共有

PixieDustを試してみる

参考

公式サイトの確認

ここでは気になったことを記しておく。

Scalaでも使える?

公式GitHubのREADMEには、 Use in python or scala という項目があり、 そこには以下のような記述がある。

1
PixieDust lets you bring robust Python visualization options to your Scala notebooks.
1
Scala Bridge. Use Scala directly in your Python notebook. Variables are automatically transfered from Python to Scala and vice-versa. Learn more.
1
Spark progress monitor. Track the status of your Spark job. No more waiting in the dark. Notebook users can now see how a cell's code is running behind the scenes.

Jupyterに独自のカーネルをインストールして使う

カーネルを追加することで、ユーザが自身の環境に依存するライブラリ(Sparkなど)をインストールしなくても 簡単に使えるようになっているようだ? ★要確認

動作確認

まずは、公式ウェブサイトのインストール手順 を読みつつ、 Qiitaの紹介ブログ を読みながら、軽く試してみることにした。 Anacondaを使っているものとし、Condaで環境を作る。

1
2
$ conda create -n PixieDustExample python=3.5 jupyter
$ conda activate PixieDustExample

また 公式ウェブサイトのインストール手順 には、以下のような記述があり、 Pythonは2.7もしくは3.5が良いようだ。

1
Note PixieDust supports both Python 2.7 and Python 3.5.

pipでpixiedustを探すと、エコシステムが形成されているようだった。

1
2
3
4
5
6
7
8
9
10
$ pip search pixiedust
pixiedust-node (0.2.5) - Pixiedust extension for Node.js
pixiedust-flightpredict (0.12) - Flight delay predictor application with PixieDust
pixiedust-wordcloud (0.2.2) - Word Cloud Visualization Plugin for PixieDust
pixiedust-twitterdemo1 (0.1) - Pixiedust demo of the Twitter Sentiment Analysis tutorials
pixiedust-twitterdemo (0.4) - Pixiedust demo of the Twitter Sentiment Analysis tutorials
pixiedust (1.1.14) - Productivity library for Jupyter Notebook
pixiedust-optimus (1.4.0) - Productivity library for Spark Python Notebook
pixiegateway (0.8) - Server for sharing PixieDust chart and running PixieApps
stem-ladiespixiedust-twitterdemo (0.2.2) - Pixiedust demo of the Twitter Sentiment Analysis tutorialsfor the STEM activity wall

そのまま入れることとする。依存関係でいくつかライブラリが入る。

1
$ pip install pixiedust

エラー。

1
twisted 18.7.0 requires PyHamcrest>=1.9.0, which is not installed.

condaでインストールする。

1
$ conda install PyHamcrest

PixieDust向けのJupyterカーネルをインストールする。 なお、本手順の前に、JDKがインストールされ、JAVA_HOMEがインストールされていることを前提とする。

1
$ jupyter pixiedust install

エラー。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ jupyter pixiedust install
Step 1: PIXIEDUST_HOME: /home/dobachi/pixiedust
Keep y/n [y]? y
Step 2: SPARK_HOME: /home/dobachi/pixiedust/bin/spark
Keep y/n [y]? y
Directory /home/dobachi/pixiedust/bin/spark does not contain a valid SPARK install
Download Spark y/n [y]? y
What version would you like to download? 1.6.3, 2.0.2, 2.1.0, 2.2.0, 2.3.0 [2.3.0]:
SPARK_HOME will be set to /home/dobachi/pixiedust/bin/spark/spark-2.3.0-bin-hadoop2.7
Downloading Spark 2.3.0
Traceback (most recent call last):

(snip)

File "/home/dobachi/.conda/envs/PixieDustExample/lib/python3.7/site-packages/install/createKernel.py", line 408, in download_spark
temp_file = self.download_file(spark_download_url)
File "/home/dobachi/.conda/envs/PixieDustExample/lib/python3.7/site-packages/install/createKernel.py", line 455, in download_file
raise Exception("{}".format(response.status_code))
Exception: 404

Sparkのダウンロードで失敗しているようだ。 軽くデバッグしたところ、URLとして渡されているのは、 http://apache.claz.org/spark/spark-2.3.0/spark-2.3.0-bin-hadoop2.7.tgzのようだった。 確かに、リンク切れ?のようだった。いったん、Spark公式サイトのダウンロードページで 指定されるURLからダウンロードするよう、createKernel.pyを修正して実行することにした。

なお、上記手順により、指定したディレクトリにSparkとScalaがインストールされる。 成功すると、以下のようなメッセージが見られる。

1
2
3
4
5
6
7
(snip)

####################################################################################################
# Congratulations: Kernel Python-with-Pixiedust_Spark-2.3 was successfully created in /home/dobachi/.local/share/jupyter/kernels/pythonwithpixiedustspark23
# You can start the Notebook server with the following command:
# jupyter notebook /home/dobachi/pixiedust/notebooks
####################################################################################################

なお、そのまま実行したら、以下のライブラリが足りないと言われたので予めインストールすることとする。 (bokeh、seabornはついでにインストール。インストールしないとRendererの選択肢が出てこない)

1
$ conda install matplotlib pandas PyYaml bokeh seaborn

さて、 Qiitaの紹介ブログ では、PixieDustのカーネルインストール時に一緒にインストールされるノートブックではなく、 日本語でわかりやすい紹介記事が書かれているが、ここではいったん、上記のインストール時のメッセージに従うこととする。

Jupyterを起動

1
$ jupyter notebook /home/dobachi/pixiedust/notebooks

あとは、適当にノートブックを眺めて実行する。

PixieDust 1 - Easy Visualizationsで気になったこと

Loading External Data節でD3 Rendererを試したところ、以下のようなエラー。

1
Object of type ndarray is not JSON serializable
共有