月: 2018年6月

  • Gemini PDA のファームウェア更新(2018-06-12)

    FlashTool の導入や使い方は「Gemini PDA の root 取得 (Windows 10 環境)」と同様なのでそちらを参照してください。

    最新のファームウェアは Planet Computers 公式に用意されています。

    今回は LATEST – Gemini x27 Android FOTA3 12/06/2018 を、前回の 20180510 の root なシステムに上書きする形で進めました。Gemini_x27_FOTA3_12062018.zip のダウンロードが完了したら展開しておきます。

    FlashTool を起動して Scatter-loading file に展開したフォルダにある Gemini_Android.txt を指定します。一覧から userdata のチェックを外します。

    FlashTool 20181206
    FlashTool 20181206

    起動している Gemini PDA を PC に接続して、Download を押します。Gemini PDA の Esc を長押ししてメニューから再起動を選びます。大きなチェックマークが表示されたら完了です。

    ケーブルを抜いて Esc を長押しして Gemini PDA を起動し、設定の「端末情報」一番下「ビルド番号」確認して Gemini-7.1-Planet-12062018-V2 になっていることを確認して終了です。

    ビルド番号

    root 環境から非 root 環境に戻しましたが、特に問題なさそうです。スクリーンショットの撮影音が復活してしまったのが、やや煩わしいくらいでしょうか。

    キー配置の変更に関しては非 root 環境でも apk の導入でできるようになったようです。キー変更のためだけに root 化してた人はこれで十分そうですね。

  • さくらのレンタルサーバーで pip できない(SSLError)

    TL; DR

    古い OpenSSL が使われるのが原因です。Python ビルド時に新しい OpenSSL を指定すれば解決します。

    $ pyenv uninstall 3.6.5
    $ CPPFLAGS="-I/usr/local/ssl/include" LDFLAGS="-L/usr/local/ssl/lib" pyenv install 3.6.5

    経緯

    さくらのレンタルサーバーはプラン スタンダード以上で SSH が使えるので、pyenv を入れたり pip を入れたりできます。pyenv を使って Python の他のバージョンをインストールするときに注意しないといけないのが、さくらでは Python 導入時のビルドで古い OpenSSL が使われてしまうことです。これの影響で pip したときに次のような SSL のエラーを吐きます。

    $ pip install bcrypt
    Collecting bcrypt
     Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:833)'),)': /simple/bcrypt/
     Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:833)'),)': /simple/bcrypt/
     Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:833)'),)': /simple/bcrypt/
     Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:833)'),)': /simple/bcrypt/
     Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLError(1, '[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:833)'),)': /simple/bcrypt/
     Could not fetch URL https://pypi.python.org/simple/bcrypt/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.python.org', port=443): Max retries exceeded with url: /simple/bcrypt/ (Caused by SSLError(SSLError(1, '[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:833)'),)) - skipping
     Could not find a version that satisfies the requirement bcrypt (from versions: )
    No matching distribution found for bcrypt

    OpenSSL のバージョンが古いのかな?とバージョンを確認しても最新版です。

    $ openssl version
    OpenSSL 1.0.2o 27 Mar 2018

    ですが pyenv install 3.6.5 した Python 上で確認してみると

    $ python
    Python 3.6.5 (default, Jun 26 2018, 10:35:21)
    [GCC 4.2.1 20070831 patched [FreeBSD]] on freebsd9
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import ssl
    >>> ssl.OPENSSL_VERSION
    'OpenSSL 0.9.8zf 19 Mar 2015'

    思いっきり 0.9.8zf と出ていますね。「Python – さくらレンタルサーバーでpip installができません(123028)|teratail」によると、普通にビルドすると最新版の OpenSSL ではなくて、古い方の OpenSSL をリンクしてしまうとのこと。

    さくらレンタルサーバでは普通にpythonをbuildすると古いopensslにつながってしまうようです。
    /usr/local/sslに新しいのが入ってるみたいなので、

    ./configure CPPFLAGS="-I/usr/local/ssl/include" LDFLAGS="-L/usr/local/ssl/lib"

    でpythonを作るとよさそうです。

    ということで一旦インストールした Python を uninstall して、OpenSSL のパスを指定して再度インストールし直します。

    $ pyenv uninstall 3.6.5
    $ CPPFLAGS="-I/usr/local/ssl/include" LDFLAGS="-L/usr/local/ssl/lib" pyenv install 3.6.5
    Downloading Python-3.6.5.tgz...
    -> https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tgz
    Installing Python-3.6.5...
    Installed Python-3.6.5 to /home/USERNAME/.pyenv/versions/3.6.5

    無事インストールできたようなので、OpenSSL のバージョンを確認してみます。

    $ python
    Python 3.6.5 (default, Jun 26 2018, 10:35:21)
    [GCC 4.2.1 20070831 patched [FreeBSD]] on freebsd9
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import ssl
    >>> ssl.OPENSSL_VERSION
    'OpenSSL 1.0.2o 27 Mar 2018'

    大丈夫そうですね。pip の更新を兼ねて pip を試してみます。

    $ pip install -U pip
    Collecting pip
     Downloading https://files.pythonhosted.org/packages/0f/74/ecd13431bcc456ed390b44c8a6e917c1820365cbebcb6a8974d1cd045ab4/pip-10.0.1-py2.py3-none-any.whl (1.3MB)
     100% |################################| 1.3MB 890kB/s
    Installing collected packages: pip
     Found existing installation: pip 9.0.3
     Uninstalling pip-9.0.3:
     Successfully uninstalled pip-9.0.3
    Successfully installed pip-10.0.1

    ばっちり動きました。

    ですが……今回の目的だった bcrypt のインストールは、libffi がなくて結局できず。py-bcrypt なら pip 一発なのでソースコードを py-bcrypt に書き替えた方が早そう。ちょっと不完全燃焼です。

    参考

  • Gemini PDA のキーボード設定

    Gemini PDA のキーボード設定

    日本語入力は Gboard に落ち着きましたが、配列の設定がよくわかっていなくて記号が入力できない状態になってしまいました。調べながら弄っているうちにようやく満足に使える設定になったので、忘れないようにメモしておきます。

    Gboard も標準でインストールされているようだったので、初期設定が終ったら Play ストアからアップデートをかけておきます。

    Gemini キーボードという 4 ステップで設定するアプリも用意されていますが、Android 標準の設定だけで十分だったので今回は使いません。

    今回のゴールは次の通り。

    • キーボードだけで直接入力と日本語入力を切り替え
    • キーの刻印通りの入力
    • バックスラッシュ\(パイプ|)を長音-に変更
    • 句読点を ZXCVBNM,. の並びに変更
    • 句読点の並び伴ってカーソルを vi 配列(←↓↑→)に変更

    Gboard とキー配列の設定

    設定の「言語と入力」を開きます。

    「仮想キーボード」を開いて「キーボードを管理」から gboard を有効にします。どうせ使わないからと、このときついでに他のキーボードは全て無効にしました。

    一つ前の画面に戻り、仮想キーボードから Gboard を開きます。「言語」を開いて「日本語」と「英語(米国)」を追加します。

    「言語と入力」まで戻って「物理キーボード」を開きます。「Gboard – 多言語入力」を開いて「English (US) Default Gemini Keyboard」を選択します(「English (US) Gemini Keyboad)」でも大丈夫だと思うけど違いがわからない)。

    これで一度 Notes などで入力してみて、キーの刻印と入力される文字が一致しているか、Shift + Space で直接入力と日本語入力の切り替えができるか試してみてください。

    参考

    キー配列のカスタマイズ(要 root)

    日本語入力が行える環境が整ったら、次は使いやすいように配列を自分好みにカスタマイズします。と言っても先人がしているのと全く同じです。日本語の文章を打ってると、まず真っ先に、よく使う句読点と長音にストレスを感じたので、考えることは皆さん似ているんだと思います。

    バックスラッシュを長音に変更するだけであれば非 root でもできるようですが、他にも /system 以下を触りたかったので今回は root 前提で進めます。

    キー配列に関係するファイルはいくつかあるようです。今回はキーコードに対するキーの割当て(keylayout)と、同時押しのキー修飾に対する割当て(keychars)の 2 つを設定します。それぞれ次のファイルを書き換えます。

    • /system/usr/keylayout/Generic.kl
    • /system/usr/keychars/Generic.kcm

    目標とするのは次の画像のような配列。M の横に句読点が並び、最下段にカーソルキーが並ぶ配列。

    /system/usr/keylayout/Generic.kl

    ES テキストエディタで次の 6 行をさくっと書き換えてしまいます。

    key 40    BACKSLASH
    key 51    COMMA
    key 52    PERIOD
    key 103   DPAD_UP
    key 105   DPAD_LEFT
    key 108   DPAD_DOWN

    この 6 行を次のように書き換えます。変更しない途中の行は飛ばしています。間違えて消さないように。

    key 40    MINUS
    key 51    DPAD_LEFT
    key 52    COMMA
    key 103   PERIOD
    key 105   DPAD_DOWN
    key 108   DPAD_UP

    /system/usr/keychars/Generic.kcm

    \ を – に変更したので Fn + \ を押しても : が出せなくなります。新たに Fn + – に : を割り当てます。”key MINUS” を探して、次の一行を追加します。

    fn: ':'

    ここまで完了したら再起動して終了です。快適なキー配列を楽しみましょう。

    参考

  • Gemini PDA の root 取得 (Windows 10 環境)

    Gemini PDA の root 取得 (Windows 10 環境)

    ※ 2018-06-27 userdata について追記しました

    句読点のキー配列が何とも慣れないので、キー配列変更のために早速 root を取得することにしました。環境は Windows 10 Pro 64 ビット版です。

    基本的には Planete Computers 公式にすべて載っていますが、日本語の情報も先人が提供してくれています。

    FlashTool の取得

    Download and Install FlashTool on Windows から、以下のファイルをダウンロードします。

    • FlashToolWindows.zip
    • FlashToolDrivers.zip

    FlashToolDrivers.zip を展開して、Install.bat を実行します。

    Gemini PDA FlashToolDrivers.zip Gemini PDA FlashToolDrivers.zip

    UAC の昇格を求められるので、許可すると 64 ビット版のドライバをインストールしてくれたようです。

    Gemini PDA FlashToolDrivers Install.bat Gemini PDA FlashToolDrivers Install.bat

    ファームウェアのダウンロード

    次のファームウェアのイメージをダウンロードします。初期の x25 かセカンドロットの x27 でダウンロードするファイルが異なるので注意してください。

    今回は Gemini_x27_10052018.zip を使って進めます。ダウンロードすると約 3.15 GB ありました。ダウンロードしたら展開しますが、展開後に約 6.92 GB まで膨れたので展開先の空きにご注意ください。展開した場所を FlashTool で開くので、パスをおぼえておいてください。

    NVRAM パーティションのバックアップ

    公式には次のような記述があるので、素直に従って NVRAM パーティションのバックアップを先に取っておきます。

    Before flashing the device with a different firmware it is a good idea to backup the current NVRAM partition. This partition stores key information for your Gemini, including the IMEI number. If it gets lost or damaged, your Gemini will not be able to take or receive calls.

    FlashToolWindows.zip を展開して、flash_tool.exe を実行します。Readback タブを開き、Add を一度押します。

    FlashTool NVRAM Backup FlashTool NVRAM Backup

    Readback を押したら、Gemini PDA を PC に接続します。初めて接続するとドライバのインストールが始まるので、次の表示が出るまでしばらく我慢します。

    Gemini PDA ドライバインストール完了 Gemini PDA ドライバインストール完了

    次に Gemini PDA を再起動します。再起動するのですが……本体横の銀色ボタンで電源オプションが出ると思ったら出なくて、どこで再起動するか本気でわからずに 10 分ほど四苦八苦。答えは Fn + Esc Esc を長押しでした。

    再起動すると自動的に FlashTool が NVRAM のバックアップを取ってくれます。完了すると次のような画面が表示されます。

    NVRAM バックアップ完了 NVRAM バックアップ完了

    完了したら一度ケーブルを抜いて Esc を長押しして Gemini PDA を起動しておきます(追記: この手順はもしかしたらケーブルの挿し直しで省略できるかも?未検証)。

    ファームウェアの書き込み

    端末が初期化されるので必ずデータのバックアップを取ってから行ってください。 userdata のチェックを外せばアプリやデータを残せます。

    いよいよ root 化を行います。FlashTool の Download タブを開き、2 行目の Scatter-loading File に展開したファームウェアに含まれる Gemini_Android_Rooted.txt を指定します。3 行目のドロップダウンから Firmware Upgrade を選びます。

    アプリやユーザーデータを残したい場合は、一覧から userdata のチェックを外します。

    FlashTool root化
    FlashTool root化

    用意ができたら Download を押し、Gemini PDA を接続して再起動します。自動で書き込みが始まるのでしばらく待ちます。

    FlashTool root化 書込中
    FlashTool root化 書込中

    完了すると大きなチェックマークが出ます。ケーブルを抜いて Fn + Esc 長押しで Gemini PDA を起動します。

    Magisk Manager のインストール

    Gemini PDA が起動したら、公式の案内通りに Magisk Manager をインストールします。お約束ですが、設定のセキュリティから「提供元不明のアプリ」を許可しておいてください。

    パッケージをインストールしたら起動します。一番下から 2 番目に「インストール」があるので実行します。

    Magisk Manager インストール Magisk Manager インストール1
    Magisk Manager インストール2 Magisk Manager インストール2

    「方法の選択」は「直接インストール(推奨)」を選びます。

    Magisk Manager インストール3 Magisk Manager インストール3
    Magisk Manager インストール4 Magisk Manager インストール4

    インストールが終ったら「再起動」します。

    root 化確認

    root 権限を要求するようなアプリで確認します。Termux で su してみたところ、無事に昇格できました。

    Termux で su - root 化の確認 Termux で su – root 化の確認
  • Python 3 と ReportLab で PDF ファイルを生成する

    Python から PDF ファイルを作成できる ReportLab を試してみます。

    インストール

    ReportLab のインストールは pip で一発ですが

    $ python -V
    Python 3.6.5
    $ pip install reportlab
    ...<省略>...
     The headers or library files could not be found for jpeg,
     a required dependency when compiling Pillow from source.

    と Pillow のインストール時に JPEG のヘッダ類がないと怒られるので、libjpeg-dev を apt でインストールしておきます。

    $ sudo apt install libjpeg-dev
    $ pip install reportlab
    Collecting reportlab
     Using cached https://files.pythonhosted.org/packages/87/f9/53b34c58d3735a6df7d5c542bf4de60d699cfa6035e113ca08b3ecdcca3f/reportlab-3.4.0.tar.gz
    Collecting pillow>=2.4.0 (from reportlab)
     Using cached https://files.pythonhosted.org/packages/89/b8/2f49bf71cbd0e9485bb36f72d438421b69b7356180695ae10bd4fd3066f5/Pillow-5.1.0.tar.gz
    Requirement already satisfied: pip>=1.4.1 in /home/pi/.pyenv/versions/3.6.5/lib/python3.6/site-packages (from reportlab) (10.0.1)
    Requirement already satisfied: setuptools>=2.2 in /home/pi/.pyenv/versions/3.6.5/lib/python3.6/site-packages (from reportlab) (39.0.1)
    Installing collected packages: pillow, reportlab
     Running setup.py install for pillow ... done
     Running setup.py install for reportlab ... done
    Successfully installed pillow-5.1.0 reportlab-3.4.0

    PDF を生成する

    公式のユーザガイド(PDF)にあるサンプルコードを実行してみます。

    from reportlab.pdfgen import canvas
    
    def hello(c):
        c.drawString(100,100,"Hello World")
    
    c = canvas.Canvas("hello.pdf")
    hello(c)
    c.showPage()
    c.save()

    hello.pdf が生成されているはずなので開いて確認します。

    ReportLab PDF hello
    ReportLab PDF hello

    簡単ですね。ただし、このままだと日本語を出力しようとしても四角(■)で埋められるだけでした。エンコーディングも関係ないようだったのでフォントを指定する必要があるようです。

    座標系はページ左下を起点としています。先の例では左下から (100, 100) の座標に文字列を描画しています。

    日本語フォント

    こちらを参考にしました。

    ReportLab には予め HeiseiMin-W3, HeiseiKakuGo-W5 が用意されているそうです。ユーザガイドの 3.6 Asian Font Support に Asian Language Packs として記載されていますね。

    Japanese, Traditional Chinese (Taiwan/Hong Kong), Simplified Chinese (mainland China) and Korean are all supported and our software knows about the following fonts:

    • chs = Chinese Simplified (mainland): ‘STSong-Light’
    • cht = Chinese Traditional (Taiwan): ‘MSung-Light’, ‘MHei-Medium’
    • kor = Korean: ‘HYSMyeongJoStd-Medium’,’HYGothic-Medium’
    • jpn = Japanese: ‘HeiseiMin-W3’, ‘HeiseiKakuGo-W5’

    これらを参考に日本語を入れてみたのが次のソースになります。ファイルは UTF-8n で保存しています。

    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4, portrait
    from reportlab.pdfbase import pdfmetrics
    from reportlab.pdfbase.cidfonts import UnicodeCIDFont
    
    c = canvas.Canvas('sample.pdf', pagesize=portrait(A4))
    
    pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))
    
    c.setFont('HeiseiKakuGo-W5', 24)
    w, h = A4
    c.drawCentredString(w / 2, h / 2, '日本語PDFのサンプルです。')
    
    c.showPage()
    c.save()
    ReportLab PDF 日本語
    ReportLab PDF 日本語

    特に問題なく表示されていますね。

    TrueType フォント

    この他にも自分で TrueType フォントを用意して指定することもできます。試しに Windows 10 の游ゴシックを使ってみました。

    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4, portrait
    from reportlab.pdfbase import pdfmetrics
    from reportlab.pdfbase.ttfonts import TTFont
    
    c = canvas.Canvas('sample.pdf', pagesize=portrait(A4))
    
    pdfmetrics.registerFont(TTFont('Yu Gothic Light', 'YuGothL.ttc'))
    
    c.setFont('Yu Gothic Light', 24)
    w, h = A4
    c.drawCentredString(w / 2, h / 2, '日本語PDFのサンプルです。')
    
    c.showPage()
    c.save()
    ReportLab PDF 日本語TTF
    ReportLab PDF 日本語TTF

    ばっちりです。*.ttf だけでなく *.ttc でも大丈夫でした。

    描画メソッド

    直線

    • canvas.line(x1, y1, x2, y2)
    • canvas.lines(linelist)

    図形

    • canvas.grid(xlist, ylist)
    • canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
    • canvas.arc(x1, y1, x2, y2)
    • canvas.rect(x, y, width, height, stroke=1, fill=0)
    • canvas.ellipse(x1, y1, x2, y2, stroke=1, fill=0)
    • canvas.wedge(x1, y1, x2, y2, startAng, extent, stroke=1, fill=0)
    • canvas.circle(x_cen, y_cen, r, stroke=1, fill=0)
    • canvas.roundRect(x, y, width, height, radius, stroke=1, fill=0)

    一行文字列(改行なし文字列)

    • canvas.drawString(x, y, text)
    • canvas.drawRightString(x, y, text)
    • canvas.drawCentredString(x, y, text)

    テキストオブジェクト

    ユーザガイドには次のように記載されています。

    For the dedicated presentation of text in a PDF document, use a text object. The text object interface provides detailed control of text layout parameters not available directly at the canvas level. In addition, it results in smaller PDF that will render faster than many separate calls to the drawString methods.

    テキストが多いなら、一回一回 drawString() を呼び出すよりもこちらを使えってことらしいです。が、いざ使ってみると canvas に beginText() なんてメソッドはないと言われて使えず。

    textobject = canvas.beginText(x, y)
    canvas.drawText(textobject)
    • textobject.setTextOrigin(x, y)
    • textobject.setTextTransform(a, b, c, d, e, f)
    • textobject.moveCursor(dx, dy) # from start of current LINE
    • (x, y) = textobject.getCursor()
    • x = textobject.getX(); y = textobject.getY()
    • textobject.setFont(psfontname, size, leading = None)
    • textobject.textOut(text)
    • textobject.textLine(text=”)
    • textobject.textLines(stuff, trim=1)

    着色

    • canvas.setFillColorCMYK(c, m, y, k)
    • canvas.setStrikeColorCMYK(c, m, y, k)
    • canvas.setFillColorRGB(r, g, b)
    • canvas.setStrokeColorRGB(r, g, b)
    • canvas.setFillColor(acolor)
    • canvas.setStrokeColor(acolor)
    • canvas.setFillGray(gray)
    • canvas.setStrokeGray(gray)

    フォント

    • canvas.setFont(psfontname, size, leading = None)

    Platypus

    ここまでは低水準(low-level)の描画メソッドばかりでした。これらを駆使して複雑な文書を作れないこともないですが、大変な労力を要します。例えば改行や改頁といった処理も自分で行わないといけません。

    ReportLab には Platypus というテンプレートエンジンが用意されいて、一般的な文書の構成であればわりと簡単にページの構成を作ることができます。

    次はユーザガイドに載っている Platypus の最も簡単なサンプルを少しだけ整形したものです。

    from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
    from reportlab.lib.styles import getSampleStyleSheet
    from reportlab.rl_config import defaultPageSize
    from reportlab.lib.units import inch
    PAGE_HEIGHT=defaultPageSize[1]; PAGE_WIDTH=defaultPageSize[0]
    styles = getSampleStyleSheet()
    
    Title = "Hello world"
    pageinfo = "platypus example"
    def myFirstPage(canvas, doc):
        canvas.saveState()
        canvas.setFont('Times-Bold',16)
        canvas.drawCentredString(PAGE_WIDTH / 2.0, PAGE_HEIGHT - 108, Title)
        canvas.setFont('Times-Roman',9)
        canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
        canvas.restoreState()
    
    def myLaterPages(canvas, doc):
        canvas.saveState()
        canvas.setFont('Times-Roman',9)
        canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo))
        canvas.restoreState()
    
    doc = SimpleDocTemplate("phello.pdf")
    Story = [Spacer(1, 2 * inch)]
    style = styles["Normal"]
    for i in range(100):
        bogustext = ("This is Paragraph number %s. " % i) * 20
        p = Paragraph(bogustext, style)
        Story.append(p)
        Story.append(Spacer(1, 0.2 * inch))
    doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)

    これによって生成された phello.pdf を表示すると、次のようになっています。

    ReportLab Platypus Sample 1/2
    ReportLab Platypus Sample 1/2
    ReportLab Platypus Sample 2/2
    ReportLab Platypus Sample 2/2

    <執筆中>

    参考文献

  • Debian 上の Python 3.6.5 で pyodbc を使う

    $ python -V
    Python 3.6.5
    $ sudo apt install unixodbc-dev
    $ pip install pyodbc

    unixodbc-dev を入れておかないと pip でのインストール中にこけます。

  • Python 3 + pyodbc + unixODBC + FreeTDS の日本語でハマる

    TL;DR

    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') するだけです。これで無事に日本語のテーブル名、列名を処理できました。

    たった一文のことですが、情報量が少ないとこれに辿りついて気づくまでが本当に大変です。