PySAL Change Log Statistics

This notebook generates the summary statistics for a package.

It assumes you are running this under the tools directory at the toplevel of the package

Change the values only in the next cell


In [1]:
package_name = 'giddy'
release_date = '2019-04-08'
start_date = '2018-08-26'

This notebook will generate a file in the current directory with the name "changelog_VERSION.md". You can edit and append this on front of the CHANGELOG file for the package release.


In [2]:
from __future__ import print_function
import os
import json
import re
import sys
import pandas

from datetime import datetime, timedelta
from time import sleep
from subprocess import check_output
try:
    from urllib import urlopen
except:
    from urllib.request import urlopen

import ssl
import yaml

context = ssl._create_unverified_context()

In [3]:
CWD = os.path.abspath(os.path.curdir)

In [4]:
CWD


Out[4]:
'/Users/weikang/Google Drive (weikang@ucr.edu)/python_repos/pysal-refactor/giddy/tools'

In [5]:
since_date = '--since="{start}"'.format(start=start_date)
since_date
since = datetime.strptime(start_date+" 0:0:0", "%Y-%m-%d %H:%M:%S")
since


Out[5]:
datetime.datetime(2018, 8, 26, 0, 0)

In [6]:
# get __version__
f = "../{package}/__init__.py".format(package=package_name)

with open(f, 'r') as initfile:
     exec(initfile.readline())

Total commits by subpackage


In [7]:
cmd = ['git', 'log', '--oneline', since_date]
ncommits = len(check_output(cmd).splitlines())

In [8]:
ncommits


Out[8]:
45

List Contributors

Some of our contributors have many aliases for the same identity. So, we've added a mapping to make sure that individuals are listed once (and only once).


In [9]:
identities = {'Levi John Wolf': ('ljwolf', 'Levi John Wolf'),
              'Serge Rey': ('Serge Rey', 'Sergio Rey', 'sjsrey', 'serge'),
              'Wei Kang': ('Wei Kang', 'weikang9009'),
              'Dani Arribas-Bel': ('Dani Arribas-Bel', 'darribas')
}

def regularize_identity(string):
    string = string.decode()
    for name, aliases in identities.items():
        for alias in aliases:
            if alias in string:
                string = string.replace(alias, name)
    if len(string.split(' '))>1:
        string = string.title()
    return string.lstrip('* ')

In [10]:
author_cmd = ['git', 'log', '--format=* %aN', since_date]

In [11]:
from collections import Counter

In [12]:
ncommits = len(check_output(cmd).splitlines())
all_authors = check_output(author_cmd).splitlines()
counter = Counter([regularize_identity(author) for author in all_authors])
#        global_counter += counter
#        counters.update({'.'.join((package,subpackage)): counter})
unique_authors = sorted(set(all_authors))

In [13]:
unique_authors = counter.keys()

In [14]:
unique_authors


Out[14]:
dict_keys(['Wei Kang', 'James Gaboardi', 'Serge Rey'])

Disaggregate by PR, Issue


In [15]:
from datetime import datetime, timedelta
ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
PER_PAGE = 100
element_pat = re.compile(r'<(.+?)>')
rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]')

In [16]:
def parse_link_header(headers):
    link_s = headers.get('link', '')
    urls = element_pat.findall(link_s)
    rels = rel_pat.findall(link_s)
    d = {}
    for rel,url in zip(rels, urls):
        d[rel] = url
    return d

def get_paged_request(url):
    """get a full list, handling APIv3's paging"""
    results = []
    while url:
        #print("fetching %s" % url, file=sys.stderr)
        f = urlopen(url)
        results.extend(json.load(f))
        links = parse_link_header(f.headers)
        url = links.get('next')
    return results

def get_issues(project="pysal/giddy", state="closed", pulls=False):
    """Get a list of the issues from the Github API."""
    which = 'pulls' if pulls else 'issues'
    url = "https://api.github.com/repos/%s/%s?state=%s&per_page=%i" % (project, which, state, PER_PAGE)
    return get_paged_request(url)


def _parse_datetime(s):
    """Parse dates in the format returned by the Github API."""
    if s:
        return datetime.strptime(s, ISO8601)
    else:
        return datetime.fromtimestamp(0)


def issues2dict(issues):
    """Convert a list of issues to a dict, keyed by issue number."""
    idict = {}
    for i in issues:
        idict[i['number']] = i
    return idict


def is_pull_request(issue):
    """Return True if the given issue is a pull request."""
    return 'pull_request_url' in issue


def issues_closed_since(period=timedelta(days=365), project="pysal/pysal", pulls=False):
    """Get all issues closed since a particular point in time. period
can either be a datetime object, or a timedelta object. In the
latter case, it is used as a time before the present."""

    which = 'pulls' if pulls else 'issues'

    if isinstance(period, timedelta):
        period = datetime.now() - period
    url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, period.strftime(ISO8601), PER_PAGE)
    allclosed = get_paged_request(url)
    # allclosed = get_issues(project=project, state='closed', pulls=pulls, since=period)
    filtered = [i for i in allclosed if _parse_datetime(i['closed_at']) > period]

    # exclude rejected PRs
    if pulls:
        filtered = [ pr for pr in filtered if pr['merged_at'] ]

    return filtered


def sorted_by_field(issues, field='closed_at', reverse=False):
    """Return a list of issues sorted by closing date date."""
    return sorted(issues, key = lambda i:i[field], reverse=reverse)


def report(issues, show_urls=False):
    """Summary report about a list of issues, printing number and title.
    """
    # titles may have unicode in them, so we must encode everything below
    if show_urls:
        for i in issues:
            role = 'ghpull' if 'merged_at' in i else 'ghissue'
            print('* :%s:`%d`: %s' % (role, i['number'],
                                        i['title'].encode('utf-8')))
    else:
        for i in issues:
            print('* %d: %s' % (i['number'], i['title'].encode('utf-8')))

In [17]:
all_issues = {}
all_pulls = {}
total_commits = 0
#prj='pysal/libpysal'
prj = 'pysal/{package}'.format(package=package_name)
issues = issues_closed_since(since, project=prj,pulls=False)
pulls = issues_closed_since(since, project=prj,pulls=True)
issues = sorted_by_field(issues, reverse=True)
pulls = sorted_by_field(pulls, reverse=True)
n_issues, n_pulls = map(len, (issues, pulls))
n_total = n_issues + n_pulls

In [18]:
issue_listing = []
for issue in issues:
    entry = "{title} (#{number})".format(title=issue['title'],number=issue['number'])
    issue_listing.append(entry)

In [19]:
pull_listing = []
for pull in pulls:
    entry = "{title} (#{number})".format(title=pull['title'],number=pull['number'])
    pull_listing.append(entry)

In [20]:
pull_listing


Out[20]:
['Add splot as a soft dependency for giddy  (#84)',
 'explicitly specifying Miniconda3 in .travis.yml (#83)',
 'configure doctest and coverage testing (#81)',
 'remove plot directive and indents (#80)',
 'fix missing reference labels in rendered docs  (#79)',
 '(ENH) Full rank and geographic rank Markov methods (#73)',
 '(BUG) update directional notebook to accommodate changes in libpysal (#78)',
 '(BUG) fix notebooks (#76)',
 'remove redundant installation in travis.yml (#75)',
 'add pypi badge to README (#74)',
 '(BUG) remove libpysal.api in Rank notebook (#72)',
 'update required versions of dependencies (#71)',
 'Update issue templates (#70)',
 'conform to PEP8 style guide (#69)',
 'update README with new doi and doc website (#68)',
 'REL: 2.0.0 (#67)']

In [21]:
message = "We closed a total of {total} issues (enhancements and bug fixes) through {pr} pull requests".format(total=n_total, pr=n_pulls)

In [22]:
message = "{msg}, since our last release on {previous}.".format(msg=message, previous=str(start_date))

In [23]:
message


Out[23]:
'We closed a total of 36 issues (enhancements and bug fixes) through 16 pull requests, since our last release on 2018-08-26.'

In [24]:
message += "\n\n## Issues Closed\n"

In [25]:
print(message)


We closed a total of 36 issues (enhancements and bug fixes) through 16 pull requests, since our last release on 2018-08-26.

## Issues Closed


In [26]:
issues = "\n".join(["  - "+issue for issue in issue_listing])
message += issues
message += "\n\n## Pull Requests\n"
pulls = "\n".join(["  - "+pull for pull in pull_listing])
message += pulls

In [27]:
print(message)


We closed a total of 36 issues (enhancements and bug fixes) through 16 pull requests, since our last release on 2018-08-26.

## Issues Closed
  - Add splot as a soft dependency for giddy  (#84)
  - .travis built on archaic Miniconda (#82)
  - explicitly specifying Miniconda3 in .travis.yml (#83)
  - configure doctest and coverage testing (#81)
  - remove plot directive and indents (#80)
  - reference labels are missing from rendered docs (#77)
  - fix missing reference labels in rendered docs  (#79)
  - (ENH) Full rank and geographic rank Markov methods (#73)
  - (BUG) update directional notebook to accommodate changes in libpysal (#78)
  - (BUG) fix notebooks (#76)
  - remove redundant installation in travis.yml (#75)
  - add pypi badge to README (#74)
  - (BUG) remove libpysal.api in Rank notebook (#72)
  - update required versions of dependencies (#71)
  - Update issue templates (#70)
  - conform to PEP8 style guide (#69)
  - Singular matrix when computing ergodic values (#32)
  - build notebooks for documentation (#6)
  - update README with new doi and doc website (#68)
  - REL: 2.0.0 (#67)

## Pull Requests
  - Add splot as a soft dependency for giddy  (#84)
  - explicitly specifying Miniconda3 in .travis.yml (#83)
  - configure doctest and coverage testing (#81)
  - remove plot directive and indents (#80)
  - fix missing reference labels in rendered docs  (#79)
  - (ENH) Full rank and geographic rank Markov methods (#73)
  - (BUG) update directional notebook to accommodate changes in libpysal (#78)
  - (BUG) fix notebooks (#76)
  - remove redundant installation in travis.yml (#75)
  - add pypi badge to README (#74)
  - (BUG) remove libpysal.api in Rank notebook (#72)
  - update required versions of dependencies (#71)
  - Update issue templates (#70)
  - conform to PEP8 style guide (#69)
  - update README with new doi and doc website (#68)
  - REL: 2.0.0 (#67)

In [28]:
people = "\n".join(["  - "+person for person in unique_authors])

In [29]:
print(people)


  - Wei Kang
  - James Gaboardi
  - Serge Rey

In [30]:
message +="\n\nThe following individuals contributed to this release:\n\n{people}".format(people=people)

In [31]:
print(message)


We closed a total of 36 issues (enhancements and bug fixes) through 16 pull requests, since our last release on 2018-08-26.

## Issues Closed
  - Add splot as a soft dependency for giddy  (#84)
  - .travis built on archaic Miniconda (#82)
  - explicitly specifying Miniconda3 in .travis.yml (#83)
  - configure doctest and coverage testing (#81)
  - remove plot directive and indents (#80)
  - reference labels are missing from rendered docs (#77)
  - fix missing reference labels in rendered docs  (#79)
  - (ENH) Full rank and geographic rank Markov methods (#73)
  - (BUG) update directional notebook to accommodate changes in libpysal (#78)
  - (BUG) fix notebooks (#76)
  - remove redundant installation in travis.yml (#75)
  - add pypi badge to README (#74)
  - (BUG) remove libpysal.api in Rank notebook (#72)
  - update required versions of dependencies (#71)
  - Update issue templates (#70)
  - conform to PEP8 style guide (#69)
  - Singular matrix when computing ergodic values (#32)
  - build notebooks for documentation (#6)
  - update README with new doi and doc website (#68)
  - REL: 2.0.0 (#67)

## Pull Requests
  - Add splot as a soft dependency for giddy  (#84)
  - explicitly specifying Miniconda3 in .travis.yml (#83)
  - configure doctest and coverage testing (#81)
  - remove plot directive and indents (#80)
  - fix missing reference labels in rendered docs  (#79)
  - (ENH) Full rank and geographic rank Markov methods (#73)
  - (BUG) update directional notebook to accommodate changes in libpysal (#78)
  - (BUG) fix notebooks (#76)
  - remove redundant installation in travis.yml (#75)
  - add pypi badge to README (#74)
  - (BUG) remove libpysal.api in Rank notebook (#72)
  - update required versions of dependencies (#71)
  - Update issue templates (#70)
  - conform to PEP8 style guide (#69)
  - update README with new doi and doc website (#68)
  - REL: 2.0.0 (#67)

The following individuals contributed to this release:

  - Wei Kang
  - James Gaboardi
  - Serge Rey

In [32]:
head = "# Changes\n\nVersion {version} ({release_date})\n\n".format(version=__version__, release_date=release_date)

In [33]:
print(head+message)


# Changes

Version 2.1.0 (2019-04-08)

We closed a total of 36 issues (enhancements and bug fixes) through 16 pull requests, since our last release on 2018-08-26.

## Issues Closed
  - Add splot as a soft dependency for giddy  (#84)
  - .travis built on archaic Miniconda (#82)
  - explicitly specifying Miniconda3 in .travis.yml (#83)
  - configure doctest and coverage testing (#81)
  - remove plot directive and indents (#80)
  - reference labels are missing from rendered docs (#77)
  - fix missing reference labels in rendered docs  (#79)
  - (ENH) Full rank and geographic rank Markov methods (#73)
  - (BUG) update directional notebook to accommodate changes in libpysal (#78)
  - (BUG) fix notebooks (#76)
  - remove redundant installation in travis.yml (#75)
  - add pypi badge to README (#74)
  - (BUG) remove libpysal.api in Rank notebook (#72)
  - update required versions of dependencies (#71)
  - Update issue templates (#70)
  - conform to PEP8 style guide (#69)
  - Singular matrix when computing ergodic values (#32)
  - build notebooks for documentation (#6)
  - update README with new doi and doc website (#68)
  - REL: 2.0.0 (#67)

## Pull Requests
  - Add splot as a soft dependency for giddy  (#84)
  - explicitly specifying Miniconda3 in .travis.yml (#83)
  - configure doctest and coverage testing (#81)
  - remove plot directive and indents (#80)
  - fix missing reference labels in rendered docs  (#79)
  - (ENH) Full rank and geographic rank Markov methods (#73)
  - (BUG) update directional notebook to accommodate changes in libpysal (#78)
  - (BUG) fix notebooks (#76)
  - remove redundant installation in travis.yml (#75)
  - add pypi badge to README (#74)
  - (BUG) remove libpysal.api in Rank notebook (#72)
  - update required versions of dependencies (#71)
  - Update issue templates (#70)
  - conform to PEP8 style guide (#69)
  - update README with new doi and doc website (#68)
  - REL: 2.0.0 (#67)

The following individuals contributed to this release:

  - Wei Kang
  - James Gaboardi
  - Serge Rey

In [34]:
outfile = 'changelog_{version}.md'.format(version=__version__)
with open(outfile, 'w') as of:
    of.write(head+message)

In [ ]:


In [ ]:


In [ ]: