Python: Flask: HTTP GET メソッドのクエリパラメータに真偽値を使いたい

うまい例をでっち上げられないので、実際のユースケースで説明する。

自宅のサーバで Flask を使った書籍管理の web アプリを運用してるんだけど(ソースコードは GitHub で公開してある)、その web アプリには JSON を返す API が定義してある。で、API のひとつ /api/books/ は書籍情報の一覧を返し、クエリパラメータとして offset と limit を取ることができる。

Flask では request.args.get でクエリパラメータの値を取得するついでに、値の型とデフォルト値を指定することができる。↓こんな感じ。

@app.route('/api/books/')
def api_books():
    offset = request.args.get('offset', default=0, type=int)
    limit = request.args.get('limit', default=100, type=int)
    ....

さて、この書籍管理アプリには「書籍を削除する」という機能がなくて、かわりに書籍を捨てた時には、データベース上の disposed カラムに True をセットして、web アプリ上では表示しないようになっている。だけど上記の API ではそこのところを考慮してなかったので、返ってくる JSON には捨てた書籍も含まれている、という状況だった。

ここからが本題。

API の返す JSON に、デフォルトでは捨てた書籍(つまり disposed=True)は含まず、クエリで include_disposed に真値をセットした時にだけ含むようにしたい。

最初の方法

上に示したコートでは、クエリパラメータの値を int に変換して取得しているんだから、同様に bool を指定してやればいいと思った。こういうふうに。

@app.route('/api/books/')
def api_books():
    offset = request.args.get('offset', default=0, type=int)
    limit = request.args.get('limit', default=100, type=int)
    include_disposed = request.args.get('include_disposed', default=False, type=bool)
    ....

開発用のサーバはエラーもなく立ち上がり、/aip/books/ にアクセスすると捨てた書籍が含まれていない JSON が、/api/books/?include_disposed=True にアクセスすると捨てた書籍も含まれた JSON が返ってきた。期待通りだ。

ところが、試しに /api/books/?include_disposed=False にアクセスしてみると、捨てた書籍も含まれた JSON が返ってきた。これは期待する動作と違う。

原因は追求していないけど、想像するに、クエリの値として渡ってきた文字列を bool(文字列) してるだけなんじゃなかろうか。だとすると、クエリで include_disposed の値がなんであるかにかかわらず、空文字列でない限りは、変数 include_disposed の値は True になるわけだ。

なんてこった。期待させやがって!

解決編

しかたがないので文字列を真偽値に変換する関数を書いた。一般に真を表すであろう文字列(true、 yes、 on、大文字小文字を問わない)の時だけ True を返し、ほかは False を返す。

def str_to_bool(s):
    p = re.compile(r'(true|yes|on)\Z', re.IGNORECASE)
    if p.match(s):
        return True
    else:
        return False

で、クエリの値を変数に代入するところはこうした。

    include_disposed = str_to_bool(request.args.get('include_disposed', default=''))

request.args.get の返す値を str_to_bool で変換している。default=” を指定しているのは、指定しないとクエリに include_disposed がなかった時に None が返ってくるため。

さて、これで期待通りの動作をするようになった。

CentOS 8: NginxでCGIを利用できるようにする

以前 Ubuntu ではやったけど、今度は CentOS で。

fcgiwrap のインストール

標準のリポジトリには無いようなので EPEL からインストール。

[takatoh@rollo ~]$ sudo dnf -y install epel-release
[takatoh@rollo ~]$ sudo dnf --enablerep=epel -y install fcgiwrap

Nginx の設定

CGI を利用したいサイトの設定ファイルの server セクションにつぎを追加。

location ~ \.cgi$ {
        root  /var/www/app;
        fastcgi_pass  unix:/var/run/fcgiwrap.socket;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        include  /etc/nginx/fastcgi_params;
}

で、Nginx をリスタート。

[takatoh@rollo ~]$ sudo systemctl restart nginx

fcgiwrap 用の設定ファイルを作成して起動。

/usr/lib/systemd/system/fcgiwrap.service

[Unit]
Description=Simple CGI Server
After=nss-user-lookup.target
Requires=fcgiwrap.socket

[Service]
EnvironmentFile=/etc/sysconfig/fcgiwrap
ExecStart=/usr/sbin/fcgiwrap ${DAEMON_OPTS} -c ${DAEMON_PROCS}
User=nginx
Group=root

[Install]
Also=fcgiwrap.socket

/usr/lib/systemd/system/fcgiwrap.socket

[Unit]
Description=fcgiwrap socket

[Socket]
ListenStream=/run/fcgiwrap.socket

[Install]
WantedBy=sockets.target

起動。

[takatoh@rollo system]$ sudo systemctl enable --now fcgiwrap

SELinux

SELinux を有効にしているので、ポリシーを作る。

nginx-server.te

module nginx-server 1.0;

require {
    type httpd_t;
    type var_run_t;
    class sock_file write;
}

#======== httpd_t ========
allow httpd_t var_run_t:sock_file write;
[takatoh@rollo ~]$ sudo checkmodule -m -M -o nginx-server.mod nginx-server.te
[takatoh@rollo ~]$ sudo semodule_package --outfile nginx-server.pp --module nginx-server.mod
[takatoh@rollo ~]$ sudo semodule -i nginx-server.pp

CGI のテスト

設定したディレクトリ(今回は /var/www/app)の中に、hello.cgi を作成。

#!/usr/bin/env ruby

print "Content-type: text/html\n\n"
print <<EOC
<html>
  <body>
    <h1>Hello</h1>
    <p>Hello, from CGI!</p>
  </body>
</html>
EOC

実行権限をつけるのを忘れずに。

今回はスマホから確認した。OKだった。

UbuntuでOneDriveをつかう

下準備

必要なパッケージをインストールする。

takatoh@apostrophe $ sudo apt install libcurl4-openssl-dev libsqlite3-dev gdebi
takatoh@apostrophe $ wget http://downloads.dlang.org/releases/2020/dmd_2.090.1-0_amd64.deb
takatoh@apostrophe $ sudo gdebi dmd_2.090.1-0_amd64.deb

OneDrive Client のインストール

takatoh@apostrophe $ git clone [email protected]:skilion/onedrive.git
takatoh@apostrophe $ cd onedrive
takatoh@apostrophe $ make
takatoh@apostrophe $ sudo make install

設定ファイルのコピー

takatoh@apostrophe $ mkdir -p ~/.config/onedrive
takatoh@apostrophe $ cp ./config ~/.config/onedrive/config

デフォルトの設定ファイルはつぎのようになっている。

# Directory where the files will be synced
sync_dir = "~/OneDrive"
# Skip files and directories that match this pattern
skip_file = ".*|~*"

サービスの起動

takatoh@apostrophe $ systemctl --user enable onedrive
takatoh@apostrophe $ systemctl --user start onedrive

初回起動

サービスとして起動したら、onedrive コマンドを実行。

takatoh@apostrophe $ onedrive

すると、端末にメッセージが表示される。「Authorize this app visiting:」につづいて URL が表示されるので、これをコピーしてブラウザでアクセスし、Microsoftアカウントにログインする。アプリから OneDrive へのアクセスを許可するかと聞かれるので「はい」を選択。新しい、空白のページが表示されるので、このページの URL をコピーして端末に戻り、「Enter the response uri:」のあとに貼り付けてエンター。これでファイルの同期が始まる。

自動起動の設定

ファイルの同期を続けるためには、onedrive コマンドを実行しておく必要があるらしい。Ubuntu にログインするときに自動的に実行するように設定する。

Alt+F2 キーを押してアクティビティ画面を表示し、「gnome-session-properties」を検索。出てきたアイコンをクリックすると「自動的に起動するプログラムの追加」ウィンドウが表示される。ここに onedrive -m コマンドを追加する。

参考ページ

 cf. UbuntuでOneDriveを使う – Qiita