buildoutで開発3: easy_install できるように公開する¶
eggの作り方が分からない, buildoutで開発1: WSGIアプリをeggで作る, buildoutで開発2: buildoutで環境を整える の続き。buildoutというよりsetuptoolsネタ。
buildoutで自動的にパッケージを取ってくる仕組みは内部的にsetuptools/easy_install.pyを使用していて、setuptoolsはデフォルトでは対象パッケージ名を pypi (Python Package Index) に探しに行く。pypiへの登録は python setup.py register
で出来るんだけど、前回作ったようなwsgiappは実験用なので登録したくないし、仕事で作っているパッケージ類は世界に公開してはまずい。ということで、easy_installの --find-links (-f)
オプションでpypi以外のページを指定する方法でpypiに公開しなくても自動インストール出来るようにする。
(setuptoolsのマニュアルを読むと、そのまんまな記載がある: Making your package available for EasyInstall, Sumiさんの日本語訳)
前提¶
pypiに登録しない
wsgiappをSubversionから取ってくる
Subversionリポジトリをhttpで公開する¶
SVNの公開方法は file:
, svn:
, http:
とあるけど、easy_installの-fオプションではページ内のリンクにfile:と書いてあってもSVNとして認識されないので、素直にhttp:を使うことにする。あと、SVN以外のリポジトリも使えないっぽい。
ということで、httpdにmod_dav_svnで設定して公開する。URLは http://localhost:8080/repos
でSVNのルートが見えるようにしておく。
easy_install用のindexページを用意する¶
easy_installの-fオプションは指定したページのリンクをチェックして、リンクURLがegg用リンクであれば使ってくれる。eggとして認識されるリンクは、URL末尾に #egg=projectname-version
という記載があればよいので、開発版wsgiappの場合は http://localhost:8080/repos/wsgiapp/trunk#egg=wsgiapp-dev
となっていればeasy_installで認識してくれるようになる。
ということで、http://localhost:8080/index.htmlというページを作って上記URLをaタグで書いておく。
easy_installでインストールしてみる¶
実際に認識されるかどうか確認する。確認したいだけなので、 --dry-run
オプションを付けて実行。dry-runのせいで最後はエラーになるけど、途中までは以下のようにsubversionからcheckoutしてくれて、index.htmlがうまく働いている事が確認出来た。
ここまでくれば、index.htmlを自動生成するようにwsgiappを改造すれば後々楽になりそう。
wsgiappで動的にPackageIndexページを生成する¶
とりあえずてきとーに、'指定PATH/package名/trunk'を'package名#egg=package名-dev'としてリンクするように、 buildoutで開発2: buildoutで環境を整える で作成したwsgiapp.scraperに新しい関数を追加する。
wsgiapp/scraper.py の追加関数部分:
import urlparse
import string
def eggifyLinks(fileobj, basepath=''):
"""\
eggifyLinks read html data from given fileobj and modify href
attributes.
>>> from StringIO import StringIO
>>> fileobj = StringIO('''\
... <html><body><a href="wsgiapp">wsgiapp</a></body></html>
... ''')
>>> content = eggifyLinks(fileobj)
>>> 'href="wsgiapp/trunk#egg=wsgiapp-dev"' in content
True
``basepath`` param effect for relative url.
>>> fileobj = StringIO('''\
... <html><body><a href="wsgiapp">wsgiapp</a></body></html>
... ''')
>>> content = eggifyLinks(fileobj, 'http://domain/sub/')
>>> 'href="http://domain/sub/wsgiapp/trunk#egg=wsgiapp-dev"' in content
True
if href ends with '/', eggifyLinks return same result.
>>> fileobj = StringIO('''\
... <html><body><a href="wsgiapp/">wsgiapp</a></body></html>
... ''')
>>> content = eggifyLinks(fileobj)
>>> 'href="wsgiapp/trunk#egg=wsgiapp-dev"' in content
True
work with full url.
>>> fileobj = StringIO('''\
... <html><body><a href="http://localhost:8080/repos/wsgiapp/">wsgiapp</a></body></html>
... ''')
>>> content = eggifyLinks(fileobj)
>>> 'href="http://localhost:8080/repos/wsgiapp/trunk#egg=wsgiapp-dev"' in content
True
if url have #id, href is not modified.
>>> fileobj = StringIO('''\
... <html><body><a href="wsgiapp#foo">wsgiapp</a></body></html>
... ''')
>>> content = eggifyLinks(fileobj)
>>> 'href="wsgiapp#foo"' in content
True
>>> '#egg' not in content
True
if url have no package name, href is not modified.
>>> fileobj = StringIO('''\
... <html><body>
... <a href="..">Parent</a>
... <a href="http://domainonly/">domain</a>
... </body></html>
... ''')
>>> content = eggifyLinks(fileobj)
>>> 'href=".."' in content
True
>>> 'href="http://domainonly/"' in content
True
>>> '#egg' not in content
True
"""
baseparts = urlparse.urlparse(basepath)
bs = BeautifulSoup(fileobj)
for elem in bs.findAll('a'):
if elem.has_key('href'):
href = elem['href']
parts = list(urlparse.urlparse(href))
# #id check
if parts[5]:
continue # #id already exist
# modify path
path = parts[2]
if path.endswith('/'):
path = path[:-1]
pkgname = path.split('/')[-1]
if not pkgname or pkgname[0] not in string.letters:
continue # pkgname does not seem package name
parts[2] = '%(path)s/trunk#egg=%(pkgname)s-dev' % locals()
# modify domain
if basepath and not parts[1]:
parts[0] = baseparts[0]
parts[1] = baseparts[1]
if parts[2][0] != '/':
p = baseparts[2]
if p.endswith('/'):
p = p[:-1]
parts[2] = p + '/' + parts[2]
# update href
elem['href'] = urlparse.urlunparse(parts)
return bs.prettify()
テストする。
呼出元を新しい関数に変更。
wsgiapp/startup.py の変更部分:
import urllib2
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/html')]
start_response(status, response_headers)
return [scraper.eggifyLinks(
urllib2.urlopen("http://localhost:8080/repos/"),
"http://localhost:8080/repos/",
)]
実際に動作させた時の出力を bin/paster request wsgi.ini /
で確認。
easy_installでうまく動くか確認するため、wsgiappをサーバー動作させてから、別コンソールでeasy_installを-fオプション付きで動かしてみてwsgiappパッケージを見つけられれば成功。8080ポートはapacheで使ってるので8180で起動するようにwsgi.iniを変更しておく。
dry run なのでsetup.pyの実行には失敗する。実際にインストールする場合は-nを外して実行してみよう。
あとは、このwsgiappをmod_wsgiで動作するようにしておけば、超簡易版のローカル用パッケージ一覧生成ツールとして使える。使えるといいなぁ。
もっとちゃんとやろうと思ったら、pysvn等でパッケージの一覧を取ってきて、各パッケージのtrunkのURLに、 #egg=パッケージ名-dev と付けたり、tagsから自動で #egg=パッケージ名-tag名 としてみたりすればいいんだけど、毎回動的にやってると重いし、そこまでやるんだったらローカルにPyPIを立ち上げた方が良いと思う。作り方は EggBasket や how to run your own private PyPI (Cheeseshop) server << Fetchez le Python を参考すればよさそう。