[Pypi-checkins] r759 - in trunk/pypi: . templates tools
martin.von.loewis
python-checkins at python.org
Sat Apr 24 12:22:22 CEST 2010
Author: martin.von.loewis
Date: Sat Apr 24 12:22:22 2010
New Revision: 759
Added:
trunk/pypi/tools/sql-migrate-20100313.sql
- copied unchanged from r758, branches/tarek-pep-345-support/tools/sql-migrate-20100313.sql
Modified:
trunk/pypi/ (props changed)
trunk/pypi/README
trunk/pypi/config.ini
trunk/pypi/pkgbase_schema.sql
trunk/pypi/pypi.wsgi
trunk/pypi/rpc.py
trunk/pypi/store.py
trunk/pypi/templates/display.pt
trunk/pypi/templates/standard_template.pt
trunk/pypi/tools/apache_count_dist.py (props changed)
trunk/pypi/tools/sql-migrate-20090327.sql (props changed)
trunk/pypi/webui.py
Log:
Merge tarek-pep-345-support.
Modified: trunk/pypi/README
==============================================================================
--- trunk/pypi/README (original)
+++ trunk/pypi/README Sat Apr 24 12:22:22 2010
@@ -1,13 +1,44 @@
-
Required packages
-----------------------
+-----------------
+
+To run the PyPI software, you need PostgreSQL, and all
+these packages located at PyPI:
+
+- cElementTree
+- zope.interface
+- zope.pagetemplate
+- zope.tal
+- zope.tales
+- zope.i18nmessageid
+- psycopg2
+- M2Crypto
+- BeautifulSoup
+- docutils
+- distutils2
+
+Quick development setup
+-----------------------
+
+Make sure you read http://wiki.python.org/moin/CheeseShopDev#DevelopmentEnvironmentHints
+and you have a working PostgreSQL DB.
+
+Make sure your config.ini is up-to-date. Change CONFIG_FILE at the begining
+of pypi.wsgi, so it looks like this::
+
+ CONFIG_FILE = 'config.ini'
+
+Then, you can create a development environment like this, if you have
+virtualenv installed::
+
+ $ virtualenv --no-site-packages --distribute .
+ $ bin/easy_install cElementTree zope.interface zope.pagetemplate
+ $ bin/easy_install zope.tal zope.tales zope.i18nmessageid psycopg2
+ $ bin/easy_install M2Crypto BeautifulSoup docutils
+
+Then you can launch the server using the pypi.wsgi script::
+
+ $ bin/python pypi.wsgi
+ Serving on port 8000...
+
+PyPI will be available in your browser at http://localhost:8000
-To run the PyPI software, you need:
- cElementTree
- zope.interface (in PyPI)
- zope.pagetemplates (in PyPI)
- zope.tal
- zope.tales
- zope.i18nmessageid
- psycopg2 and PostgreSQL
- M2Crypto
Modified: trunk/pypi/config.ini
==============================================================================
--- trunk/pypi/config.ini (original)
+++ trunk/pypi/config.ini Sat Apr 24 12:22:22 2010
@@ -7,6 +7,7 @@
[webui]
mailhost = mail.commonground.com.au
adminemail = richard at commonground.com.au
+replyto = richard at commonground.com.au
url = http://localhost/cgi-bin/pypi.cgi
pydotorg = http://www.python.org/
simple_script = /simple
@@ -18,9 +19,9 @@
simple_sign_script = /serversig
[logging]
-file =
-mailhost =
-fromaddr =
+file =
+mailhost =
+fromaddr =
toaddrs =
[mirrors]
Modified: trunk/pypi/pkgbase_schema.sql
==============================================================================
--- trunk/pypi/pkgbase_schema.sql (original)
+++ trunk/pypi/pkgbase_schema.sql Sat Apr 24 12:22:22 2010
@@ -1,10 +1,10 @@
-- Table structure for table: users
-CREATE TABLE users (
- name TEXT PRIMARY KEY,
- password TEXT,
- email TEXT,
+CREATE TABLE users (
+ name TEXT PRIMARY KEY,
+ password TEXT,
+ email TEXT,
gpg_keyid TEXT,
- last_login TIMESTAMP,
+ last_login TIMESTAMP
);
CREATE INDEX users_email_idx ON users(email);
@@ -12,7 +12,7 @@
CREATE TABLE openids (
id TEXT PRIMARY KEY,
- name TEXT REFERENCES users,
+ name TEXT REFERENCES users
);
CREATE TABLE openid_sessions (
@@ -41,7 +41,7 @@
CREATE TABLE cookies (
cookie text PRIMARY KEY,
name text references users,
- last_seen timestamp,
+ last_seen timestamp
);
CREATE INDEX cookies_last_seen ON cookies(last_seen);
@@ -54,33 +54,33 @@
CREATE INDEX rego_otk_otk_idx ON rego_otk(otk);
-- Table structure for table: rego_otk
-CREATE TABLE rego_otk (
- name TEXT REFERENCES users,
+CREATE TABLE rego_otk (
+ name TEXT REFERENCES users,
otk TEXT,
date TIMESTAMP );
CREATE INDEX rego_otk_name_idx ON rego_otk(name);
-- Table structure for table: journals
-CREATE TABLE journals (
- name TEXT,
- version TEXT,
- action TEXT,
- submitted_date TIMESTAMP,
- submitted_by TEXT REFERENCES users,
+CREATE TABLE journals (
+ name TEXT,
+ version TEXT,
+ action TEXT,
+ submitted_date TIMESTAMP,
+ submitted_by TEXT REFERENCES users,
submitted_from TEXT
);
CREATE INDEX journals_name_idx ON journals(name);
CREATE INDEX journals_version_idx ON journals(version);
-CREATE INDEX journals_latest_releases ON
- journals(submitted_date, name, version)
+CREATE INDEX journals_latest_releases ON
+ journals(submitted_date, name, version)
WHERE version IS NOT NULL AND action='new release';
-CREATE INDEX journals_changelog ON
+CREATE INDEX journals_changelog ON
journals(submitted_date, name, version, action);
-- Table structure for table: packages
-CREATE TABLE packages (
- name TEXT PRIMARY KEY,
+CREATE TABLE packages (
+ name TEXT PRIMARY KEY,
stable_version TEXT,
normalized_name TEXT,
autohide BOOLEAN DEFAULT TRUE,
@@ -122,7 +122,7 @@
-- Table structure for table: releases
-CREATE TABLE releases (
+CREATE TABLE releases (
name TEXT REFERENCES packages ON UPDATE CASCADE,
version TEXT,
author TEXT,
@@ -137,25 +137,22 @@
keywords TEXT,
platform TEXT,
download_url TEXT,
+ requires_python TEXT,
cheesecake_installability_id INTEGER REFERENCES cheesecake_main_indices,
cheesecake_documentation_id INTEGER REFERENCES cheesecake_main_indices,
cheesecake_code_kwalitee_id INTEGER REFERENCES cheesecake_main_indices,
_pypi_ordering INTEGER,
_pypi_hidden BOOLEAN,
- cheesecake_installability_id INTEGER REFERENCES cheesecake_main_indices,
- cheesecake_documentation_id INTEGER REFERENCES cheesecake_main_indices,
- cheesecake_code_kwalitee_id INTEGER REFERENCES cheesecake_main_indices,
PRIMARY KEY (name, version)
);
CREATE INDEX release_pypi_hidden_idx ON releases(_pypi_hidden);
-
-- Table structure for table: trove_classifiers
-- l2, l3, l4, l5 is the corresponding parent;
-- 0 if there is no parent on that level (each node is its
-- own parent)
-CREATE TABLE trove_classifiers (
- id INTEGER PRIMARY KEY,
+CREATE TABLE trove_classifiers (
+ id INTEGER PRIMARY KEY,
classifier TEXT UNIQUE,
l2 INTEGER,
l3 INTEGER,
@@ -219,12 +216,66 @@
CREATE INDEX rel_obs_version_id_idx ON release_obsoletes(version);
CREATE INDEX rel_obs_name_version_idx ON release_obsoletes (name,version);
+-- Table structure for table: release_requires_external
+CREATE TABLE release_requires_external (
+ name TEXT,
+ version TEXT,
+ specifier TEXT,
+ FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
+);
+CREATE INDEX rel_req_ext_name_idx ON release_requires_external(name);
+CREATE INDEX rel_req_ext_version_id_idx ON release_requires_external(version);
+CREATE INDEX rel_req_ext_name_version_idx ON release_requires_external(name,version);
+
+-- Table structure for table: release_requires_dist
+CREATE TABLE release_requires_dist (
+ name TEXT,
+ version TEXT,
+ specifier TEXT,
+ FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
+);
+CREATE INDEX rel_req_dist_name_idx ON release_requires_dist(name);
+CREATE INDEX rel_req_dist_version_id_idx ON release_requires_dist(version);
+CREATE INDEX rel_req_dist_name_version_idx ON release_requires_dist(name,version);
+
+-- Table structure for table: release_provides_dist
+CREATE TABLE release_provides_dist (
+ name TEXT,
+ version TEXT,
+ specifier TEXT,
+ FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
+);
+CREATE INDEX rel_prov_dist_name_idx ON release_provides_dist(name);
+CREATE INDEX rel_prov_dist_version_id_idx ON release_provides_dist(version);
+CREATE INDEX rel_prov_dist_name_version_idx ON release_provides_dist(name,version);
+
+-- Table structure for table: release_obsoletes_dist
+CREATE TABLE release_obsoletes_dist (
+ name TEXT,
+ version TEXT,
+ specifier TEXT,
+ FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
+);
+CREATE INDEX rel_obs_dist_name_idx ON release_obsoletes_dist(name);
+CREATE INDEX rel_obs_dist_version_id_idx ON release_obsoletes_dist(version);
+CREATE INDEX rel_obs_dist_name_version_idx ON release_obsoletes_dist(name,version);
+
+-- Table structure for table: release_project_url
+CREATE TABLE release_project_url (
+ name TEXT,
+ version TEXT,
+ specifier TEXT,
+ FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
+);
+CREATE INDEX rel_proj_url_name_idx ON release_project_url(name);
+CREATE INDEX rel_proj_url_version_id_idx ON release_project_url(version);
+CREATE INDEX rel_proj_url_name_version_idx ON release_project_url(name,version);
-- Table structure for table: package_files
-- python version is only first two digits
-- actual file path is constructed <py version>/<a-z>/<name>/<filename>
-- we remember filename because it can differ
-CREATE TABLE release_files (
+CREATE TABLE release_files (
name TEXT,
version TEXT,
python_version TEXT,
@@ -243,10 +294,10 @@
-- Table structure for table: package_urls
-CREATE TABLE release_urls (
+CREATE TABLE release_urls (
name TEXT,
version TEXT,
- url TEXT,
+ url TEXT,
packagetype TEXT,
FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
);
@@ -266,9 +317,9 @@
-- Table structure for table: roles
-- Note: roles are Maintainer, Admin, Owner
-CREATE TABLE roles (
- role_name TEXT,
- user_name TEXT REFERENCES users,
+CREATE TABLE roles (
+ role_name TEXT,
+ user_name TEXT REFERENCES users,
package_name TEXT REFERENCES packages ON UPDATE CASCADE
);
CREATE INDEX roles_pack_name_idx ON roles(package_name);
@@ -287,7 +338,7 @@
-- Table structure for table: timestamps
-- Note: stamp_name is ftp, http
CREATE TABLE browse_tally (
- trove_id INTEGER PRIMARY KEY,
+ trove_id INTEGER PRIMARY KEY,
tally INTEGER
);
@@ -307,7 +358,7 @@
in_reply_to INTEGER REFERENCES comments ON DELETE CASCADE
);
CREATE TABLE ratings(
- id SERIAL UNIQUE;
+ id SERIAL UNIQUE,
name TEXT,
version TEXT,
user_name TEXT REFERENCES users ON DELETE CASCADE,
Modified: trunk/pypi/pypi.wsgi
==============================================================================
--- trunk/pypi/pypi.wsgi (original)
+++ trunk/pypi/pypi.wsgi Sat Apr 24 12:22:22 2010
@@ -6,18 +6,24 @@
store.keep_conn = True
+CONFIG_FILE = 'config.ini'
+
class Request:
def __init__(self, environ, start_response):
self.start_response = start_response
- self.rfile = cStringIO.StringIO(environ['wsgi.input'].read())
+ try:
+ length = int(environ['CONTENT_LENGTH'])
+ except ValueError:
+ length = 0
+ self.rfile = cStringIO.StringIO(environ['wsgi.input'].read(length))
self.wfile = cStringIO.StringIO()
- self.config = config.Config('/data/pypi/config.ini')
-
+ self.config = config.Config(CONFIG_FILE )
+
def send_response(self, code, message='no details available'):
self.status = '%s %s' % (code, message)
self.headers = []
-
+
def send_header(self, keyword, value):
self.headers.append((keyword, value))
@@ -47,3 +53,11 @@
r = Request(environ, start_response)
webui.WebUI(r, environ).run()
return [r.wfile.getvalue()]
+
+if __name__ == '__main__':
+ # very simple wsgi server so we can play locally
+ from wsgiref.simple_server import make_server
+ httpd = make_server('', 8000, application)
+ print "Serving on port 8000..."
+ httpd.serve_forever()
+
Modified: trunk/pypi/rpc.py
==============================================================================
--- trunk/pypi/rpc.py (original)
+++ trunk/pypi/rpc.py Sat Apr 24 12:22:22 2010
@@ -7,7 +7,7 @@
class RequestHandler(SimpleXMLRPCDispatcher):
"""A request dispatcher for the PyPI XML-RPC API."""
-
+
def __init__(self):
SimpleXMLRPCDispatcher.__init__(self, True, 'utf-8')
self.register_function(list_packages)
@@ -23,7 +23,7 @@
self.register_function(ratings)
self.register_introspection_functions()
self.register_multicall_functions()
-
+
def __call__(self, webui_obj):
webui_obj.handler.send_response(200, 'OK')
webui_obj.handler.send_header('Content-type', 'text/xml')
@@ -77,7 +77,9 @@
def release_data(store, package_name, version):
info = store.get_package(package_name, version).as_dict()
del info['description_html']
- for col in ('requires', 'provides', 'obsoletes'):
+ for col in ('requires', 'provides', 'obsoletes', 'requires_dist',
+ 'obsoletes_dist', 'project_url', 'provides_dist',
+ 'requires_external'):
rows = store.get_release_relationships(package_name, version, col)
info[col] = [row['specifier'] for row in rows]
classifiers = [r[0] for r in store.get_release_classifiers(package_name,
Modified: trunk/pypi/store.py
==============================================================================
--- trunk/pypi/store.py (original)
+++ trunk/pypi/store.py Sat Apr 24 12:22:22 2010
@@ -79,6 +79,7 @@
def utf8get(fields):
if fields[n] is None: return fields[n]
return fields[n].decode('utf-8', 'replace')
+
return utf8get
def itemgetter(n):
@@ -199,7 +200,6 @@
journal entry.
'''
date = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())
-
cursor = self.get_cursor()
# see if we're inserting or updating a package
if not self.has_package(name):
@@ -268,7 +268,9 @@
old.append('classifiers')
# get old classifiers list
- for col in ('requires', 'provides', 'obsoletes'):
+ for col in ('requires', 'provides', 'obsoletes', 'requires_dist',
+ 'provides_dist', 'obsoletes_dist',
+ 'requires_external', 'project_url'):
relationships[col] = self.get_release_relationships(name,
version, col)
relationships[col].sort()
@@ -311,7 +313,10 @@
info['description_html'] = ''
# perform the insert
- cols = 'name version author author_email maintainer maintainer_email home_page license summary description description_html keywords platform download_url _pypi_ordering _pypi_hidden'.split()
+ cols = ('name version author author_email maintainer '
+ 'maintainer_email home_page license summary description '
+ 'description_html keywords platform requires_python '
+ 'download_url _pypi_ordering _pypi_hidden').split()
args = tuple([info.get(k, None) for k in cols])
params = ','.join(['%s']*len(cols))
scols = ','.join(cols)
@@ -353,7 +358,9 @@
(name, version, trove_id))
# handle relationship specifiers
- for col in ('requires', 'provides', 'obsoletes'):
+ for col in ('requires', 'provides', 'obsoletes', 'requires_dist',
+ 'provides_dist', 'obsoletes_dist',
+ 'requires_external', 'project_url'):
if not info.has_key(col) or relationships.get(col, []) == info[col]:
continue
safe_execute(cursor, '''delete from release_%s where name=%%s
@@ -455,8 +462,8 @@
_Package = FastResultRow('''name stable_version version author author_email
maintainer maintainer_email home_page license summary description
- description_html keywords platform download_url _pypi_ordering!
- _pypi_hidden! cheesecake_installability_id!
+ description_html keywords platform requires_python download_url
+ _pypi_ordering! _pypi_hidden! cheesecake_installability_id!
cheesecake_documentation_id! cheesecake_code_kwalitee_id!''')
def get_package(self, name, version):
''' Retrieve info about the package from the database.
@@ -467,7 +474,8 @@
sql = '''select packages.name as name, stable_version, version, author,
author_email, maintainer, maintainer_email, home_page,
license, summary, description, description_html, keywords,
- platform, download_url, _pypi_ordering, _pypi_hidden,
+ platform, requires_python, download_url, _pypi_ordering,
+ _pypi_hidden,
cheesecake_installability_id,
cheesecake_documentation_id,
cheesecake_code_kwalitee_id
@@ -656,6 +664,64 @@
safe_execute(cursor, 'update packages set autohide=%s where name=%s',
[value, name])
+ def _get_package_url(self, name):
+ name = name.split()[0]
+ cursor = self.get_cursor()
+ sql = 'select * from packages where name=%s'
+ safe_execute(cursor, sql, (name, ))
+ exists = cursor.fetchone() is not None
+ if not exists:
+ return None
+ return self.config.url + '/' + name
+
+ def get_package_requires_dist(self, name, version):
+ cursor = self.get_cursor()
+ safe_execute(cursor, '''select specifier from release_requires_dist
+ where name=%s and version=%s ''', (name, version))
+ packages = []
+ for package in cursor.fetchall():
+ pack = {'name': package[0],
+ 'href': self._get_package_url(package[0])}
+ packages.append(pack)
+ return packages
+
+ def get_package_provides_dist(self, name, version):
+ cursor = self.get_cursor()
+ safe_execute(cursor, '''select specifier from release_provides_dist
+ where name=%s and version=%s ''', (name, version))
+ packages = []
+ for package in cursor.fetchall():
+ pack = {'name': package[0],
+ 'href': self._get_package_url(package[0])}
+ packages.append(pack)
+ return packages
+
+ def get_package_obsoletes_dist(self, name, version):
+ cursor = self.get_cursor()
+ safe_execute(cursor, '''select specifier from release_obsoletes_dist
+ where name=%s and version=%s ''', (name, version))
+ packages = []
+ for package in cursor.fetchall():
+ pack = {'name': package[0],
+ 'href': self._get_package_url(package[0])}
+ packages.append(pack)
+ return packages
+
+ def get_package_requires_external(self, name, version):
+ cursor = self.get_cursor()
+ safe_execute(cursor, '''select specifier from release_requires_external
+ where name=%s and version=%s ''', (name, version))
+ return [package[0] for package in cursor.fetchall()]
+
+ def get_package_project_url(self, name, version):
+ cursor = self.get_cursor()
+ safe_execute(cursor, '''select specifier from release_project_url
+ where name=%s and version=%s ''', (name, version))
+ project_urls = []
+ for project in cursor.fetchall():
+ project_urls.append(project[0].split(','))
+ return project_urls
+
def get_package_comments(self, name):
cursor = self.get_cursor()
safe_execute(cursor, 'select comments from packages where name=%s',
@@ -877,7 +943,9 @@
# delete ancillary table entries
for tab in ('files', 'provides', 'requires', 'obsoletes',
- 'classifiers'):
+ 'classifiers', 'requires_dist', 'provides_dist',
+ 'obsoletes_dist', 'requires_external',
+ 'project_url'):
safe_execute(cursor, '''delete from release_%s where
name=%%s and version=%%s'''%tab, (name, version))
safe_execute(cursor, 'delete from description_urls where name=%s and version=%s',
@@ -891,7 +959,7 @@
safe_execute(cursor, '''insert into journals (name, version, action,
submitted_date, submitted_by, submitted_from) values
(%s, %s, %s, %s, %s, %s)''', (name, version, 'remove', date,
- self.username, self.userip))
+ self.username, self.userip))
def remove_package(self, name):
''' Delete an entire package from the database.
@@ -904,7 +972,9 @@
# delete ancillary table entries
for tab in ('files', 'provides', 'requires', 'obsoletes',
- 'classifiers'):
+ 'classifiers', 'requires_dist', 'provides_dist',
+ 'obsoletes_dist', 'requires_external',
+ 'project_url'):
safe_execute(cursor, 'delete from release_%s where name=%%s'%tab,
(name, ))
@@ -969,7 +1039,7 @@
'''Copy a user-s rating of package name from one version to another;
return the comment if any'''
cursor = self.get_cursor()
- safe_execute(cursor, '''insert into ratings(name,version,user_name,date,rating)
+ safe_execute(cursor, '''insert into ratings(name,version,user_name,date,rating)
select name,%s,user_name,now(),rating from ratings
where name=%s and version=%s and user_name=%s''',
(toversion, name, fromversion, self.username))
@@ -979,10 +1049,10 @@
if cid:
cid = cid[0]
safe_execute(cursor, '''insert into comments(rating, user_name, date, message, in_reply_to)
- select currval('ratings_id_seq'), user_name, date, message, in_reply_to
+ select currval('ratings_id_seq'), user_name, date, message, in_reply_to
from comments where id=%s''', (cid,))
safe_execute(cursor, '''insert into comments_journal(name, version, id, submitted_by, date, action)
- values(%s, %s, currval('comments_id_seq'), %s, now(), %s)''', (name, toversion,
+ values(%s, %s, currval('comments_id_seq'), %s, now(), %s)''', (name, toversion,
self.username, 'copied %s' % cid))
safe_execute(cursor, '''select message from comments
@@ -996,7 +1066,7 @@
safe_execute(cursor, """insert into comments_journal(name, version, id, submitted_by, date, action)
select %s, %s, id, %s, now(), 'deleted' from ratings where user_name=%s and name=%s and version=%s""",
(name, version, self.username, self.username, name, version))
- safe_execute(cursor, "delete from ratings where user_name=%s and name=%s and version=%s",
+ safe_execute(cursor, "delete from ratings where user_name=%s and name=%s and version=%s",
(self.username, name, version))
_Rating=FastResultRow('id! date! user rating!')
@@ -1004,7 +1074,7 @@
def get_ratings(self, name, version):
'''Return ratings,messages for a release.'''
cursor = self.get_cursor()
- safe_execute(cursor, '''select id, date, user_name, rating from ratings
+ safe_execute(cursor, '''select id, date, user_name, rating from ratings
where name=%s and version=%s order by date''', (name, version))
res = cursor.fetchall()
safe_execute(cursor, '''select c.id, c.rating, c.user_name, c.date, c.message, c.in_reply_to from
@@ -1098,14 +1168,14 @@
def has_rating(self, name, version):
'''Check whether user has rated this release'''
cursor = self.get_cursor()
- safe_execute(cursor, '''select count(*) from ratings
+ safe_execute(cursor, '''select count(*) from ratings
where user_name=%s and name=%s and version=%s''', (self.username, name, version))
return cursor.fetchall()[0][0]
def latest_rating(self, name):
'''Return the user-s latest rating on any release, or None.'''
cursor = self.get_cursor()
- safe_execute(cursor, '''select version from ratings
+ safe_execute(cursor, '''select version from ratings
where user_name=%s and name=%s order by date desc limit 1''', (self.username, name))
res = cursor.fetchone()
if res:
@@ -1321,7 +1391,7 @@
'''
cursor = self.get_cursor()
safe_execute(cursor, 'insert into sshkeys(name, key) values(%s, %s)', (username, key))
-
+
def delete_sshkey(self, id):
'''Delete an SSH key given by ID.
'''
Modified: trunk/pypi/templates/display.pt
==============================================================================
--- trunk/pypi/templates/display.pt (original)
+++ trunk/pypi/templates/display.pt Sat Apr 24 12:22:22 2010
@@ -156,6 +156,64 @@
</ul>
</li>
+
+
+ <li tal:condition="data/requires_dist | nothing">
+ <strong>Requires Distributions</strong>
+ <ul class="nodot">
+ <li tal:repeat="require data/requires_dist">
+ <a tal:condition="require/href" tal:attributes="href require/href"
+ tal:content="require/name"/>
+ <tal:block tal:condition="not:require/href"
+ tal:replace="require/name">
+ </tal:block>
+ </li>
+ </ul>
+ </li>
+
+ <li tal:condition="data/provides_dist | nothing">
+ <strong>Provides Distributions</strong>
+ <ul class="nodot">
+ <li tal:repeat="provide data/provides_dist">
+ <a tal:condition="provide/href" tal:attributes="href provide/href"
+ tal:content="provide/name"/>
+ <tal:block tal:condition="not:provide/href"
+ tal:replace="provide/name">
+ </tal:block>
+
+ </li>
+ </ul>
+ </li>
+
+ <li tal:condition="data/obsoletes_dist | nothing">
+ <strong>Obsoletes Distributions</strong>
+ <ul class="nodot">
+ <li tal:repeat="obsolete data/obsoletes_dist">
+ <a tal:condition="obsolete/href" tal:attributes="href obsolete/href"
+ tal:content="obsolete/name"/>
+ <tal:block tal:condition="not:obsolete/href"
+ tal:replace="obsolete/name">
+ </tal:block>
+
+ </li>
+ </ul>
+ </li>
+
+ <li tal:condition="data/requires_external | nothing">
+ <strong>Requires Externals</strong>
+ <ul class="nodot">
+ <li tal:repeat="require data/requires_external">
+ <tal:block tal:replace="require">
+ </tal:block>
+ </li>
+ </ul>
+ </li>
+
+ <li tal:condition="data/requires_python | nothing">
+ <strong>Requires Python</strong>: <tal:block tal:replace="data/requires_python"></tal:block>
+ </li>
+
+
<li tal:condition="data/roles/Owner | nothing">
<strong>Package Index Owner:</strong>
<span tal:content="python:', '.join(data['roles']['Owner'])" />
Modified: trunk/pypi/templates/standard_template.pt
==============================================================================
--- trunk/pypi/templates/standard_template.pt (original)
+++ trunk/pypi/templates/standard_template.pt Sat Apr 24 12:22:22 2010
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<metal:macro define-macro="page"
- xmlns="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
@@ -14,14 +14,111 @@
<link rel="stylesheet" tal:attributes="href string:${app/config/pydotorg}css/pypi.css" type="text/css"/>
<link rel="alternate" type="application/rss+xml" title="RSS: 30 latest updates" href="http://www.python.org/pypi?:action=rss"/>
- <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/screen-switcher-default.css" type="text/css" id="screen-switcher-stylesheet" rel="stylesheet" />
- <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/netscape4.css" type="text/css" rel="stylesheet" />
- <link media="print" tal:attributes="href string:${app/config/pydotorg}styles/print.css" type="text/css" rel="stylesheet" />
- <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/largestyles.css" type="text/css" rel="alternate stylesheet" title="large text" />
- <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/defaultfonts.css" type="text/css" rel="alternate stylesheet" title="default fonts" />
- <link rel="stylesheet" media="screen" href="/css/docutils.css" type="text/css"/>
-
- <metal:slot define-slot="head" />
+ <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/screen-switcher-default.css" type="text/css" id="screen-switcher-stylesheet" rel="stylesheet" />
+ <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/netscape4.css" type="text/css" rel="stylesheet" />
+ <link media="print" tal:attributes="href string:${app/config/pydotorg}styles/print.css" type="text/css" rel="stylesheet" />
+ <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/largestyles.css" type="text/css" rel="alternate stylesheet" title="large text" />
+ <link media="screen" tal:attributes="href string:${app/config/pydotorg}styles/defaultfonts.css" type="text/css" rel="alternate stylesheet" title="default fonts" />
+ <link rel="stylesheet" media="screen" href="/css/docutils.css" type="text/css"/>
+
+ <metal:slot define-slot="head" />
+
+ <style type="text/css" rel="stylesheet">
+ #document-floating {
+ width: 15em;
+ }
+ .floating-box {
+ float: right;
+ }
+ .homepage-box h4, #document-project-url h4, .externallinks h4
+ {
+ font-family: Verdana, Geneva, "Bitstream Vera Sans", Helvetica, sans-serif;
+ font-size: 88%;
+ font-weight: bold;
+ padding: 3px 0 4px 12px;
+ margin-bottom:8px;
+ border-bottom:3px solid #d5ad42;
+ background-color: #F4F4F4;
+ text-align: left;
+ z-index: 0;
+ -moz-border-radius-topleft: 16px;
+ }
+
+ #document-project-url h4, .homepage-box h4, .externallinks h4 {
+ -moz-border-radius-topleft:11px;
+ background-color:#376A94;
+ color:#FFFFFF;
+ font-family:Arial,Verdana,Geneva,"Bitstream Vera Sans",Helvetica,sans-serif;
+ font-size:94%;
+ font-weight:bold;
+ margin:0;
+ padding:3px 0 4px 12px;
+ text-align:left;
+ z-index:0;
+ }
+
+ #document-project-url
+ {
+ font-family: Verdana, Geneva, "Bitstream Vera Sans", Helvetica, sans-serif;
+ font-size: 98%;
+ text-align: left;
+ color: #3C4B6B;
+ background-color: #F7F6F0;
+ width: 15em;
+ border: 1px solid #B7BECC;
+ z-index: 999;
+ float:right;
+ margin: 0 12px 12px 12px;
+ padding-bottom: 10px;
+ list-style:none;
+ -moz-border-radius-topleft: 12px;
+ -moz-border-radius-bottomright: 12px;
+ }
+
+ #document-project-url ul
+ {
+ list-style: none;
+ display: block;
+ border:0;
+ padding: 0;
+ margin: 0 3em 0 1.1em;
+ color: #3C4B6B;
+ background: none;
+ width: auto;
+ font-size: 103%;
+ font-family: Verdana, Geneva, "Bitstream Vera Sans", Helvetica, sans-serif;
+ }
+
+ #document-project-url a:link, #document-project-url a:visited
+ {
+ color:#3C4B6B;
+ text-decoration: none;
+ }
+
+ #document-project-url a:hover
+ {
+ color: #000000;
+ text-decoration: underline;
+ }
+
+ #document-project-url h4 a:link, #document-project-url h4 a:visited
+ {
+ color: #FFFFFF;
+ }
+
+
+ #document-project-url h4 a:hover
+ {
+ color: #FFFFFF;
+
+ }
+ #document-project-url form {
+ margin:12px;
+ }
+ #document-project-url input {
+ margin-top:3px;
+ }
+ </style>
</head>
<body>
<!-- Logo -->
@@ -49,15 +146,15 @@
<!-- XXX: reinstate this <div id="screen-switcher"></div> -->
</div>
<div id="left-hand-navigation">
-
+
<!-- Main Menu NEED LEVEL TWO HEADER AND FOOTER -->
<div id="menu">
- <ul class="level-one">
+ <ul class="level-one">
<li class="selected">
<a class="selected" tal:attributes="href app/url_path">Package Index</a>
-
+
<ul class="level-two">
-
+
<tal:for-nav repeat="link app/navlinks_html">
<span tal:replace="structure link"/>
</tal:for-nav>
@@ -76,13 +173,13 @@
</li>
-
+
<li class=""><a href="http://www.python.org/about" class="" title="About The Python Language">About</a>
</li><li class=""><a href="http://www.python.org/news" class="" title="">News</a>
</li><li class=""><a href="http://www.python.org/doc" class="" title="">Documentation</a>
</li><li class=""><a href="http://www.python.org/download" title="">Download</a>
-
+
</li><li class=""><a href="http://www.python.org/community" class="" title="">Community</a>
</li><li class=""><a href="http://www.python.org/psf" class="" title="Python Software Foundation">Foundation</a>
@@ -91,7 +188,7 @@
</li>
</ul>
</div>
-
+
</div>
<div id="content-body">
<div id="body-main">
@@ -100,81 +197,96 @@
<div id="breadcrumb">
<a tal:attributes="href app/url_path">Package Index</a>
<tal:block condition="exists:data/version">
- <span class="breadcrumb-separator">></span>
+ <span class="breadcrumb-separator">></span>
<a tal:content="data/title" tal:attributes="href python:app.packageURL(data['name'], data['version'])"></a>
</tal:block>
-
+
</div>
- <div id="document-navigation" style="overflow-y: auto; max-height: 15em; overflow-x: hidden;">
- <tal:if-not-user condition="not: app/loggedin">
- <h4>Not Logged In</h4>
-
- <ul>
- <li tal:condition="app/username"><a tal:attributes="href python:app.link_action('clear_auth')">Clear Basic Auth</a></li>
- <li><a tal:attributes="href python: app.link_action('login')">Login</a></li>
- <li><a tal:attributes="href python: app.link_action('register_form')">Register</a></li>
- <li><a tal:attributes="href python: app.link_action('forgotten_password_form')">Lost Login?</a></li>
- <li>Use <a tal:attributes="href string:${app/url_path}?:action=openid">OpenID</a>
- <tal:block tal:repeat="prov data/providers">
- <a tal:attributes="href prov/login">
- <img tal:attributes="src prov/favicon; title prov/title"/>
- </a>
- </tal:block>
- </li>
- </ul>
+ <div id="document-floating" class="floating-box">
- </tal:if-not-user>
+ <div id="document-navigation" class="floating-box">
+ <tal:if-not-user condition="not: app/loggedin">
+ <h4>Not Logged In</h4>
+
+ <ul>
+ <li tal:condition="app/username"><a tal:attributes="href python:app.link_action('clear_auth')">Clear Basic Auth</a></li>
+ <li><a tal:attributes="href python: app.link_action('login')">Login</a></li>
+ <li><a tal:attributes="href python: app.link_action('register_form')">Register</a></li>
+ <li><a tal:attributes="href python: app.link_action('forgotten_password_form')">Lost Login?</a></li>
+ <li>Use <a tal:attributes="href string:${app/url_path}?:action=openid">OpenID</a>
+ <tal:block tal:repeat="prov data/providers">
+ <a tal:attributes="href prov/login">
+ <img tal:attributes="src prov/favicon; title prov/title"/>
+ </a>
+ </tal:block>
+ </li>
+ </ul>
- <tal:if-user condition="app/loggedin">
+ </tal:if-not-user>
- <h4>Welcome <span tal:replace="app/username"/></h4>
- <li>
- <a tal:attributes="href python:app.link_action('user_form')">Your details</a>
- </li>
+ <tal:if-user condition="app/loggedin">
- <tal:let define="packages python:app.store.user_packages(app.username)">
- <tal:if-packages condition="packages">
- <li>
- Your packages:
- </li>
- <ul>
-
+ <h4>Welcome <span tal:replace="app/username"/></h4>
+ <li>
+ <a tal:attributes="href python:app.link_action('user_form')">Your details</a>
+ </li>
+
+ <tal:let define="packages python:app.store.user_packages(app.username)">
+ <tal:if-packages condition="packages">
<li>
- <tal:block repeat="pkg_info packages">
- <a tal:attributes="href python: app.link_action('pkg_edit', name=pkg_info[0])"
- tal:content="python:pkg_info[0]">package</a><br />
- </tal:block>
+ Your packages:
</li>
- </ul>
- </tal:if-packages>
- </tal:let>
-
- <li>
- <a tal:attributes="href python:app.link_action('logout')">Logout</a>
- </li>
- </tal:if-user>
+ <ul>
+ <li>
+ <tal:block repeat="pkg_info packages">
+ <a tal:attributes="href python: app.link_action('pkg_edit', name=pkg_info[0])"
+ tal:content="python:pkg_info[0]">package</a><br />
+ </tal:block>
+ </li>
+ </ul>
+ </tal:if-packages>
+ </tal:let>
+
+ <li>
+ <a tal:attributes="href python:app.link_action('logout')">Logout</a>
+ </li>
+ </tal:if-user>
-
</div>
+ <tal:block condition="data/project_url | nothing">
+ <div id="document-project-url" class="floating-box">
+ <h4>Project Links</h4>
+ <ul>
+ <tal:block tal:repeat="project data/project_url">
+ <li>
+ <a tal:content="python:project[0]" tal:attributes="href python:project[1]"></a>
+ </li>
+ </tal:block>
+ </ul>
+ </div>
+ </tal:block>
+
+ </div>
+
<div class="section">
<h1 tal:content="data/title"></h1>
<metal:slot define-slot="body">
-
+
This template has not replaced its body slot.
-
+
</metal:slot>
-
+
</div>
</div>
<div id="footer"><div id="credits">
<a href="http://www.python.org/about/website">Website maintained by the Python community</a><br />
- <a href="http://www.xs4all.com/" title="Web and email hosting provided by xs4all, Netherlands">hosting by xs4all</a> /
+ <a href="http://www.xs4all.com/" title="Web and email hosting provided by xs4all, Netherlands">hosting by xs4all</a> /
<a href="http://www.pollenation.net/" title="Design and content management system by Pollenation Internet, Yorkshire">design by pollenation</a>
</div>
Copyright © 1990-2010, <a href="http://www.python.org/psf">Python Software Foundation</a><br />
Modified: trunk/pypi/webui.py
==============================================================================
--- trunk/pypi/webui.py (original)
+++ trunk/pypi/webui.py Sat Apr 24 12:22:22 2010
@@ -4,6 +4,7 @@
import re, zipfile, logging, pprint, sets, shutil, Cookie, subprocess
from zope.pagetemplate.pagetemplatefile import PageTemplateFile
from distutils.util import rfc822_escape
+from distutils2.metadata import DistributionMetadata
# Importing M2Crypto patches urllib; don't let them do that
orig = urllib.URLopener.open_https.im_func
@@ -335,7 +336,6 @@
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
context['standard_template'] = PyPiPageTemplate(
"standard_template.pt", template_dir)
-
template = PyPiPageTemplate(filename, template_dir)
content = template(**context)
@@ -1021,7 +1021,8 @@
def display_pkginfo(self, name=None, version=None):
'''Reconstruct and send a PKG-INFO metadata file.
'''
-
+ # XXX tarek need to add 1.2 support here
+ #
info, name, version = self._get_pkg_info(name, version)
if not info:
return self.fail('No such package / version',
@@ -1161,7 +1162,10 @@
# RJ: disabled cheesecake because the (strange) errors were getting annoying
# columns = 'name version author author_email maintainer maintainer_email home_page download_url summary license description description_html keywords platform cheesecake_installability_id cheesecake_documentation_id cheesecake_code_kwalitee_id'.split()
- columns = 'name version author author_email maintainer maintainer_email home_page download_url summary license description description_html keywords platform'.split()
+ columns = ('name version author author_email maintainer '
+ 'maintainer_email home_page requires_python download_url '
+ 'summary license description description_html keywords '
+ 'platform').split()
release = {'description_html': ''}
for column in columns:
@@ -1223,6 +1227,13 @@
total += r['rating']
tally[r['rating']] += 1
+ # New metadata
+ requires_dist = self.store.get_package_requires_dist(name, version)
+ provides_dist = self.store.get_package_provides_dist(name, version)
+ obsoletes_dist = self.store.get_package_obsoletes_dist(name, version)
+ project_url = self.store.get_package_project_url(name, version)
+ requires_external = self.store.get_package_requires_external(name, version)
+
for c in comments:
add = c, []
parent_comments[c['id']] = add[1]
@@ -1259,7 +1270,7 @@
rating = ', %s points' % rating
else:
rating = ''
- result.append("<li>%s (%s%s):<br/>%s %s" %
+ result.append("<li>%s (%s%s):<br/>%s %s" %
(c['user'], date, rating, message, reply))
if children:
result.extend(render_comments(children, False))
@@ -1288,7 +1299,13 @@
usinglatest=using_latest,
latestversion=latest_version,
latestversionurl=latest_version_url,
- action=self.link_action())
+ action=self.link_action(),
+ requires_dist=requires_dist,
+ provides_dist=provides_dist,
+ obsoletes_dist=obsoletes_dist,
+ requires_external=requires_external,
+ project_url=project_url,
+ requires_python=release.get('requires_python', ''))
def index(self, nav_current='index', releases=None):
''' Print up an index page
@@ -1569,7 +1586,10 @@
data['platform'] = ','.join(data['platform'])
# make sure relationships are lists
- for name in ('requires', 'provides', 'obsoletes'):
+ for name in ('requires', 'provides', 'obsoletes',
+ 'requires_dist', 'provides_dist',
+ 'obsoletes_dist',
+ 'requires_external', 'project_url'):
if data.has_key(name) and not isinstance(data[name],
types.ListType):
data[name] = [data[name]]
@@ -1671,7 +1691,10 @@
v = self.form[k]
if k == '_pypi_hidden':
v = v == '1'
- elif k in ('requires', 'provides', 'obsoletes'):
+ elif k in ('requires', 'provides', 'obsoletes',
+ 'requires_dist', 'provides_dist',
+ 'obsoletes_dist',
+ 'requires_external', 'project_url'):
if not isinstance(v, list):
v = [x.strip() for x in re.split('\s*[\r\n]\s*', v)]
else:
@@ -1689,7 +1712,10 @@
data[k.lower()] = v
# make sure relationships are lists
- for name in ('requires', 'provides', 'obsoletes'):
+ for name in ('requires', 'provides', 'obsoletes',
+ 'requires_dist', 'provides_dist',
+ 'obsoletes_dist',
+ 'requires_external', 'project_url'):
if data.has_key(name) and not isinstance(data[name],
types.ListType):
data[name] = [data[name]]
@@ -1716,6 +1742,23 @@
self.write_template('message.pt', title='Package verification',
message='Validated OK')
+ def _validate_metadata_1_2(self, data):
+ # loading the metadata into
+ # a DistributionMetadata instance
+ # so we can use its check() method
+ metadata = DistributionMetadata()
+ for key, value in data.items():
+ metadata[key] = value
+ metadata['Metadata-Version'] = '1.2'
+ missing, warnings = metadata.check()
+
+ # raising the first problem
+ if len(missing) > 0:
+ raise ValueError, '"%s" is missing' % missing[0]
+
+ if len(warnings) > 0:
+ raise ValueError, warnings[0]
+
def validate_metadata(self, data):
''' Validate the contents of the metadata.
'''
@@ -1724,7 +1767,10 @@
if not data.get('version', ''):
raise ValueError, 'Missing required field "version"'
if data.has_key('metadata_version'):
+ metadata_version = data['metadata_version']
del data['metadata_version']
+ else:
+ metadata_version = '1.0' # default
# Traditionally, package names are restricted only for
# technical reasons; / is not allowed because it may be
@@ -1750,6 +1796,10 @@
except ValueError, message:
raise ValueError, 'Bad "provides" syntax: %s'%message
+ # check PEP 345 fields
+ if metadata_version == '1.2':
+ self._validate_metadata_1_2(data)
+
# check classifiers
if data.has_key('classifiers'):
d = {}
@@ -1866,7 +1916,7 @@
if not self.form.has_key('msg'):
raise FormError
comment = self.store.get_comment(self.form['msg'])
- self.write_template('comment.pt', title='Reply to comment',
+ self.write_template('comment.pt', title='Reply to comment',
comment=comment)
def addcomment(self):
@@ -1890,7 +1940,7 @@
comment_email(self.store, name, version, self.username, comment, [orig['user']])
return self.display(name=name, version=version)
-
+
def delcomment(self):
if not self.authenticated:
@@ -2716,15 +2766,15 @@
return self.register_form()
def rp_discovery(self):
- payload = '''<xrds:XRDS
- xmlns:xrds="xri://$xrds"
- xmlns="xri://$xrd*($v*2.0)">
- <XRD>
- <Service priority="1">
- <Type>http://specs.openid.net/auth/2.0/return_to</Type>
- <URI>%s</URI>
- </Service>
- </XRD>
+ payload = '''<xrds:XRDS
+ xmlns:xrds="xri://$xrds"
+ xmlns="xri://$xrd*($v*2.0)">
+ <XRD>
+ <Service priority="1">
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ <URI>%s</URI>
+ </Service>
+ </XRD>
</xrds:XRDS>
''' % (self.config.url+'?:action=openid_return')
self.handler.send_response(200)
More information about the Pypi-checkins
mailing list