pysetup3のご紹介

python2ではsetuptoolsやdistutilsがありますが、python3では "packaging"という新しいパッケージ用ライブラリが搭載されます。

pysetup3はこのpackagingを使うためのコマンドです。これは、setuptoolsやdistutilsを使うためのコマンドがpipやeasy_installである、という関係と同じです。

まとめるとこういう関係になります。(正確にはちょっとずれていますがまあこんなものかと)

ライブラリ ツール
python3.2まで distutils/setuptools/distribute easy_install/pip/buildout
python3.3〜 packaging pysetup3

なお、packagingはpython2.4〜3.2ではdistutils2という名前でバックポートされ、サードパーティパッケージとして配布されます。

pysetup3のドキュメントはこちらです。
http://docs.python.org/dev/install/pysetup.html

使い方

pysetup3はpython3.3から標準搭載されます。python 3.3はまだリリースされていないので、使うには http://hg.python.org/cpython/ から開発版を取得してくる必要があります。あるいは、python2〜3.2で使うにはhttps://bitbucket.org/tarek/distutils2/wiki/Home からソースをとってくる必要があります。なお、distutils2の場合、listコマンドが使えないなどの制限がありますのでご注意ください。

さて、pysetup3の使い方です。まずはhelpを見ます。

  % pysetup3 --help
  Usage: pysetup [options] action [action_options]

Actions:
    run: Run one or several commands
    metadata: Display the metadata of a project
    install: Install a project
    remove: Remove a project
    search: Search for a project in the indexes
    list: List installed projects
    graph: Display a graph
    create: Create a project
    generate-setup: Generate a backward-compatible setup.py

ちなみに、distutils2ではこうなります。

  Actions:
    run: Run one or several commands
    metadata: Display the metadata of a project
    install: Install a project
    remove: Remove a project
    search: Search for a project
    graph: Display a graph
    create: Create a Project

listがないのはどういうことだって言う感じですが…

検索 (search)

ということで、まずはbuchoパッケージを検索してみます。

  % pysetup3 search bucho
  not implemented
  %

...


えーっと。実装されてないって…

気を取り直して次行きましょう。

インストール (install)

落としてきたtar.gzを使って install してみます。

  % pysetup3 install bucho-0.1.1.tar.gz
  Installing from archive: '/home/prosou/shirou/bucho-0.1.1.tar.gz'
  Traceback (most recent call last):
    File "setup.py", line 2, in <module>
      import setuptools
  ImportError: No module named 'setuptools'
  failed to install

あー、buchoはsetuptoolsに依存しているのですね。pysetup3 install distribute をしたらこのエラーは出なくなりましたが、代わりに

  option --single-version-externally-managed not recognized

と言われてしまいました。よく分かりません…

pysetup3の作者、tarekのパッケージなら平気だと思います。

  % pysetup3.3 install flake8-1.0.tar.gz
    .....

  % pysetup3.3 list
   'flake8' 1.0 (from '/home/rudi/local/lib/python3.3/site-packages/flake8-1.0-py3.3.dist-info')
  Found 1 projects installed.

うまくインストールできたようです。

なお、インストールは

からもインストールできます。また、

  % pysetup3 install project==1.0

というようにすることで、インストールするバージョンを決めることもできます。

インストール済みのパッケージリスト (list)

listで今インストールされているパッケージのリストが出ます。

  % pysetup3.3 list
   'flake8' 1.0 (from '/home/rudi/local/lib/python3.3/site-packages/flake8-1.0-py3.3.dist-info')
  Found 1 projects installed.

情報の表示 (metadata)

パッケージの情報を得るにはmetadataを使います。

  % pysetup3.3 metadata flake8
  Metadata-Version:
      1.1
  Name:
      flake8
  Version:
      1.0
  Platform:
      UNKNOWN
  Supported-Platform:
  Summary:
      code checking using pep8 and pyflakes
  Description:
      ======  
  (以下省略)

また、-fをつけることにより、表示される情報を制限することも出来ます。

  % pysetup3 metadata -f name -f version flake8
  Name:
      flake8
  Version:
      1.0

インストールされているものにしか使えないようです。

グラフの表示 (graph)

  % pysetup3.3 graph flake8
  'flake8' 1.0

依存するパッケージのグラフを表示してくれるそうです。

アンインストール (remove)

  % pysetup3.3 remove flake8
  'flake8' cannot be removed.
  Error: [Errno 18] Cross-device link

what? …ちょっと今回は追いませんが、これがきちんと出来るのがいいところなのに…

プロジェクトを作成する (create)

createコマンドを打つと、setup.cfgを対話型で作ってくれます。途中でyを答えると今のディレクトリをチェックして全部含めてくれますので便利です。

ちなみに、pysetupはsetup.pyではなく、setup.cfgを使うようになることに注意してください。 (このあたりはpycon 2011 JPのtarekの講演を参考にしてください。)

  % pysetup3.3 create
  Project name [setup]: test
  Current version number [1.0.0]: 
  Project description summary: 
     > Thi is test
  Author name: shirou
  Author email address: rudy@example.com
  Project home page: test
  Do you want me to automatically build the file list with everything I
  can find in the current directory? If you say no, you will have to
  define them manually. (y/n): y
  Do you want to set Trove classifiers? (y/n): y
  Please select the project status:
  0 - Planning
  1 - Pre-Alpha
  2 - Alpha
  3 - Beta
  4 - Production/Stable
  5 - Mature
  6 - Inactive
  
  Status: 1
  What license do you use?: BSD
  Matching licenses:
  
     1) License :: OSI Approved :: BSD License
  
  Type the number of the license you wish to use or ? to try again:: 1
  What license do you use?:
  Do you want to set other trove identifiers? (y/n) [n]: n
  Wrote "setup.cfg".

また、setup.pyがあると

  % pysetup3 create
  A legacy setup.py has been found.
  Would you like to convert it to a setup.cfg? (y/n)
   [y]: y

と聞いてくれます。でもsphinxを試したら ImportError: No module named 'sphinx' と言われてしまいました。

互換性のためにsetup.pyを作る (genearte-setup)

  % generate-setup
  The setup.py was generated

とすると、setup.pyが出来上がります。でも、現段階では別にディレクトリの内容を読んでくれるわけではなく、単にsetup.pyのひな形ができるだけです。

コマンド (run)

pysetup3には今まで述べてきたコマンドの他にも setup.py 相当コマンドがあります。 setup.pyが使われなくなる代わりに、今までsetup.pyで行ってきたことがここに入っているという感じですね。

  % pysetup3 run --list-commands

  List of available commands:
    bdist: create a built (binary) distribution
    bdist_dumb: create a "dumb" built distribution
    bdist_wininst: create an executable installer for Windows
    build: build everything needed to install
    build_clib: build C/C++ libraries used by extension modules
    build_ext: build C/C++ extension modules (compile/link to build
    directory)
    build_py: build pure Python modules (copy to build directory)
    build_scripts: build scripts (copy and fix up shebang line)
    check: check PEP compliance of metadata
    clean: clean up temporary files from 'build' command
    install_data: install platform-independent data files
    install_dist: install everything from build directory
    install_distinfo: create a .dist-info directory for the distribution
    install_headers: install C/C++ header files
    install_lib: install all modules (extensions and pure Python)
    install_scripts: install scripts (Python or otherwise)
    register: register a release with PyPI
    sdist: create a source distribution (tarball, zip file, etc.)
    test: run the project's test suite
    upload: upload distribution to PyPI
    upload_docs: upload HTML documentation to PyPI

まとめ

というわけで、python3.3から使えるようになるpysetupコマンドについて述べてきました。正直まだ使えないなというレベルではありますが、3.3リリースが予定されているのは来年8月ですし、これからどんどん良くなっていくと思います。

明日は初心者向けのエントリを書いてくださる @takanory さんにお願いします。

Blockdiag Advent Calander 1日目 href機能

blockdiagアドベントカレンダー、初日です。
今回はblockdiag 1.1.1から入ったhref機能についてご紹介します。

href機能とは、ノードの属性にhrefを指定することで、SVG形式で出力する時そのノードをクリックをしたらhref属性で指定したURLに飛ぶ、という機能です。

例えば、

diag {
  A [label="blockdiag", href="http://blockdiag.com"]
  B [label="python", href="http://python.org"]

  A -> B
}

としておき、SVG形式で生成します。SVGファイルをブラウザで開いてそれぞれのノードをクリックすると、指定したURLに飛びます。

ただし、残念ながらxlink:hrefに対応していないSVGエディタでは飛びませんし、PNG形式では無意味です。

sphinxとの連携

現在sphinxcontribにもこの機能を使うように変更を行っている最中です。反映されると、以下のように書けるようになります。

.. blockdiag::

   diagram {

    A [label="blockdiag", href="http://blockdiag.com"]
    B [label="python", href=":ref:python"]

    A -> B
   }

ノードBのhrefに注目してください。:ref:pythonと指定してあります。sphinxでは

.. _python:

を章や節の前に(一行開けて)書くとそこに対して :ref:python でリンクを貼れます。これと同じ事がsphinxのblockdiag内でもできます。

また、sphinxと連携した場合、PNG形式の場合でもクリッカブルマップを自動的に設定してクリックできるようになります。

使い道

自動生成を行うと、大きな図が出来てしまうことがあります。そういう場合は分割させたいのですが、関連が分からなくなってしまいます。この様な時にhref機能があると便利です。

今後

今後グループにもこの機能を追加するかもしれません。blockdiagにはグループを別のファイルに分割する機能(-sオプション)がありますので、リンクを自動的にしてくれるならば、大きな図でも見やすくできるかもしれないと考えています。

mercurialのファイルを(なぜか)タイムスタンプで履歴管理

履歴管理はファイル名に日付を付けるのが基本ですよね。

ということで、mercurialのチェンジセットに含まれているファイルを、日付を付けたファイル名で展開するスクリプトを作りました。レポジトリのディレクトリに移動し、このスクリプトを実行してください。

…ええ、ええ、言いたいことはよーく分かっています。でもですね、なにも言わないでください…

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# This script extracts changed files in changesets with timestamp suffix.
# 
# Yes, I know timestamp based history management is not the mercurial way.
# but, but, sigh...
#
# license: new BSD
# 

from mercurial import ui
from mercurial import hg

import datetime
import os

MAXAGE = 10  # 展開するチェンジセットの数
FORMAT = "%Y%m%d-%H%M%S"

u = ui.ui()
repo = hg.repository(u, '.')

tip = repo.changectx("tip")
tip_rev = tip.rev()

for i in range(tip_rev, tip_rev - MAXAGE, -1):
    ctx = repo.changectx(i)
    unix_timestamp = ctx.date()[0]
    tz = ctx.date()[1]  # currently not used
    d = datetime.datetime.fromtimestamp(unix_timestamp)

    for filename in ctx.files():
        fctx = ctx[filename]
        fname = os.path.join(repo.root,
                             "{0}-{1}".format(filename, d.strftime(FORMAT))
                             )
        try:
            f = open(fname, "w")
            f.write(fctx.data())
        finally:
            if f:
                f.close()

denyhostsの設定を見直した

生活のかなりの部分を依存しているサーバをさくらのVPSサーバに移行したんだけど、sshアタックがすごい。同じホストから1秒間に3回ぐらいくる。

もちろんdenyhostsを入れてるんだけど、それでもすごい数のアタックが来ていることがログから分かってて、なんでかな、と思って設定ファイルをみてたらその理由が分かりました。

# DAEMON_SLEEP: when DenyHosts is run in daemon mode (--daemon flag)
# this is the amount of time DenyHosts will sleep between polling
# the SECURE_LOG.  See the comments in the PURGE_DENY section (above)
# for details on specifying this value or for complete details
# refer to:    http://denyhosts.sourceforge.net/faq.html#timespec
#
DAEMON_SLEEP = 30s

という設定があり、初期設定は30秒になっている。これはなにかと言うと、daemonモードでチェックする間隔を設定するもの。つまり、daemonモードではこの間隔でしかチェックしないので、今のように1秒間に3回くらってもすぐには対応できない、というわけ。

もちろん、辞書攻撃じゃ30秒では破れないだろうからセキュリティ的には問題ない(と思う)だろうけど、ログがたくさんになるのはちょっとね。

というわけで、ちょっと間隔を狭めてみた。これでどうなるかな。

fabricでトンネルする

最近fabricを使っていろいろ作業しています。

で、踏み台サーバごしにアクセスする必要があるマシンがあるのですが、fabricの中でトンネルを作って作業したいなと思いました、ちょっと調べてみると https://gist.github.com/856179 にcodeがありましたので、それを利用させてもらいました。ありがとうございます。

具体的には以下のような感じにしました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fabric.api import run, env

# 事前にtunnel.pyを https://gist.github.com/856179 から入手
from tunnel import make_tunnel

def staging():
    env.hosts = ['example1']

def get_hostname():
    with make_tunnel('your_tunnel_host_name') as t:
        run('hostname')

なお、env.host_stringを使っている場合は、tunnelの中でenv.host_stringを127.0.0.1に書き換えてしまいますので、withの前で別のところにとっておきましょう。

Beautiful SoupでHTMLやXMLをparseしよう

Beautiful SoupはHTMLやXMLをparseしてくれるパーサーライブラリです。でも、Beautiful Soupは単なるパーサーじゃなくてちょっと賢い奴なんです。

今回、このBeautiful Soupのドキュメントを翻訳しました。

http://www.tdoc.info/beautifulsoup/

下に軽く紹介していますが、詳しくはこのドキュメントをご覧ください。

Beautiful Soupをざっくり紹介

Beautiful Soupがどういうやつかって?例えばこんなHTMLがあったとしましょう。

<HTML>
<body>

改行<br>
したいよね
<br/>
<! --あれ、空白が入ってるちょっと変なコメント-->
<p>
bodyが閉じてないよ?
</html>

結構変なHTMLですよね。でも、巷にはこういうのも結構あるんです。で、これをtest1.htmlだとします。これをこんな感じのスクリプトでパースしてみると、

soup = BeautifulSoup(open("test1.html"))
print soup.prettify()

こうなります。

<html>
 <body>
  改行
  <br />
  したいよね
  <br />
  <!--あれ、空白が入ってるちょっと変なコメント-->
  <p>
   bodyが閉じてないよ?
  </p>
 </body>
</html>

他にも、

<html>
<form>
 <table>
 <td><input name="input1">一行目二列目
 <tr><td>二行目
 </form>あれ、テーブル閉じてないよ?
 <td>こっちは?
</html>

<html>
 <form>
  <table>
   <td>
    <input name="input1" />
    一行目二列目
   </td>
   <tr>
    <td>
     二行目
    </td>
   </tr>
  </table>
 </form>
 あれ、テーブル閉じてないよ?
 <td>
  こっちは?
 </td>
</html>

という感じにしてくれます。

インストール

ふつーpipですよねー。

% pip install beautifulsoup

使い方

まずはimportします。

from BeautifulSoup import BeautifulSoup          # HTMLの場合こっち
from BeautifulSoup import BeautifulStoneSoup     # XMLの場合こっち
import BeautifulSoup # 全部の場合これ

その後、BeautifulSoupコンストラクタを作って、あとはこれを使います。

soup = BeautifulSoup(html_string)

タグを検索

例えば、こういうHTMLドキュメントがあったとします。

<html>
<h1>H1です</h1>
段落
<p>
ですよ<br/>
</p>
<table>
<tr id="tr-1">閉じてないけどtr-1です<td>td-1-1</td><td>td-1-2
<tr id="tr-2">tr-2です<td class="td">td-2-1</td><td>td-3
</html>

タグはタグの名前で検索できますし、nextSiblingとかを使って順々にたどることも出来ます。もちろんforで回すことも出来ます。

parentで上に、contentsで下に、nextSiblingとprevSiblingで同じ階層にたどる、という感じでしょうか。

soup = BeautifulSoup(doc)
print(soup.h1) # h1を表示
print("-------------------------------")
print(soup.table.td) # <table><td>の最初を表示
print("-------------------------------")
print(soup.table.tr) # <table><tr>の最初表示
print("-------------------------------")
tr1 = soup.table.tr
print(tr1.nextSibling) # tr-1のnextSibling
print("-------------------------------")
print(tr1.nextSibling["id"]) # tr-2のid属性
print("-------------------------------")
print(tr1.nextSibling.contents[1]["class"]) # tr-2の子のtdのclass属性
print("-------------------------------")

とすると、こう出ます。

<h1>H1です</h1>
-------------------------------
<td>td-1-1</td>
-------------------------------
<tr id="tr-1">閉じてないけどtr-1です<td>td-1-1</td><td>td-1-2
</td></tr>
-------------------------------
<tr id="tr-2">tr-2です<td class="td">td-2-1</td><td>td-3
</td></tr>
-------------------------------
tr-2
-------------------------------
td

検索

パースしたツリーを検索できます。

print(soup.findAll('td')) # tdを全部見つけてきます。
print("-------------------------------")
import re
print(soup.findAll(re.compile('^t'))) # tで始まるタグを全部持ってきます
print("-------------------------------")
[<td>td-1-1</td>, <td>td-1-2
</td>, <td class="td">td-2-1</td>, <td>td-3
</td>]
-------------------------------
[<table>
<tr id="tr-1">閉じてないけどtr-1です<td>td-1-1</td><td>td-1-2
</td></tr><tr id="tr-2">tr-2です<td class="td">td-2-1</td><td>td-3
</td></tr></table>, <tr id="tr-1">閉じてないけどtr-1です<td>td-1-1</td><td>td-1-2
</td></tr>, <td>td-1-1</td>, <td>td-1-2
</td>, <tr id="tr-2">tr-2です<td class="td">td-2-1</td><td>td-3
</td></tr>, <td class="td">td-2-1</td>, <td>td-3
</td>]
-------------------------------

他にも

Beautiful Soupはこんなこともできます。

  • 属性の値を入れ替えたり、新しい属性を付けたりする
  • あるエレメントを別なエレメントに置き換える
  • UnicodeDammitというクラスを使ってエンコーディングを判定する
  • HTMLの実体参照を対応するユニコード文字に変換する

詳しくは翻訳したドキュメントを参照してください。