【Python3】データがstrではなくbytesで返ってくる問題【MySQL】

PyMySQL3を使ってDB内のデータ取得をしたところ、varchar型に設定されたカラムのデータがbytes型で返ってきてしまう問題に遭遇。
後々のためメモ。

環境

Python 3.4.3
MySQL 5.6
PyMySQL3

解決策

PyMySQL3への接続とクエリは以下のような平凡なものである。

import pymysql

conn = pymysql.connect(params)
cur = conn.cursor()
cur.execute("SELECT id, name from some_table")
result = cur.fetchone()
print(result['name'])

ここでnameカラムがvarchar型だと、<class 'bytes'>として値が返ってきてしまった。

この問題を解決するためには、DBで定義されているカラムのcollation(照合順序)を変更する必要がある。

現在のcollationを確認するにはmysqlに接続、DBを選択し、

show table status;

を実行すればよい。

Name: some_table
(略)
Collation: utf8_bin

上記のように、collationがutf8_binになっているのがbytes型でデータが返却される原因のようだ。
というわけで下記を実行。

ALTER TABLE some_table COLLATE utf8_general_ci;
ALTER TABLE some_table MODIFY COLUMN name VARCHAR(255) CHARACTER SET `utf8` COLLATE `utf8_general_ci`;

これでカラムの設定と既存のデータがutf8_binからutf8_general_ciに変更された。

pythonに戻り再度実行すると、データがstrで返って来ていることが確認出来るはずである。

参考

python - PyMySQL returns bytes instead of str - Stack Overflow
馬場誠Blog(東京都世田谷区経堂のWebクリエイター)» ブログアーカイブ » 照合順序をutf8_general_ciからutf8_unicode_ciへ変更する

【Python3】HTMLのエスケープされた記号を元に戻す

環境

Python 3.4.3

方法

xml.sax.saxutilsモジュールのunescapeメソッドを使えばOK。

from xml.sax.saxutils import unescape
text = '&amp; &lt; &gt;'
print(unescape(text))  # => '& < >'

第二引数にdictを与えると、独自辞書の定義も可能とのこと。

from xml.sax.saxutils import unescape
text = '&lt;ルイズ貧乳&gt;'
unescape(text, {'貧乳': 'かわいい'})  # => '<ルイズかわいい>'

参考

19.11. xml.sax.saxutils — SAX ユーティリティ — Python 2.7ja1 documentation
236:HTMLをエスケープする

【Python3】sqlite3.ProgrammingError: Incorrect number of bindings supplied【SQLite3】

また引っかかりそうなのでメモ。

環境

Python 3.4.3
sqlite3

解決策

executeメソッドSQLを実行しようとしたところ、

sqlite3.ProgrammingError: Incorrect number of bindings supplied.

と怒られてしまいました。

原因は単純で、executeメソッドの第二引数の形式です。
executeメソッドでは、第二引数はタプルで指定する必要が有るため、以下のようになります。

name = 'some_user_name'

# 駄目な例
c.execute('select * from users where name=?', name)

# 正しい例
c.execute('select * from users where name=?', (name,))

指定する変数がひとつの場合、最後に余計なカンマが付着するため若干気持ち悪いですが、Pythonの文法規則上やむを得ないですね。

【MySQL】データアップデート時に自動で時刻を更新する

フルスタックのWebアプリケーションフレームワークだと自動でやってくれますが、小規模フレームワークだと自力でやらねばならないためメモ。

CREATE TABLE some_tabel (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

これでcreatedには新規に作成された時の時刻が、modifiedにはupdateされる度にその時刻が挿入されます。

【CentOS6】鍵認証によるssh接続

ローカルのMacからリモートのCentOSへ鍵認証でssh接続するための手順をメモっておきます。
公開鍵および秘密鍵は予め作成が済んでいるものとします。

環境

Mac OS X Yosemite
CentOS 6.6

鍵の設定

まずはローカルMacからリモートに公開鍵の送付。

scp ~/.ssh/id_rsa.pub some_user@xxx.xxx.xxx.xxx:~/.

次にリモートのCentOSにログインし、公開鍵の設定を行う。

mkdir ~/.ssh  # .sshディレクトリがない場合
mv ~/id_rsa.pub ~/.ssh/.
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

次にsshd_configの修正。

sudo emacs /etc/ssh/sshd_config

各行のコメントアウトを外し、AuthorizedKeysFileでは公開鍵の場所の指定をする。

RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile      ~/.ssh/authorized_keys

またパスワード認証は不要なので、PasswordAuthentication noに設定する。

以上終了後sshdの再起動。

sudo service sshd restart

ローカルMacに戻り、sshログイン。

ssh -i ~/.ssh/id_rsa some_user@xxx.xxx.xxx.xxx

無事鍵認証でssh接続成功!

ついでにローカルMacのconfigファイルにssh接続の設定を追記。

emacs ~/.ssh/config
Host some_host
  HostName xxx.xxx.xxx.xxx
  User some_user
  port 22
  IdentityFile ~/.ssh/id_rsa

上記設定追記後、下記コマンドでssh可能。

ssh some_host

面倒な入力がなくなって便利!

参考

RSA公開鍵認証によるssh接続設定について(Macbook -> VPS) - Qiita

【CentOS6】新規ユーザーの追加とsudo権限の付与

環境

CentOS 6.6

ユーザー作成と権限付与

各コマンドはsudo権限のあるユーザーで実行する。

またsudoは、wheelグループに属しているユーザーのみ可能という方針で行う。
visudoコマンド単体で/etc/sudoersの編集は可能だが、/etc/sudoersの下部に記載されている#includedir /etc/sudoers.dによって、/etc/sudoers.d/下のファイルが読み込まれるため、ここでは/etc/sudoers.d/下に新規で設定ファイルを作成し、そのファイルに設定を記載する。
ちなみに#コメントアウト的な意味ではないので注意。

まずはユーザーの追加とパスワードの設定。

$ sudo useradd hoge
$ sudo passwd hoge

次にsudo権限の付与。

$ visudo -f /etc/sudoers.d/wheel

当たり前だが、emacsではなくviで開くので注意。
下記設定を新規記載。

%wheel ALL=(ALL) ALL

wheelユーザーに権限付与。
文法ミス等してしまうと、sudoコマンドが実行不可になってしまう危険があるので注意。visudoで開いており、警告を出してくれるはずなので大丈夫だとは思われる。

最後にユーザーをwheelグループに追加。

$ sudo usermod -G wheel hoge

新しく作ったユーザーに変更し、sudoが必要なコマンドを実行してみる。

$ su hoge
$ sudo visudo
[sudo] password for hoge

無事sudo権限を持ったユーザーの作成に成功!

参考

CentOS - sudoできるユーザを制限する。 - Qiita
CentOS - sudoers は編集せずに sudoers.d の中に設定を書こう - Qiita

【MeCab】Python3で解析時にbuiltins.UnicodeDecodeError【Flask】

Twitterから取得したデータをMeCabで処理し、Flaskを用いたWebアプリケーション上で表示しようとしたところ、下記のようなエラーが出現。

builtins.UnicodeDecodeError
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfc in position 0: invalid start byte

もしかしたらまたハマるかもしれないので念のためメモ。

環境

Python 3.4.3
MeCab 0.996

解決策

原因は不明だが、エラーメッセージを見るにエンコーディングがどうので死んでしまう模様。
ちなみにMeCabの実装コードは下記のような感じ。

import MeCab
mecab = MeCab.Tagger()
node = mecab.parseToNode("私はルイズが好きです。なぜなら、彼女はとてもキュートだからです。")
while node:
    print(node.surface)
    node = node.next

中々解決策に辿りつけなかったのだが、一行追記することでエラーの回避が可能に。
原因は正直良くわからない。
修正したコードは以下。

import MeCab
mecab = MeCab.Tagger()
mecab.parse('')  # これを追記!
node = mecab.parseToNode("私はルイズが大好きです。なぜなら、彼女はとてもとてもキュートだからです。ルイズかわいい!")
while node:
    print(node.surface)
    node = node.next

元々Python3でMeCabparseToNodeメソッドを使用すると、最初に返されるnodeが空の値というバグがあり、この解決のためにmecab.parse('')をするらしいのだが、UnicodeDecodeErrorへも対策として効果を発揮してくれた。

原因はよく分からないが、動くようになったので取り敢えず良しとする。

参考

Ubuntu14.04とPython3でMeCabを使う方法 | トライフィールズ

【Bottle】@viewデコレータを自前で定義する【Jinja2】

通常Bottleを使用する場合は、使用するテンプレートに併せて必要なメソッドやデコレータをインポートして用いることで、viewテンプレートの描画を非常に簡単に行うことが出来ます。

例えば、Jinja2テンプレートを使用し、メソッドでviewテンプレートの描画を行う場合は以下のようになります。

from bottle import route, jinja2_template as template

@route('/')
def index():
    return template('index.html', data = some_func())

また、同様の処理をデコレータで実装すると以下のようになります。

from bottle import route, jinja2_view as view

@route('/')
@view('index.html')
def index():
    return dict(data=some_func())

このメソッド群は非常に便利ですが、細かいセッティングをしようとすると、少なくない問題が発生します。
例えば、テンプレート側で何らかの関数を利用したい場合、jinja2.Environmentglobalsに追加しますが、bottleに用意されているメソッドでそれを実装するのは少々難しいです(そこまでソースコードを読んでいないので可能かもしれません)。

というわけで、templateの展開を行うviewデコレータを自力で実装してしまえという結論に至ります。

環境

Bottle v0.12
Jinja2 v2.8

実装

新しいview用のファイルをdecoview.pyという名前で作成し、以下のようにします。

# decoview.py
import functools
import jinja2

env = jinja2.Environment(
    loader=jinja2.FileSystemLoader('./path/to/views'),
    autoescape = True
)

env.globals.update({
    'some_func_key', some_func()
})

def some_func():
    return 'Called some_func!'

def view(template_name):
    def decorator(view_func):
        @functools.wraps(view_func)
        def wrapper(*args, **kwargs):
            response = view_func(*args, **kwargs)
            if isinstance(response, dict):
                template = env.get_or_select_template(template_name)
                return template.render(**response)
            else:
                return response
        return wrapper
    return decorator

せっかくなのでautoescapeTrueにしてみました。

新しく作成したdecoviewをインポートして使用すれば、jinja2テンプレートファイルの中でsome_func()が実行可能です。

# index.py
from bottle import route
from decoview import view

@route('/')
@view('index.html')
def index():
    return {}

例えば下記のようにjinja2テンプレートを呼び出します。

<!-- index.html -->
{{ some_func_key }}

きっとCalled some_func!という文字列が表示されていることと思います。

参考

API — Jinja2 Documentation (2.8-dev)
Jinja2 templates and Bottle | Reliably Broken