SF.net SVN: gar:[23514] csw/mgar/gar/v2/lib/python

wahwah at users.sourceforge.net wahwah at users.sourceforge.net
Sat Apr 26 15:28:26 CEST 2014


Revision: 23514
          http://sourceforge.net/p/gar/code/23514
Author:   wahwah
Date:     2014-04-26 13:28:26 +0000 (Sat, 26 Apr 2014)
Log Message:
-----------
A script to notify inactive maintainers.

It keeps some state on disk to avoid notifying the same people twice.

Modified Paths:
--------------
    csw/mgar/gar/v2/lib/python/activity.py
    csw/mgar/gar/v2/lib/python/maintainer_activity_report.py

Added Paths:
-----------
    csw/mgar/gar/v2/lib/python/notify_inactive_maintainers.py

Modified: csw/mgar/gar/v2/lib/python/activity.py
===================================================================
--- csw/mgar/gar/v2/lib/python/activity.py	2014-04-26 11:23:43 UTC (rev 23513)
+++ csw/mgar/gar/v2/lib/python/activity.py	2014-04-26 13:28:26 UTC (rev 23514)
@@ -3,6 +3,7 @@
 import datetime
 import dateutil.parser
 import logging
+import json
 
 from collections import namedtuple
 
@@ -21,6 +22,14 @@
 MAINTAINER_STOPLIST = [
     'orphaned',
 ]
+# This should be moved to a better place.
+MAINTAINER_WHITELIST = set([
+    # People who haven't released packages in a long time, but are otherwise
+    # active.
+    'claudio',
+    'rmottola',
+    'wbonnet',
+])
 
 def RevDeps(pkgs):
   revdeps = {}
@@ -119,3 +128,16 @@
   maintainers = ComputeMaintainerActivity(maintainers)
 
   return maintainers, bad_dates
+
+
+# http://stackoverflow.com/questions/12122007/python-json-encoder-to-support-datetime
+class DateTimeEncoder(json.JSONEncoder):
+  def default(self, obj):
+    if isinstance(obj, datetime.datetime):
+      return obj.isoformat()
+    elif isinstance(obj, datetime.date):
+      return obj.isoformat()
+    elif isinstance(obj, datetime.timedelta):
+      return (datetime.datetime.min + obj).time().isoformat()
+    else:
+      return super(DateTimeEncoder, self).default(obj)

Modified: csw/mgar/gar/v2/lib/python/maintainer_activity_report.py
===================================================================
--- csw/mgar/gar/v2/lib/python/maintainer_activity_report.py	2014-04-26 11:23:43 UTC (rev 23513)
+++ csw/mgar/gar/v2/lib/python/maintainer_activity_report.py	2014-04-26 13:28:26 UTC (rev 23514)
@@ -7,13 +7,14 @@
 image of activity of a maintainer.
 """
 
-import logging
-import requests
 import argparse
+import cPickle
 import datetime
 import dateutil.parser
 import jinja2
-import cPickle
+import json
+import logging
+import requests
 
 import concurrent.futures
 
@@ -252,7 +253,8 @@
     if username in maintainers:
       maintainers[username] = (
           maintainers[username]._replace(csw_db_status=status,
-                                         date_created=date_created))
+                                         date_created=date_created,
+                                         fullname=d['fullname']))
     else:
       maintainers[username] = (
           activity.Maintainer(
@@ -311,7 +313,19 @@
         counts=counts)
     outfd.write(rendered.encode('utf-8'))
 
+  # Save a JSON file too.
+  json_out = args.output
+  if json_out.endswith('.html'):
+    json_out = json_out[:-5] + '.json'
+  with open(json_out, 'w') as outfd:
+    json.dump(dict(
+      maintainers=maintainers,
+      maintainers_in_unstable=maintainers_in_unstable,
+      analysis_by_username=analysis_by_username,
+      counts=counts
+    ), outfd, cls=activity.DateTimeEncoder, indent=2)
 
+
 if __name__ == '__main__':
   logging.basicConfig(level=logging.DEBUG)
   main()

Added: csw/mgar/gar/v2/lib/python/notify_inactive_maintainers.py
===================================================================
--- csw/mgar/gar/v2/lib/python/notify_inactive_maintainers.py	                        (rev 0)
+++ csw/mgar/gar/v2/lib/python/notify_inactive_maintainers.py	2014-04-26 13:28:26 UTC (rev 23514)
@@ -0,0 +1,153 @@
+#!/opt/csw/bin/python2.7
+# coding: utf-8
+
+import anydbm
+import datetime
+import dateutil.parser
+import jinja2
+import json
+import logging
+import os
+import os.path
+import requests
+import smtplib
+import textwrap
+
+from Cheetah import Template
+from email.mime.text import MIMEText
+
+from lib.python import activity
+
+EMAIL_OF_PERSON_RUNNING_THIS_SCRIPT = '<please-enter>@opencsw.org'
+PERSON_RUNNING_THIS_SCRIPT = 'Firstname Lastname'
+
+EMAIL_TMPL = """If you don't care about OpenCSW packages,
+you can stop reading now.
+
+Hi {{ firstname }},
+
+{% if maintainer.pkgs %}
+It's been {{ time_ago }} since you last uploaded a package. 
+There still are packages in the unstable catalog that are owned by you.
+{% else %}
+{{ time_ago.capitalize() }} ago, you've signed up to be a package maintainer at
+OpenCSW, but I looked in the unstable catalog, and I don't see any packages
+from you in there.
+{% endif %}
+
+What's the situation on your end, are you still interested in building packages
+at OpenCSW? If so, please write back to me. If not, you can ignore this
+message, and I will retire your account. This means that I will lock down your
+buildfarm access, take away write access to the Subversion repository, and your
+ at opencsw.org email address will start bouncing messages. I won't delete your
+home directory on the buildfarm.
+
+This is all reversible, if you wish to get your access back, write to
+board at opencsw.org and your access will be restored.
+
+--Maciej
+"""
+
+class Notifier(object):
+
+  def __init__(self):
+    filename = os.path.join(os.environ['HOME'],
+                            '.checkpkg',
+                            'retiring-notifications.db')
+    logging.info('Keeping state in %s', filename)
+    self.state = anydbm.open(filename, 'c')
+
+  def Notify(self, address, subject, message):
+    # logging.info('Would send email to %s', address)
+    # print message
+    # return
+    address = address.encode('utf-8')
+    if address in self.state:
+      state = json.loads(self.state[address])
+      logging.info('%s was already notified on %s', address, state['date'])
+      return
+    else:
+      state = {
+          'address': address,
+          'subject': subject,
+          'message': message,
+          'date': datetime.datetime.now(),
+      }
+    msg = MIMEText(message)
+    msg["Subject"] = subject
+    from_address = '%s <%s>' % (PERSON_RUNNING_THIS_SCRIPT,
+                                EMAIL_OF_PERSON_RUNNING_THIS_SCRIPT)
+    msg['From'] = from_address
+    msg['To'] = address
+    msg['Bcc'] = EMAIL_OF_PERSON_RUNNING_THIS_SCRIPT
+    msg['Cc'] = 'OpenCSW board <board at opencsw.org>'
+    s = smtplib.SMTP('mail.opencsw.org')
+    try:
+      logging.info('Sending email to %s', address)
+      addresses = [address, 'board at opencsw.org',
+                   EMAIL_OF_PERSON_RUNNING_THIS_SCRIPT]
+      s.sendmail(from_address,
+          addresses,
+          msg.as_string())
+      logging.debug("E-mail sending finished.")
+      self.state[address] = json.dumps(state, cls=activity.DateTimeEncoder)
+    except smtplib.SMTPRecipientsRefused as exc:
+      logging.warning(
+          "Sending email to %s failed: %s.",
+          repr(address), exc)
+    s.quit()
+
+def main():
+  # This data can be generated on the fly, but for now we'll just use
+  # previously generated data.
+  url = 'http://buildfarm.opencsw.org/obsolete-pkgs/maintainer-activity.json'
+  data = requests.get(url).json()
+  maintainers = data['maintainers']
+  analysis_by_username = data['analysis_by_username']
+  notifier = Notifier()
+  for username in maintainers:
+    if username in activity.MAINTAINER_WHITELIST:
+      logging.debug('Skipping %s (is on the whitelist)', username)
+      continue
+    if not analysis_by_username[username]['to_retire']:
+      continue
+    maintainer = activity.Maintainer(*maintainers[username])
+    logging.debug('to retire: %s / %s', maintainer.username, maintainer.fullname)
+    maintainer = maintainer._replace(
+        last_activity=dateutil.parser.parse(maintainer.last_activity))
+    if maintainer.date_created:
+      maintainer = maintainer._replace(
+          date_created=dateutil.parser.parse(maintainer.date_created))
+    else:
+      maintainer = maintainer._replace(
+          date_created=dateutil.parser.parse('1970-01-01'))
+    activity_date = max(maintainer.date_created, maintainer.last_activity)
+    months_ago = int((datetime.datetime.now() - activity_date).days / 30 + 0.5)
+    if months_ago > 12 * 20:
+      time_ago = 'some time'
+    elif months_ago > 12:
+      time_ago = 'about %d years' % ((months_ago + 6) / 12)
+    else:
+      time_ago = 'about %d months' % months_ago
+
+    rendered_paragraphs = []
+    assert maintainer.fullname is not None, ('Error in the DB somewhere, '
+                                             'check the maintainer table '
+                                             'in the CSW database on the '
+                                             'website')
+    for paragraph in EMAIL_TMPL.split('\n\n'):
+      template = jinja2.Template(paragraph)
+      rendered = template.render(
+          firstname=maintainer.fullname.split(' ')[0],
+          maintainer=maintainer,
+          time_ago=time_ago,
+          status=analysis_by_username[username])
+      rendered_paragraphs.append(textwrap.fill(rendered.strip()))
+    message = '\n\n'.join(x.encode('utf-8') for x in rendered_paragraphs)
+    notifier.Notify('%s at opencsw.org' % username,
+                    'Cleanup of inactive maintainer accounts', message)
+
+
+if __name__ == '__main__':
+  logging.basicConfig(level=logging.DEBUG)
+  main()

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



More information about the devel mailing list