diff --git a/.gitignore b/.gitignore index d257fe7..4b49ff9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,24 @@ .rope* .idea/ notebooks/.ipynb_checkpoints/ -.DS_Store \ No newline at end of file +.DS_Store +.ipynb_checkpoints/ +*.bak +.eggs/ +*.egg-info/ + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ \ No newline at end of file diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..aa65c2b --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,28 @@ +v<1.1.0>, 2018-05-17 + +This release is the first tagged release of pointpats on Github. +Starting from this release, pointpats supports python 3.5 and 3.6 only. + +GitHub stats for 2017/04/22 - 2018/05/17 + +These lists are automatically generated, and may be incomplete or contain duplicates. + + +We closed a total of 9 issues, 4 pull requests and 5 regular issues; +this is the full list (generated with the script +:file:`tools/github_stats.py`): + +Pull Requests (4): + +* :ghpull:`12`: b'chore: libpysal is 3 only now so removing travis tests on python 2' +* :ghpull:`11`: b'try removing conversion and see if this passes' +* :ghpull:`10`: b'updating license' +* :ghpull:`9`: b'Update the Readme file, change the relative URL links to absolute URLs' + +Issues (5): + +* :ghissue:`12`: b'chore: libpysal is 3 only now so removing travis tests on python 2' +* :ghissue:`11`: b'try removing conversion and see if this passes' +* :ghissue:`10`: b'updating license' +* :ghissue:`9`: b'Update the Readme file, change the relative URL links to absolute URLs' +* :ghissue:`8`: b'Relative links to notebooks on pypi landing page are broken' diff --git a/pointpats/distance_statistics.py b/pointpats/distance_statistics.py index 6e4e572..ff47be1 100644 --- a/pointpats/distance_statistics.py +++ b/pointpats/distance_statistics.py @@ -669,11 +669,11 @@ class Genv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Genv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Genv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -748,11 +748,11 @@ class Fenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Fenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Fenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -828,11 +828,11 @@ class Jenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Jenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Jenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -905,11 +905,11 @@ class Kenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Kenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Kenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -980,11 +980,11 @@ class Lenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Lenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Lenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) diff --git a/pointpats/pointpattern.py b/pointpats/pointpattern.py index 496366b..9e3111e 100644 --- a/pointpats/pointpattern.py +++ b/pointpats/pointpattern.py @@ -45,7 +45,7 @@ class PointPattern(object): Examples -------- - >>> from points.pointpattern import PointPattern + >>> from pointpats.pointpattern import PointPattern >>> points = [[66.22, 32.54], [22.52, 22.39], [31.01, 81.21], ... [9.47, 31.02], [30.78, 60.10], [75.21, 58.93], ... [79.26, 7.68], [8.23, 39.93], [98.73, 77.17], @@ -60,9 +60,9 @@ class PointPattern(object): >>> pp.lambda_hull 0.0022667153468973137 >>> pp.hull_area - 5294.0039500000003 + 5294.00395 >>> pp.mbb_area - 7638.2000000000007 + 7638.200000000001 """ def __init__(self, points, window=None, names=None, coord_names=None): diff --git a/pointpats/process.py b/pointpats/process.py index ef3bd4f..94243ec 100644 --- a/pointpats/process.py +++ b/pointpats/process.py @@ -190,10 +190,10 @@ class PoissonPointProcess(PointProcess): Examples -------- - >>> import pysal as ps + >>> import libpysal as ps >>> import numpy as np - >>> from points.window import Window, poly_from_bbox - >>> from pysal.contrib import shapely_ext + >>> from pointpats.window import Window, poly_from_bbox + >>> from libpysal.cg import shapely_ext Open the virginia polygon shapefile >>> va = ps.open(ps.examples.get_path("virginia.shp")) @@ -339,10 +339,10 @@ class PoissonClusterPointProcess(PointProcess): Examples -------- - >>> import pysal as ps + >>> import libpysal as ps >>> import numpy as np - >>> from points.window import Window, poly_from_bbox - >>> from pysal.contrib import shapely_ext + >>> from pointpats.window import Window, poly_from_bbox + >>> from libpysal.cg import shapely_ext Open the virginia polygon shapefile >>> va = ps.open(ps.examples.get_path("virginia.shp")) @@ -433,8 +433,8 @@ def realize(self, n): l, b, r, t = self.window.bbox d = self.radius # get parent points - pxs = np.random.uniform(l, r, (n/self.children, 1)) - pys = np.random.uniform(b, t, (n/self.children, 1)) + pxs = np.random.uniform(l, r, (int(n/self.children), 1)) + pys = np.random.uniform(b, t, (int(n/self.children), 1)) cents = np.hstack((pxs, pys)) # generate children points pnts = [runif_in_circle(self.children, d, center) for center in cents] diff --git a/setup.py b/setup.py index ea76950..3709c16 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,9 @@ from distutils.command.build_py import build_py +with open('README.rst') as file: + long_description = file.read() + pth = os.path.dirname(os.path.abspath(__file__))+ '/requirements.txt' REQUIREMENTS = [i.strip() for i in open(pth).readlines()] @@ -15,6 +18,7 @@ setup(name='pointpats', version=version, description='Methods and Functions for planar point pattern analysis', + long_description = long_description, url='https://github.com/pysal/pointpats', maintainer='Hu Shao', maintainer_email='shaohutiger@gmail.com', diff --git a/tools/github_stats.py b/tools/github_stats.py new file mode 100644 index 0000000..7298c55 --- /dev/null +++ b/tools/github_stats.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +"""Simple tools to query github.com and gather stats about issues. + +Adapted from: https://github.com/ipython/ipython/blob/master/tools/github_stats.py +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from __future__ import print_function + +import json +import re +import sys + +from datetime import datetime, timedelta +from subprocess import check_output +from urllib.request import urlopen +import ssl + +context = ssl._create_unverified_context() + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +ISO8601 = "%Y-%m-%dT%H:%M:%SZ" +PER_PAGE = 100 + +element_pat = re.compile(r'<(.+?)>') +rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]') + +#----------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------- + +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, context=context) + results.extend(json.load(f)) + links = parse_link_header(f.headers) + url = links.get('next') + return results + +def get_issues(project="pysal/pointpats", 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/pointpats", 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'))) + +#----------------------------------------------------------------------------- +# Main script +#----------------------------------------------------------------------------- + +if __name__ == "__main__": + # Whether to add reST urls for all issues in printout. + show_urls = True + + # By default, search one month back + tag = None + if len(sys.argv) > 1: + try: + days = int(sys.argv[1]) + except: + tag = sys.argv[1] + else: + tag = check_output(['git', 'describe', '--abbrev=0']).strip() + + if tag: + cmd = ['git', 'log', '-1', '--format=%ai', tag] + tagday, tz = check_output(cmd).strip().rsplit(' ', 1) + since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S") + else: + since = datetime.now() - timedelta(days=days) + + print("fetching GitHub stats since %s (tag: %s)" % (since, tag), file=sys.stderr) + # turn off to play interactively without redownloading, use %run -i + if 1: + issues = issues_closed_since(since, pulls=False) + pulls = issues_closed_since(since, pulls=True) + + # For regular reports, it's nice to show them in reverse chronological order + 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 + + # Print summary report we can directly include into release notes. + print() + since_day = since.strftime("%Y/%m/%d") + today = datetime.today().strftime("%Y/%m/%d") + print(".. _github-stats:") + print('Github stats') + print('============') + print() + print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag)) + print() + print("These lists are automatically generated, and may be incomplete or contain duplicates.") + print() + if tag: + # print git info, in addition to GitHub info: + since_tag = tag+'..' + cmd = ['git', 'log', '--oneline', since_tag] + ncommits = len(check_output(cmd).splitlines()) + + author_cmd = ['git', 'log', '--format=* %aN', since_tag] + all_authors = check_output(author_cmd).splitlines() + unique_authors = sorted(set(all_authors)) + + print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits)) + print() + print('\n'.join(unique_authors)) + print() + + print() + print("We closed a total of %d issues, %d pull requests and %d regular issues;\n" + "this is the full list (generated with the script \n" + ":file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues)) + print() + print('Pull Requests (%d):\n' % n_pulls) + report(pulls, show_urls) + print() + print('Issues (%d):\n' % n_issues) + report(issues, show_urls)