~stefanor/+junk/ubuntu-sponsorship-miner

1 by Stefano Rivera
Initial commit
1
#!/usr/bin/env python
2
3
import cgi
4
import cgitb
5
import re
6 by Stefano Rivera
Missing import, type -> render
6
import urllib
1 by Stefano Rivera
Initial commit
7
8
import genshi.template
9
import genshi.template.text
10
import psycopg2
11
import psycopg2.extensions
12
13
URL = 'ubuntu-sponsorships.cgi'
2 by Stefano Rivera
Reorganise
14
STATIC_URL = '../ubuntu-sponsorships'
15
STATIC_PATH = '../htdocs/ubuntu-sponsorships'
1 by Stefano Rivera
Initial commit
16
DB = 'service=udd'
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
17
LIMIT = 1000
1 by Stefano Rivera
Initial commit
18
19
class AttrDict(dict):
20
    """Dictionary with attribute access"""
21
22
    def __init__(self, **kwargs):
23
        for key, value in kwargs.iteritems():
24
            self[key] = value
25
26
    def __getattr__(self, name):
27
        try:
28
            return self[name]
29
        except KeyError, e:
30
            raise AttributeError(e)
31
32
33
def get_template(name, type_='html'):
34
    """Load a template"""
2 by Stefano Rivera
Reorganise
35
    loader = genshi.template.TemplateLoader(STATIC_PATH)
1 by Stefano Rivera
Initial commit
36
    kwargs = {}
37
    if type_ == 'text':
38
        kwargs['cls'] = genshi.template.text.NewTextTemplate
39
    return loader.load(name, **kwargs)
40
7 by Stefano Rivera
Styled form, search options
41
def locate_sponsorships(sponsor, sponsor_search, sponsoree, sponsoree_search):
1 by Stefano Rivera
Initial commit
42
    """Query UDD for sponsorhips. Yields rows"""
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
43
44
    # String-substituted into query:
7 by Stefano Rivera
Styled form, search options
45
    assert sponsor_search in ('name', 'email')
46
    assert sponsoree_search in ('name', 'email')
47
1 by Stefano Rivera
Initial commit
48
    # Make sure we are dealing with Unicode:
49
    psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
50
    psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
51
    conn = psycopg2.connect(DB)
52
    conn.set_client_encoding('UTF-8')
53
    cur = conn.cursor()
54
55
    sponsor = sponsor.replace('*', '%')
13 by Stefano Rivera
Accept null sponsor / sponsoree
56
    if sponsor == '':
57
        sponsor = '%'
1 by Stefano Rivera
Initial commit
58
    sponsoree = sponsoree.replace('*', '%')
13 by Stefano Rivera
Accept null sponsor / sponsoree
59
    if sponsoree == '':
60
        sponsoree = '%'
1 by Stefano Rivera
Initial commit
61
62
    cur.execute("""
63
        SELECT source, version, date, changed_by, changed_by_name,
64
               changed_by_email, signed_by, signed_by_name, signed_by_email,
65
               distribution,
66
               array_to_string(ARRAY(
67
                 SELECT bug
68
                 FROM ubuntu_upload_history_launchpad_closes
69
                 WHERE source = ubuntu_upload_history.source
70
                   AND version = ubuntu_upload_history.version
71
               ), ' ') AS fixed
72
        FROM ubuntu_upload_history
7 by Stefano Rivera
Styled form, search options
73
        WHERE lower(changed_by_%s) LIKE lower(%%s)
14 by Stefano Rivera
Sponsorships only
74
          AND lower(signed_by_%s) LIKE lower(%%s)
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
75
          AND signed_by_name <> changed_by_name
17 by Stefano Rivera
ORDER by date
76
        ORDER BY date
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
77
        LIMIT %i;
78
    """ % (sponsoree_search, sponsor_search, LIMIT)
7 by Stefano Rivera
Styled form, search options
79
    , (sponsoree, sponsor))
1 by Stefano Rivera
Initial commit
80
    keys = ('source version date changed_by changed_by_name changed_by_email '
81
            'signed_by signed_by_name signed_by_email distribution fixed'
82
           ).split()
83
    for row in cur.fetchall():
84
        upload = AttrDict(**dict(zip(keys, row)))
85
        yield upload
86
87
def mine_sponsorships(sponsorships):
88
    """Iterate over sponsorships and guess what we can
89
    yields sponsorhips
90
    """
91
    resyncable_re = re.compile(r'[-\d](fakesync|build)\d+$')
92
    ubuver_re = re.compile(r'[-\d](\d+)?ubuntu(\d+)$')
93
    for upload in sponsorships:
94
        upload['fixed'] = upload['fixed'].split()
95
        upload['release'] = upload.distribution.split('-')[0]
96
        info = set()
97
98
        if upload.distribution.endswith('-proposed'):
99
            info.add('sru')
100
        if upload.distribution.endswith('-security'):
101
            info.add('security')
102
103
        m = resyncable_re.search(upload.version)
104
        if m is not None:
105
            if m.group(1) == 'fakesync':
106
                info.add('sync')
107
            elif m.group(1) == 'build':
108
                info.add('rebuild')
109
            else:
110
                info.add(m.group(1))
111
112
        m = ubuver_re.search(upload.version)
113
        if m is not None:
114
            if m.group(2) == '1':
115
                if m.group(1) == '0':
116
                    info.add('upgrade')
117
118
        upload['info'] = sorted(info)
119
        # Things we can't determine without changelog / lpapi:
120
        # sync, merge
121
        yield upload
122
7 by Stefano Rivera
Styled form, search options
123
def display_sponsorships(sponsor, sponsor_search, sponsoree, sponsoree_search,
124
                         type_):
1 by Stefano Rivera
Initial commit
125
    """Return rendering of sponsorships"""
126
    if type_ == 'html':
127
        template = get_template('sponsorships.xml')
128
    else:
129
        template = get_template('sponsorships.txt', 'text')
7 by Stefano Rivera
Styled form, search options
130
1 by Stefano Rivera
Initial commit
131
    params = {
3 by Stefano Rivera
Provide static URL for templates
132
              'static': STATIC_URL,
1 by Stefano Rivera
Initial commit
133
              'url': URL,
5 by Stefano Rivera
Correct script tag, provide links on HTML search
134
              'text_url': URL + '?' + urllib.urlencode({
7 by Stefano Rivera
Styled form, search options
135
                  'render': 'text',
136
                  'sponsor': sponsor,
137
                  'sponsor_search': sponsor_search,
138
                  'sponsoree': sponsoree,
12 by Stefano Rivera
Carry sponsoree_search through correctly
139
                  'sponsoree_search': sponsoree_search,
7 by Stefano Rivera
Styled form, search options
140
              }),
8 by Stefano Rivera
Missing parameters
141
              'sponsorships': mine_sponsorships(locate_sponsorships(
142
                  sponsor,
143
                  sponsor_search,
144
                  sponsoree,
145
                  sponsoree_search,
146
              )),
1 by Stefano Rivera
Initial commit
147
             }
148
    return template.generate(**params).render(type_)
149
150
def display_form():
151
    """Return rendered search form"""
152
    template = get_template('form.xml')
153
    params = {
3 by Stefano Rivera
Provide static URL for templates
154
              'static': STATIC_URL,
1 by Stefano Rivera
Initial commit
155
              'url': URL,
156
             }
157
    return template.generate(**params).render('html')
158
159
def main():
160
    cgitb.enable()
161
    form = cgi.FieldStorage()
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
162
1 by Stefano Rivera
Initial commit
163
    body = 'ERROR: No body'
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
164
165
    render = form.getfirst('render', '')
166
    sponsor = form.getfirst('sponsor', '')
167
    sponsoree = form.getfirst('sponsoree', '')
168
    if render and (sponsor or sponsoree):
169
        sponsor_search = form.getfirst('sponsor_search')
170
        if sponsor_search not in ('name', 'email'):
171
            sponsor_search = 'name'
172
        sponsoree_search = form.getfirst('sponsoree_search')
173
        if sponsoree_search not in ('name', 'email'):
174
            sponsoree_search = 'name'
175
        if render not in ('html', 'text'):
176
            render = 'html'
177
        body = display_sponsorships(sponsor, sponsor_search, sponsoree,
178
                                    sponsoree_search, render)
1 by Stefano Rivera
Initial commit
179
    else:
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
180
        render = 'html'
1 by Stefano Rivera
Initial commit
181
        body = display_form()
182
15 by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries
183
    if render == 'text':
184
        print "Content-Type: text/plain; charset=utf-8"
185
    else:
1 by Stefano Rivera
Initial commit
186
        print "Content-Type: text/html; charset=utf-8"
187
    print ""
188
    print body
189
190
if __name__ == '__main__':
191
    main()