12 | Welcome to pypi.python.org, the Python community's software hosting service.
13 |
14 |
15 |
16 | Here you'll find a Python package to suit almost any need.
17 |
18 |
19 |
20 | The project was started in 2002,
21 | and has since grown to include the contributions of over a dozen Python
22 | programmers, packages
23 | and millions of downloads.
24 |
25 |
26 |
27 | The Python Package Index (PyPI) is hosted by the Python Software Foundation (who run the python.org systems) but the project is community-run. Currently there's two maintainers, Richard Jones and Martin von Löwis.
28 | To contact the admins, please
29 | use the
30 | Get help
31 | or
32 | Bug reports
33 | links.
34 |
35 |
36 |
37 | The index itself is written in Python but pre-dates modern Python web frameworks (more detail).
38 |
39 |
40 |
41 | To submit a package use
42 | "python setup.py upload"
43 | and to use a package from this index either
44 | "pip install package"
45 | or download, unpack and "python setup.py install" it.
46 |
47 |
48 |
49 | Security: GPG and SSH ... XXX details?
50 |
51 |
52 |
You can access the data in PyPI from a program using either the per-package
53 | JSON data or the XML-RPC interface.
54 |
55 |
PyPI now provides a Mirror infrastructure to allow
56 | users to continue downloading file even if pypi.python.org is unavailable.
57 |
58 |
59 | PyPI has also been known as The Python Cheese Shop (and indeed cheeseshop.python.org still works).
60 |
61 |
62 |
And now for something completely different...
63 |
64 | Customer: Now then, some cheese please, my good man.
65 |
66 | Owner: (lustily) Certainly, sir. What would you like?
67 |
68 | Customer: Well, eh, how about a little red Leicester.
69 |
70 | Owner: I'm, a-fraid we're fresh out of red Leicester, sir.
71 |
32 |
33 | There are packages in the categories you have selected.
34 | Please select more categories, or show all
35 | matching packages now.
36 |
37 |
38 | Please select a category from below.
39 |
40 |
The Python Package Index is a repository of software for the Python
9 | programming language. There are currently
10 | 12343
11 | packages here.
12 |
13 | To contact the PyPI admins, please use the
14 | Support
15 | or
16 | Bug reports
17 | links.
18 |
19 |
20 |
21 |
22 |
23 | Get Packages
24 |
25 | To use a package from this index either
26 | "pip install package"
27 | (get pip)
28 | or download, unpack and "python setup.py install" it.
29 |
47 | To interoperate with the index
48 | use the
49 | JSON,
50 | XML-RPC or
51 | HTTP
52 | interfaces.
53 | Use local mirroring or caching to make installation more robust.
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Updated
Package
Description
61 |
62 |
63 |
67 |
68 |
69 |
date
70 |
link
71 |
summary
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
And now for something completely different...
81 |
82 | Customer: Now then, some cheese please, my good man.
83 |
84 | Owner: (lustily) Certainly, sir. What would you like?
85 |
86 | Customer: Well, eh, how about a little red Leicester.
87 |
88 | Owner: I'm, a-fraid we're fresh out of red Leicester, sir.
89 |
The public PyPI mirrors using *.pypi.python.org DNS aliases have been
9 | deprecated as specified in
10 | PEP 449 (Removal of
11 | the PyPI Mirror Auto Discovery and Naming Scheme). Package
12 | files uploaded to PyPI are now served through a CDN which is more reliable.
13 | You are welcome to continue to run your own private mirrors, but the DNS
14 | aliases for the mirror network ([a..z].pypi.python.org) were shut down on
15 | February 15th, 2014.
16 |
17 |
If you'd like to use a mirror, resources like
18 | http://www.pypi-mirrors.org/
19 | are available to find public mirrors of PyPI.
20 |
21 |
Additionally, the "mirror authenticity" API specified in the PEP below
22 | will be deprecated in the near future, and use of it is discouraged.
23 |
24 |
Use the bandersnatch
25 | mirroring client to set up your own mirror.
Use the field below to enter your OpenID, to either login (if the ID is already known to us), or register with PyPI. Alternatively, select one of the icons to have your provider guide you in the identifier selection.
9 |
13 |
If you would like to see additional provider icons added on the main page, please report that to the bug tracker. Notice that we have the following requirements for adding a new provider:
14 |
15 |
must be in wide use, using procedures that the community trusts
Please ensure your password is of a reasonable length (>8 characters) and
33 | mixes letters, cases and numbers. Please don't use the same password
34 | as with other services.
11 | Each project may have a release for each version of the
12 | project that is released. You may use this form to hide releases
13 | from users.
14 |
15 |
16 |
Administer the Role
17 | assigned to users for this project.
18 |
19 |
For all activities relating to Project/Release changes except for hiding releases, you must use
20 |
21 | the Warehouse test instance
22 |
23 |
24 | Warehouse
25 |
26 |
27 |
28 |
67 |
68 |
By default, each new release will hide all other release from the
69 | regular display. If you want to select yourself which projects to hide,
70 | uncheck the button below
71 |
84 |
85 |
86 |
If you would like to DESTROY any existing documentation hosted at
87 | : Please use
89 |
90 | the Warehouse test instance
91 |
92 |
93 | Warehouse
94 |
95 |
10 | The list of all packages previously hosted at this URL has been deprecated,
11 | the resources necessary to render this page have become untenable as PyPI has
12 | grown.
13 |
14 |
15 |
16 | If you previously used this list for some purpose, you may consider using the
17 | simple index itself as a replacement.
18 |
9 | This form allows to modify your PyPI account information, and to delete your account.
10 |
15 | You cannot delete your account since you
16 | are still listed as owner of some packages.
17 |
18 |
55 |
56 |
Please ensure your password is of a reasonable length (>8 characters) and
57 | mixes letters, cases and numbers. Please don't use the same password
58 | as with other services.
59 |
60 |
61 |
A confirmation email will be sent to the address you nominate above.
62 |
Please ensure you will be able to receive email from admin@mail.pypi.python.org
63 | (check any "sender confirmation" systems you might be using.)
64 |
To complete the registration process, you must visit the link indicated in the
65 | email.
66 |
67 |
68 |
69 |
75 |
76 |
77 |
78 |
Your OpenIDs:
79 |
80 |
81 |
86 |
87 |
88 |
89 |
90 |
Claim OpenID, either from one of these providers:
91 |
92 |
93 |
94 | or enter your ID explicitly:
95 |
If you have a query or report to make regarding security please contact
11 | Donald Stufft and/or Ernest W. Durbin III. Both have GPG keys on key
12 | servers like pgp.mit.edu.
13 |
14 |
15 |
Donald's GPG key has key id 0x6E3CBCE93372DCFA (full fingerprint 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA) and his email address is donald@python.org
16 |
17 |
Ernest's GPG key has key id 0x88159C24830F6F7E (full fingerprint 11CD 3DD9 8D7E 61C7 6D1A 3224 8815 9C24 830F 6F7E) and his email address is ernest@python.org
18 |
19 |
You may also report issues in the PyPI bug tracker where reports may be made private.
20 |
21 |
Your Security
22 |
23 |
You may sign your uploads with GPG using the "--sign" argument to "python setup.py upload".
24 |
25 |
The file checksums provided with files on PyPI exists only to provide some download corruption protection. It is not intended to provide any sort of security regarding tampering. Please use GPG signing and verification via your Web of Trust for that.
9 | By registering to upload materials to PyPI, I agree and
10 | affirmatively acknowledge the following:
11 |
12 |
13 |
14 |
15 | "Content" means any data, content, code, video, images or
16 | other materials of any type that I upload, submit, or
17 | otherwise transmit to or through PyPI. Acceptable Content is
18 | restricted to Python packages and related information.
19 |
20 |
21 |
22 |
23 | Any Content uploaded to PyPI is provided on a non-confidential
24 | basis, and is not a trade secret.
25 |
26 |
27 |
28 |
29 | I retain all right, title, and interest in the Content (to the
30 | same extent I possessed such right, title and interest prior to
31 | uploading the Content), but by uploading I grant or warrant (as
32 | further set forth below) that the PSF is free to disseminate
33 | the Content, in the form provided to the PSF. Specifically,
34 | that means:
35 |
36 |
37 |
38 |
39 | If I upload Content covered by a royalty-free license
40 | included with such Content, giving the PSF the right to
41 | copy and redistribute such Content unmodified on PyPI as I
42 | have uploaded it, with no further action required by the
43 | PSF (an "Included License"), I represent and warrant that
44 | the uploaded Content meets all the requirements necessary
45 | for free redistribution by the PSF and any mirroring
46 | facility, public or private, under the Included License.
47 |
48 |
49 |
50 |
51 | If I upload Content other than under an Included License,
52 | then I grant the PSF and all other users of the web site an
53 | irrevocable, worldwide, royalty-free, nonexclusive license
54 | to reproduce, distribute, transmit, display, perform, and
55 | publish the Content, including in digital form.
56 |
57 |
58 |
59 |
60 |
61 |
62 | For the avoidance of doubt, my warranty or license as set forth
63 | above applies to the Content exactly in the form provided to
64 | the PSF, and does not, other than as may be provided in the
65 | Included License, grant the PSF or any users of the website a
66 | license to make or distribute derivative works based on my
67 | Content.
68 |
69 |
70 |
71 |
72 | I further warrant that I do not know of any applicable
73 | unlicensed patent or trademark rights that would be infringed
74 | by my uploaded Content.
75 |
76 |
77 |
78 |
79 | I represent and warrant that I have complied with all government
80 | regulations concerning the transfer or export of any Content I
81 | upload to PyPI. In particular, if I am subject to United States
82 | law, I represent and warrant that I have obtained any required
83 | governmental authorization for the export of the Content I upload.
84 | I further affirm that any Content I provide is not intended for use
85 | by a government end-user engaged in the manufacture or distribution
86 | of items or services controlled on the Wassenaar Munitions List as
87 | defined in part 772 of the United States Export Administration
88 | Regulations, or by any other embargoed user.
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/tests/rpc-test.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import config
3 | import store
4 | import unittest
5 | import rpc
6 | import cStringIO
7 |
8 | class XMLRPC ( unittest.TestCase ):
9 |
10 | def setUp( self ):
11 | # get a storage object to use in calls to xmlrpc functions
12 | self.store = store.Store( config.Config( "/tmp/pypi/config.ini" ) )
13 |
14 | def testEcho( self ):
15 | result = rpc.echo(self.store, 'a', 'b', 1, 3.4)
16 | self.failUnlessEqual(result, ('a', 'b', 1, 3.4))
17 |
18 | def testIndex( self ):
19 | result = rpc.index( self.store )
20 | self.failUnless( len( result ) > 0 )
21 |
22 | def testSearch( self ):
23 | result = rpc.search( self.store, "sql" )
24 | self.failUnless( len( result ) > 0 )
25 |
26 | if __name__ == '__main__':
27 | unittest.main()
28 |
--------------------------------------------------------------------------------
/thfcgi.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pypi/legacy/a418131180a14768328713df5533c600c425760d/thfcgi.py
--------------------------------------------------------------------------------
/tools/apache_count.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import sys, os, re, psycopg, ConfigParser, urlparse, gzip, bz2
4 | from mx.DateTime import DateTime
5 | from mx.DateTime.Timezone import utc_offset
6 |
7 | logre=re.compile(r"\[(?P..)/(?P...)/(?P....):"
8 | r"(?P..):(?P..):(?P..) "
9 | r'(?P.*)\] "GET (?P[^ "]+) HTTP/1.." 200')
10 |
11 | month_names=['jan','feb','mar','apr','may','jun',
12 | 'jul','aug','sep','oct','nov','dec']
13 | month_index = {}
14 | for i in range(12):
15 | month_index[month_names[i]] = i+1
16 |
17 | def main(argv):
18 | if len(argv) != 3:
19 | print "Usage: apache_count.py configfile logfile"
20 | raise SystemExit
21 | # Read config file
22 | p = ConfigParser.ConfigParser()
23 | p.read(argv[1])
24 | # Read server-relative URI prefix
25 | files_url = urlparse.urlsplit(p.get('webui', 'files_url'))[2]
26 | # Setup database connection
27 | dbname = p.get('database', 'name')
28 | dbuser = p.get('database', 'user')
29 | dbpass = p.get('database', 'password')
30 | dbconn = psycopg.connect(database=dbname, user=dbuser, password=dbpass)
31 | cursor = dbconn.cursor()
32 |
33 | filename = argv[2]
34 | if filename.endswith(".gz"):
35 | f = gzip.open(filename)
36 | elif filename.endswith(".bz2"):
37 | f = bz2.BZ2File(filename)
38 | else:
39 | f = open(filename)
40 |
41 | cursor.execute("select value from timestamps where name='http'")
42 | last_http = cursor.fetchone()[0]
43 |
44 | downloads = {}
45 | for line in f:
46 | m = logre.search(line)
47 | if not m:
48 | continue
49 | path = m.group('path')
50 | if not path.startswith(files_url):
51 | continue
52 | day = int(m.group('day'))
53 | month = m.group('month').lower()
54 | month = month_index[month]
55 | year = int(m.group('year'))
56 | hour = int(m.group('hour'))
57 | minute = int(m.group('min'))
58 | sec = int(m.group('sec'))
59 | date = DateTime(year, month, day, hour, minute, sec)
60 | zone = utc_offset(m.group('zone'))
61 | date = date - zone
62 |
63 | if date < last_http:
64 | continue
65 |
66 | filename = os.path.basename(path)
67 | # see if we have already read the old download count
68 | if not downloads.has_key(filename):
69 | cursor.execute("select downloads from release_files "
70 | "where filename=%s", (filename,))
71 | record = cursor.fetchone()
72 | if not record:
73 | # No file entry. Could be a .sig file
74 | continue
75 | # make sure we're working with a number
76 | downloads[filename] = record[0] or 0
77 | # add a download
78 | downloads[filename] += 1
79 |
80 | if not downloads:
81 | return
82 |
83 | # Update the download counts
84 | for filename, count in downloads.items():
85 | cursor.execute("update release_files set downloads=%s "
86 | "where filename=%s", (count, filename))
87 | # Update the download timestamp
88 | date = psycopg.TimestampFromMx(date)
89 | cursor.execute("update timestamps set value=%s "
90 | "where name='http'", (date,))
91 | dbconn.commit()
92 |
93 | if __name__=='__main__':
94 | main(sys.argv)
95 |
--------------------------------------------------------------------------------
/tools/apache_reader.py:
--------------------------------------------------------------------------------
1 | """
2 | Reads apache log files
3 | """
4 | import bz2
5 | import gzip
6 | import re
7 | import os
8 |
9 | # list of recognized user agents
10 | SETUPTOOLS_UA = (re.compile((r'^.* setuptools/(?P[0-9]\..*)$')), 'setuptools/%s')
11 | URLLIB_UA = (re.compile(r'^Python-urllib/(?P[23]\.[0-9])$'), 'Python-urllib/%s')
12 | SAFARI_UA = (re.compile(r'^Mozilla.* .* Version/(?P.*) Safari/.*$'), 'Safari/%s')
13 | GOOGLEBOT = (re.compile(r'Googlebot-Mobile/(?P.*);'), 'Googlebot-Mobile/%s')
14 | MSNBOT = (re.compile(r'^msnbot/(?P.*) '), 'msnbot/%s')
15 | FIREFOX_UA = (re.compile(r'^Mozilla.*? Firefox/(?P[23])\..*$'), 'Firefox/%s')
16 | PLAIN_MOZILLA = (re.compile(r'^Mozilla/(?P.*?) '), 'Mozilla/%s')
17 |
18 | logre = re.compile(r"\[(?P..)/(?P...)/(?P....):"
19 | r"(?P..):(?P..):(?P..) "
20 | r'(?P.*)\] "GET (?P[^ "]+) HTTP/1.." 200 .*? (?:".*?")? '
21 | r'"(User-Agent: )?(?P.*)"$', re.DOTALL)
22 |
23 | month_names=['jan','feb','mar','apr','may','jun',
24 | 'jul','aug','sep','oct','nov','dec']
25 | month_index = {}
26 |
27 | for i in range(12):
28 | month_index[month_names[i]] = i+1
29 |
30 | def month_to_index(month):
31 | return month_index[month.lower()]
32 |
33 | class ApacheLogReader(object):
34 | """provides an iterator over apache logs"""
35 |
36 | def __init__(self, filename, files_url='', mode=None):
37 | if mode is None:
38 | ext = os.path.splitext(filename)[-1]
39 | if ext in ('.bz2', '.gz'):
40 | mode = 'r:%s' % ext[1:]
41 | else:
42 | mode = 'r'
43 | if ':' in mode:
44 | mode, compr = mode.split(':')
45 | else:
46 | mode, compr = mode, None
47 | if compr not in ('bz2', 'gz', None):
48 | raise ValueError('%s mode not supported' % compr)
49 | if compr == 'bz2':
50 | self._data = bz2.BZ2File(filename, mode)
51 | elif compr == 'gz':
52 | self._data = gzip.open(filename)
53 | else:
54 | self._data = open(filename, mode)
55 |
56 | self.files_url = files_url
57 |
58 | def __iter__(self):
59 | return self
60 |
61 | def package_name(self, path):
62 | path = [p for p in path.split('/') if p != '']
63 | return path[-2]
64 |
65 | def get_simplified_ua(self, user_agent):
66 | """returns a simplified version of the user agent"""
67 | for expr, repl in (URLLIB_UA, SETUPTOOLS_UA, SAFARI_UA, GOOGLEBOT,
68 | MSNBOT, FIREFOX_UA, PLAIN_MOZILLA):
69 | res = expr.search(user_agent)
70 | if res is not None:
71 | return repl % res.group('version')
72 | return user_agent
73 |
74 | def next(self):
75 |
76 | while True:
77 | line = self._data.next().strip()
78 | m = logre.search(line)
79 | if m is None:
80 | continue
81 | path = m.group('path')
82 | filename = os.path.basename(path)
83 | filename = filename.split('?')[0]
84 | if not path.startswith(self.files_url) or filename == '':
85 | continue
86 | res = m.groupdict()
87 | res['month'] = month_to_index(res['month'])
88 | res['useragent'] = self.get_simplified_ua(res['useragent'])
89 | res['filename'] = filename
90 | res['packagename'] = self.package_name(path)
91 | res['day'] = int(res['day'])
92 | res['year'] = int(res['year'])
93 | res['hour'] = int(res['hour'])
94 | res['minute'] = int(res['min'])
95 | return res
96 |
97 | raise StopIteration
98 |
99 |
--------------------------------------------------------------------------------
/tools/daily.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys
3 | import os
4 |
5 | prefix = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6 | sys.path.insert(0, prefix)
7 |
8 | # Workaround current bug in docutils:
9 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
10 | import docutils.utils
11 |
12 | import config
13 | import store
14 |
15 | CONFIG_FILE = os.environ.get("PYPI_CONFIG", os.path.join(prefix, 'config.ini'))
16 |
17 | conf = config.Config(CONFIG_FILE)
18 | store = store.Store(conf)
19 |
20 | cursor = store.get_cursor()
21 |
22 | cursor.execute("delete from cookies where last_seen < now()-INTERVAL'1day';")
23 | cursor.execute("delete from openid_sessions where expires < now();")
24 | cursor.execute("delete from openid_nonces where created < now()-INTERVAL'1day'; ")
25 | cursor.execute("delete from openids where name in (select name from rego_otk where date < now()-INTERVAL'7days');")
26 | cursor.execute("delete from accounts_email where user_id in (select id from accounts_user where username in (select name from rego_otk where date < now()-INTERVAL'7days' and name not in (select user_name from roles)));")
27 | cursor.execute("delete from accounts_user where username in (select name from rego_otk where date < now()-INTERVAL'7days' and name not in (select user_name from roles));")
28 |
29 | store.commit()
30 |
--------------------------------------------------------------------------------
/tools/daily.sql:
--------------------------------------------------------------------------------
1 | -- run as a cronjob: psql packages -f tools/daily.sql -o /dev/null
2 | delete from cookies where last_seen < now()-INTERVAL'1day';
3 | delete from openid_sessions where expires < now();
4 | delete from openid_nonces where created < now()-INTERVAL'1day';
5 | delete from openids where name in (select name from rego_otk where date < now()-INTERVAL'7days');
6 | delete from users where name in (select name from rego_otk where date < now()-INTERVAL'7days' and name not in (select user_name from roles));
7 |
--------------------------------------------------------------------------------
/tools/demodata.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys, os, urllib
3 |
4 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 | sys.path.append(root)
6 | # Work around http://sourceforge.net/p/docutils/bugs/214/
7 | import docutils.utils
8 | import admin, store, config
9 |
10 | cfg = config.Config(root+'/config.ini')
11 | st = store.Store(cfg)
12 |
13 | # Demo data starts here
14 |
15 | # an admin
16 | otk = st.store_user('fred', 'fredpw', 'fred@python.test')
17 | st.delete_otk(otk)
18 | st.add_role('fred', 'Admin', None)
19 | # an owner
20 | otk = st.store_user('barney', 'barneypw', 'barney@python.test')
21 | st.delete_otk(otk)
22 |
23 | # package spam
24 | st.set_user('barney', '127.0.0.1', True)
25 | for version in ('0.8', '0.9', '1.0'):
26 | st.store_package('spam', version, {
27 | 'author':'Barney Geroellheimer',
28 | 'author_email':'barney@python.test',
29 | 'homepage':'http://spam.python.test/',
30 | 'license':'GPL',
31 | 'summary':'The spam package',
32 | 'description': 'spam '*500,
33 | 'classifiers':["Development Status :: 6 - Mature",
34 | "Programming Language :: Python :: 2"],
35 | '_pypi_hidden':False
36 | })
37 |
38 | # package eggs
39 | for version in ('0.1', '0.2', '0.3', '0.4'):
40 | st.store_package('eggs', version, {
41 | 'author':'Barney Geroellheimer',
42 | 'author_email':'barney@python.test',
43 | 'homepage':'http://eggs.python.test/',
44 | 'license':'GPL',
45 | 'summary':'The eggs package',
46 | 'description':'Does anybody want to provide real data here?',
47 | 'classifiers':["Development Status :: 3 - Alpha",
48 | "Programming Language :: Python :: 3"],
49 | 'requires_dist':['spam'],
50 | '_pypi_hidden':version!='0.4'
51 | })
52 |
53 | st.commit()
54 |
--------------------------------------------------------------------------------
/tools/downloadstats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Generates download stats for all days in the given log files,
3 | # except for the oldest and the newest day.
4 | import sys, os, csv
5 | import apache_reader, apache_stats
6 |
7 | statsdir = '/data/www/pypi/local-stats/'
8 |
9 | days = set()
10 | records = []
11 | for fn in sys.argv[1:]:
12 | for record in apache_reader.ApacheLogReader(fn, '/packages'):
13 | days.add((record['year'], record['month'], record['day']))
14 | records.append(record)
15 |
16 | days = sorted(days)[1:-1]
17 |
18 | class Stats(apache_stats.LocalStats):
19 | def _get_logs(self, logfile, files_url):
20 | return records
21 | stats = Stats()
22 | for year,month,day in days:
23 | stats.build_local_stats(year, month, day, None, statsdir+'days')
24 |
25 |
--------------------------------------------------------------------------------
/tools/dumpstats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import csv
3 | import psycopg2
4 | import bz2
5 | import ConfigParser
6 |
7 |
8 | def main(config, out):
9 | # Setup database connection
10 | c = ConfigParser.ConfigParser({'user': '', 'password': ''})
11 | c.read(config)
12 | dbname = c.get('database', 'name')
13 | dbuser = c.get('database', 'user')
14 | dbpass = c.get('database', 'password')
15 | dbhost = c.get('database', 'host')
16 | dbconn = psycopg2.connect(database=dbname, user=dbuser, password=dbpass,
17 | host=dbhost)
18 | cursor = dbconn.cursor()
19 | cursor.execute('select name,version,filename,downloads from release_files')
20 | with bz2.BZ2File(out, 'w') as f:
21 | w = csv.writer(f)
22 | w.writerows(cursor.fetchall())
23 | dbconn.close()
24 |
25 | if __name__ == '__main__':
26 | import sys
27 | main(sys.argv[1], sys.argv[2])
28 |
--------------------------------------------------------------------------------
/tools/duplicate_users.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python
2 | import os
3 | import sys
4 | import itertools
5 |
6 | # Workaround current bug in docutils:
7 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
8 | import docutils.utils
9 |
10 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11 | sys.path = [root] + sys.path
12 |
13 | import store
14 | import config
15 |
16 |
17 | c = config.Config("config.ini")
18 | store = store.Store(c)
19 | store.open()
20 | cursor = store.get_cursor()
21 |
22 | cursor.execute("SELECT LOWER(name) FROM users GROUP BY LOWER(name) HAVING COUNT(*) > 1")
23 | duplicated = set([x[0] for x in cursor.fetchall()])
24 |
25 | duplicates = {}
26 | users = {}
27 |
28 | for username in duplicated:
29 | cursor.execute("SELECT name, email, last_login FROM users WHERE LOWER(name)=LOWER(%s)", (username,))
30 | dups = cursor.fetchall()
31 |
32 | duplicates[username] = [x[0] for x in dups]
33 |
34 | for x in dups:
35 | users[x[0]] = x
36 |
37 | print len(users), "duplicated users found with", len(duplicated), "total ci unique"
38 |
39 | total_users = users.values()
40 | total_names = set(x[0] for x in total_users)
41 |
42 | delete = set(total_names)
43 |
44 | # Exclude any user who has ever submitted a journal from deletion
45 | cursor.execute("SELECT DISTINCT ON (submitted_by) submitted_by FROM journals")
46 | journaled = set(x[0] for x in cursor.fetchall())
47 | delete -= journaled
48 |
49 | # Exclude any user who is assigned a role on a package
50 | cursor.execute("SELECT DISTINCT ON (user_name) user_name FROM roles")
51 | roles = set(x[0] for x in cursor.fetchall())
52 | delete -= roles
53 |
54 | # Exclude any user who has logged in
55 | cursor.execute("SELECT DISTINCT ON (name) name FROM users WHERE last_login != NULL")
56 | logged_in = set(x[0] for x in cursor.fetchall())
57 | delete -= logged_in
58 |
59 | if delete:
60 | cursor.execute("DELETE FROM users WHERE name in %s", (tuple(delete),))
61 |
62 | store.commit()
63 | store.close()
64 |
--------------------------------------------------------------------------------
/tools/email_renamed_users.py:
--------------------------------------------------------------------------------
1 | import smtplib
2 | import pickle
3 | import sys
4 | import os
5 |
6 | from email.mime.text import MIMEText
7 |
8 | # Workaround current bug in docutils:
9 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
10 | import docutils.utils
11 |
12 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13 | sys.path = [root] + sys.path
14 |
15 | import config
16 | import store
17 |
18 | config = config.Config("config.ini")
19 | store = store.Store(config)
20 |
21 | EMAIL_PLURAL = """
22 | Hello there!
23 |
24 | PyPI has begun to enforce restrictions on what a valid Python package name
25 | contains.
26 |
27 | These rules are:
28 |
29 | * Must contain ONLY ASCII letters, digits, underscores, hyphens, and periods
30 | * Must begin and end with an ASCII letter or digit
31 |
32 | You are listed as an owner or maintainer on %(old)s.
33 |
34 | Due to the new rules these packages will be renamed to %(new)s.
35 |
36 | These new names represent what someone using pip or easy_install would already
37 | have had to use in order to install your packages.
38 |
39 | I am sorry for any inconvenience this may have caused you.
40 | """
41 |
42 |
43 | EMAIL_SINGLE = """
44 | Hello there!
45 |
46 | PyPI has begun to enforce restrictions on what a valid Python package name
47 | contains.
48 |
49 | These rules are:
50 |
51 | * Must contain ONLY ASCII letters, digits, underscores, hyphens, and periods
52 | * Must begin and end with an ASCII letter or digit
53 |
54 | You are listed as an owner or maintainer on "%(old)s".
55 |
56 | Due to the new rules this package will be renamed to "%(new)s".
57 |
58 | This new name represents what someone using pip or easy_install would
59 | already have had to use in order to install your package.
60 |
61 | I am sorry for any inconvenience this may have caused you.
62 | """
63 |
64 | with open("renamed.pkl") as pkl:
65 | renamed = pickle.load(pkl)
66 |
67 |
68 | # Build up a list of all users to email
69 | users = {}
70 | for old, new in renamed:
71 | for role in store.get_package_roles(new):
72 | user_packages = users.setdefault(role["user_name"], [])
73 | user_packages.append((old, new))
74 |
75 | sent = []
76 |
77 | # Email each user
78 | server = smtplib.SMTP(config.mailgun_hostname)
79 | if config.smtp_starttls:
80 | server.starttls()
81 | if config.smtp_auth:
82 | server.login(config.smtp_login, config.smtp_password)
83 | for username, packages in users.iteritems():
84 | packages = sorted(set(packages))
85 |
86 | user = store.get_user(username)
87 |
88 | if not user["email"]:
89 | continue
90 |
91 | if len(packages) > 1:
92 | msg = MIMEText(EMAIL_PLURAL % {
93 | "old": ", ".join(['"%s"' % x[0] for x in packages]),
94 | "new": ", ".join(['"%s"' % x[1] for x in packages]),
95 | })
96 | elif packages:
97 | msg = MIMEText(EMAIL_SINGLE % {
98 | "old": packages[0][0],
99 | "new": packages[0][1],
100 | })
101 |
102 | msg["Subject"] = "Important notice about your PyPI packages"
103 | msg["From"] = "donald@python.org"
104 | msg["To"] = user["email"]
105 |
106 | server.sendmail("donald@python.org", [user["email"]], msg.as_string())
107 | sent.append(("donald@python.org", [user["email"]], msg.as_string()))
108 | server.quit()
109 |
110 | with open("sent.pkl", "w") as pkl:
111 | pickle.dump(sent, pkl)
112 |
--------------------------------------------------------------------------------
/tools/index-trove.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys
3 | import os
4 | import time
5 |
6 | prefix = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 | sys.path.insert(0, prefix)
8 |
9 | # Workaround current bug in docutils:
10 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
11 | import docutils.utils
12 |
13 | from psycopg2.extras import RealDictCursor
14 | import json
15 | import requests
16 |
17 | import config
18 | import store
19 |
20 | CONFIG_FILE = os.environ.get("PYPI_CONFIG", os.path.join(prefix, 'config.ini'))
21 |
22 | conf = config.Config(CONFIG_FILE)
23 |
24 | if conf.database_releases_index_name is None or conf.database_releases_index_url is None:
25 | sys.exit()
26 |
27 | new_index = "trove-%s-%s" % (conf.database_releases_index_name, int(time.time()))
28 | print("creating new index %s" % (new_index,))
29 | store = store.Store(conf)
30 | store.open()
31 |
32 | cursor = store._conn.cursor(cursor_factory=RealDictCursor)
33 |
34 | cursor.execute("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE")
35 | cursor.execute("SET statement_timeout = '600s'")
36 | cursor.execute("select r.name as name, rl.summary as summary, array_agg(distinct trove_id) as trove_classifiers, array_agg(distinct t.l2) || array_agg(distinct t.l3) || array_agg(distinct t.l4) || array_agg(distinct t.l5) as categories from release_classifiers r join releases rl on (rl.name=r.name and rl.version=r.version) join trove_classifiers t on r.trove_id=t.id where not rl._pypi_hidden group by r.name, rl.summary")
37 | while True:
38 | packages = cursor.fetchmany(1000)
39 | if len(packages) == 0:
40 | break
41 | operations = []
42 | for package in packages:
43 | operations.append(json.dumps({"index": {"_index": new_index, "_type": "release_classifiers", "_id": package['name']}}))
44 | operations.append(json.dumps(package))
45 | r = requests.post(conf.database_releases_index_url + "/_bulk", data="\n".join(operations))
46 |
47 | actions = [{"add": {"index": new_index, "alias": "trove-%s" % (conf.database_releases_index_name,)}}]
48 | r = requests.get(conf.database_releases_index_url + "/*/_alias/" + "trove-%s" % (conf.database_releases_index_name,))
49 | for alias, data in r.json().iteritems():
50 | if conf.database_releases_index_name in data['aliases'].keys():
51 | actions.append({"remove": {"index": alias, "alias": conf.database_releases_index_name}})
52 |
53 | print("updating alias for %s to %s" % (conf.database_releases_index_name, new_index))
54 | r = requests.post(conf.database_releases_index_url + "/_aliases", json={'actions': actions})
55 | if r.status_code != 200:
56 | print("failed to update alias for %s to %s" % (conf.database_releases_index_name, new_index))
57 | sys.exit(1)
58 |
59 | r = requests.get(conf.database_releases_index_url + "/_stats")
60 | indicies = [i for i in r.json()['indices'].keys() if i.startswith("trove-%s" % (conf.database_releases_index_name,) + '-')]
61 | for index in indicies:
62 | if index != new_index:
63 | print('deleting %s, because it is not %s' % (index, new_index))
64 | r = requests.delete(conf.database_releases_index_url + "/" + index)
65 | if r.status_code != 200:
66 | print('failed to delete %s' % (index))
67 |
--------------------------------------------------------------------------------
/tools/index.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys
3 | import os
4 | import time
5 |
6 | prefix = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 | sys.path.insert(0, prefix)
8 |
9 | # Workaround current bug in docutils:
10 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
11 | import docutils.utils
12 |
13 | from psycopg2.extras import RealDictCursor
14 | import json
15 | import requests
16 |
17 | import config
18 | import store
19 | from pkg_resources import safe_name
20 |
21 | CONFIG_FILE = os.environ.get("PYPI_CONFIG", os.path.join(prefix, 'config.ini'))
22 |
23 | conf = config.Config(CONFIG_FILE)
24 |
25 | if conf.database_releases_index_name is None or conf.database_releases_index_url is None:
26 | sys.exit()
27 |
28 | new_index = "%s-%s" % (conf.database_releases_index_name, int(time.time()))
29 | print("creating new index %s" % (new_index,))
30 | store = store.Store(conf)
31 | store.open()
32 |
33 | cursor = store._conn.cursor(cursor_factory=RealDictCursor)
34 |
35 | cursor.execute("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE")
36 | cursor.execute("SET statement_timeout = '600s'")
37 | cursor.execute("SELECT DISTINCT ON (name, _pypi_hidden) name, version, _pypi_ordering, _pypi_hidden, author, author_email, maintainer, maintainer_email, home_page, license, summary, description, keywords, platform, download_url FROM releases ORDER BY name, _pypi_hidden, _pypi_ordering DESC")
38 | while True:
39 | releases = cursor.fetchmany(10000)
40 | if len(releases) == 0:
41 | break
42 | operations = []
43 | for release in releases:
44 | operations.append(json.dumps({"index": {"_index": new_index, "_type": "release", "_id": "%s-%s" % (release['name'], release['version'])}}))
45 | release['name_exact'] = safe_name(release['name']).lower()
46 | operations.append(json.dumps(release))
47 | r = requests.post(conf.database_releases_index_url + "/_bulk", data="\n".join(operations))
48 |
49 | actions = [{"add": {"index": new_index, "alias": conf.database_releases_index_name}}]
50 | r = requests.get(conf.database_releases_index_url + "/*/_alias/" + conf.database_releases_index_name)
51 | for alias, data in r.json().iteritems():
52 | if conf.database_releases_index_name in data['aliases'].keys():
53 | actions.append({"remove": {"index": alias, "alias": conf.database_releases_index_name}})
54 |
55 | print("updating alias for %s to %s" % (conf.database_releases_index_name, new_index))
56 | r = requests.post(conf.database_releases_index_url + "/_aliases", json={'actions': actions})
57 | if r.status_code != 200:
58 | print("failed to update alias for %s to %s" % (conf.database_releases_index_name, new_index))
59 | sys.exit(1)
60 |
61 | r = requests.get(conf.database_releases_index_url + "/_stats")
62 | indicies = [i for i in r.json()['indices'].keys() if i.startswith(conf.database_releases_index_name + '-')]
63 | for index in indicies:
64 | if index != new_index:
65 | print('deleting %s, because it is not %s' % (index, new_index))
66 | r = requests.delete(conf.database_releases_index_url + "/" + index)
67 | if r.status_code != 200:
68 | print('failed to delete %s' % (index))
69 |
--------------------------------------------------------------------------------
/tools/integrate-redis-stats.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | import datetime
5 | import redis
6 | import string
7 |
8 | from itertools import izip, izip_longest
9 |
10 | # Workaround current bug in docutils:
11 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
12 | import docutils.utils
13 |
14 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15 | sys.path = [root] + sys.path
16 |
17 | import store
18 | import config
19 |
20 | c = config.Config(os.path.join(root, "config.ini"))
21 | redis = redis.Redis.from_url(c.count_redis_url)
22 |
23 | # Get our search for the previous hour keys
24 | current = datetime.datetime.utcnow()
25 | lasthour = current - datetime.timedelta(hours=1)
26 | search = "downloads:hour:%s:*:*" % lasthour.strftime("%y-%m-%d-%H")
27 |
28 | # Make sure we haven't integrated this already
29 | if redis.sismember("downloads:integrated", search):
30 | print("Already Integrated '%s'" % search)
31 | sys.exit(0)
32 |
33 | # Fetch all of the keys
34 | keys = redis.keys(search)
35 |
36 | if not keys:
37 | print("No keys match '%s'" % search)
38 | sys.exit(0)
39 |
40 | # Fetch all of the download counts (in batches of 200)
41 | counts = []
42 | for batch in izip_longest(*[iter(keys)] * 200):
43 | batch = [x for x in batch if x is not None]
44 | counts.extend(redis.mget(*batch))
45 |
46 | # Combine the keys with the counts
47 | downloads = izip(
48 | (int(y) for y in counts),
49 | (x.split(":")[-1] for x in keys),
50 | )
51 |
52 | # Update the database
53 | store = store.Store(c)
54 | cursor = store.get_cursor()
55 | cursor.executemany(
56 | "UPDATE release_files SET downloads = downloads + %s WHERE filename = %s",
57 | (d for d in downloads if not set(d[1]) - set(string.printable)),
58 | )
59 | store.commit()
60 | store.close()
61 |
62 | # Add this to our integrated set
63 | redis.sadd("downloads:integrated", search)
64 |
--------------------------------------------------------------------------------
/tools/integratestats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys, os, socket, psycopg2, urllib, re, bz2, cStringIO, ConfigParser
3 | sys.path.append(os.path.dirname(__file__)+"/..")
4 | import apache_stats
5 |
6 | statsdir = '/data/www/pypi/stats'
7 |
8 | def integrate(config, data, dbconn):
9 | for (filename, browser, package), count in data.items():
10 | cursor.execute('update release_files set downloads=downloads+%s where filename=%s',
11 | (count, filename))
12 | dbconn.commit()
13 |
14 | def integrate_remote(config, host, dbconn, dbupdate=True):
15 | url = 'http://%s.pypi.python.org/local-stats/days/' % host
16 | try:
17 | index = urllib.urlopen(url).read()
18 | except Exception, e:
19 | print 'ERROR %s for %s' % (e, url)
20 | return
21 | files = set(re.findall('href=.(20..-..-..).bz2', index))
22 | try:
23 | integrated = open(statsdir+'/integrated/'+host).readlines()
24 | integrated = set([x.strip() for x in integrated])
25 | except IOError:
26 | integrated = set()
27 | missing = files-integrated
28 | stats = apache_stats.LocalStats()
29 | for m in missing:
30 | url = 'http://%s.pypi.python.org/local-stats/days/%s.bz2' % (host, m)
31 | try:
32 | data = urllib.urlopen(url).read()
33 | except Exception, e:
34 | print 'ERROR %s for %s' % (e, url)
35 | continue
36 | data = bz2.decompress(data)
37 | data = cStringIO.StringIO(data)
38 | year, month, day = m.split('-')
39 | # index integration
40 | delta = stats.integrate_stats(statsdir, year, month, day, data)
41 | if dbupdate:
42 | # database integration
43 | integrate(config, delta, dbconn=dbconn)
44 | integrated.add(m)
45 | open(statsdir+'/integrated/'+host, 'w').write('\n'.join(sorted(integrated)))
46 |
47 | def main():
48 | # Setup database connection
49 | c = ConfigParser.ConfigParser({'user':'', 'password':''})
50 | c.read(sys.argv[1])
51 | dbname = c.get('database', 'name')
52 | dbuser = c.get('database', 'user')
53 | dbpass = c.get('database', 'password')
54 | dbhost = c.get('database', 'host')
55 | dbconn = psycopg2.connect(database=dbname, user=dbuser, password=dbpass, host=dbhost)
56 |
57 | lasts = socket.gethostbyname_ex('last.pypi.python.org')
58 | # look for name X.pypi.python.org
59 | lasts = [lasts[0]] + lasts[1]
60 | for last in lasts:
61 | if last[1:] == '.pypi.python.org':
62 | break
63 | else:
64 | raise ValueError, "Could not properly resolve last mirror name"
65 | last = last.split('.')[0]
66 | host = 'a'
67 | while True:
68 | if host == "d":
69 | # This is just a CNAME back to a.pypi.python.org
70 | continue
71 |
72 | integrate_remote(sys.argv[1], host, dbconn=dbconn)
73 | if host == last:
74 | break
75 | host = chr(ord(host)+1)
76 | dbconn.close()
77 |
78 | main()
79 |
80 |
--------------------------------------------------------------------------------
/tools/keygen:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | openssl dsaparam -out param 2048
3 | openssl gendsa -out privkey param
4 | openssl dsa -in privkey -pubout -out pubkey
5 | rm param
6 |
--------------------------------------------------------------------------------
/tools/migrate.py:
--------------------------------------------------------------------------------
1 | # migrate a historical SQLITE database to a postgres one
2 |
3 | import sqlite
4 | import psycopg
5 |
6 | old = sqlite.connect(db='pkgbase.db')
7 | cursor = old.cursor()
8 | e = cursor.execute
9 |
10 | new = psycopg.connect(database='pypi')
11 | new_cursor = new.cursor()
12 | f = new_cursor.execute
13 |
14 | t = [
15 | 'users name password email public_key',
16 | 'packages name stable_version',
17 | 'releases name version author author_email maintainer maintainer_email home_page license summary description keywords platform download_url _pypi_ordering _pypi_hidden',
18 | 'trove_classifiers id classifier',
19 | 'release_classifiers name version trove_id',
20 | 'journals name version action submitted_date submitted_by submitted_from',
21 | 'rego_otk name otk',
22 | 'roles role_name user_name package_name',
23 | ]
24 | for table in t:
25 | l = table.split()
26 | tn = l[0]
27 | print tn
28 | cols = ', '.join(l[1:])
29 | args = ', '.join(['%s']*(len(l)-1))
30 | e('select %(cols)s from %(tn)s'%locals())
31 | for row in cursor.fetchall():
32 | d = list(row)
33 | if '_pypi_ordering' in l[1:]: d[-2] = d[-2] and int(float(d[-2]))
34 | f('insert into %(tn)s (%(cols)s) values (%(args)s)'%locals(),
35 | tuple(d))
36 |
37 | new.commit()
38 |
--------------------------------------------------------------------------------
/tools/mksqlite.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import os
3 | dbpath = "packages.db"
4 |
5 | if os.path.exists(dbpath):
6 | print "Remove",dbpath,"first"
7 | raise SystemExit
8 |
9 | print "Creating database", dbpath
10 | sqlite = os.popen('sqlite3 '+dbpath, "w")
11 | passthrough = True
12 | for line in open('pkgbase_schema.sql'):
13 | if 'nosqlite-end' in line:
14 | # end of disabled block
15 | passthrough = True
16 | print >>sqlite
17 | continue
18 | if 'nosqlite' in line:
19 | passthrough = False
20 | print >>sqlite
21 | continue
22 | if not passthrough:
23 | print >> sqlite
24 | continue
25 | # make sqlite happy: SERIAL is not a valid type
26 | sqlite.write(line.replace('SERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY'))
27 | sqlite.close()
28 |
--------------------------------------------------------------------------------
/tools/pw_reset_mailout.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import csv
3 | import time
4 | import smtplib
5 |
6 | import store
7 | import config
8 |
9 |
10 | def main():
11 | c = config.Config('/data/pypi/config.ini')
12 | if len(sys.argv) != 2:
13 | st = store.Store(c)
14 | st.open()
15 | w = csv.writer(sys.stdout)
16 | for user in st.get_users():
17 | w.writerow((user['name'], user['email']))
18 | else:
19 | with open(sys.argv[1]) as f:
20 | for name, email in csv.reader(f):
21 | message = TEXT.format(name=name, email=email)
22 | # send email
23 | s = smtplib.SMTP()
24 | s.connect('localhost')
25 | s.sendmail('richard@python.org', [email], message)
26 | s.close()
27 | time.sleep(.1)
28 |
29 | TEXT = '''From: richard@python.org
30 | To: {email}
31 | Subject: PyPI security notice
32 |
33 |
34 | TL;DR: please log into PyPI and change your password.
35 |
36 | Dear PyPI user {name},
37 |
38 | Recently we have been auditing and improving security of the Python Package
39 | Index (PyPI) and other python.org hosts.
40 |
41 | You may be aware that the wiki.python.org host was compromised. Since we must
42 | assume that all passwords stored in that system are also compromised, and we
43 | also assume that some users share passwords between python.org systems, we are
44 | performing a password reset of all PyPI accounts in one week's time, at
45 | 2013-02-22 00:00 UTC.
46 |
47 | If you log in before that deadline and change your password then you'll be
48 | fine, otherwise you'll need to use the password recovery form after the reset
49 | has occurred.
50 |
51 | Additionally, we would ask you to begin to access PyPI using HTTPS through the
52 | web. We're in the process of installing a new SSL certificate so the current
53 | Big Red Certificate Warning should go away very soon.
54 |
55 | We are in the process of updating the Python packaging toolset to use HTTPS.
56 |
57 | These steps are but a couple of those we're intending to take to better secure
58 | PyPI. If you are interested in these matters I encourage you to participate in
59 | the discussion on the catalog SIG:
60 |
61 | http://mail.python.org/mailman/listinfo/catalog-sig
62 |
63 | Finally, we apologise for any inconvenience these changes have caused.
64 |
65 |
66 | Richard Jones
67 | richard@python.org
68 | PyPI Maintainer
69 | '''
70 |
71 | if __name__ == '__main__':
72 | main()
73 |
--------------------------------------------------------------------------------
/tools/rsyslog-cdn.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -u
2 | import sys
3 | import redis
4 | import csv
5 | import os
6 | import posixpath
7 | import datetime
8 | import logging
9 | import logging.handlers
10 |
11 | from email.utils import parsedate
12 |
13 | # Make sure our PyPI directory is on the sys.path
14 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15 | sys.path = [root] + sys.path
16 |
17 | import config
18 |
19 | conf = config.Config(os.environ.get("PYPI_CONFIG", os.path.join(root, "config.ini")))
20 |
21 | PRECISIONS = [
22 | ("hour", "%y-%m-%d-%H", datetime.timedelta(days=2)),
23 | ("daily", "%y-%m-%d", datetime.timedelta(days=32)),
24 | ]
25 |
26 |
27 | logger = logging.getLogger("rsyslog-cdn")
28 | logger.setLevel(logging.DEBUG)
29 | logger.addHandler(logging.handlers.SysLogHandler(address="/dev/log"))
30 |
31 | store = redis.Redis.from_url(conf.count_redis_url)
32 |
33 |
34 | def make_key(precision, when, key):
35 | return "downloads:%s:%s:%s" % (
36 | precision[0], when.strftime(precision[1]), key)
37 |
38 |
39 | def incr(when, project, filename):
40 | # Increment our rolling counts in Redis
41 | for prec in PRECISIONS:
42 | key = make_key(prec, when, project)
43 | store.incr(key)
44 | store.expireat(key, when + prec[2])
45 |
46 | # Increment our filename based bucket in Redis
47 | for prec in PRECISIONS:
48 | key = make_key(prec, when, ":".join([project, filename]))
49 | store.incr(key)
50 | store.expireat(key, when + prec[2])
51 |
52 |
53 | def process(line):
54 | if "last message repeated" in line:
55 | logger.error("Duplicate Line in rsyslog-cdn")
56 |
57 | try:
58 | row = list(csv.reader([line], delimiter=" "))[0]
59 | path = row[6].split(" ", 1)[1]
60 | except Exception:
61 | logger.error("Invalid Fastly Log Line: '%s'" % line)
62 | return
63 |
64 | # We only care about /packages/ urls
65 | if not path.startswith("/packages/"):
66 | return
67 |
68 | # We need to get the Project and Filename
69 | directory, filename = posixpath.split(path)
70 | project = posixpath.basename(directory)
71 |
72 | # We need to get the time this request occurred
73 | rtime = datetime.datetime(*parsedate(row[4])[:6])
74 |
75 | incr(rtime, project, filename)
76 |
77 |
78 | if __name__ == "__main__":
79 | line = sys.stdin.readline()
80 | while line:
81 | try:
82 | process(line)
83 | except Exception:
84 | logger.exception("Error occured while processing '%s'", line)
85 | raise
86 | line = sys.stdin.readline()
87 |
--------------------------------------------------------------------------------
/tools/sanitize_package_names.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python
2 | import re
3 | import os
4 | import sys
5 | import pickle
6 |
7 | # Workaround current bug in docutils:
8 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
9 | import docutils.utils
10 |
11 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12 | sys.path = [root] + sys.path
13 |
14 | import store
15 | import config
16 |
17 |
18 | c = config.Config("config.ini")
19 | store = store.Store(c)
20 | store.open()
21 | cursor = store.get_cursor()
22 |
23 | valid_name = re.compile(
24 | r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
25 | re.IGNORECASE,
26 | )
27 |
28 |
29 | def bad_names():
30 | cursor.execute("""
31 | SELECT name from packages WHERE
32 | name !~* '^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$'
33 | """)
34 | return set(x[0] for x in cursor.fetchall())
35 |
36 | renamed = []
37 |
38 | junk_names = bad_names()
39 | print("Found %s bad names" % len(junk_names))
40 |
41 | print("Rename packages where the only invalid name is a space")
42 | for name in junk_names:
43 | if name.strip() != name:
44 | continue
45 |
46 | if valid_name.search(name.replace(" ", "-")) is not None:
47 | new_name = re.sub("\s+", "-", name)
48 | renamed.append((name, new_name))
49 | store.rename_package(name, new_name)
50 | else:
51 | new_name = re.sub("[^A-Za-z0-9.]+", "-", name).strip("_-.")
52 | if valid_name.search(new_name) is None:
53 | continue
54 | num = 2
55 | onew_name = new_name
56 | while [x for x in store.find_package(new_name) if x != name]:
57 | new_name = "%s%s" % (onew_name, str(num))
58 | num += 1
59 | renamed.append((name, new_name))
60 | store.rename_package(name, new_name)
61 |
62 | with open("renamed.pkl", "w") as pkl:
63 | pickle.dump(renamed, pkl)
64 |
65 | # Commit the changes
66 | store.commit()
67 |
68 | junk_names = bad_names()
69 | print("Found %s bad names" % len(junk_names))
70 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20050320-2.sql:
--------------------------------------------------------------------------------
1 |
2 | -- Table structure for table: release_provides
3 | CREATE TABLE release_provides (
4 | name TEXT,
5 | version TEXT,
6 | specifier TEXT,
7 | FOREIGN KEY (name, version) REFERENCES releases (name, version)
8 | );
9 | CREATE INDEX rel_prov_name_idx ON release_provides(name);
10 | CREATE INDEX rel_prov_version_id_idx ON release_provides(version);
11 |
12 |
13 | -- Table structure for table: release_requires
14 | CREATE TABLE release_requires (
15 | name TEXT,
16 | version TEXT,
17 | specifier TEXT,
18 | FOREIGN KEY (name, version) REFERENCES releases (name, version)
19 | );
20 | CREATE INDEX rel_req_name_idx ON release_requires(name);
21 | CREATE INDEX rel_req_version_id_idx ON release_requires(version);
22 |
23 |
24 | -- Table structure for table: release_obsoletes
25 | CREATE TABLE release_obsoletes (
26 | name TEXT,
27 | version TEXT,
28 | specifier TEXT,
29 | FOREIGN KEY (name, version) REFERENCES releases (name, version)
30 | );
31 | CREATE INDEX rel_obs_name_idx ON release_obsoletes(name);
32 | CREATE INDEX rel_obs_version_id_idx ON release_obsoletes(version);
33 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20050320.sql:
--------------------------------------------------------------------------------
1 |
2 | DROP TABLE package_files;
3 |
4 | DROP TABLE package_urls;
5 |
6 | CREATE TABLE release_files (
7 | name TEXT,
8 | version TEXT,
9 | python_version TEXT,
10 | packagetype TEXT,
11 | comment_text TEXT,
12 | filename TEXT UNIQUE,
13 | md5_digest TEXT UNIQUE,
14 | FOREIGN KEY (name, version) REFERENCES releases (name, version)
15 | );
16 | CREATE INDEX release_files_name_idx ON release_files(name);
17 | CREATE INDEX release_files_version_idx ON release_files(version);
18 | CREATE INDEX release_files_packagetype_idx ON release_files(packagetype);
19 |
20 |
21 | CREATE TABLE release_urls (
22 | name TEXT,
23 | version TEXT,
24 | url TEXT,
25 | packagetype TEXT,
26 | FOREIGN KEY (name, version) REFERENCES releases (name, version)
27 | );
28 | CREATE INDEX release_urls_name_idx ON release_urls(name);
29 | CREATE INDEX release_urls_version_idx ON release_urls(version);
30 | CREATE INDEX release_urls_packagetype_idx ON release_urls(packagetype);
31 |
32 |
33 | ALTER TABLE users DROP COLUMN public_key;
34 |
35 | ALTER TABLE USERS ADD COLUMN gpg_keyid TEXT;
36 |
37 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20050322.sql:
--------------------------------------------------------------------------------
1 |
2 | ALTER TABLE releases ADD COLUMN description_html TEXT;
3 |
4 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20050402.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE timestamps (
2 | name TEXT PRIMARY KEY,
3 | value TIMESTAMP
4 | );
5 | INSERT INTO timestamps(name, value) VALUES('http','1970-01-01');
6 | INSERT INTO timestamps(name, value) VALUES('ftp','1970-01-01');
7 |
8 | ALTER TABLE release_files ADD COLUMN downloads INTEGER;
9 | ALTER TABLE release_files ALTER downloads SET DEFAULT 0;
10 |
11 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20070409.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO timestamps(name, value) VALUES('browse_tally','1970-01-01');
2 | CREATE TABLE browse_tally (
3 | trove_id INTEGER PRIMARY KEY,
4 | tally INTEGER
5 | );
6 |
7 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20070706.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE trove_classifiers ADD COLUMN l2 INTEGER;
2 | ALTER TABLE trove_classifiers ADD COLUMN l3 INTEGER;
3 | ALTER TABLE trove_classifiers ADD COLUMN l4 INTEGER;
4 | ALTER TABLE trove_classifiers ADD COLUMN l5 INTEGER;
5 |
6 | CREATE INDEX rel_class_name_version_idx ON release_classifiers(name, version);
7 |
8 | -- run 'store.py config.ini checktrove' after installing this change
9 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20070709.sql:
--------------------------------------------------------------------------------
1 | create index journals_latest_releases on journals(submitted_date, name, version) where version is not null and action='new release';
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20070713.sql:
--------------------------------------------------------------------------------
1 | create index journals_changelog on journals(submitted_date, name, version, action);
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20070721.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE description_urls (
2 | name TEXT,
3 | version TEXT,
4 | url TEXT,
5 | FOREIGN KEY (name, version) REFERENCES releases (name, version)
6 | );
7 | CREATE INDEX description_urls_name_idx ON description_urls(name);
8 | CREATE INDEX description_urls_name_version_idx ON description_urls(name, version);
9 | grant all on description_urls to pypi;
10 |
11 | -- run 'store.py config.ini updateurls' after installing this change
12 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20080512.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE packages ADD COLUMN normalized_name TEXT;
2 | \echo Run store.py update_normalized_text now
3 | ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
4 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20081007.sql:
--------------------------------------------------------------------------------
1 | -- before running this, verify that the constraints
2 | -- that get deleted are the same ones that get recreated
3 | -- (with cascading update)
4 | begin;
5 | alter table releases drop constraint "$1";
6 | alter table releases add foreign key (name) references packages(name) on update cascade;
7 |
8 | alter table release_provides drop constraint "$1";
9 | alter table release_provides add foreign key (name, version) references releases(name, version) on update cascade;
10 |
11 | alter table release_requires drop constraint "$1";
12 | alter table release_requires add foreign key (name, version) references releases(name, version) on update cascade;
13 |
14 | alter table release_obsoletes drop constraint "$1";
15 | alter table release_obsoletes add foreign key (name, version) references releases(name, version) on update cascade;
16 |
17 | alter table release_files drop constraint "$1";
18 | alter table release_files add foreign key (name, version) references releases(name, version) on update cascade;
19 |
20 | alter table release_urls drop constraint "$1";
21 | alter table release_urls add foreign key (name, version) references releases(name, version) on update cascade;
22 |
23 | alter table description_urls drop constraint "$1";
24 | alter table description_urls add foreign key (name, version) references releases(name, version) on update cascade;
25 |
26 | alter table release_classifiers drop constraint "$2";
27 | alter table release_classifiers add foreign key (name, version) references releases(name, version) on update cascade;
28 |
29 | alter table roles drop constraint "$2";
30 | alter table roles add foreign key (package_name) references packages(name) on update cascade;
31 |
32 | commit;
33 |
34 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090124.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE packages ADD COLUMN autohide BOOLEAN DEFAULT TRUE;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090208.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE release_files ADD COLUMN upload_time TIMESTAMP;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090327.sql:
--------------------------------------------------------------------------------
1 | -- Table structure for table: mirrors
2 | CREATE TABLE mirrors (
3 | root_url TEXT PRIMARY KEY,
4 | user_name TEXT REFERENCES users,
5 | index_url TEXT,
6 | last_modified_url TEXT,
7 | local_stats_url TEXT,
8 | stats_url TEXT,
9 | mirrors_url TEXT
10 | );
11 |
12 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090329.sql:
--------------------------------------------------------------------------------
1 | alter table mirrors drop index_url;
2 | alter table mirrors drop last_modified_url;
3 | alter table mirrors drop local_stats_url;
4 | alter table mirrors drop stats_url;
5 | alter table mirrors drop mirrors_url;
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090331.sql:
--------------------------------------------------------------------------------
1 | alter table mirrors rename root_url to ip;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090829.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE openids (
2 | id TEXT PRIMARY KEY,
3 | name TEXT REFERENCES users
4 | );
5 |
6 | CREATE TABLE openid_sessions (
7 | id SERIAL PRIMARY KEY,
8 | provider TEXT,
9 | url TEXT,
10 | assoc_handle TEXT,
11 | expires TIMESTAMP,
12 | mac_key TEXT
13 | );
14 |
15 | CREATE TABLE openid_stypes (
16 | id INTEGER REFERENCES openid_sessions ON DELETE CASCADE,
17 | stype TEXT
18 | );
19 | CREATE INDEX openid_stypes_id ON openid_stypes(id);
20 |
21 | CREATE TABLE openid_nonces (
22 | created TIMESTAMP,
23 | nonce TEXT
24 | );
25 | CREATE INDEX openid_nonces_created ON openid_nonces(created);
26 | CREATE INDEX openid_nonces_nonce ON openid_nonces(nonce);
27 |
28 | CREATE TABLE cookies (
29 | cookie text PRIMARY KEY,
30 | name text references users,
31 | last_seen timestamp
32 | );
33 | CREATE INDEX cookies_last_seen ON cookies(last_seen);
34 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20090914.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE ratings(
2 | name TEXT,
3 | version TEXT,
4 | user_name TEXT REFERENCES users ON DELETE CASCADE,
5 | date TIMESTAMP,
6 | rating INTEGER,
7 | message TEXT,
8 | PRIMARY KEY (name, version, user_name),
9 | FOREIGN KEY (name, version) REFERENCES releases ON UPDATE CASCADE ON DELETE CASCADE
10 | );
11 | CREATE INDEX rating_name_version ON ratings(name, version);
12 | GRANT ALL ON ratings TO pypi;
--------------------------------------------------------------------------------
/tools/sql-migrate-20091015.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 |
3 | ALTER TABLE ratings ADD id SERIAL UNIQUE;
4 | GRANT ALL ON ratings_id_seq TO pypi;
5 |
6 | CREATE TABLE comments(
7 | id SERIAL PRIMARY KEY,
8 | rating INTEGER REFERENCES ratings(id) ON DELETE CASCADE,
9 | user_name TEXT REFERENCES users ON DELETE CASCADE,
10 | date TIMESTAMP,
11 | message TEXT,
12 | in_reply_to INTEGER REFERENCES comments ON DELETE CASCADE
13 | );
14 | GRANT ALL ON comments TO pypi;
15 | GRANT ALL ON comments_id_seq TO pypi;
16 |
17 | INSERT INTO comments(rating, user_name, date, message)
18 | SELECT id, user_name, date, message FROM ratings WHERE message!='';
19 |
20 | ALTER TABLE ratings DROP COLUMN message;
21 |
22 | CREATE TABLE comments_journal(
23 | name text,
24 | version text,
25 | id INTEGER,
26 | submitted_by TEXT REFERENCES users ON DELETE CASCADE,
27 | date TIMESTAMP,
28 | action TEXT,
29 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON DELETE CASCADE
30 | );
31 | GRANT ALL ON comments_journal TO pypi;
32 |
33 | END;
34 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20091125.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE rego_otk ADD COLUMN date TIMESTAMP;
2 | UPDATE rego_otk SET date=now();
3 | BEGIN;
4 | ALTER TABLE rego_otk DROP CONSTRAINT "$1";
5 | ALTER TABLE rego_otk ADD CONSTRAINT "$1" FOREIGN KEY (name) REFERENCES users ON DELETE CASCADE;
6 | END;
--------------------------------------------------------------------------------
/tools/sql-migrate-20091204.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE packages ADD COLUMN comments BOOLEAN DEFAULT TRUE;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20091228.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX rel_req_name_version_idx ON release_requires (name,version);
2 | CREATE INDEX rel_prov_name_version_idx ON release_provides (name,version);
3 | CREATE INDEX rel_obs_name_version_idx ON release_obsoletes (name,version);
4 | CREATE INDEX release_files_name_version_idx ON release_files(name,version);
5 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20100102.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE sshkeys(
2 | id SERIAL PRIMARY KEY,
3 | name TEXT REFERENCES users ON DELETE CASCADE,
4 | key TEXT
5 | );
6 | CREATE INDEX sshkeys_name ON sshkeys(name);
7 | GRANT ALL ON sshkeys TO pypi;
8 | GRANT ALL ON sshkeys_id_seq TO pypi;
9 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20100103.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX rego_otk_otk_idx ON rego_otk(otk);
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20100313.sql:
--------------------------------------------------------------------------------
1 | -- New fields
2 | ALTER TABLE releases ADD COLUMN requires_python TEXT;
3 |
4 | --
5 | -- New tables
6 | --
7 |
8 | -- Table structure for table: release_requires_python
9 | CREATE TABLE release_requires_python (
10 | name TEXT,
11 | version TEXT,
12 | specifier TEXT,
13 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
14 | );
15 | CREATE INDEX rel_req_python_name_idx ON release_requires_python(name);
16 | CREATE INDEX rel_req_python_version_id_idx ON release_requires_python(version);
17 | CREATE INDEX rel_req_python_name_version_idx ON release_requires_python(name,version);
18 |
19 | -- Table structure for table: release_requires_external
20 | CREATE TABLE release_requires_external (
21 | name TEXT,
22 | version TEXT,
23 | specifier TEXT,
24 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
25 | );
26 | CREATE INDEX rel_req_ext_name_idx ON release_requires_external(name);
27 | CREATE INDEX rel_req_ext_version_id_idx ON release_requires_external(version);
28 | CREATE INDEX rel_req_ext_name_version_idx ON release_requires_external(name,version);
29 |
30 | -- Table structure for table: release_requires_dist
31 | CREATE TABLE release_requires_dist (
32 | name TEXT,
33 | version TEXT,
34 | specifier TEXT,
35 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
36 | );
37 | CREATE INDEX rel_req_dist_name_idx ON release_requires_dist(name);
38 | CREATE INDEX rel_req_dist_version_id_idx ON release_requires_dist(version);
39 | CREATE INDEX rel_req_dist_name_version_idx ON release_requires_dist(name,version);
40 |
41 | -- Table structure for table: release_provides_dist
42 | CREATE TABLE release_provides_dist (
43 | name TEXT,
44 | version TEXT,
45 | specifier TEXT,
46 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
47 | );
48 | CREATE INDEX rel_prov_dist_name_idx ON release_provides_dist(name);
49 | CREATE INDEX rel_prov_dist_version_id_idx ON release_provides_dist(version);
50 | CREATE INDEX rel_prov_dist_name_version_idx ON release_provides_dist(name,version);
51 |
52 | -- Table structure for table: release_obsoletes_dist
53 | CREATE TABLE release_obsoletes_dist (
54 | name TEXT,
55 | version TEXT,
56 | specifier TEXT,
57 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
58 | );
59 | CREATE INDEX rel_obs_dist_name_idx ON release_obsoletes_dist(name);
60 | CREATE INDEX rel_obs_dist_version_id_idx ON release_obsoletes_dist(version);
61 | CREATE INDEX rel_obs_dist_name_version_idx ON release_obsoletes_dist(name,version);
62 |
63 | -- Table structure for table: release_project_url
64 | CREATE TABLE release_project_url (
65 | name TEXT,
66 | version TEXT,
67 | specifier TEXT,
68 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
69 | );
70 | CREATE INDEX rel_proj_url_name_idx ON release_project_url(name);
71 | CREATE INDEX rel_proj_url_version_id_idx ON release_project_url(version);
72 | CREATE INDEX rel_proj_url_name_version_idx ON release_project_url(name,version);
73 |
74 |
75 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20100724.sql:
--------------------------------------------------------------------------------
1 | begin;
2 | alter table ratings drop constraint ratings_pkey;
3 | alter table ratings add primary key (id);
4 | alter table ratings add unique(name,version,user_name);
5 | end;
--------------------------------------------------------------------------------
/tools/sql-migrate-20100814.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 | CREATE TABLE release_dependencies (
3 | name TEXT,
4 | version TEXT,
5 | kind INTEGER,
6 | specifier TEXT,
7 | FOREIGN KEY (name, version) REFERENCES releases (name, version) ON UPDATE CASCADE
8 | );
9 | grant all on release_dependencies to pypi;
10 |
11 | insert into release_dependencies(name, version, kind, specifier)
12 | select name, version, 1, specifier from release_requires;
13 | insert into release_dependencies(name, version, kind, specifier)
14 | select name, version, 2, specifier from release_provides;
15 | insert into release_dependencies(name, version, kind, specifier)
16 | select name, version, 3, specifier from release_obsoletes;
17 | insert into release_dependencies(name, version, kind, specifier)
18 | select name, version, 4, specifier from release_requires_dist;
19 | insert into release_dependencies(name, version, kind, specifier)
20 | select name, version, 5, specifier from release_provides_dist;
21 | insert into release_dependencies(name, version, kind, specifier)
22 | select name, version, 6, specifier from release_obsoletes_dist;
23 | insert into release_dependencies(name, version, kind, specifier)
24 | select name, version, 7, specifier from release_requires_external;
25 | insert into release_dependencies(name, version, kind, specifier)
26 | select name, version, 8, specifier from release_project_url;
27 |
28 | CREATE INDEX rel_dep_name_idx ON release_dependencies(name);
29 | CREATE INDEX rel_dep_name_version_idx ON release_dependencies(name, version);
30 | CREATE INDEX rel_dep_name_version_kind_idx ON release_dependencies(name, version, kind);
31 |
32 | drop table release_requires;
33 | drop table release_provides;
34 | drop table release_obsoletes;
35 | drop table release_requires_dist;
36 | drop table release_provides_dist;
37 | drop table release_obsoletes_dist;
38 | drop table release_requires_external;
39 | drop table release_project_url;
40 |
41 | COMMIT;
42 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110129.sql:
--------------------------------------------------------------------------------
1 | alter TABLE rego_otk add constraint rego_otk_unique unique(otk);
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110220.sql:
--------------------------------------------------------------------------------
1 | begin;
2 | alter table comments_journal drop constraint comments_journal_name_fkey;
3 | alter table comments_journal
4 | add foreign key (name, version) references releases
5 | on update cascade on delete cascade;
6 | end;
7 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110822.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE openids DROP CONSTRAINT openids_name_fkey;
2 | ALTER TABLE openids ADD CONSTRAINT openids_name_fkey FOREIGN KEY (name) REFERENCES users ON DELETE CASCADE;
3 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110823-2.sql:
--------------------------------------------------------------------------------
1 | create table csrf_tokens (
2 | name text REFERENCES users(name) ON DELETE CASCADE,
3 | token text,
4 | end_date timestamp without time zone,
5 | PRIMARY KEY(name)
6 | );
7 |
8 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110823.sql:
--------------------------------------------------------------------------------
1 | alter table packages add bugtrack_url text;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110831.sql:
--------------------------------------------------------------------------------
1 | insert into users(name, password, email, gpg_keyid, last_login)
2 | values('deleted user', 'invalid', '', '', '2000-01-01');
3 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20110905.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE openid_whitelist
2 | (
3 | "name" text NOT NULL,
4 | trust_root text NOT null,
5 | created timestamp without time zone,
6 | CONSTRAINT openid_whitelist__pkey PRIMARY KEY (name, trust_root)
7 | );
8 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20111119.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE openid_discovered (
2 | created TIMESTAMP,
3 | url TEXT PRIMARY KEY,
4 | services BYTEA,
5 | op_endpoint TEXT,
6 | op_local TEXT
7 | );
8 | alter table openid_sessions drop provider;
9 | drop table openid_stypes;
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20111120.sql:
--------------------------------------------------------------------------------
1 | begin;
2 | CREATE TABLE oid_nonces
3 | (
4 | server_url VARCHAR(2047) NOT NULL,
5 | timestamp INTEGER NOT NULL,
6 | salt CHAR(40) NOT NULL,
7 | PRIMARY KEY (server_url, timestamp, salt)
8 | );
9 |
10 | CREATE TABLE oid_associations
11 | (
12 | server_url VARCHAR(2047) NOT NULL,
13 | handle VARCHAR(255) NOT NULL,
14 | secret BYTEA NOT NULL,
15 | issued INTEGER NOT NULL,
16 | lifetime INTEGER NOT NULL,
17 | assoc_type VARCHAR(64) NOT NULL,
18 | PRIMARY KEY (server_url, handle),
19 | CONSTRAINT secret_length_constraint CHECK (LENGTH(secret) <= 128)
20 | );
21 |
22 | GRANT ALL ON oid_nonces, oid_associations TO pypi;
23 | commit;
24 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20111130.sql:
--------------------------------------------------------------------------------
1 | begin;
2 | alter table journals drop constraint "$1";
3 | alter table journals add foreign key (submitted_by) references users (name) on update cascade;
4 | end;
5 | begin;
6 | alter table roles drop constraint "$1";
7 | alter table roles add foreign key (user_name) references users (name) on update cascade;
8 | end;
9 | begin;
10 | alter table cookies drop constraint cookies_name_fkey;
11 | alter table cookies add foreign key (name) references users (name) on update cascade on delete cascade;
12 | end;
13 | begin;
14 | alter table openids drop constraint openids_name_fkey;
15 | alter table openids add foreign key (name) references users (name) on update cascade on delete cascade;
16 | end;
17 | begin;
18 | alter table sshkeys drop constraint sshkeys_name_fkey;
19 | alter table sshkeys add foreign key (name) references users (name) on update cascade on delete cascade;
20 | end;
21 | begin;
22 | alter table csrf_tokens drop constraint csrf_tokens_name_fkey;
23 | alter table csrf_tokens add foreign key (name) references users (name) on update cascade on delete cascade;
24 | end;
25 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20120312.sql:
--------------------------------------------------------------------------------
1 | alter table releases add description_from_readme BOOLEAN;
2 |
3 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20120314.sql:
--------------------------------------------------------------------------------
1 | -- tables for the oauth library
2 |
3 | CREATE TABLE oauth_consumers (
4 | consumer varchar(32) primary key,
5 | secret varchar(64) not null,
6 | date_created date not null,
7 | created_by TEXT REFERENCES users ON UPDATE CASCADE,
8 | last_modified date not null,
9 | description varchar(255) not null
10 | );
11 |
12 | CREATE TABLE oauth_request_tokens (
13 | token varchar(32) primary key,
14 | secret varchar(64) not null,
15 | consumer varchar(32) not null,
16 | callback text,
17 | date_created date not null,
18 | "user" TEXT REFERENCES users ON UPDATE CASCADE ON DELETE CASCADE
19 | );
20 |
21 | CREATE TABLE oauth_access_tokens (
22 | token varchar(32) primary key,
23 | secret varchar(64) not null,
24 | consumer varchar(32) not null,
25 | date_created date not null,
26 | last_modified date not null,
27 | "user" TEXT REFERENCES users ON UPDATE CASCADE ON DELETE CASCADE
28 | );
29 |
30 | CREATE TABLE oauth_nonce (
31 | timestamp integer not null,
32 | consumer varchar(32) not null,
33 | nonce varchar(32) not null,
34 | token varchar(32)
35 | );
36 |
37 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20120325.sql:
--------------------------------------------------------------------------------
1 | begin;
2 | CREATE TABLE oauth_request_tokens2 (
3 | token varchar(32) primary key,
4 | secret varchar(64) not null,
5 | consumer varchar(32) not null,
6 | callback text,
7 | date_created date not null,
8 | user_name TEXT REFERENCES users ON UPDATE CASCADE ON DELETE CASCADE
9 | );
10 |
11 | CREATE TABLE oauth_access_tokens2 (
12 | token varchar(32) primary key,
13 | secret varchar(64) not null,
14 | consumer varchar(32) not null,
15 | date_created date not null,
16 | last_modified date not null,
17 | user_name TEXT REFERENCES users ON UPDATE CASCADE ON DELETE CASCADE
18 | );
19 |
20 | insert into oauth_request_tokens2(token, secret, consumer,
21 | callback, date_created, user_name)
22 | select token, secret, consumer, callback, date_created,
23 | user from oauth_request_tokens;
24 | drop table oauth_request_tokens;
25 | alter table oauth_request_tokens2 rename to oauth_request_tokens;
26 |
27 | insert into oauth_access_tokens2(token, secret, consumer,
28 | date_created, last_modified, user_name)
29 | select token, secret, consumer, date_created, last_modified,
30 | user from oauth_access_tokens;
31 | drop table oauth_access_tokens;
32 | alter table oauth_access_tokens2 rename to oauth_access_tokens;
33 |
34 | end;
35 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20130114.sql:
--------------------------------------------------------------------------------
1 | alter table journals add column id serial;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20130319-2.sql:
--------------------------------------------------------------------------------
1 | alter table description_urls add id serial primary key;
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20130319.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "packages" ADD COLUMN "hosting_mode" text NOT NULL DEFAULT 'pypi-scrape-crawl';
2 | ALTER TABLE "packages" ALTER COLUMN "hosting_mode" SET DEFAULT 'pypi-explicit';
3 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20130523.sql:
--------------------------------------------------------------------------------
1 | CREATE UNIQUE INDEX ON users (LOWER(name));
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20130917.sql:
--------------------------------------------------------------------------------
1 | -- Placeholder for Warehouse
2 |
--------------------------------------------------------------------------------
/tools/sql-migrate-20140702.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO accounts_user (password, last_login, is_superuser, username, name,
2 | is_staff, is_active, date_joined)
3 | VALUES ('!', now(), false, 'deleted-user', 'deleted user', false, false, now());
--------------------------------------------------------------------------------
/tools/sqlite_create.py:
--------------------------------------------------------------------------------
1 | # HISTORICAL USE ONLY
2 |
3 | # IS THE CODE THAT USED TO CONSTRUCT THE SQLITE DATABASE
4 |
5 | # IS NOT COMPLETE
6 |
7 | # HISTORICAL USE ONLY
8 | cursor.execute('''
9 | create table ids (
10 | name varchar,
11 | num varchar
12 | )''')
13 | cursor.execute('''
14 | create table packages (
15 | name varchar,
16 | stable_version varchar
17 | )''')
18 | cursor.execute('''
19 | create table releases (
20 | name varchar,
21 | version varchar,
22 | author varchar,
23 | author_email varchar,
24 | maintainer varchar,
25 | maintainer_email varchar,
26 | home_page varchar,
27 | license varchar,
28 | summary varchar,
29 | description varchar,
30 | keywords varchar,
31 | platform varchar,
32 | download_url varchar,
33 | _pypi_ordering varchar,
34 | _pypi_hidden varchar
35 | )''')
36 | cursor.execute('''
37 | create table trove_classifiers (
38 | id varchar,
39 | classifier varchar
40 | )''')
41 | cursor.execute('''
42 | create table release_classifiers (
43 | name varchar,
44 | version varchar,
45 | trove_id varchar
46 | )''')
47 | cursor.execute('''
48 | create table journals (
49 | id integer primary key autoincrement,
50 | name varchar,
51 | version varchar,
52 | action varchar,
53 | submitted_date timestamp,
54 | submitted_by varchar,
55 | submitted_from varchar
56 | )''')
57 | cursor.execute('''
58 | create table users (
59 | name varchar,
60 | password varchar,
61 | email varchar,
62 | public_key varchar
63 | )''')
64 | cursor.execute('''
65 | create table rego_otk (
66 | name varchar,
67 | otk varchar
68 | )''')
69 | cursor.execute('''
70 | create table roles (
71 | role_name varchar,
72 | user_name varchar,
73 | package_name varchar
74 | )''')
75 |
76 | # init the id counter
77 | cursor.execute('''insert into ids (name, num) values
78 | ('trove_classifier', 1)''')
79 |
80 | # indexes
81 | SQLs = [
82 | "create index ids_name_idx on ids(name)",
83 | "create index journals_name_idx on journals(name)",
84 | "create index journals_version_idx on journals(version)",
85 | "create index packages_name_idx on packages(name)",
86 | "create index rego_otk_name_idx on rego_otk(name)",
87 | "create index reset_otk_name_idx on reset_otk(name)",
88 | "create index reset_otk_otk_idx on reset_otk(otk)",
89 | "create index rel_class_name_idx on release_classifiers(name)",
90 | "create index rel_class_trove_id_idx on "
91 | "release_classifiers(trove_id)",
92 | "create index rel_class_version_id_idx on "
93 | "release_classifiers(version)",
94 | "create index release_name_idx on releases(name)",
95 | "create index release_pypi_hidden_idx on releases(_pypi_hidden)",
96 | "create index release_version_idx on releases(version)",
97 | "create index roles_pack_name_idx on roles(package_name)",
98 | "create index roles_user_name_idx on roles(user_name)",
99 | "create index trove_class_class_idx on "
100 | "trove_classifiers(classifier)",
101 | "create index trove_class_id_idx on trove_classifiers(id)",
102 | "create index users_email_idx on users(email)",
103 | "create index users_name_idx on users(name)",
104 | ]
105 | for sql in SQLs:
106 | cursor.execute(sql)
107 |
108 | # admin user
109 | adminpw = ''.join([random.choice(chars) for x in range(10)])
110 | adminpw = sha.sha(adminpw).hexdigest()
111 | cursor.execute('''
112 | insert into users (name, password, email) values
113 | ('admin', '%s', NULL)
114 | '''%adminpw)
115 | cursor.execute('''
116 | insert into roles (user_name, role_name, package_name) values
117 | ('admin', 'Admin', NULL)
118 | ''')
119 |
--------------------------------------------------------------------------------
/tools/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pypi/legacy/a418131180a14768328713df5533c600c425760d/tools/tests/__init__.py
--------------------------------------------------------------------------------
/tools/tests/pypi.access.log.1.bz2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pypi/legacy/a418131180a14768328713df5533c600c425760d/tools/tests/pypi.access.log.1.bz2
--------------------------------------------------------------------------------
/tools/tests/pypi.cfg:
--------------------------------------------------------------------------------
1 | [webui]
2 |
3 | files_url = /packages
4 |
5 | [database]
6 |
7 | name = localhost
8 | user = tarek
9 | password = secret
10 |
11 | [mirrors]
12 | folder = tests/mirrors
13 | local-stats = tests/local-stats
14 | global-stats = tests/global-stats
15 |
16 |
--------------------------------------------------------------------------------
/tools/touch_all_files.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | """
3 | This script touches all files known to the database, creating a skeletal
4 | mirror for local development.
5 | """
6 |
7 | import sys, os
8 | import store
9 |
10 | def get_paths(cursor, prefix=None):
11 | store.safe_execute(cursor, "SELECT python_version, name, filename FROM release_files")
12 |
13 | for type, name, filename in cursor.fetchall():
14 | yield os.path.join(prefix, type, name[0], name, filename)
15 |
16 | if __name__ == '__main__':
17 | import config
18 | try:
19 | config = config.Config(sys.argv[1])
20 | except IndexError:
21 | print "Usage: touch_all_files.py config.ini"
22 | raise SystemExit
23 |
24 | datastore = store.Store(config)
25 | datastore.open()
26 | cursor = datastore.get_cursor()
27 | prefix = config.database_files_dir
28 |
29 | for path in get_paths(cursor, prefix):
30 | dir = os.path.dirname(path)
31 | if not os.path.exists(dir):
32 | print "Creating directory %s" % dir
33 | os.makedirs(dir)
34 | if not os.path.exists(path):
35 | print "Creating file %s" % path
36 | open(path, "a").write('Contents of '+path+'\n')
37 |
--------------------------------------------------------------------------------
/tools/upgradepw.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import base64
3 | import os
4 | import sys
5 |
6 | # Workaround current bug in docutils:
7 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
8 | import docutils.utils
9 |
10 |
11 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12 | sys.path.append(root)
13 |
14 | import config
15 | import store
16 | import passlib.registry
17 |
18 | bcrypt = passlib.registry.get_crypt_handler("bcrypt")
19 | bcrypt_sha1 = passlib.registry.get_crypt_handler("bcrypt_sha1")
20 |
21 | cfg = config.Config(os.path.join(root, "config.ini"))
22 | st = store.Store(cfg)
23 |
24 | print "Migrating passwords to bcrypt_sha1 from unsalted sha1....",
25 |
26 | st.open()
27 | for i, u in enumerate(st.get_users()):
28 | user = st.get_user(u['name'])
29 | # basic sanity check to allow it to run concurrent with users accessing
30 | if len(user['password']) == 40 and "$" not in user["password"]:
31 | # Hash the existing sha1 password with bcrypt
32 | bcrypted = bcrypt.encrypt(user["password"])
33 |
34 | # Base64 encode the bcrypted password so that it's just a blob of data
35 | encoded = base64.b64encode(bcrypted)
36 |
37 | st.setpasswd(user['name'], bcrypt_sha1._hash_prefix + encoded,
38 | hashed=True,
39 | )
40 |
41 | # Commit every 20 users
42 | if not i % 20:
43 | st.commit()
44 | st.open()
45 |
46 | st.commit()
47 | st.close()
48 |
49 | print "[ok]"
50 |
--------------------------------------------------------------------------------
/tools/utf8convert.py:
--------------------------------------------------------------------------------
1 | ''' Implements a store of disutils PKG-INFO entries, keyed off name, version.
2 | '''
3 | import sys, os, re, psycopg, time, sha, random, types, math, stat, errno
4 | import logging
5 |
6 | conn = psycopg.connect(database="pypi",
7 | user="pypi")
8 |
9 | cursor = conn.cursor()
10 | cursor.execute("SELECT * FROM releases")
11 | for release in cursor.dictfetchall():
12 | newitem = {}
13 | for k,v in release.items():
14 | if type(v) is str:
15 | newitem[k]=v.decode("latin-1").encode('utf-8')
16 | cols = []
17 | values = []
18 | for k,v in newitem.items():
19 | cols.append("%s=%%s" % k)
20 | values.append(v)
21 | cols = ",".join(cols)
22 | values.append(newitem['name'])
23 | values.append(newitem['version'])
24 | stmt="update releases set %s where name=%%s and version=%%s" % cols
25 | cursor.execute(stmt, values)
26 | conn.commit()
27 |
--------------------------------------------------------------------------------
/tools/worker.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import os.path
4 | import sys
5 |
6 | import raven
7 | import redis
8 | import rq
9 | import rq.contrib.sentry
10 |
11 | # Workaround current bug in docutils:
12 | # http://permalink.gmane.org/gmane.text.docutils.devel/6324
13 | import docutils.utils
14 |
15 | # Make sure our PyPI directory is on the sys.path
16 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 | sys.path = [root] + sys.path
18 |
19 | import config
20 |
21 | conf = config.Config(os.environ.get("PYPI_CONFIG", os.path.join(root, "config.ini")))
22 | redis_conn = redis.Redis.from_url(conf.queue_redis_url)
23 |
24 | # Create our queues
25 | if sys.argv[1:]:
26 | queues = [rq.Queue(name, connection=redis_conn) for name in sys.argv[1:]]
27 | else:
28 | queues = [rq.Queue(connection=redis_conn)]
29 |
30 | # Create our Worker
31 | worker = rq.Worker(queues, connection=redis_conn)
32 |
33 | # Create our Sentry Client
34 | if conf.sentry_dsn:
35 | raven_client = raven.Client(conf.sentry_dsn)
36 | rq.contrib.sentry.register_sentry(raven_client, worker)
37 |
38 | # Run our worker, fetching jobs from the queue
39 | worker.work()
40 |
--------------------------------------------------------------------------------
/trove.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import sys
4 |
5 | class Node:
6 | def __init__(self, id=None, name=None, path=None, path_split=None):
7 | self.arcs = {}
8 | self.id = id
9 | self.name = name
10 | self.path = path
11 | self.path_split = path_split
12 | if path_split:
13 | self.level = len(path_split)
14 | else:
15 | self.level = 1
16 |
17 | def __repr__(self):
18 | return ''%(self.id, self.name)
19 |
20 | def subtree_ids(self):
21 | result = [self.id]
22 | for node in self.arcs.values():
23 | result.extend(node.subtree_ids())
24 | return result
25 |
26 | class Trove:
27 | def __init__(self, cursor):
28 | self.root = Node()
29 | self.trove = {}
30 | cursor.execute('select id,classifier from trove_classifiers order by classifier')
31 |
32 | # now generate the tree
33 | for id, line in cursor.fetchall():
34 | id = int(id)
35 | d = self.root
36 | # make this a tuple so we can use it as a key
37 | path_split = tuple([s.strip() for s in line.split('::')])
38 | for arc in path_split:
39 | if d.arcs.has_key(arc):
40 | d = d.arcs[arc]
41 | else:
42 | n = Node(id, arc, line.strip(), path_split)
43 | self.trove[id] = n
44 | d.arcs[arc] = n
45 | d = n
46 | self.FIELDS = self.root.arcs.keys()
47 |
48 | def getid(self, path):
49 | node = self.root
50 | for arc in path:
51 | node = node.arcs[arc]
52 | return node.id
53 |
54 | def __getitem__(self, key):
55 | return self.trove[key]
56 |
57 | EXCLUSIVE_FIELDS = {
58 | 'Development Status':1,
59 | 'Natural Language':1,
60 | 'License':1,
61 | }
62 |
63 |
--------------------------------------------------------------------------------