ZenFone 4 Maxを購入した

普段使いのスマートフォンはNexus4でしたが、バッテリーの保ちが悪くなってきたのと、ストレージが足りなくて厳しくなっていたので、機種変更を検討しました。

Nexus4は2013年に1台目を購入し、フロントパネルを何度も修理したりして使ったあと、2年前ぐらいに中古で2台目を購入して使ってました。

今回は予算は3万円ぐらいでバッテリーの保ちが良い端末を探し、ZenFone 4 Max(ZC520KL)に決めました。ヨドバシカメラで2万6千円ぐらい。ポイント使い切って支払は2万円。

f:id:nullpobug:20180511173802j:plain

ASUSZenPadを持ってるので、操作面の違和感はさほどなし。画面サイズが5.2インチで、Nexus4より一回りでかい。ポケットに入れるにはそろそろつらいかな。

アプリ操作中に一瞬画面が暗転したり、ポップアップを閉じる動作のときにチラツキのようなものがあって、少し気になる。

この端末を最低でも3年ぐらいは使いたい。

DjangoフレームワークとVue.js (Vuex)を使ってアプリケーションを作る

以前作成した DjangoフレームワークとVue.jsを使ってアプリケーションを作る - 偏った言語信者の垂れ流し の、Vuex対応版です。

よかったらGitHubでStarをつけてください! github.com controllerとしていた部分を、VuexのStoreに書き換え、Vueコンポーネントも同様にVuexのコードに書き換えました。

どのぐらいの粒度でstateを作成するのか、UI側の表示制御(モーダル表示など)とどう組み合わせるのかなど、参考になれば幸いです。

NGINX UnitでDjangoアプリケーションを動かしてみる

NGINX Unitは、NGINXの開発元が作ってるアプリケーションサーバー。

http://unit.nginx.org/

Pythonのアプリケーションも動かせるとのことなので、Djangoアプリで試してた。

試した環境は、Vagrant上でUbuntu 16.04 LTS、Python 3.6.2(deadsnakes PPA)、Django 2.0.4、NGINX UnitはGithubのmaster(749f7c0)

インストール

ドキュメント通りだとPython3.6用のモジュールが提供されないので、ビルドしてインストールした。

python3.6やpython3.6-develなどはdeadsnakes PPAからインストール済み。

インストール先は、 /opt/unit/ としました。

git clone https://github.com/nginx/unit.git
cd unit
./configure
./configure python --config=python3.6-config
make all
sudo make install DESTDIR=/opt/unit/

※参考にしたページには、virtualenvに対応するためにパッチを当てる旨の記載があったが、masterで問題が修正されたので特に変更なしで動いた。

unitdの起動

オプション指定なしだと起動時のカレントディレクトリにログファイル unit.log が作成される。また、カレントディレクトリから相対パスで拡張モジュールを探しにいくので、 --module で指定したほうがよさそう。

今回はUbuntuへのログイン時のユーザー vagrant で起動してみる。

cd /home/vagrant/
/opt/unit/sbin/unitd --modules /opt/unit/build/

これで、unitdが起動し、API経由で設定を投入できる状態になった。

Djangoのアプリケーションを用意する

「Hello, World!」と表示するだけなので手抜きで。

cd /home/vagrant/
python3.6 -m venv venv
. venv/bin/activate
django-admin startproject myproject

myproject/myproject/settings.py(変更部分):

ALLOWED_HOSTS = ['*']  # ホスト側のブラウザから確認したかったので設定

myproject/myproject/urls.py:

from django.urls import path
from django.http import HttpResponse

def index(request):
    return HttpResponse('Hello, World!')

urlpatterns = [
    path('', index),
]

設定の投入

curlで投入してみるので、設定をファイルで用意する。

myproject.json:

{
    "listeners": {
        "*:8000": {
            "application": "myproject"
        }
    },
    "applications": {
        "myproject": {
            "type": "python 3.6",
            "processes": 1,
            "path": "/home/vagrant/myproject/",
            "home": "/home/vagrant/venv/",
            "module": "myproject.wsgi"
        }
    }
}

home キーの値にvenvのディレクトリを指定するが、末尾にスラッシュがないとエラーになった。ソースを確認したところ、よしなにしてくれない。

設定の投入が問題なければ、レスポンスは正常終了の旨が返される。

$ curl -X PUT -d @./myproject.json --unix-socket ./control.unit.sock 'http://localahost/'
{
        "success": "Reconfiguration done."
}

Webブラウザで 8000 ポートを確認し、「Hello, World!」と表示されればOK。

感想

  • NGINX Unitは動的に設定を投入できることが特徴ということだが、現状ではそれ以外にあまり利点がないので、gunicornやuWSGIで事足りる場合は、そちらを使ったほうが良さそう。
    • 無停止でワーカープロセス数を増やすぐらいならgunicornやuWSGIでも可能
  • 1つのホストでUnit経由で複数のアプリケーションをホストするにしても、リソース制御などがほしくなるから、Dockerか何かコンテナに対応してないと使いづらいか...
    • その用途なら今だとdocker-composeで事足りそうな気も。
    • クラスタ化して使えるようになったとしても、それはk8sでよいような気もするし..
  • NGINX Controllerで制御する自前のプロダクトということでUnitを作っているのかな?
  • HTTP/2のルーティングに対応するなら、NGINXとの組み合わせでオーバーヘッドが減るかも?
  • アプリケーションのプロセスは、killしても自動で再起動されてた

参考

Django REST FrameworkのフィールドでJSONを扱う

DjangoのモデルにTextFieldでJSON文字列を保存しておき、APIDjango REST Frameworkで提供する例。

JSONデータを保存して、読み込み時にもJSON形式のまま提供したかった。

restframeworkのシリアライザのFieldクラスを継承し、JSONを扱うフィールドを定義して使う。

コード

app1/models.py:

from django.db import models


class Model1(models.Model):
    class Meta:
        ordering = ['pk']

    data = models.TextField("JSONデータ", null=True, blank=True)

app1/serializers.py:

import json
from rest_framework import serializers

from .models import Model1


class JSONDataField(serializers.Field):
    """JSONテキストのシリアライザ,デシリアライザフィールド
    """
    def to_representation(self, obj):
        if obj:
            return json.loads(obj)
        return None

    def to_internal_value(self, data):
        return json.dumps(data, indent=2)


class Model1Serializer(serializers.ModelSerializer):
    data = JSONDataField()

    class Meta:
        model = Model1
        fields = [
            'id', 'data'
        ]

app1/views.py:

import coreapi
import coreschema
from rest_framework import viewsets
from rest_framework.schemas import AutoSchema

from .serializers import Model1Serializer
from .models import Model1


class Model1Schema(AutoSchema):
    def get_manual_fields(self, path, method):
        # 作成, 更新だけdataフィールドの定義を差し込む
        if method not in ['POST', 'PUT', 'PATCH']:
            return []
        return [
            coreapi.Field(
                "data",
                required=False,
                location="form",
                schema=coreschema.Object()
            )
        ]


class Model1ViewSet(viewsets.ModelViewSet):
    serializer_class = Model1Serializer
    queryset = Model1.objects.all()
    schema = Model1Schema()

REST Frameworkで自動生成されるAPIドキュメントで扱えるように、schema定義を入れています。

app1/urls.py:

from django.urls import path

from rest_framework.documentation import include_docs_urls
from rest_framework.routers import DefaultRouter

from . import views

router = DefaultRouter()
router.register(r'model1', views.Model1ViewSet, base_name='model1')

urlpatterns = [
    path('docs/', include_docs_urls(title='app1 API', public=True)),
] + router.urls

サンプルコード一式

動作例

list: f:id:nullpobug:20180408175330p:plain

create: f:id:nullpobug:20180408175335p:plain

PythonでUNIXドメインソケットを使ったHTTPサーバーとクライアントを作る

Pythonでプロセス間通信をしたい、プロトコルはHTTPで、データフォーマットはJSON。なるべく1つのスクリプトファイルでサーバーやクライアントを提供したい。

こんな感じの要件。

元々Flaskが使われてるプロジェクトだったので、Flaskで全部やればいいかーと思ってました。

しかし、WerkzeugがUNIXドメインソケットに対応していなかったので、Waitressを使うことにしました。

クライアント側はRequrestsを使おうとしたけど、そちらも追加のコードが必要でした。

これはrequests-unixsocketという都合のいいパッケージがあったので使いました。

Pythonのバージョンは3.6。

ソースコード

requirements.txt

flask
waitress
requests
requests-unixsocket

server.py

from waitress import serve
from flask import Flask, jsonify

app = Flask(__name__)


@app.route('/endpoint/')
def index():
    return jsonify({'result': 'OK'})


def main():
    serve(app, unix_socket='/tmp/app.sock')


if __name__ == '__main__':
    main()

client.py

import requests_unixsocket


def main():
    session = requests_unixsocket.Session()
    resp = session.get('http+unix://%2ftmp%2fapp.sock/endpoint/')
    print(resp.json())


if __name__ == '__main__':
    main()

実行結果

server

(venv) $ python server.py
Serving on http://unix:/tmp/app.sock

client

(venv) $ python client.py
{'result': 'OK'}

便利ですね。

『エキスパートPythonプログラミング 改訂2版』を読みました

『エキスパートPythonプログラミング 改訂2版』のレビューのお手伝いをしたので見本誌をいただきました。

エキスパートPythonプログラミング改訂2版

エキスパートPythonプログラミング改訂2版

雑感

  • 対象はPythonにある程度慣れてる人向け
  • Pythonを使いこなすための知識がいろいろ
    • クラスやデータ構造をうまく使うとか
    • 並行処理とか

本書で学べるPythonの内容

  • Pythonでのクラス、オブジェクト指向プログラミング
  • どのようにPythonモジュールを構成していくか、パッケージを作るか
  • C言語などでのPythonモジュール作成方法
  • パフォーマンスチューニングの方法
  • Pythonでの並行処理、async、await構文

など。Pythonの入門的な解説はなし。

まとめ

本書は入門書ではありません。Pythonをより使いこなすための知識を得られる内容になっています。

Pythonにある程度慣れてきた人に、次の一歩としてオススメしたい本です。

『いちばんやさしいPythonの教本』を読みました

PyQの関連で献本いただいたので『いちばんやさしいPythonの教本』を読みました。前職の同僚が執筆した本です。

いちばんやさしいPythonの教本 人気講師が教える基礎からサーバサイド開発まで (「いちばんやさしい教本」シリーズ)

いちばんやさしいPythonの教本 人気講師が教える基礎からサーバサイド開発まで (「いちばんやさしい教本」シリーズ)

雑感

  • 用語説明が丁寧
  • 図版が多くてわかりやすい
    • 注釈も多いですが、読みづらくならないよう、レイアウトされていて良い

「いちばんやさしい」とタイトルにあるとおり、プログラミングの知識なしで読み始められるような内容になっていました。

本書で学べるPythonの内容

  • Pythonのインストール、開発環境の整備
  • Pythonの文法
    • クラスは含まない
  • Pythonスクリプトの作り方
    • 複数のモジュールにまたがるプログラムの作成
  • サードパーティ製モジュールの使い方
  • Webアプリケーション作成

クラスやオブジェクト指向プログラミングの解説はされていません。 入門書だし、ボリュームの都合上、入り切らないでしょうし、仕方ないかと思います。

本書で入門し、足りなければ他の書籍も読んでみるのが良いですね。 本書のChapter10にも他の本の紹介や、勉強の仕方が説明されています。

まとめ

図版や注釈が多く、見やすくレイアウトされているため、はじめてプログラミングする人でも読みやすい内容になっています。

「いちばんやさしい」にふさわしいPythonの本ですね。