$ python -V Python 3.6.5 $ sudo apt install unixodbc-dev $ pip install pyodbc
unixodbc-dev を入れておかないと pip でのインストール中にこけます。
$ python -V Python 3.6.5 $ sudo apt install unixodbc-dev $ pip install pyodbc
unixodbc-dev を入れておかないと pip でのインストール中にこけます。
connect = pyodbc.connect('DSN=SQLServer;UID=user;PWD=password;') connect.setencoding('utf-8') connect.cursor.execute('SELECT 列 FROM テーブル WHERE 番号 = 5;')
connect.setencoding() でエンコーディングを指定したら解決しました。
古い基幹システムに SQL Server 2008 が使われており、テーブル名やカラム名が全て日本語で構成されていた関係で、Linux 上の Python から接続するのにひと手間かかっていました。DB 自体は UTF-8 で処理されているので、Python 2 からはクエリを query.encode('utf-8')
とすることでうまく処理できていました。さらっと書いていますが、Python にまだ慣れていないときに開発していたので、かなり苦労して辿りついた結論です。
今回は Bottle を使うに当って Python 3 に移行する関係から DB 接続周りを一新する必要があったのが事の始まりです。DB への接続自体は Python 2 と同じで特に問題もなかったのですが、いざ SELECT 分を execute してみると次のようなエラーを吐きました。
pyodbc.ProgrammingError: ('42S22', "[42S22] [FreeTDS][SQL Server]Invalid column name 'F'. (207) (SQLExecDirectW)")
このエラーには見覚えがあり、さらにカラム名は日本語で指定したはずなのに見慣れない文字になっていることから、日本語のエンコーディング周りの問題だろうということはすぐにわかりました。
まず試してみたのが Python 2 と同じ手法でクエリを encode(‘utf-8’) する方法です。これの結果は
TypeError: The first argument to execute must be a string or unicode query.
ユニコードでよこせと怒られてしまいました。なら最初はユニコードで渡してるからそれで良いのではないか。
次に疑ったのはファイルの文字コードを他のエンコーディングで保存していないかです。でもソースコード冒頭には # -*- coding: utf-8 -*- と記述した上で、間違いなく UTF-8N で保存しています。試しに Shift JIS で保存すると、次のエラーで実行すらできません。
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0x8f in position 0: invalid start byte
調べていても、そもそも Linux の Python から Windows Server の SQL Server を扱う事例自体が少なすぎて、情報があまり得られません。そこで、pyodbc のソースを読んでみようと思ったら、そのものずばりの情報がありました。
記事冒頭に書いた、DB に connect() した後 connection.setencoding('utf-8')
するだけです。これで無事に日本語のテーブル名、列名を処理できました。
たった一文のことですが、情報量が少ないとこれに辿りついて気づくまでが本当に大変です。
普段は閉鎖的なシステム開発が多いため、基本的には標準ライブラリで全て自分で実装するというスタイルを採ってきました。全て自分の実装であるが故にバグや挙動の把握がしやすいという利点はありますが、それ故にバグの温床になっているという事実もあります。何より実装と検証に時間がかかるため、生産性が非常に悪くなります。最近になって生産性向上のためにフレームワークにも手を出そうと思い立ちました。
Python のフレームワークといえば Django が有名どころで、実際に使っていた時期もありました。しかし、高機能であるが故に自分にとっては複雑すぎて長続きしませんでした。今ではもう綺麗さっぱり忘れてしまっています。
10 年近く経っているので事情も変ってきているだろうと調べていると、Bottle という Web フレームワークを見つけました。Flask というのも人気があるようですが、1 ファイルでの実装に魅力を感じて Bottle を選びました。今回はその Bottle について簡単に記します。
Bottle は bottle.py の 1 ファイルで構成される、軽量で高速な Web フレームワークです。テンプレートエンジンも搭載しており、小規模な開発では扱いやすいフレームワークです。
公式のインストール方法には最新版の bottle.py を取得するだけのこんな方法が書かれていて、1 ファイルのコンパクトさがよくわかります。
$ wget https://bottlepy.org/bottle.py
pip が使える環境であれば
$ pip install bottle
apt が使える環境であれば
$ sudo apt install python-bottle
とすればよいでしょう。パスや設置のことを考えるとパッケージマネージャでインストールした方が良い思いますが、最新版を使わなければいけない事情があれば直接ダウンロードしましょう。
Web サーバを建てて、アクセスすると “Hello world!” を返すだけの簡単なコードです。同様のコードが公式サイトにも載っています。
# -*- coding: utf-8 -*- from bottle import route, run @route('/') def hello(): return '<p>Hello world!</p>' run(host = '0.0.0.0', port = 8080)
これを例えば hello.py と保存して、次のように実行します。
$ python sample.py Bottle v0.12.13 server starting up (using WSGIRefServer())... Listening on http://0.0.0.0:8080/ Hit Ctrl-C to quit.
別の PC でも良いので Web ブラウザから http://[HOST_ADDRESS]:8080/ にアクセスしてみると、”Hello world!” が表示されると思います。終了するには書かれている通り、 Ctrl + C で Python 自体を終了させます。
実行方法からわかるように、ただの Python スクリプトを実行しているだけなので、ソース冒頭に Python 実行ファイルへのパス(#!/usr/local/bin/python 等)を書いておいて実行権限を付与しておけば、直接実行することも可能です。
#!/usr/local/bin/python ...(略)...
$ chmod +x hello.py $ ./hello.py Bottle v0.12.13 server starting up (using WSGIRefServer())... Listening on http://0.0.0.0:8080/ Hit Ctrl-C to quit.
さきほどの hello.py は次のように記述しても全く同じ動作をします。これはオブジェクト指向な書き方で、どちらの書き方を選ぶかは好みです。名前の衝突を避けるために使われることもあります。
# -*- coding: utf-8 -*- from bottle import Bottle, run app = Bottle() @app.route('/') def hello(): return '<p>Hello world!</p>' run(app, host = '0.0.0.0', port = 8080)
ちなみに @ がついているのは Python のデコレータ(decorator)と呼ばれるもので、関数のラッパーのようなものです。関数定義の前に置くことでその関数をデコレータで包みます。ここでは詳しくは述べません。
リクエストに応じて処理を変更したいとき、CGI であれば目的に応じたファイルで分けます。list を表示するのに list.py, contact フォームのために contact.py など。Bottle を使えば 1 ファイルで複数のリクエストに対する処理を書くことができます。
次のサンプルでは、http://[HOST_ADDRESS]:8080/ または http://[HOST_ADDRESS]:8080/ にアクセスすると “Home” が表示され、http://[HOST_ADDRESS]:8080/hello にアクセスすると “Hello!” と表示されます。
# -*- coding: utf-8 -*- from bottle import route, run @route('/') @route('/index') def index(): return '<p>Home</p>' @route('/hello') def hello(): return '<p>Hello!</p>' run(host = '0.0.0.0', port = 8080)
これでページ分けが行えますね。
ここまでは予め用意した内容を出力しているだけですが、実際の Web アプリケーションではリクエストに応じて内容を変化させることがほとんどだと思います。クライアントからサーバへ情報を送るにはいくつか方法があります。例えば GET, POST, Cookie, … 等ですが、ルーティングの続きとして GET リクエストによるルーティングを試してみます。
次のサンプルは http://[HOST_ADDRESS]:8080/hello/[YOUR_NAME] にアクセスすると、”Hello, [YOUR_NAME]!” と表示されます。
# -*- coding: utf-8 -*- from bottle import template, route, run @route('/hello/<str>') def hello(str): return template('<p>Hello, {{name}}!</p>', name = str) run(host = '0.0.0.0', port = 8080)
@route(‘/hello/<str>’) の <str> がワイルドカードとして扱われ、引数のように値を使うことができます。これによって URI 自体が意味を成すため、リンクを張ったときに内容を把握しやくなります。
ここで [YOUR_NAME] を指定しなかったらどうなるでしょうか?試しにアクセスしてみると Error: 404 Not Found と返ってきます。
ここで次のようにデフォルト引数を与えてあげると、/hello/ でアクセスするとデフォルト引数が使われて “Hello, Guest!” と表示されます。
# -*- coding: utf-8 -*- from bottle import template, route, run @route('/hello/') @route('/hello/<str>') def hello(str = 'Guest'): return template('<p>Hello, {{name}}!</p>', name = str) run(host = '0.0.0.0', port = 8080)
ここで注意する必要があるのは、”/hello/” と “/hello” をきちんと区別するということです。このサンプルで http://[HOST_ADDRESS]:8080/hello にアクセスしても 404 Not Found となってしまいます。普段から意識していれば問題ありませんが、ディレクトリであっても末尾の ‘/’ を省略する癖がある人は気を付けた方が良いでしょう。
@route() で使うワイルドカードは、値をフィルターで限定することができます。次の例では int は整数に限定され、float は小数を受け付け、path は ‘/’ を含むパス文字列、re は ‘:’ に続く正規表現に一致する文字列を受け付けます。
@route('/item/<id:int>') @route('/release/<ver:float>') @route('/static/<path:path>') @route('/view/<name:re:[a-z]+>')
Bottle が用意しているデコレーターは @route() の他にも @get(), @post(), @put(), @delete(), @patch(), @error() があります。HTML でよく使われる GET と POST に絞って使ってみることにします。
次のサンプルは簡易的なログイン機能を設けたものです。
# -*- coding: utf-8 -*- from bottle import get, post, request, run @get('/login') def login_form(): return '''<form action="/login" method="post"> <p><label>ID:</label> <input type="text" name="id" /> <label>Password:</label> <input type="password" name="password" /> <input type="submit" value="Login" /></p> </form>''' @post('/login') def login(): id = request.forms.get('id') pw = request.forms.get('password') if id == 'foo' and pw == 'bar': return '<p>You have successfully logged in.</p>' else: return '<p>Login failed.</p>' run(host = '0.0.0.0', port = 8080)
ここで注目していただきたいのは、同じ /login にアクセスしているのに GET と POST では異なる処理が行われているということです。デコレータをうまく使うことによって、簡潔な URI を実現できることがおわかりいただけると思います。
Bottle アプリケーションでは、画像や CSS, JavaScript といった静的なファイルを扱う場合にも自分でルーティングする必要があります。公式のチュートリアルを参考に、/static/ 以下へのアクセスは /home/user/static/ 以下のファイルを返すルーティングを実装したのが次になります。
# -*- coding: utf-8 -*- from bottle import static_file, route, run @route('/static/<filepath:path>') def static(filepath): return static_file(filepath, '/home/user/static') run(host = '0.0.0.0', port = 8080)
static_file() の引数は static_file(filename, root, …) となっていて、root を基準とした filename の内容を返す実装になっています。実装を見てみると root は関数内部で絶対パスへ変換されている(root = os.path.join(os.path.abspath(root), ”))ので相対パスも指定できますが、実行時のディレクトリ(current directory, working directory)に依存するので注意する必要があります。
static_file() の引数に download = True を与えると、そのファイルを Content-Disposition: attachment として出力するので、ダウンロード扱いにできます。このときのファイル名は元ファイルのファイル名となります。download = ‘filename.ext’ とすればダウンロード時のファイル名を指定できます。
@error(404) のように、ステータスコードを含んだデコレータを関数の前に置くだけです。
# -*- coding: utf-8 -*- from bottle import error, run @error(404) def error_404(e): return '<h1>404 Not Found</h1><p>{}</p>'.format(e) run(host = '0.0.0.0', port = 8080)
これでエラー時に任意のページを表示させることができます。
逆にエラーページに飛ばしたい場合は、import abort した上で abort(401, ‘Authorization required’) のように呼び出します。
# -*- coding: utf-8 -*- from bottle import abort, route, run @route('/admin') def admin(): abort(401, 'Authorization required.') run(host = '0.0.0.0', port = 8080)
Bottle で扱えるクライアントからのデータには、以下のようなものがあります。それぞれ簡単な例を挙げます。
HTTP ヘッダを辞書形式で取得できます。下記は headers 内の全ての値を出力します。
# -*- coding: utf-8 -*- from bottle import route, run, request @route('/req/headers') def req_headers(): ret = '<dl>' for k in request.headers: ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.headers.get(k)) ret += '</dl>' return ret run(host = '0.0.0.0', port = 8080)
余談ですが、Edge のUser-Agentってこんなに色々な名前が入っているんですね。KHTML から拡張されてきた歴史は知っていましたが、Chrome と Safari だけでなく AppleWebKit の名前まで入っていて、どれが実体なのかぱっと見わけがわかりません。ついでなのでちょっと調べてみると、同じこと思っている方は他にもいるみたいです。
ということでモバイル版はもっとひどいようで、Android は “EdgA” であり、iOSは “EdgiOS” らしいです。Edge ですらなくなってます。
全ての Cookie を辞書で返します。Cookie の名前がわかっているなら request.get_cookie(‘cookie_name’) で直接取得できます。
# -*- coding: utf-8 -*- from bottle import route, run, request, response @route('/req/set-cookie') def set_cookie(): response.set_cookie('foo', 'bar') response.set_cookie('key', 'value') return '<p>Set some cookies.</p>' @route('/req/cookies') def req_cookies(): ret = '<dl>' for k in request.cookies: ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.cookies.getunicode(k)) ret += '</dl>' return ret run(host = '0.0.0.0', port = 8080)
QUERY_STRING と呼ばれる、URL の ? 以下の部分を扱います。request.query_string は ? 以下を一つの文字列として返します。request.query は key=value 形式の辞書を返します。
# -*- coding: utf-8 -*- from bottle import route, post, run, request, response @route('/req/query') def req_query(): ret = '<p>{}</p>'.format(request.query_string) ret += '<dl>' for k in request.query: ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.query.getunicode(k)) ret += '</dl>' return ret run(host = '0.0.0.0', port = 8080)
公式チュートリアルでは id=foo&page=5 のような QUERY_STRING が与えられた場合に、次のように値を取得するサンプルも掲載されています。
id = request.query.id page = request.query.page
一般的な method=”POST” な form の値は、request.forms で取得します。これも name=value 形式の辞書になっています。name が予めわかっていれば request.forms.get(‘name’) で取得できます。
# -*- coding: utf-8 -*- from bottle import route, post, run, request @route('/req/forms') def post_form(): ret = '''<form action="" method="post"> <p>ID: <input name="id" type="text" /> Password: <input name="pw" type="password" /> <input type="submit" value="送信" /></p> </form>''' return ret @post('/req/forms') def req_forms(): ret = '<dl>' for k in request.forms: ret += '<dt>{}</dt><dd>{}</dd>'.format(k, request.forms.getunicode(k)) ret += '</dl>' return ret run(host = '0.0.0.0', port = 8080)
ファイルの受信も簡単に行えます。request.files は input の name を key とし、送られたファイルを持つ辞書になっています。name がわかっていれば直接 get() でファイルの内容を取得できます。
# -*- coding: utf-8 -*- from bottle import route, post, run, request @route('/req/files') def file_form(): ret = '''<form action="" method="post" enctype="multipart/form-data"> <p>File1: <input name="file1" type="file" /> File2: <input name="file2" type="file" /> <input type="submit" value="Send" /></p> </form>''' return ret @post('/req/files') def file_form(): ret = '<dl>' for k in request.files: f = request.files.get(k) ret += '<dt>{}</dt><dd>{}({})</dd>'.format(k, f.filename, f.content_type) f.save('/home/user/files') ret += '</dl>' return ret run(host = '0.0.0.0', port = 8080)
get() で取得できるのは class FileUpload のオブジェクトです。この FileUpload について簡単に書きます。以下、file は class FileUpload のオブジェクトとします。
file.raw_filename で元のファイル名を取得できますが、”may contain unsafe characters” とリファレンスにあるように、ユニコードの特殊文字の扱いを考えなければセキュリティ上の問題になる可能性があります。代りに file.filename で取得できるファイル名は安全なものに処理されているので、そのまま保存に使っても問題ありません。ただし、”ASCII letters, digits, dashes, underscores and dots” の範囲内でしか処理されないため、日本語等は全て省略されてしまいます。元のファイル名は何らかの別の形で残すように処理した方が良いでしょう。
file.save(destination = ‘/path/to/dir/’) とすると、ディレクトリ destination に file.filename という名前で保存されます。事前に destination に書込権限があることを確認してください。
送信側の form に enctype=”multipart/form-data” を入れ忘れないように注意しましょう。今まで CGI でファイルを扱ったことがなかったのでこれに気付かず、しばらくハマりました。
テンプレートエンジンについて簡単に説明しておくと、予め雛型となるファイルを用意しておいて、変える必要がある部分のみを書き換えてくれるというものです。
例えば HTML であれば、DOCTYPE から始まるヘッダに相当する部分はどのページでもさほど違いはありません。title 要素の値が違うくらいでしょうか。これらを毎回出力するのは非効率的ですし、1 つの変更を適用させるためにすべての出力を書き換える必要ができます。CSS が普及するまで HTML に直接デザインを書き入れてたのと同じくらい非効率です。
Bottle には SimpleTemplate Engine と呼ばれるテンプレートエンジンが用意されています。その名の通りとてもシンプルなエンジンですが、テンプレート内に Python コードを埋め込むことができるため、簡素ながらも柔軟性があります。PHP と似たようなもの、といったら経験者にはわかりやすいでしょうか。
最も簡単な例として、次のようなコードを用意しました。
# -*- coding: utf-8 -*- from bottle import template, route, run @route('/<dir>/<name>') def hello(dir, name): return template('<p>Dir: {{DIR}}<br />Name: {{NAME}}</p>', DIR = dir, NAME = name) run(host = '0.0.0.0', port = 8080)
{{ と }} で囲まれた部分が置き換え対象となり、第 2 引数以降で与えられる値に置き換えられます。第 2 引数を辞書にすることも可能で、上の例であれば {‘DIR’ : dir, ‘NAME’ : name} を第 2 引数にします。
{{ と }} で囲まれた式を変数のように扱いましたが、Python コードそのものを書くこともできます。次は bool 値が True であれば “TRUE” と、False であれば “FALSE” と表示するコード埋め込んだものです。
# -*- coding: utf-8 -*- from bottle import template, route, run @route('/flag') def home(): return template('<p>{{"TRUE" if flag else "FALSE"}}</p>', flag = True) run(host = '0.0.0.0', port = 8080)
変数の値を埋め込むとき、XSS 攻撃防止のために HTML の特別文字は全てエスケープされます。
‘%’ から始まる行は Python コードとして処理されます。’%’ の前後に空白があっても構いません。また、Python の構文であるインデントも必要ありません。インデントで表していたブロックの範囲は、”% end”と明示するまで続きます。
次のコードは、埋め込みコードを使って 0 から 9 の数字をリストアイテムとして出力します。
# -*- coding: utf-8 -*- from bottle import template, route, run @route('/loop') def loop(): t = '''<ul> % for count in range(10): <li>{{count}}</li> % end </ul>''' return template(t) run(host = '0.0.0.0', port = 8080)
ここまでは、把握しやすいようにソースコードの中に直接 HTML を書いてテンプレートを記述していましたが、実際は別ファイルとして用意しないとテンプレートの意味がなくなってしまいます。
例として、ページタイトルと本文だけを埋め込めるようにしたテンプレートを作って表示してみます。
Bottle を実行しているディレクトリの下に views ディレクトリを作成し、次のソースを html.tpl として保存します。
<!DOCTYPE html> <head> <title>{{TITLE}}</title> </head> <body> {{BODY}} </body> </html>
そして次の Python コードを実行します。
# -*- coding: utf-8 -*- from bottle import template, route, run @route('/') def home(): return template('html.tpl', TITLE = 'Bottle Sample', BODY = '<p>Testing template engine.</p>') run(host = '0.0.0.0', port = 8080)
おや、HTML のタグがそのまま表示されてしまいましたね。先程述べたように、XSS 攻撃を防ぐために HTML の特別文字は全てエスケープされます。エスケープを行いたくないときは、{{ と }} の中の変数名に ‘!’ を付けます。
<!DOCTYPE html> <head> <title>{{TITLE}}</title> </head> <body> {{!BODY}} </body> </html>
エスケープを無効にする際には、XSS に十分に注意してください。できるだけ HTML はテンプレート側に記述してしまい、値だけを流し込むのが理想だと思います。
先のコードでは template() で直接テンプレートを呼び出していましたが、これをデコレータで指定することもできます。
# -*- coding: utf-8 -*- from bottle import template, view, route, run @route('/') @view('html.tpl') def home(): return dict(TITLE = 'Bottle Sample', BODY = '<p>Testing template engine.</p>') run(host = '0.0.0.0', port = 8080)
今まで Python 2.xで開発を行っていましたが、Python 3.x に移行する必要があって 2.x と 3.x が混在する環境をどう構築するか悩んでいました。pyenv や virtualenv, pyvenv という便利なものがあるようですが、それぞれ役割が違うようです。
今回はバージョン管理だけを行いたかったので、pyenv に絞った内容です。
pyenv が主に行うのはユーザー環境下での Python のバージョン管理です。ユーザー環境下であってシステム全体ではないところが重要で、システムを利用する他のユーザに影響を与えません。
以下は提供サイト(https://github.com/pyenv/pyenv)にある説明です。
pyenv does…
- Let you change the global Python version on a per-user basis.
- Provide support for per-project Python versions.
- Allow you to override the Python version with an environment variable.
- Search commands from multiple versions of Python at a time. This may be helpful to test across Python versions with tox.
特定のディレクトリで使う Python のバージョン(per-project Python versions)、それ以外のディレクトリで使う Python のバージョン(global Python version) を簡単に切り替えられるのが特徴です。仕組としてはシェルの PATH の探索順を利用しているようです。
環境は Debian (Raspbian) 8.0 を想定しています。
インストールといってもシェルスクリプトなので、git からコピーして環境変数を設定するだけです。
# pyenv installのとき必要になるパッケージのインストール $ sudo apt update $ sudo apt install build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils # gitのインストール(gitが入っていたら不要) $ sudo apt install git # pyenvのインストール $ git clone https://github.com/yyuu/pyenv.git ~/.pyenv # 環境変数の設定 $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc $ echo 'eval "$(pyenv init -)"' >> ~/.bashrc $ source ~/.bashrc # 確認 $ pyenv -v pyenv 1.2.4-2-gdad0fc7
まず最初にインストールされているバージョンを確認します。
$ pyenv versions * system (set by /home/pi/.pyenv/version) $ python -V Python 2.7.9
何もインストールしてないので、システムに標準で入っている Python のみ状態です。この状態から pyenv 環境に使いたいバージョンの Python を追加していきます。
インストールできる Python のバージョンは “pyenv install -l” または “pyenv install –list”で確認できます。
$ pyenv install -l Available versions: 2.1.3 ... ...(省略)... ... stackless-3.5.4
この中から、例えば Python 3.6.5 をインストールするなら次のようにするだけです。ビルドを伴うのでそれなりに時間がかかります。
$ pyenv install 3.6.5 Downloading Python-3.6.5.tar.xz... -> https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz Installing Python-3.6.5... Installed Python-3.6.5 to /home/pi/.pyenv/versions/3.6.5 $ pyenv versions * system (set by /home/pi/.python-version) 3.6.5
インストールが完了したらバージョンを切り替えてみます。現在のディレクトリにだけ効力を持たせるには local を、全てのディレクトリで効力を持たせるには global を指定します。
$ pyenv local 3.6.5 $ python -V Python 3.6.5
local は指定したディレクトリ以下(子ディレクトリも含む)で効果を持ちます。そのディレクトリだけでなく、子ディレクトリにも影響が及ぶことに注意します。
$ python -V Python 2.7.9 $ pyenv local 3.6.5 $ python -V Python 3.6.5 $ cd .. $ python -V Python 2.7.9 $ cd ./user/dir/ $ python -V Python 3.6.5
global はそのユーザがログインしているとき、全てのディレクトリで効果を持ちます。どのディレクトリに移動しても、global で指定されたバージョンが実行されます。このとき、local でバージョンが指定されているディレクトリは local で指定したものが優先されます。
$ python -V Python 2.7.9 $ pyenv local 3.6.5 $ python -V Python 3.6.5 $ cd .. $ python -V Python 3.6.5 $ cd ./user/dir/ $ python -V Python 3.6.5
Python のバージョン管理を行う pyenv の説明は以上です。2.x と 3.x の共存や切替はこれで簡単に行えますが、環境が複数になると次はプロジェクト毎にモジュールの管理を行う必要が出てきます。
そこで冒頭に挙げた virtualenv や venv といったものが登場します。これらはまた別の記事にまとめます。
マイニング用の電源に 80PLUS Platinum 認証の Seasonic SSR-750PX を導入した。あまり下調べせずに選んだ自分が悪いのだが、この電源、プラグインケーブルの差込口だけ見ると PCIe 用は 5 本いけそうなのにPCIe 6 + 2pin * 2 のケーブルが2本しか付属してこない。
付属するケーブルは製品ページの下の方にちゃんと載っているが、今更どうしようもないのでケーブルだけ入手できないかを調べた。
結論から言うと、モジュラー型の電源ケーブルをメーカー側が取り扱っていることは Seasonic に限らずほとんどないようだ。売っているところもあるにはあるみたいだが、ほぼない。
かくいうオウルテックも公式の FAQ を見るに、別途販売はしていない。
純正品を手に入れるのは絶望的というのはわかったが、端子がついているのに分岐ケーブルを使うなんてことはできるだけしたくない。
どうにかできないものかと調べていると、CableMod という、様々な電源メーカーに対応したケーブルを作っているところを見つけた。日本では OLIOSPEC が販売しているようだ。
セット品もあったが欲しい組み合せではないため、Configurator で自分好みの組み合せを注文することにした。
電源のメーカーを選ぶと電源ユニットの一覧が出てくる。今回は Seasonic FOCUS PLUS 750 Platinum を選択。
欲しいケーブルを選んで追加すると長さを調整できる。端子が複数ある場合でも、端子間の長さも変更できる。さらにケーブル 1 列単位で好きな色に変更することもでき、自由度はかなり高い。
結局注文したのは
の 5 本で、送料(Flat Rate) 1,700 円と合せて 8,400 円となった。支払は PayPal でササっと済ませた。
電源本体と合計すると 30,000 円を超えているので、素直にマイニング用電源を買えば良かったと少し後悔している。今更言っても仕方ないので、今あるパーツでできる限り良いものを組もうと思う。
$ sudo apt install build-essential libffi-dev python-dev $ sudo pip install bcrypt
これで import bcrypt するだけです。