aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2015-12-12 19:37:57 -0800
committerMike Crute <mcrute@gmail.com>2015-12-12 19:39:36 -0800
commit37d9ed4ae3e0b0193f990363df9dcea191475b35 (patch)
tree09563c4811c86a65968834f9aa0f0a367935f163
downloadpy_release_tools-37d9ed4ae3e0b0193f990363df9dcea191475b35.tar.bz2
py_release_tools-37d9ed4ae3e0b0193f990363df9dcea191475b35.tar.xz
py_release_tools-37d9ed4ae3e0b0193f990363df9dcea191475b35.zip
Initial importrelease-0.1.0
-rw-r--r--.gitignore14
-rw-r--r--README.rst112
-rw-r--r--py_release_tools/__init__.py0
-rw-r--r--py_release_tools/commands.py158
-rwxr-xr-xsetup.py40
5 files changed, 324 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..548304c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
1*.pyc
2.pip/
3.pip2/
4.pip27/
5.pip3/
6.pip32/
7.pip34/
8.pip35/
9build/
10coverage.xml
11dist/
12htmlcov/
13*.egg-info/
14.coverage/
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..85f4e6a
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,112 @@
1======================
2Python Releasing Tools
3======================
4
5This code is licensed under the MIT license.
6
7This is a suite of tools that are useful for releasing Python code for public
8or private use. There are setuptools extensions to validate various conventions
9and to perform release activities.
10
11Installation and Usage
12======================
13This package is generally not installed directly but instead listed in the
14``setup_requires`` option of the setuptools ``setup`` function. For example::
15
16 from setuptools import setup
17
18 setup(
19 name="my_clever_package",
20 version="1.0.0",
21 setup_requires=[
22 "py_release_tools",
23 ]
24 )
25
26Commands are exported as distuils commands and should be automatically
27available provided the package is installed. The tool provides the commands
28documented below and are generally run by defining a ``release`` `alias
29<http://pythonhosted.org/setuptools/setuptools.html#alias-define-shortcuts-for-commonly-used-commands>`_
30in the project's ``setup.cfg`` file. The author's typical project ``setup.cfg``
31contains these aliases::
32
33 [aliases]
34 validate = cover_tests pep8
35 release = validate increment_semver git_push sdist upload
36 release_major = validate increment_semver -M git_push sdist upload
37 release_minor = validate increment_semver -m git_push sdist upload
38
39Commands
40========
41increment_semver
42----------------
43This command will update the ``setup.py`` file version number following the
44rules of `Semantic Versioning (semver) <http://semver.org>`_. This command will
45re-write and commit the project's ``setup.py`` file. It assumes that the
46version line is formatted as such, with some amount of leading whitespace::
47
48 version="1.20.1"
49
50It will rewrite all lines that look like this in the file.
51
52The version format is::
53
54 MAJOR.MINOR.PATCH
55
56For more information check out the semver docs.
57
58Version generation increments a version component by one. By default a patch
59version is generated. Passing the ``-m`` or ``--minor`` flags to the command
60will increment the minor version and set the patch version to zero. Passing
61``-M`` or ``--major`` will increment the major version and set both the minor
62and patch versions to zero.
63
64This command will also create a tag in the git repository of format
65``release-{semver}``.
66
67git_push
68--------
69This command runs a ``git push`` command to push the ``master`` branch to the
70remote ``origin``. The command will also push tags. If your git repository
71doesn't use these naming conventions the command will fail.
72
73cover_tests
74-----------
75This command will setup python
76`coverage <https://pypi.python.org/pypi/coverage>`_ monitoring and invoke the
77setuptools ``test`` target. Coverage data will be written to ``.coverage`` in
78the same directory as the ``setup.py`` file.
79
80This command will also generate a Cobertura coverage report as ``coverage.xml``
81and an HTML report in the ``htmlcov`` folder.
82
83Failure of the tests will cause a failure of the build so it is suitable to use
84this command as a replacement for the builtin ``test`` command. This command
85also suppresses the system exit that the builtin ``test`` command generates so
86other commands can be chained after this one.
87
88pep8
89----
90This command will run a `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_
91code style validation on all Python files in the project, including the
92setup.py file.
93
94Contributing
95============
96If you would like to contribute to Pydora please visit the project's
97`GitHub page <https://github.com/mcrute/py_release_tools>`_ and open a pull
98request with your changes. To have the best experience contributing, please:
99
100* Don't break backwards compatibility of public interfaces
101* Write tests for your new feature/bug fix
102* Ensure that existing tests pass
103* Update the readme/docstrings, if necessary
104* Follow the coding style of the current code-base
105* Ensure that your code is PEP8 compliant
106* Validate that your changes work with Python 2.7+ and 3.x
107
108All code is reviewed before acceptance and changes may be requested to better
109follow the conventions of the existing API.
110
111he build system runs ``python setup.py validate`` on all supported Python
112versions. You can, and should, run this on your pull request before submitting.
diff --git a/py_release_tools/__init__.py b/py_release_tools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/py_release_tools/__init__.py
diff --git a/py_release_tools/commands.py b/py_release_tools/commands.py
new file mode 100644
index 0000000..e57f99c
--- /dev/null
+++ b/py_release_tools/commands.py
@@ -0,0 +1,158 @@
1import os
2import re
3import sys
4from io import BytesIO
5from distutils import log
6from distutils.cmd import Command
7from subprocess import check_output
8from distutils.errors import DistutilsError
9
10
11def simple_call(cmd):
12 return check_output(cmd.split(" "))
13
14
15class SimpleCommand(Command):
16 """Default behavior for simple commands
17 """
18
19 user_options = []
20
21 def initialize_options(self):
22 pass
23
24 def finalize_options(self):
25 pass
26
27 def install_requires(self):
28 if self.distribution.install_requires:
29 self.distribution.fetch_build_eggs(
30 self.distribution.install_requires)
31
32 if self.distribution.tests_require:
33 self.distribution.fetch_build_eggs(
34 self.distribution.tests_require)
35
36 def run(self):
37 self.install_requires()
38 self._run()
39
40
41class IncrementSemanticVersion(SimpleCommand):
42 """Increment Semantic Version and Commmit to Git
43
44 Version incrementing uses semantic versioning. This command accepts -M or
45 --major, -m or --minor to increment a major or minor release. If no flags
46 are passed then a patch release is created.
47 """
48
49 user_options = [
50 ("major", "M", "increment version for major release"),
51 ("minor", "m", "increment version for minor release"),
52 ]
53
54 boolean_options = ("major", "minor")
55
56 def initialize_options(self):
57 self.major = False
58 self.minor = False
59
60 def _new_version(self, version):
61 major, minor, patch = [int(i) for i in version.split(".")]
62
63 if self.major:
64 return "{}.0.0".format(major + 1)
65 elif self.minor:
66 return "{}.{}.0".format(major, minor + 1)
67 else:
68 return "{}.{}.{}".format(major, minor, patch + 1)
69
70 def _update_version(self):
71 pattern = re.compile('^(\s+)version="([0-9\.]+)"')
72 output = BytesIO()
73
74 with open("setup.py", "r") as fp:
75 for line in fp:
76 result = pattern.match(line)
77
78 if not result:
79 output.write(line)
80 else:
81 spaces, version = result.groups()
82 new_version = self._new_version(version)
83 output.write(
84 '{}version="{}",\n'.format(spaces, new_version))
85
86 with open("setup.py", "w") as fp:
87 fp.write(output.getvalue())
88
89 return new_version
90
91 def _run(self):
92 if simple_call("git status --porcelain").strip():
93 raise DistutilsError("Uncommited changes, "
94 "commit all changes before release")
95
96 new_version = self._update_version()
97
98 check_output([
99 "git", "commit", "-m", "Release {}".format(new_version)])
100
101 simple_call("git tag release-{}".format(new_version))
102
103
104class GitPush(SimpleCommand):
105 """Push changes and tags to git origin
106 """
107
108 description = "push changes to git origin"
109
110 def _run(self):
111 simple_call("git push origin master")
112 simple_call("git push --tags")
113
114
115class TestsWithCoverage(SimpleCommand):
116 """Run Unit Tests with Coverage
117 """
118
119 description = "run unit tests with coverage"
120
121 def _run(self):
122 from coverage import coverage
123
124 cov = coverage(data_file=".coverage", branch=True,
125 source=self.distribution.packages)
126 cov.start()
127
128 # Unittest calls exit. How naughty.
129 try:
130 self.run_command("test")
131 except SystemExit:
132 pass
133
134 cov.stop()
135 cov.xml_report(outfile="coverage.xml")
136 cov.html_report()
137
138
139class PEP8CheckStyle(SimpleCommand):
140 """Run PEP8 Code Style Valiation
141 """
142
143 description = "run PEP8 style validations"
144
145 def _run(self):
146 from pep8 import StyleGuide
147
148 self.run_command("egg_info")
149 files = self.get_finalized_command("egg_info")
150
151 report = StyleGuide().check_files([
152 p for p in files.filelist.files if p.endswith(".py")])
153
154 if report.total_errors:
155 raise DistutilsError(
156 "Found {} PEP8 violations".format(report.total_errors))
157 else:
158 log.info("No PEP8 violations found")
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..a422483
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,40 @@
1#!/usr/bin/env python
2
3from setuptools import setup, find_packages
4
5setup(
6 name="py_release_tools",
7 version="0.1.0",
8 description="Python release tools",
9 long_description=open("README.rst", "r").read(),
10 author="Mike Crute",
11 author_email="mcrute@gmail.com",
12 url="https://github.com/mcrute/py_release_tools",
13 packages=find_packages(),
14 install_requires=[
15 "pep8>=1.6.2",
16 "mock>=1.0.1",
17 "coverage>=4.0.3",
18 ],
19 entry_points={
20 "distutils.commands": [
21 ("increment_semver = "
22 "py_release_tools.commands:IncrementSemanticVersion"),
23 "git_push = py_release_tools.commands:GitPush",
24 "cover_tests = py_release_tools.commands:TestsWithCoverage",
25 "pep8 = py_release_tools.commands:PEP8CheckStyle",
26 ]
27 },
28 classifiers=[
29 "Development Status :: 5 - Production/Stable",
30 "Environment :: Console",
31 "Framework :: Setuptools Plugin",
32 "Intended Audience :: Developers",
33 "License :: OSI Approved :: MIT License",
34 "Operating System :: OS Independent",
35 "Programming Language :: Python :: 2",
36 "Programming Language :: Python :: 3",
37 "Topic :: Software Development :: Build Tools",
38 "Topic :: Software Development :: Testing",
39 ]
40)