:date: 2009-07-30 17:29:57 :tags: python, Programming, web ============================================================= 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がうまく働いている事が確認出来た。 .. topic:: easy_install --dry-run :class: dos | > easy_install --dry-run -f http://localhost:8080/ wsgiapp | Searching for wsgiapp | Reading http://localhost:8080/ | Best match: wsgiapp dev | Downloading http://localhost:8080/repos/wsgiapp/trunk#egg=wsgiapp-dev | Doing subversion checkout from http://localhost:8080/repos/wsgiapp/trunk to \\temp\\easy_install-tlvn6p\trunk | Processing trunk | Running setup.py .... ここまでくれば、index.htmlを自動生成するようにwsgiappを改造すれば後々楽になりそう。 wsgiappで動的にPackageIndexページを生成する ------------------------------------------- とりあえずてきとーに、'指定PATH/package名/trunk'を'package名#egg=package名-dev'としてリンクするように、 `buildoutで開発2: buildoutで環境を整える`_ で作成したwsgiapp.scraperに新しい関数を追加する。 wsgiapp/scraper.py の追加関数部分: .. code-block:: python 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('''\ ...
wsgiapp ... ''') >>> content = eggifyLinks(fileobj) >>> 'href="wsgiapp/trunk#egg=wsgiapp-dev"' in content True ``basepath`` param effect for relative url. >>> fileobj = StringIO('''\ ... wsgiapp ... ''') >>> 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('''\ ... wsgiapp ... ''') >>> content = eggifyLinks(fileobj) >>> 'href="wsgiapp/trunk#egg=wsgiapp-dev"' in content True work with full url. >>> fileobj = StringIO('''\ ... wsgiapp ... ''') >>> 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('''\ ... wsgiapp ... ''') >>> 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('''\ ... ... Parent ... domain ... ... ''') >>> 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() テストする。 .. topic:: テスト :class: dos | > bin/test.exe | Running zope.testing.testrunner.layer.UnitTests tests: | Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds. | Ran 2 tests with 0 failures and 0 errors in 0.887 seconds. | Tearing down left over layers: | Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds. 呼出元を新しい関数に変更。 wsgiapp/startup.py の変更部分: .. code-block:: python 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 /`` で確認。 .. topic:: paster request :class: dos | > bin/paster request wsgi.ini / | | |