Tag/Python

モデルのsaveが変わったよ。

Djangotrunkqueryset-refactor がマージされたタイミングで、 モデルの save メソッドにも改良が加えられてたのでメモメモ。

マージ前(r7476)save メソッド自体に実際の処理が書かれてた。

# django/db/models/base.py@213行目あたり
def save(self, raw=False):
    dispatcher.send(signal=signals.pre_save, sender=self.__class__,
                    instance=self, raw=raw)
    # 中略...

    dispatcher.send(signal=signals.post_save, sender=self.__class__,
                    instance=self, created=(not record_exists), raw=raw)

ので、データ登録の際に何か処理を挟みたい場合、 オーバーライドして処理を追加してあげて、 良いところで親の save をコールしてあげる事で実装してた(と思う)。

class Hige(models.Model):
    """
    HIGEなモデル
    """
    def save(self, raw=False):
        # なにか追加したい処理が入る。
        super(Hige, self).save(raw=raw)

でも、これだとダイレクトにデータを保存したい場合に、 適当な名前でもうひとつ save メソッドを作ってやる必要があった。

class Hige(models.Model):
    """
    HIGEなモデル
    """
    def save(self, raw=False):
        """
        adminとかでは処理を追加したいので、
        オーバーライドせざるを得ないんだ。
        """
        # なにか追加したい処理が入る。
        super(Hige, self).save(raw=raw)

    def direct_save(self, raw=False):
        """
        たまーに処理を追加しないのも必要。
        """
        super(Hige, self).save(raw=raw)

こういう状況になると、ものっすごい残念な気持ちになってた訳なのですが、 これが マージ後(r7477) でちょっと素敵に改良されてるよ。

# django/db/models/base.py@293行目あたり
def save(self):
    """
    Save the current instance. Override this in a subclass if you want to
    control the saving process.
    """
    self.save_base()

def save_base(self, raw=False, cls=None):
    """
    Does the heavy-lifting involved in saving. Subclasses shouldn't need to
    override this method. It's separate from save() in order to hide the
    need for overrides of save() to pass around internal-only parameters
    ('raw' and 'cls').
    """
    # 省略...

実際のデータ登録の処理は全部 save_base メソッドで実装しておいて、 save メソッドはそのフックになってるよ。 これだと、ダイレクトにデータを登録する必要が出て来た場合でも、 save_base をコールすれば良いだけ。 なので、特にモデルに変更を加える必要は無い。

class Hige(models.Model):
    """
    HIGEなモデル
    """
    def save(self):
        """
        処理を追加したいのでオーバーライド。
        raw=Falseは無くなってるよ。
        """
        # なにか追加したい処理が入る。

        # self.save_base()ってしてないのはなんとなく。
        super(Hige, self).save()

これでもう残念な気持ちにならなくて良いね。 また一つ、 Django が好きになりましたとさ :-)

Posted at: 
2008/05/02 00:29:35
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/05/02/changed-models-save-on-django/trackback/

Pythonでスクリプトからインタプリタを起動する

Google App Engine を弄ってるついでに覚えたメモ。 Python なスクリプトからインタプリタを起動するには、

import code
code.interact()

って、したら良いらしいよ。 IPython がインストールされてるのであれば、

import IPython
IPython.Shell.IPShell().mainloop()

で起動出来るよ。 なので、こんな感じにすると良いのかも。

try:
    import IPython
    IPython.Shell.IPShell().mainloop()
except ImportError:
    import code
    code.interact()

これで IPython がインストールされてれば IPython が使えるし、 されて無ければデフォルトでインタプリタが起動出来るね。

Posted at: 
2008/04/15 02:49:03
0 Comments
1 TrackBack
Tags: 
Python
Trackback: 
http://humming.via-kitchen.com/2008/04/15/execute-interpreter-on-pythonscript/trackback/

DjangoのPaginatorが変わってるよ。

Djangor7306ObjectPaginator がdeprecated扱いになったので、 どんな風に変わったのか調べてみたよ。

テストを走らせてみると、こんな感じのWarningを思いっきり吐かれた。

DeprecationWarning: The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.

なるほど、 Paginator に変わったらしい。 実際にどう変わったのかな?って確認しようと、 django/core/paginator.py をちょっと覗いてみると、 QuerySetPaginator って派生クラスもある。 どこが違うかと言うと、 _get_count なメソッドだけがオーバーライドされてる。

まずは Paginator_get_count を見てみるとこうなってる。

def _get_count(self):
    "Returns the total number of objects, across all pages."
    if self._count is None:
        self._count = len(self.object_list) # <-- lenでカウントを取得
    return self._count
count = property(_get_count)

_countなプロパティがセットされて無ければ、 lenを使ってカウントを取得してる。

今度は QuerySetPaginator_get_count を見てみる。

def _get_count(self):
    if self._count is None:
        self._count = self.object_list.count() # <-- QuerySetのcountをコール
    return self._count
count = property(_get_count)

_countなプロパティがセットされて無ければ、 QuerySet のcountをコールして、COUNTなSQLを発行してカウントを取得してる。なるほどー。 って事は、 Paginator を使うよりも、 QuerySetPaginator を使う方が良いね。 今までの ObjectPaginatorQuerySet しか扱えなかったんだから、 WarningでもQuerySetPaginator insteadって出して欲しいなぁ。ってか出すべきな気がする。

念のために汎用ビューを見てみると、 やっぱり QuerySetPaginator を使ってるよ。

もう一つ、今回の変更で加わったのが Page なクラス。 今まで ObjectPaginatorget_page をコールすると、 返り値は QuerySet だった。

try:
    # 返り値のobject_listはQuerySet
    object_list = paginator.get_page(page - 1)
except InvalidPage:
    raise Http404

それが Paginatorpage をコールした時の返り値は Page になる。

try:
    # 返り値のpage_objはPage
    # マイナス1しなくて良くなった!
    page_obj = paginator.page(page)
except InvalidPage:
    raise Http404

# Pageの持つobject_listプロパティにQuerySetが入ってる
object_list = page_obj.object_list

この Page なクラスはページネイション関連のデータを保持する実装になっていて、 汎用ビューで今まで使えてたページネイション関連の変数は、 全部この Page がメソッドとして実装してる。 結構な変更がかかってるけども、 明らかに使いやすくなってると思う。 今までのがデータの引き回しとかが多くて、微妙に使いづらかったってのもあるけども :-P

ObjectPaginatorQuerySetPaginator にリプレイスする時は、 django/views/generic/list_detail.py を見れば分かりやすいと思うよ :-)

Posted at: 
2008/03/26 03:31:44
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/03/26/change-paginator-on-django/trackback/

Pythonの__metaclass__を少し理解したよ。

Pythonクックブックのオブジェクト指向プログラミングを読んでて、 下の2つは等価だと知った。(今更か?)

example1:

class Someclass(Somebase):
    __metaclass__ = type
    x = 23

example2:

Someclass = type('Someclass', (Somebase,), {'x': 23})

って事は、 example2 の方でも type.__new__ がコールされてるって事なのかな? ちょっと試しにやってみる。

>>> class HogeMetaclass(type):
...     def __new__(cls, name, bases, attrs):
...         """
...         printするだけの拡張
...         """
...         print "__new__ has been called."
...         return super(HogeMetaclass, cls).__new__(cls, name, bases, attrs)
...
>>> Hoge = HogeMetaclass('Hoge', (object,), {})
__new__ has been called.    # <- ちゃんと出た。
>>> Hoge
<class '__main__.Hoge'>

ちゃんとコールされた!なるほど。 って言うより、実際の動きと自分の捉え方が全く逆だった事を、 Python リファレンスマニュアル の「 3.3.3 クラス生成をカスタマイズする 」を読み直してみてようやく理解したよ。 __metaclass__.__new__ がダイレクトに呼び出されるのでは無くて、 __metaclass__ がコールされた結果として __new__ が呼び出されるのね。

これが理解出来て、やっとこさ Djangonewforms.form_for_modelnewforms.form_for_instance で何が行われてるかがちゃんと理解出来たよ :-)

Posted at: 
2008/03/17 00:33:31
0 Comments
1 TrackBack
Tags: 
book
Python
Trackback: 
http://humming.via-kitchen.com/2008/03/17/little-learning-metaclass-on-python/trackback/

MacのPythonを野良Portsからデフォルトに

MacPython野良Ports からデフォルトにしたよ。 色々と easy_install で入れたのでとりあえずメモ。

  • readline-2.4.2
  • yolk-0.3.0
  • ipython-0.8.2
  • docutils-0.4
  • Pygments-0.9
  • MySQL_python-1.2.2
  • pysqlite-2.4.1
  • SQLAlchemy-0.4.4
  • lxml-2.0.2
  • httplib2-0.4.0
  • pytc-0.3
  • simplejson-1.7.4
  • python-twitter-0.5
  • Paste-1.6
  • PasteScript-1.6.2
  • PasteDeploy-1.3.1

Django はsvn-trunkを使ってるので、 シンボリックリンク貼り直しておしまい。

デフォルトの Python のreadline問題は知ってたので、 Yの砂箱 さんの「 LeopardにバンドルされてるPythonでreadlineを有効にする方法を見つけた。 」を参考に回避。 無事に日本語とかヒストリーとか、いつもの使い勝手が復活。

MySQL_python も、そのまま easy_install するとエラるのを知ってたので、 パッチ当ててから叩く。肝心のパッチファイルは MacPorts の中に入ってるのを拝借してくる。

$ cd /usr/local/src
#   パッチファイル2枚をコピってくる
$ cp /opt/local/var/macports/sources/rsync.macports.org/release/ports/python/py25-mysql/files/* ./
$ curl -O http://osdn.dl.sourceforge.net/sourceforge/mysql-python/MySQL-python-1.2.2.tar.gz
$ tar zxf ./MySQL-python-1.2.2.tar.gz
$ cd MySQL-python-1.2.2
#   パッチ2枚を当てる
$ patch -p0 < ../patch-_mysql.c.diff
$ patch -p0 < ../patch-setup_posix.py.diff
$ cd ../
$ sudo easy_install -UZ ./MySQL-python-1.2.2

ちゃんと MySQL_python も入ったよ。めでたしめでたし :-)

後は pysvn と戦うだけ。 ホントにコイツとは相性悪いんだよなぁ。

Posted at: 
2008/03/16 19:32:52
0 Comments
1 TrackBack
Tags: 
Mac
Python
Trackback: 
http://humming.via-kitchen.com/2008/03/16/change-python-on-mac/trackback/

Pasteとかeasy_installとか

easy_install の勉強しようと思ってた時に、 Pylons 触ってた時に使ってた Paste ってどうなんだろ?って思って、 分からないままにちょっと試してみたよ。

とりあえず Paste のインストール。 今回は PasteScript も必要なので、一緒にインストールする。

$ sudo easy_install Paste
$ sudo easy_install PasteScript

PasteScript をインストールした時に、 一緒に PasteDeploy も入ったけれども気にしない。 インストールが終ると paster なスクリプトが一緒にインストールされるので、 これを使って作業する。

まずは適当なディレクトリに移動して、 プロジェクトを作る。 pastercreate オプションを付けて実行すると、 新規作成の為に色々聞いてくるので、 流れにそって答えていく。

$ paster create
Selected and implied templates:
  PasteScript#basic_package  A basic setuptools-enabled package

Enter project name: myproject
Variables:
  egg:      myproject
  package:  myproject
  project:  myproject
Enter version (Version (like 0.1)) ['']: 0.1
Enter description (One-line description of the package) ['']: my first project
Enter long_description (Multi-line description (in reST)) ['']: my first project
Enter keywords (Space-separated keywords/tags) ['']: test
(省略)

出来たディレクトリの中を見てみると、 必要なものが一通り出来てるよ。 なので、このまま easy_install で叩いてもちゃんとインストール出来る。 中身を確認するために、オプションを指定してインストールしてみる。

$ sudo easy_install -UZ ./myproject
Processing myproject
Running setup.py -q bdist_egg --dist-dir /Users/nobu/tmp/myproject/egg-dist-tmp-GzZuQc
Adding myproject 0.1dev to easy-install.pth file

Installed /opt/local/lib/python2.5/site-packages/myproject-0.1dev-py2.5.egg
Processing dependencies for myproject==0.1dev
Finished processing dependencies for myproject==0.1dev

ちゃんとインストールされたか確認してみる。

>>> import myproject
>>> dir(myproject)
['__builtins__', '__doc__', '__file__', '__name__', '__path__']

ちゃんとインストールされてるよ!素晴し過ぎる! 何もしてないから、コレだけだと何も出来ないけども。

で、これだけだと全然面白くないので、 何か実際作ってみたいなぁ。なんて。 そこで思い付いたのが、 pateo さんが中心になってオープンソースで開発されている monologista のクライアントAPIの野良パッケージ! これを勝手に easy_install 出来るようにしてみる。

まずはプロジェクト作成から。 twitter のパッケージに合わせて、 python-monologista な名前にして paster で作成する。

$ paster create

で、中身を編集していく。 中を見てみるとこんな感じになってる。

$ cd python-monologista
$ ls
python_monologista.egg-info/ pythonmonologista/           setup.cfg                    setup.py

デフォルトで作られるソースの中身がディレクトリなんだけども、 今回の monologista のクライアントはファイル1枚なので、 ディレクトリは削除してしまって、ファイルに置き換えてしまう。

$ rm -rf ./pythonmonologista

肝心のファイルはと言うと、 monologista – Trac で公開されているので、 そこからありがたく拝借する。 が、ソースの管理に mercurial が使われていて、 ファイル1枚だけを落してくる方法が分らなかったので、 地味にコピペでファイルを作成。

そのあと、 python_monologista.egg-info の中にある SOURCES.txt を編集してつじつまを合わせる。

setup.cfg
setup.py
python_monologista.egg-info/PKG-INFO
python_monologista.egg-info/SOURCES.txt
python_monologista.egg-info/dependency_links.txt
python_monologista.egg-info/entry_points.txt
python_monologista.egg-info/not-zip-safe
python_monologista.egg-info/top_level.txt
monologista.py                  # <-- コレを足す
pythonmonologista/__init__.py   # <-- コレを消す

次に、同じく python_monologista.egg-info の中にある top_level.txt を編集。

monologista.py      # <-- コレを足す
pythonmonologista   # <-- コレを消す

ここまでで1度インストールしてみる。 ちょっとだけやる気をみせて、tarで固めてからのテスト。

$ sudo easy_install -UZ python-monologista.tgz
Processing python-monologista.tgz
Running python-monologista/setup.py -q bdist_egg --dist-dir /tmp/easy_install-XXgsgg/python-monologista/egg-dist-tmp-FKUCd7
warning: install_lib: 'build/lib' does not exist -- no Python modules to install
Adding python-monologista 0.1dev to easy-install.pth file

Installed /opt/local/lib/python2.5/site-packages/python_monologista-0.1dev-py2.5.egg
Processing dependencies for python-monologista==0.1dev
Finished processing dependencies for python-monologista==0.1dev

おお?なんかwarning吐いてるなぁ。 インストールするものが無いとか言われてる。 たしかにインストール先のディレクトリを見ても、 monologista.py が入ってない。

色々探してみると、 setup.py の中の setup に、 py_modules で指定して渡してやると良いらしい。

from setuptools import setup, find_packages
import sys, os

version = '0.1'

setup(name='python-monologista',
      version=version,
      py_modules=['monologista'],   # <-- コレを足す
      # (省略)
      )

これでもう1度インストールしてみる。

$ sudo easy_install -UZ pytthon-monologista.tgz

今度はwarning吐かずにインストール出来たよ! インストール先を見ても、ちゃんと monologista.py が入ってる! なので、早速シェルでインポートしてみる。

>>> from monologista import Api
>>> dir(Api)
['SHOW_URI',
 'TIMELINE_URI',
 'TODO_URI',
 'UPDATE_URI',
 '__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__str__',
 '__weakref__',
 'get_url',
 'public_timeline',
 'update',
 'user_timeline',
 'validator']

おおお!出来たよ! かなり嬉し過ぎます!! 思わず声に出してガッツポーズ :-D (現在 5:00 AM)

これで monologista にAPIが実装されればスクリプトからゴニョゴニョ出来るね。 monologista の1.0がリリースが待ち遠しいね。

Paste とか PasteScript 以前に setuptools の事を良く理解出来てないので、 これを機会に真面目に勉強しようと思ったよ。 PastePasteScript を使う事で簡単にパッケージ化する方法を覚えて、 少しでも多くアウトプットしていけたら良いっすなぁ。

使ってるものを理解する事って大切だなぁ。って再確認出来た良い機会になりましたとさ。

Posted at: 
2008/03/09 05:17:45
4 Comments
1 TrackBack
Tags: 
Paste
Python
Trackback: 
http://humming.via-kitchen.com/2008/03/09/paste-and-easy_install/trackback/

OptionParserメモ

Python 使ってコマンドラインからゴニョゴニョしようと思って、 調べてみたら OptionParser なんてものがあったので試してみたメモ。

6.21 optparse -- より強力なコマンドラインオプション解析器 を見ながらhoge.pyとかにして試してみる。

#!/usr/bin/env python
# vim: encoding=utf=8

from optparse import OptionParser

parser = OptionParser()

def main():
    options, args = parser.parse_args()
    print options
    print args

if __name__ == '__main__':
    main()

これだけでヘルプの表示までは出来るようになってるらしい。 -h--help でヘルプが表示出来るらしく、 実際にやってみるとこんな感じになったよ。

$ ./hoge.py -h
Usage: hoge.py [options]

Options:
  -h, --help  show this help message and exit

ここからオプションを add_option なメソッドを使って、 必要なオプションを詰め込んでいけば良いらしい。 リファレンス見ながらやってみる。

parser = OptionParser()
parser.add_option("-f", "--file", dest="filename",
                  help="write report to FILE", metabar="FILE")
parser.add_option("-q", "--quiet", action="store_false",
                  dest="verbose", default=True,
                  help="don't print status messages to stdout")

これでもう1回ヘルプを表示してみると、

$ ./hoge.py -h
Usage: hoge.py [options]

Options:
  -h, --help            show this help message and exit
  -f FILE, --file=FILE  write report to FILE
  -q, --quiet           don't print status messages to stdout

ちゃんと増えてるよ。 ここまで簡単に出来ると、 今までシェルで書いてたのがバカらしくなってくるなぁ。

実際に適当に実行してみて、 どんなふうに値が取れるのか見てみると、 キーワード指定したものはoptionsに、普通に指定したものはargsに入ってくるみたい。

$ ./hoge.py -f hoge.txt
{'verbose': True, 'filename': 'hoge.txt'}
[]

$ ./hoge.py -f hige.txt -q
{'verbose': False, 'filename': 'hige.txt'}
[]

$ ./hoge.py hoge -f hige.txt -q
{'verbose': False, 'filename': 'hige.txt'}
['hoge']

add_option の引数のキーワードの種類とか説明は、 リファレンスを見るのが一番良いって覚えておく。

Posted at: 
2008/03/09 00:26:16
0 Comments
1 TrackBack
Tags: 
CUI
Python
Trackback: 
http://humming.via-kitchen.com/2008/03/09/optionparser-memo/trackback/

Djangoのpre_saveとpost_saveがちょっと便利に

Django のちょっとしたコネタ。

モデルの save メソッドには、 raw って引数があるのですが、 r7054 から pre_savepost_save にもコレが入ってくるようになっております。

なので、 pre_savepost_save でコレを見てやる事で、 処理を分岐させる事が出来るようになりました。

rawTrue の場合、 余計な作業は省いてね。って事なので、 このブログではフックの最初で見てあげるようにしております。

def post_save_entry(instance, created, raw):
    """
    エントリーのセーブ後のフック
    """
    if raw or not created:
        # raw=Trueか新規登録じゃ無い場合はココでおしまい。
        return
    # 実際やりたい処理がつづく。

loaddata してデータを取り込む時なんかも rawTrue で入ってくるので、 こんな感じにしておくと余計な処理を省けるので便利。

Posted at: 
2008/02/17 00:39:07
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/17/more-useful-save-hook-on-django/trackback/

Djangoでパーマネントリダイレクト

前のブログからの変更でURLが結構変わったので、 どうしようかな?って思ってたんですが、 Django の汎用ビューにある redirect_to を使ってみる事に。

中を見てみると、必須な引数は url のみで、 残りの引数で置換してリダイレクトする。って仕組み。 また urlNone にすると、 ステータスコード410なレスポンスを返してくれる。

今回は /weblog/ はリダイレクトしてあげて、 /bookmark/ に来たのは410を返すようにしたかったので、 こんな感じにしてみたよ。

# urls.py
urpatterns = patterns('django.views.generic.simple',
    #   weblogに来たリクエストをリダイレクト。
    (r'^weblog/(?P<path>.*)$', 'redirect_to', {'url': '/%(path)s'}),
    #   bookmarkに来たリクエストは410を返す。
    (r'^bookmark/(?P<path>.*)$', 'redirect_to', {'url: None'}),
)

410の場合、ブラウザで見ると何も表示されないのが残念だけども、 気になるようならコピペして弄ってあげれば良いと思う。

こういうちょっとしたのが既に実装されてるってのは、 地味に嬉しかったりする。

Posted at: 
2008/02/10 01:35:20
0 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/10/permanent-redirect-on-django/trackback/

ブログ乗り換えました!

新しく作り直したブログに乗り換えました! て言っても、まだまだ安定してないと思われます。 ココからじゃんじゃんバグを直して行くよ!

Django のソースを眺めながら作った(読めてるかどうかは別)ので、 また少し Django と仲良くなれたような気がする。

URLが変わってしまったトコロはリダイレクトさせてる(つもり)なのですが、 リンク切れを発見された方は、教えて頂けるとありがたいです。 無くなっちゃったURLに関しては...。 まぁ、それはご愛嬌と言う事で。

ソースは ここらへん に転がっております。 ので、ツッコミとかも頂けると両手をあげて喜びます!

Djangor7054 以前で動かしてしまって、 ちょっとしたハプニングに見舞われたのは秘密です :-)

Posted at: 
2008/02/09 03:30:08
6 Comments
1 TrackBack
Tags: 
Django
memo
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/09/change-blog/trackback/

Categories

Archives