Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
223 lines (199 sloc) 7.46 KB
from optparse import make_option
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils.dateparse import parse_datetime
from django.utils.functional import curry
from django.contrib.auth.models import User
import pytz
import requests
from pinax.blog.models import Post, Revision, Section
from pinax.blog.utils import load_path_attr
RENAMES = [
("biblion", "pinax-blog"),
("agon", "pinax-points"),
("agon-ratings", "pinax-ratings")
]
RENDER_FUNC = curry(
load_path_attr(
settings.PINAX_BLOG_MARKUP_CHOICE_MAP["markdown"]["parser"]
)
)
class Command(BaseCommand):
help = "generate release notes blog post drafts"
option_list = BaseCommand.option_list + (
make_option(
"--auth-token",
type="string",
dest="token",
help="your github auth token"
),
make_option(
"--org",
type="string",
dest="org",
help="github org"
),
make_option(
"--package",
type="string",
dest="pkg",
help="PyPI Package"
)
)
def _next_url(self, headers):
if headers.get("Link"):
link = headers.get("Link")
next_link = [l.split("; ") for l in link.split(", ") if "next" in l]
if next_link:
return next_link[0][0].replace("<", "").replace(">", "")
def _fetch_url(self, url, params=None):
r = self.session.get(url, params=params)
print "Rate Limits {} / {}: {} with {}".format(
r.headers.get("X-RateLimit-Limit"),
r.headers.get("X-RateLimit-Remaining"),
url,
params
)
data = r.json()
next_url = self._next_url(r.headers)
if next_url:
data.extend(
self._fetch_url(next_url)
)
return data
def _fetch(self, path, **kwargs):
kwargs.update(dict(per_page=100))
return self._fetch_url(
"https://api.github.com{}".format(path),
params=kwargs
)
def fetch_repos(self, org):
return self._fetch("/orgs/{}/repos".format(org), type="sources")
def fetch_commits(self, org, repo, until, since=None):
commits = []
url = "/repos/{}/{}/commits".format(org, repo)
if since:
commits = self._fetch(url, since=since, until=until)
else:
commits = self._fetch(url, until=until)
return commits
def format_title(self, name, release_url, version, date, commits):
return "{} {} Released".format(name, version)
def format_slug(self, name, release_url, version, date, commits):
return "{}-{}-released".format(name, version.replace(".", "-"))
def format_teaser(self, name, release_url, version, date, commits):
return "A new release of {}".format(name)
def format_content(self, name, release_url, version, date, commits):
content = u"This release includes the following:\n\n"
for commit in commits:
content += u"* {}\n".format(
commit["commit"]["message"].split("\n")[0]
)
content += u"\n\n[Download tarball from PyPI]({})".format(release_url)
if commits[-1]["parents"]:
begin_sha = commits[-1]["parents"][0]["sha"]
else:
begin_sha = commits[-1]["sha"]
content += u"\n\n[View changeset on GitHub]({})".format(
commits[0]["html_url"].replace(
"commit/",
"compare/{}...".format(begin_sha)
)
)
return content
def format_description(self, name, release_url, version, date, commits):
return "A new release of {}.".format(name)
def create_post(self, name, release_url, version, date, commits):
title = self.format_title(name, release_url, version, date, commits)
slug = self.format_slug(name, release_url, version, date, commits)
teaser = self.format_teaser(name, release_url, version, date, commits)
content = self.format_content(name, release_url, version, date, commits)
description = self.format_description(name, release_url, version, date, commits)
if Post.objects.filter(slug=slug).exists():
return
post = Post.objects.create(
section=self.section,
author=self.author,
title=title,
slug=slug,
markup="markdown",
teaser_html=RENDER_FUNC(teaser),
content_html=RENDER_FUNC(content),
description=description,
created=date,
updated=date,
published=date
)
post.tags.add(name)
Revision.objects.create(
post=post,
title=post.title,
teaser=teaser,
content=content,
author=post.author,
updated=post.updated,
published=post.published
)
def get_releases(self, name, pypi_name=None):
if pypi_name is None:
pypi_name = name
releases = []
if name in ["pinax"]:
releases
print "Processing {}".format(pypi_name)
try:
pypi_data = self.pypi_session.get(
"http://pypi.python.org/pypi/{}/json".format(pypi_name)
).json()
for release in pypi_data["releases"]:
if pypi_data["releases"][release]:
rdata = pypi_data["releases"][release][0]
url = rdata["url"]
date = pytz.timezone("UTC").localize(
parse_datetime(rdata["upload_time"])
)
releases.append(
(date, release, url, rdata["upload_time"] + "Z")
)
except Exception:
pass
releases.sort()
return releases
def generate_release_note(self, org, name, pypi_name=None):
releases = self.get_releases(name, pypi_name)
prev = None
for release in releases:
commits = self.fetch_commits(org, name, release[3], prev)
commits = [
c
for c in commits
if not c["commit"]["message"].startswith("Merge pull")
]
if len(commits) == 0:
continue
prev = release[3]
self.create_post(pypi_name or name, release[2], release[1], release[0], commits)
def handle(self, *args, **options):
auth_token = options["token"]
org = options["org"]
pkg = options["pkg"]
if auth_token is None and org is not None:
print "You must supply an auth token and GitHub org"
return
self.author = User.objects.get(pk=1)
self.session = requests.Session()
self.session.headers.update({
"Accept": "application/vnd.github.v3+json",
"Authorization": "token {}".format(auth_token)
})
self.section = Section.objects.get(slug="release-notes")
self.pypi_session = requests.Session()
if pkg is None:
public_repos = [r for r in self.fetch_repos(org) if not r["private"]]
for repo in public_repos:
self.generate_release_note(org, repo["name"])
if org == "pinax":
for rename in RENAMES:
self.generate_release_note(org, rename[1], rename[0])
else:
self.generate_release_note(org, pkg)