くろねこ日記

ソフトウェアに関する技術メモが多いです.

MongoEngineつかってみた

はじめに

前回MongoDBをつかってみたという記事を載せました.その記事ではpyMongoを利用したMongoDBの操作を紹介しましたが,今日はPython上でMongoDBをOSMとして扱うことのできるMongoEngineの紹介をします.

MongoEngineインストール

インストールは簡単

pip install mongoengine

のみです.

使い方

学生情報を扱うデータベースを例に話を進めていきます. まずは,ドキュメントの追加と参照について説明します

追加と参照

  • データベース名:studentinfo

  • クラス名: Student(学生情報を扱う)

    • 要素:Name(学生名)

    • 要素:Number(学生番号)

import mongoengine

mongoengine.connect("studentinfo")

class Student(mongoengine.Document):
    Name = mongoengine.StringField()
    Number = mongoengine.IntField()

#DBの初期化を行なう(ブログの内容には直接関係しない)
Student.drop_collection()

db=Student()
db.Name="Taro"
db.Number=1
db.save()

for data in Student.objects:
    print data.Name
    print data.Number

出力だけならこれだけです.

もし特定のデータ(Number1にしましょう)だけ取りだしたいなら

import mongoengine

mongoengine.connect("studentinfo")

class Student(mongoengine.Document):
    Name = mongoengine.StringField()
    Number = mongoengine.IntField()

#DBの初期化を行なう(ブログの内容には直接関係しない)
Student.drop_collection()

db=Student()
db.Name="Taro"
db.Number=1
db.save()

for data in Student.objects(Number=1):
    print data.Name
    print data.Number

のようにすればNumber1のデータだけが表示されます

親子関係にあるデータベース

では次はMongoEngineで親子関係にあるデータベースを組んでみましょう. 例題として先程のStudentの加えて試験の結果を子とします.

  • データベース名:studentinfo

  • クラス名:Student

    • 要素:Name(学生名)

    • 要素:Number(学生番号)

  • クラス名:Math

    • 要素:score(点数)
  • クラス名:Science

    • 要素:score(点数)
  • クラス名:English

    • 要素:score(点数)

とします.

この場合はStudentから各科目(Math,Science,English)を呼べるようにします.

import mongoengine

mongoengine.connect("studentinfo")

class Math(mongoengine.Document):
    score = mongoengine.IntField()

class English(mongoengine.Document):
    score = mongoengine.IntField()

class Student(mongoengine.Document):
    name = mongoengine.StringField()
    math = mongoengine.ReferenceField("Math")
    english = mongoengine.ReferenceField("English")

こうします.実際にStudentを経由してMathやEnglishのデータを書き込むには 次のようにします.ここでは[学生名taro,数学100点,英語50点]というデータと[学生名jiro,数学70点,英語80点]として書いていきます

taro = Student()
math = Math()
english = English()
math.score=100
math.save()
english.score=50
english.save()
taro.name="taro"
taro.math=math
taro.english=english
taro.save()

jiro = Student()
math = Math()
english = English()
math.score=70
math.save()
english.score=80
english.save()
jiro.name="jiro"
jiro.math=math
jiro.english=english
jiro.save()

とします.mathとenglishそれぞれのインスタンスに各点数を代入したのちstudentのクラス変数に渡してますね.

値を取り出すには

for student in Student.objects:
     print student.name
     print student.math.score
     print student.english.score

のようにします. おそらく

taro
100
50
jiro
70
80

と出力されるはずです.

もし複数件入力されており,名前が"taro"だけのデータが欲しいときには

for student in Student.objects(name="taro"):
     print student.name
     print student.math.score
     print student.english.score     

結果

taro
100
50

のようにobjectsにname="taro"のように指定するだけです.

ここの部分を一つのコードにまとめると

import mongoengine

mongoengine.connect("studentinfo")

#クラスの定義
class Math(mongoengine.Document):
    score = mongoengine.IntField()

class English(mongoengine.Document):
    score = mongoengine.IntField()

class Student(mongoengine.Document):
    name = mongoengine.StringField()
    math = mongoengine.ReferenceField("Math")
    english = mongoengine.ReferenceField("English")

#DBの初期化を行なう(ブログの内容には直接関係しない)
Student.drop_collection()

#taroとjiroのドキュメントを追加
taro = Student()
math = Math()
english = English()
math.score=100
math.save()
english.score=50
english.save()
taro.name="taro"
taro.math=math
taro.english=english
taro.save()
jiro = Student()
math = Math()
english = English()
math.score=70
math.save()
english.score=80
english.save()
jiro.name="jiro"
jiro.math=math
jiro.english=english
jiro.save()

#Studentクラスに書かれているドキュメントをすべて取得
for student in Student.objects:
     print student.name
     print student.math.score
     print student.english.score

#taroのドキュメントのみ取得
for student in Student.objects(name="taro"):
     print student.name
     print student.math.score
     print student.english.score

上書き

次に既存のデータを上書きしたときについてですが,上記のjiroをsaburoに変更してみましょう.

jiro = Student.objects(name="jiro")
jiro.update(set__name="saburo")

のようにobjectsでデータを抜きだしてからupdate文で更新します. 更新の際にはset__のようにqueryを指定することができます.set__ query以外にもpush__ , unset__ , pull__などがあります 詳しくはこちらをお読みください(http://mongoengine-odm.readthedocs.org/guide/querying.html) .

まとめ

MongoEngineでデータの追加や参照,更新についてまとめました.

MongoDBつかってみた

はじめに

Flaskやsinatraを使ってウェブアプリケーションを作成する際に,どのDBを採用するかというのは仕様決定上重要になります.

僕はこれまでDBというとMySQLSQLiteのようなRDBMSを使っていたのですが,sinatra札幌さんのほうでflask上でNoSQLであるMongoDBを使用する機会を頂き,ここにMongoDBの概要と使い方,Pythonからどう制御するのかについて簡単にまとめました.

MongoDBとは

RDBMSを使用していた方にとってはMongoDBはちょっと異質に見えるかもしれません. RDBMSではデータ構造を明確化し,数学的な堅牢性を持っているイメージですね.SQLとよばれる言語を使用することでデータのソーティングなどを行ないます.

MongoDBではデータ構造はRDBほど明確化しなくても使用することができ,SQLのような言語を使う必要がありません.ドキュメント指向データベースと呼ばれており,データのやりとりはドキュメントとよばれる構造データにJSON形式で記述して使います.そのためプログラミング言語からSELECT * FROM テーブル名のようにコード中にSQLを書くこと必要がなく,使用言語がJSONフォーマットを扱えさえすれば配列やリストのような形でDBを操作できます.

MongoDBの用語

RDBMSを使用しているとデータベースの選択やレコードの選択などを考えてしまいますが,MongoDBは独特のドキュメント指向データベースと呼ばれている通りRDBMSとは異なる概念を持っています. ここではその概念と用語についてまとめてみました.

  • データベース:一つのアプリケーションで使用するコレクションを格納しておくもの.コレクションは複数持つことができる.

  • コレクション:ドキュメントを格納しておくもの.複数のドキュメントを持つことができる.

  • ドキュメント:商品ID,品名,価格などのデータそのものを格納できる.商品ID,品名のみなど可変長に扱える.入力形式はハッシュのようにKeyとValueの関係で保持される.

つまり包含関係で言えば「ドキュメント⊆コレクション⊆データベース」となります.

とりあえず動かしてみる

MongoDBを起動した状態で「mongodb」とシェル上からタイプするとMongoDBにアクセスできます.ここでは次のようなものを満すデータベースを作ってみましょう.

  • データベース名:examDBという学校の試験を

*コレクション:Math, Englishの2教科を現わすコレクション

*ドキュメント:Name,Gradeの2項目.ただしGradeはテストを受けていない場合はつけない場合がある.

*要素:NameはTaro,Jiro, Saburoの3人.Taroは「Math40点,Englishは受けていない」. JIroは「Math100点,Englishは80点」.Saburoは「Mathは90点Englishは70点」.

データベースの生成や選択は

 use examDB

としEnterを叩くだけです.簡単ですね 次にコレクション名とドキュメントを生成します.コレクションはMathとEnglishの2項目,ドキュメントはNameとGradeの2項目ですね.MongoDBではそれらを直接指定できます. まずはMathにそれぞれ項目を入力していきましょう.

db.Math.insert({Name : "Taro", Grade: "40"})
db.Math.insert({Name : "Jiro", Grade: "100"})
db.Math.insert({Name : "Saburo", Grade: "90"})

です. dbは上記のuseコマンドでexamDBをすでに選択しています.db.Mathとすることでコレクション名Mathを生成し操作できます.次にドキュメントを挿入したいのでdb.Math.insertのように指定できます.insertについては中身はJSONフォーマットで入力するだけなのでdb.Math.insert({ドキュメント項目名(Key) : ドキュメント項目値(Value), ・・・})です. 上記では3項目あるので3行分記述してます.

次にEnglishについても同様に打ちましょう.

db.English.insert({Name : "Taro"})
db.English.insert({Name : "Jiro", Grade: "80"})
db.English.insert({Name : "Saburo", Grade:"70"})

ですね.Taroは受けていないのでGradeの項目は入力してません.

では次は挿入されたデータを呼び出してみましょう. たとえば,Mathを呼び出したいときには MongoDBでは

db.Math.find()

だけです.これで入力した3項目が返ってきます.

MathのうちTaroのデータだけ呼びたいときには

db.Math.find(Name:"Taro")

だけです.

ここでは触れてませんが,コレクションの中にも子コレクションを生成することもできます. その場合もコレクション名が増えるだけで操作は,findやinsertを使用することができます.

PythonからMongoDBを操作する

PythonからMongoDBを操作してみましょう. PythonからMongoDBを扱えるライブラリにpymongoというのがあります. 「pip install pymongo」とタイプすることでインストールができると思います.

では先程の操作をpymongoからやってみましょう

from pymongo import MongoClient

client = MongoClient()#接続先を指定Localhostの場合はvoidで良い
db = client['examDB']#データベースを選択
#Mathの項目追加
db.Math.insert({'Name' : 'Taro', 'Grade':'40'})
db.Math.insert({'Name' : 'Jiro', 'Grade': '100'})
db.Math.insert({'Name' : 'Saburo', 'Grade': '90'})
#Englishの項目追加
db.English.insert({'Name' : 'Taro'})
db.English.insert({'Name' : 'Jiro', 'Grade': '80'})
db.English.insert({'Name' : 'Saburo', 'Grade':'70'})
#Mathのfind
for data in db.Math.find():
     print(data)
#MathのTaroだけ呼びだす
for data in db.Math.find({'Name':'Taro'}):
     print(data)

これらの形であればリストに格納したりイテレータのような形で扱うことができますね.

まとめ

MongoDBはRDBMSとは違ったSQLを使用しないデータベースであり,Jsonフォーマットでデータを扱えます.

概念用語としてデータベース,コレクション,ドキュメントがあります. ここでは学校のテストデータを例にmongoDBとPython上で試してみました.

kivyでデスクトップアプリケーション

はじめに

クロスプラットフォームGUIアプリを開発するためのフレームワークにkivyがあります kivyのホームページ

ここ最近,デスクトップアプリケーション開発といえば個人的にkivyを使用することが多くなりました.pythonにはpyQTwxpython,Tkinterなど多くありますが,pyQTはウェブや書籍を参考にできるほど文献が少ないこと,wxpythonの場合は更新がすでに止まっており,採用には至りませんでした.Tkinterを使うことも考えられますが,kivyのほうがスマートフォン上で動作するという違いがあり興味が湧いたのでkivyを使うに至りました.

ここではkivyの基本構成について忘れないようにメモしておきます.

必要なリファレンスについては

kivy documentation

PythonでかんたんiOSアプリプログラミング―Kivyによるマルチタッチアプリケーション制作

を参考にしました.

kivyのインストール

少し前のブログをお読みください kivyをpipからインストールしてみた

本記事ではPythonは2.7.6,kivyのバージョンは1.8を対象に書いております.

kivyの開発

まずはHelloWorld

kivyには様々なウィジェットがあります.そのなかにもラベルと呼ばれるアプリケーション上に文字列を表現するものがあります.ここではラベル上に「HelloWorld」を出力するコードを示します.

from kivy.app import App
from kivy.uix.label import Label

class TestApp(App):
    def build(self):
        return Label(text='Hello World')

TestApp().run()

これだけですね.

from kivy.app import Appについてはアプリのインポート,二行目はラベルのインポートです.

公式サイトのhomepageにも載っていますが,labelとLabelの箇所をそれぞれbutton,Buttonに変更すればボタン上にHelloWorldが表示されます.

pythonでファイルを指定すれば実行できます.

ちょっとまともなGUIアプリ

さて,HelloWorldの例を読むだけではreturnでボタンを返すだけでは面白くないかもしれません. ここでは僕が普段利用しているファイル関係を示しておこうと思います.

以下に基本的なファイル関係を示しておきます.

├── main.py
├── my.kv

main.pyという実行ファイル,my.kvというGUIレイアウトなどを記述するファイルです. メインとなる実行ファイルについてはHelloWorldを表示したようにPythonで処理を記述するものです.

my.kvはkivyにおけるデザイナの位置やIDと呼ばれるGUI部品とコードを紐付けるものを定義するものです.pythonコードで部品を設置しなくてもmy.kvに部品を配置することで,main.pyでは見た目を気にせず処理内容にだけ注力することができます.

また,名前を変更することも可能ですが,初期設定ではmy.kvという名前のファイルを読むように設定されており,そこについてはここでは触れません.

以下に,ボタンを押したらラベルの値に現在時刻を表示するアプリを例にmain.pyとmy.kvの設定や関係を説明していきます..

まずはmy.kvの説明をします.

今回作るアプリはボタンを押したらLabel上に現在時刻を表示するものです. そのため,ウィジェット名とラベル,ボタンをそれぞれ一個づつ配置する必要がありますので配置していきましょう.

まずは,ウィジェット名と任意の名前をラベル・ボタンそれぞれに付けていきましょう.

ここでは

  • ウィジェット名はtimerWidget

  • ラベルの名前はlabeldatetime

  • ボタンの名前はbuttoncount

としました.

それぞれの部品設定を設定を以下に示します.

labeldatetimeの設定

  • id:labeldatetimeval

  • フォントサイズ:50pt

  • 位置(横,縦):(中央,アプリ上部)

  • 初期設定の文字列:「time」

buttoncountの設定

*ID: buttoncountval

*フォントサイズ:50pt

*位置(横,縦):(中央,アプリ上部から下方向に向かって180ピクセル)

*ボタンサイズ(幅,高さ):(140,70)

*設定の文字列:「Click!」

これをkvファイルで表現すると次のようになります.

my.kv

#:kivy 1.8.0

<timerWidget>:
    labeldatetime: labeldatetimeval
    buttoncount: buttoncountval

    #Labeldatetime
    Label:
        id: labeldatetimeval
        font_size: 50
        center_x: (root.width/2)
        top: root.top
        text: 'time'
    #buttoncount
    Button:
        id: buttoncountval
        font_size: 50
        center_x: (root.width/2)
        size: 140,70
        top: root.top-180
        text: 'Click!'

まず一番上で,kivyのバージョンとウィジェット名,部品IDとpythonコードで使用する部品名を紐付けています. あとはそれぞれフォントサイズや位置,部品サイズ,部品の文字列などを定義しているだけです.

次にmain.pyで処理を書いていきましょう.

ここではウィジェットとして定義した部品を操作する箇所です.ボタンが押されたらその都度表示時刻を現在時刻に置きかえるというものです.

main.py

# -*- coding: utf-8 -*-

import kivy
kivy.require('1.8.0')

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
import datetime

class timerWidget(Widget):
    labeldatetime = ObjectProperty(None)
    buttoncount = ObjectProperty(None)

class MyApp(App):

    def buttoncount_clicked(self, src):
        self.root.labeldatetime.text = str(datetime.datetime.now())

    def build(self):
        self.root = timerWidget()
        self.root.buttoncount.bind(on_press=self.buttoncount_clicked)
        return self.root


if __name__ == '__main__':
    MyApp().run()

上から説明するとまずは,kivyのバージョン,アプリ起動に必要なAppとWidget,kvファイルで定義したWidgetを取りだすObjectProperty,現在時刻を扱うdatetimeをインポートしています.

class timerWidget(Widget)ではmy.kvで紐付けられている部品を取りだしたり描画する箇所です. my.kvで定義した変数を左辺に置いて右辺にはObjectProperty(None)と記述するだけです.

class MyApp(App)では起動に必要な準備やイベントハンドラと呼ばれるボタンをクリックしたときの処理などを書いていきます.

timerWidgetを生成. つぎにボタンをクリックしたときの処理をbind(on_press= )で定義しています. 最後にウィジェット自体を返すだけです.

次にMyApp().run()で実行です

以下に実行画面を示します

実行画面(クリック時)

f:id:kuroneko0208:20140315162302p:plain

まとめ

簡単なkivyの使い方をまとめてみました. 必要な部品などの配置はkvファイルが担当し,部品を操作するのはmain.pyです.

kivyにはもちろんこれ以外にもたくさんの部品があります.

また形には見えない部品もあります.例を挙げれば一定の時間で自動で実行するClockなどです.機会があればその辺についても書きたいと思います.

kivyをpipからインストールしてみた[2014/9再編集]

2014年9月現在一部インストール方法ではうまく導入できなかったので少し修正しました.

はじめに

Pythonにはkivyというクロスプラットフォームで動作するPython用のGUI開発ライブラリがあります。 それを使用することで、MacOSX,Linux,Windowsの3つのプラットフォームで動くほか、iOSAndroid上でも動作するアプリも開発ができます。

今日はこのインストールで少し躓いたのでpyenvという様々なバージョンを導入できるツールからkivyまでの導入をメモとして残しておきます。

環境はUbuntu13.04 32bit、シェルはzshです。 pyenvでは「2.7.6」をインストールするものとします。

pyenv

pyenvのインストールと2.7.6をインストールする例を交えながら使い方を説明します。

pyenvのインストール

まずは、apt-get(またはaptitude)でpyenvのための必要パッケージを導入していきます。

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev

そのあとは、https://github.com/yyuu/pyenv#installationに従ってインストールするだけです。

cd
git clone git://github.com/yyuu/pyenv.git .pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zprofile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zprofile
echo 'eval "$(pyenv init -)"' >> ~/.zprofile
exec $SHELL

ですね。

pyenvの使い方

こちらもさきほどのページの通りです(https://github.com/yyuu/pyenv#command-reference)

メモしておきますと

インストール可能なバージョン一覧

pyenv install -l

任意のバージョンのPythonをインストール(ここでは2.7.6)

pyenv install 2.7.6

任意のバージョンに切り替える(グローバルな設定)

pyenv global 2.7.6
pyenv rehash

これでpython2.7.6を使用できるようになりました。

kivy

pyenvでインストールしたPythonにはpipが内蔵されています。 今回はこのpipを使ってkivyのインストールを行ないます。 こちらに書いてあるインストール手順を踏んで行ないました。 http://kivy.org/docs/guide2/basic.html

インストール手順ページにある通り、kivyはcythonが必要なのでインストールし、その後はなぜかpygameをインストールしてから、kivyをインストールするという順のようですね(ここのページに辿りつくのに苦労しました・・・)。 この記事でもこの流れに沿って書いていきます。

cythonのインストール

cythonは次のコマンドで一発だと思います。

pip install cython

pygameのインストール

ここのURLを参照して必要パッケージを導入しました。 http://stackoverflow.com/questions/7652385/where-can-i-find-and-install-the-dependencies-for-pygame

sudo apt-get install python-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsdl1.2-dev libsmpeg-dev python-numpy subversion libportmidi-dev ffmpeg libswscale-dev libavformat-dev libavcodec-dev

これにさらにfreetypeのconfigがうまくいってない旨のエラーがでましたので、

sudo apt-get install freetype6-dev

で追加しておきました。

あとはpygamemercurialリポジトリを経由してpipでインストールします。

pip install hg+http://bitbucket.org/pygame/pygame

kivyのインストール

いよいよkivy本体のインストールですね

pip install git+https://github.com/kivy/kivy.git

でインストールが成功すれば終わりです。

pip install kivy では2014年9月現在ではコケてしまいましたので, githubのmasterブランチから直接インストールしてあげるという方法を取りました.

まとめ

pyenvからkivyまでをインストールしました。

pyenvで2.7.6をインストールしたあとはpipを使ってcython,pygameという順にインストールしていき、最後にkivyをインストールしました。

pyenvやpygameをインストールするときには必要パッケージをaptから取り込んでおく必要があります。また、zshbashを使う環境によって設定を記述するファイルを分ける必要があります。今回はzshで導入を行ないました。

Sinatraでグラフ画像を表示

SinatraAdventCalendar11日目の記事です(http://www.adventar.org/calendars/262)

f:id:kuroneko0208:20131211022345p:plain

昨日はRSSFeed(http://advent.nzwsch.com/rss-feed)でした.

今回は,sinatraで遊んでみたような内容です.

力を抜いて読んでください.

はじめに

今年の初頭あたりにipodに入れていた歩数計のデータをウェブブラウザ上からグラフ表示してみたのですが,当時,僕はフレームワークを使ったことがありませんでした.内容としてはpythonからipodに入っていた歩数計データを読みだして,matplotlibでグラフ生成したあと,htmlに読み込ませるというものです.

フレームワークを使えばもっと素早く簡単にできるだろうと考えて,今回RubySinatraでウェブ上にグラフ画像を表示してみました.

内容は同じでは面白みがないので,東京電力の提供している電力使用量をグラフ化するコードにしました. 一年間の温度変化などでも良かったのですが,更新頻度の高いデータのほうが動的なアプリケーションとして面白いと判断したため このような題材を選びました.

他には二酸化炭素などの公害の原因となる物質の量をグラフ化してみようと考えましたが,きっちりとWebAPI化が進んでいるようで, 面白みが無いと判断して今回はやめました.

処理の内容

ユーザが訪れるたびにcsvファイルをダウンロードしてGruffというライブラリでグラフ画像を生成し,ウェブ上に表示するというものです.

一応,csvダウンロード先のウェブサイトへの負担を考えて,5分立たなければダウンロードできない仕組みとしてます.

f:id:kuroneko0208:20131210051417p:plain

図:処理のフローチャート

各ファイルの構成は次のようになっています

.
├── grufftest.rb
├── juyo-j.csv
├── public
│   └── PowerUsageGraph.png
└── views
    ├── index.erb
    └── layout.erb

必要なライブラリの解説

まずはsinatraですね.こちらは必須です. gem install sinatraでインストールが可能です.

今回はgruffというRubyで見た目重視のグラフライブラリを使用してみました.

matplotlibはGnuPlotのように見やすいのですが,飾り気がないので面白みがありません(グラフに本来求める要素ではないですからね).

こちらのライブラリはgem install gruffでインストールできます.

gruffの使い方は簡単で,公式サイト(http://nubyonrails.com/pages/gruff)に沿えばすぐに使い方がわかると思います.

以下にサンプルのコードを示します

require 'rubygems'
require 'gruff'

g = Gruff::Line.new
g.title = "My Graph" 

g.data("Apples", [1, 2, 3, 4, 4, 3])
g.data("Oranges", [4, 8, 7, 9, 8, 9])
g.data("Watermelon", [2, 3, 1, 5, 6, 8])
g.data("Peaches", [9, 9, 10, 8, 7, 9])

g.labels = {0 => '2003', 2 => '2004', 4 => '2005'}

g.write('my_fruity_graph.png')

g.dataには項目名と数値リテラルの配列を渡し,g.labelsにはハッシュで横軸の項目を入れているんですね. そこまで整ったらg.writeで保存したいグラフ画像名を入れて保存です.

コード

ではさっそくコードを載せます.

main.rb

require 'gruff'
require 'csv'
require 'sinatra'
require 'date'

def graphgenerate(y,x_name,y_name,graphname) 
    g = Gruff::Line.new
    g.title = graphname
    g.data(graphname,y)
    g.labels = {0 => '0',12 => '1',24 => '2',36 => '3',48 => '4',60 => '5',72 => '6',84 => '7',96 => '8',108 => '9',120 => '10',132 => '11',144 => '12',156 => '13',168 => '14',180 => '15',192 => '16',204 => '17',216 => '18',228 => '19',240 => '20',252 => '21',264 => '22',276 => '23'}
    g.x_axis_label = x_name
    g.y_axis_label = y_name
    g.write('./public/'+graphname)
end

def filedownload(url,filename)
    system('wget '+url+' -O'+ filename)
    system('nkf -w --overwrite '+ filename)
end

def fileanalysis
    filedownload('http://www.tepco.co.jp/forecast/html/images/juyo-j.csv','juyo-j.csv')
    powerdata=[]
    count = 0
    datatime=""
    CSV.foreach("./juyo-j.csv","r") do |data|
        if(count==0) then
            datatime=data[0]
        end
        if(count>46) then
            powerdata << data[2].to_f*10000/10**6#kWはわかりにくいのでMW
        end
        count=count+1
    end
    graphgenerate(powerdata,'Hour','Power Usage[MW]','PowerUsageGraph.png')
    return datatime

end

get '/' do
    if((DateTime.now.to_time-File.stat("./juyo-j.csv").mtime.to_time).to_i > 300) then
        @graphdatetime=fileanalysis
    end
    @graphimage="PowerUsageGraph.png"
    erb :index
end

index.erb

<h1>本日の電力使用状況(東京電力)</h1>
<p>参照:http://www.tepco.co.jp/forecast/html/images/juyo-j.csv</p>
<img src=<%= @graphimage  %>>
<h2><%= @graphdatetime %></h2>

layout.erb

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>本日の電力使用量</title>
</head>
<body>
    <%= yield %>
</body>
</html>

簡単な解説

次のような3つのメソッドの呼び出しで動作を実現しています.

・graphgenerate(y,x_name,y_name,graphname)

・filedownload(url,filename)

・fileanalysis

・graphgenerateですが,こちらでグラフ画像を生成しています. サンプルにあるものをいじっただけです.

・filedownloadはwgetコマンドをrubyの上から実行し,ダウンロードしてきたcsvファイルをutf-8に変換しています. rubyの上でも実現できるかもしれませんが,てっとり早い方法なのでこのような処理をしています.

・fileanalysisは上二つのメソッドをまとめたものです. get '/' do以下のメソッドで記述しても良いですが,長くなったのでこのようなメソッドにまとめました. CSVを読み込んでいるところではcountが0であるときと,47以上のときに実行されるif文があります.これは更新日時が1行目にあり,グラフ化したいデータが47行目にあるためです.Rubyではこのあたりもっと簡潔に書けそうですが何かないでしょうか?

なお,生成した画像はpublicディレクトリに移さなければerb側で表示ができませんのでおいておきました.

erbの配置については,前のブログに書いたとおりです.よろしければ参考にしてください (http://kuroneko0208.hatenablog.com/entries/2013/11/26).

表示結果

localhost:4567にアクセスすると次のように表示されます.

f:id:kuroneko0208:20131211014925p:plain

まとめ

sinatraを使って東京電力の提供している電力使用量をグラフを表示してみました.

グラフ生成にはGruffというビジュアル重視のグラフ生成ライブラリを使用することで,グラフ画像の生成を行ないます.

グラフ画像はpublicにおかなければ反映されないのでおく必要があります.

フレームワーク素人がFlask触ってみた

こんにちは

昨夜に引き続きブログを更新してみます. 前回の内容はこちら(http://kuroneko0208.hatenablog.com/entry/2013/11/26/034323)

今日は,昨晩更新したSinatraと同じ内容をPythonのFlaskで書きなおしてみました.

昨日と同じ流れで進めていきますね.

まずはHelloWorld

#coding:utf-8
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "helloworld"

app.run()

flaskを利用するため最初の行で呼びだしてきております. 次に@という形でデコレータを表現しております. Sinatraとは若干書きかたが違うもののルートを示していることはおそらくSinatraをお使いのかたはわかるかと思います. Rubyの場合はreturnをつけなくても最後に記述した内容がreturnされるらしいのですが, Pythonにはそのような機能は無いはずですので(?)明示的に示しております. app.run()で実行を示しております.

ちゃんとしたhtmlページに表示させたい

昨夜見せた内容をPythonでディレクトリを構成すると次のようになります.

.
├── main.py
└── templates
    ├── index.html
    └── layout.html

実はPythonではMVCモデルと呼ばずにMVTモデルと言います. MVCでいうViewにあたるものがPythonではTemplatesなのです. ちょっとややこしいのですが,このようになっております. layout.htmlやindex.htmlはsinatra同様,共通部品を集めたものをlayout.htmlへ index.htmlが呼びだされたときだけに基本出力したいときにはindex.htmlへ記述します.

以下にmain.pyがlayout.htmlとindex.htmlを利用してウェブアプリケーションを立ち上げるコードを示します.

main.py

#coding:utf-8

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html',titlename="helloworld")

app.run()

layout.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>{{ title }}</title>
</head>
{% block body %}
{% endblock %}
</html>

index.html

{% extends "layout.html" %}
{% block body %}
<h2> テスト </h2>
{% endblock %}

まずmain.pyから説明していきますと,importやindex関数の中にreturn render_template('index.html',title="helloworld")があるかと思います. こちらの関数でtemplateエンジンのhtmlをレンダリングするように命令しています. 今回呼びたいのがindex.htmlで,タイトルが"helloworld"としたのでこのようになりました.

では次はlayout.htmlについて見ていきます. こちらは{{ title }}{% blockbody %}'から'{% endblock %}があるかと思います. こちらがテンプレートエンジンの書き方になっておりまして,{{ なにかの変数 }}という形で書くことでmain.pyで渡した変数を呼びだせます. {% blockbody %}{% endblock %}についてはsinatraでいうところのyieldと言ったところです.ようするにmain.pyの書き方ではindex.htmlが呼ばれておりますから,この場合index.htmlの内容が出力されます.

index.htmlについて見ていきましょう. {% extends "layout.html" %}で共通化部品(親のこと)を指定しています. {% block body %}{% endblock %}で囲ったところが呼び出されるようになっておりますから,main.pyを実行するとh2要素タグのサイズの「テスト」という文字列が画面に出てきます.

htmlからテキストボックスの内容をPythonに送信

次に昨日もやったテキストボックスの内容をpostでpythonに渡して処理させるコードを書きます. 昨日と同様にタイトルを逐一書きかえるというものとしました.

では以下コード main.py

#coding:utf-8

from flask import Flask, render_template,request

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html',title="helloworld")


@app.route('/sendtext', methods=['POST'])
def sendtext():
    return render_template('index.html',title=request.form['message'])

app.run()

index.html

{% extends "layout.html" %}
{% block body %}
<h2> テスト </h2>
<form method="post" action="/sendtext">
    <input type="text" name="message"><input type="submit" value="Send">
</form>
{% endblock %}

layout.html

先程と同様なので省略

main.pyから見ていきますと,postで受けとるのに@app.route('/sendtext', methods=['POST'])としていますね.実は第二引数としてmethods=['POST']を書けばポストの処理となります. それをrequest.form['message']という形でindex.htmlから送られてくるだろう'message'を受けとってtitleに代入して再びレンダリングしています.

一応最後に,index.htmlを見ますと通常のhtmlらしく"message"というテキストボックスとSendというボタンがあります.テキストボックスで書いた内容を/sendtextに向けてpostで送信するというものとなっております.

少し感想

RubySinatraPythonのFlaskの二つを試した感想として,Sinatraのほうがウェブ上での文献の数が非常に多く,Rubyをお使いのかたはFlaskを触る利点があまりないかもしれません.僕自身もPythonばかり使ってきましたが,Sinatraの文献の多さやとっつきやすさで2日に分けた記事を書くことができました.

Flaskの場合はまだまだ日本語記事が少ないことなどがありまして,調べにくいのですが,機能面で言えば,基本的にはSinatraに見劣りしないくらいのフレームワークだと思います.Pythonを書いてきた人にとっては重要なリソースもあるでしょうから,わざわざSinatraを触る必要がないかもしれません.ただ,僕のようにフレームワーク初心者にとっては,Sinatraの文献の数に助けられ,結果的にFlaskで簡単なアプリケーションを作るまでいけましたので,Sinatraもおすすめです.

そのようなわけで結論として,Ruby使いはFlaskを使う利点が見あたらないが,Python使いにとってはどちらも学んだほうが良いと言ったところでしょうか.

フレームワーク素人がRubyのSinatra触ってみた

こんにちは

最近,Python以外にもちょっとしたきっかけでRubySinatraに手を出しました.

僕自身もともとウェブフレームワークに触れることを今までしなかったことから,あの独特の開発のしかたに妙な苦手意識を持ってました.

ここでは似たような境遇にある?かたへ少し参考になればと思い残しておきます.

まずはハローワールド

main.rb

require 'sinatra'

get '/' do
   "HelloWorld"
end

まずはrequireでsinatraを呼んできます. そのあとにget '/' doというsinatra流の文があります. これはgetメソッド(普通にURLでアクセスした場合のこと)と'/'というlocalhost直下を意味する文からなりたってます. この場合意味は「普通にURLでlocalhostを叩いた場合に実行」と読むことができます で,"HelloWorld"と書いてあるのが文字列となってまして,これが表示されます.

ちゃんとしたhtmlページに表示させたい

今のような書きかたでは"hello world"を出すだけであまりできそうなことがありません. 通常はhtml(例えばindex)を意味するファイルを生成してひっぱってきて表示するのが基本です.

Sinatraの場合,erbという形式でhtmlを表現することになります(他にもHAMLというものがありますが,ここではerbにしておきます). 以下にこれらの基本構成を表示しておきます.

.
├── main.rb
└── views
    ├── index.erb
    └── layout.erb

main.rbに先程のような呼び出されたアクションを記述して views以下にその結果を垂れ流すイメージですね.

views以下を見るとindex.erbとlayout.erbの二つがあると思います. これはテンプレートエンジンの特徴なのですが,基本的な共通するコードをlayoutに書いておいて,index.erbにはindexが呼び出されたときの処理を書くというやりかたです. 例えばabout.erbのようにaboutページを生成された場合ヘッダーの部分やtitleの部分,cssの部分などはindexと同じようにしておきたいというケースがあるかと思いますが,そのような場合に有効です.

以下にそれっぽく動くmain.rb,index.erb,layout.erbを記述しておきます.

main.rb

require 'sinatra'

get '/' do
    @title = "ハローワールド"
    erb :index
end

layout.erb

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title><%= @title %></title>
</head>
<body>
    <%= yield %>
</body>
</html>

index.erb

<h2>テスト</h2>

このように書くだけです. まずmain.rbの@title = "ハローワールド"はindexにtitleという"ハローワールド"が代入された変数をindex.erbに渡すための書きかたです.@変数名としておくことこのような機能が使えます. erb :indexというのはindex.erbを呼びだすときの文です. たとえばhamlで書きたいときにはhaml :indexという形式になります.

次に,layout.erbですが,これはhtmlの基本的な骨組だけを書いておく部分です. <!DOCTYPE html>は必ずどのページにも登場しますね?そういった決まりきった共通項を書いておく部分です.このようにlayout.erbに書いておくと勝手にsinatraが解釈してくれます. それからlayout.erbの中には<%= @title %><%= yield %>とありますね. <%= @title %>の場合はrbファイルで書かれた@titleが渡された内容を出力していることになります. <%= yield %>のほうは,indexページが呼ばれたときには「index.erbの中身を展開しなさい」という意味になります <%= なんとか %>という書きかたに違和感があると思いますが,こちらはerb流の書きかたになっております.

つぎにindex.erbはといえば<h2>テスト</h2>ですね つまりルートにアクセスした場合ページにはテストとh2のサイズで表示されます.

erbからテキストボックスの内容をrubyに送信

今はただ単純にウェブページに記述しておくだけでした. ここではユーザの入力に応じた処理としてテキストボックスの内容をボタンが押されたらrbファイルにpost形式で送る方法を書いておきます.例としてタイトルを逐一変更するようなものを作ってみます.

先程と同じディレクトリ構成で記述していきますと,

main.rb

require 'sinatra'

get '/' do
    @title = "ハローワールド"
    erb :index
end
post '/sendtext' do
    @title = params[:message]
    erb :index
end

index.erb

<h2>テスト</h2>
<form method="post" action="/sendtext">
    <input type="text" name="message"><input type="submit" value="Send">
</form>

layout.erb 先程と同様なので省略

main.rbではpost '/sendtext' doというgetからpostになっていることがわかると思います. これはpostで送られてきた処理に対して実行されるものです./sendtextとありますからlocalhost以下に/sendtextというurlが付加されてきた場合を指します.

つまりこの場合「sendtextというurlが末尾についてきてかつpostの場合はここを実行」ということになります.

中身はと言えば,@title = params[:message]とありますね.index.erbで定義したinputのname名]で受けとれます.この場合は@titleに代入しているので

「erbで定義したinput textのname名の値をタイトルにする」ということになります.

erb :indexでこの処理が終ったら次に飛ばす場所を指定してます.今回は元の画面に飛んでもらえば良いのでこのような書き方になってます. redirectでルートを指定できますが,そうすると変数が消えてしまうので,erb :indexという書き方にしました.

以上でしょうか.一先ず文字を出力したり入力したりといったことを簡単にまとめてみました. helperというのを使えばメソッドを定義して実行できるという便利なメソッドもありますが,疲れたのでここまでとします.