diff options
author | Mike Crute <mcrute@gmail.com> | 2015-12-12 19:37:57 -0800 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2015-12-12 19:39:36 -0800 |
commit | 37d9ed4ae3e0b0193f990363df9dcea191475b35 (patch) | |
tree | 09563c4811c86a65968834f9aa0f0a367935f163 | |
download | py_release_tools-37d9ed4ae3e0b0193f990363df9dcea191475b35.tar.bz2 py_release_tools-37d9ed4ae3e0b0193f990363df9dcea191475b35.tar.xz py_release_tools-37d9ed4ae3e0b0193f990363df9dcea191475b35.zip |
Initial importrelease-0.1.0
-rw-r--r-- | .gitignore | 14 | ||||
-rw-r--r-- | README.rst | 112 | ||||
-rw-r--r-- | py_release_tools/__init__.py | 0 | ||||
-rw-r--r-- | py_release_tools/commands.py | 158 | ||||
-rwxr-xr-x | setup.py | 40 |
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/ | ||
9 | build/ | ||
10 | coverage.xml | ||
11 | dist/ | ||
12 | htmlcov/ | ||
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 | ====================== | ||
2 | Python Releasing Tools | ||
3 | ====================== | ||
4 | |||
5 | This code is licensed under the MIT license. | ||
6 | |||
7 | This is a suite of tools that are useful for releasing Python code for public | ||
8 | or private use. There are setuptools extensions to validate various conventions | ||
9 | and to perform release activities. | ||
10 | |||
11 | Installation and Usage | ||
12 | ====================== | ||
13 | This 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 | |||
26 | Commands are exported as distuils commands and should be automatically | ||
27 | available provided the package is installed. The tool provides the commands | ||
28 | documented below and are generally run by defining a ``release`` `alias | ||
29 | <http://pythonhosted.org/setuptools/setuptools.html#alias-define-shortcuts-for-commonly-used-commands>`_ | ||
30 | in the project's ``setup.cfg`` file. The author's typical project ``setup.cfg`` | ||
31 | contains 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 | |||
39 | Commands | ||
40 | ======== | ||
41 | increment_semver | ||
42 | ---------------- | ||
43 | This command will update the ``setup.py`` file version number following the | ||
44 | rules of `Semantic Versioning (semver) <http://semver.org>`_. This command will | ||
45 | re-write and commit the project's ``setup.py`` file. It assumes that the | ||
46 | version line is formatted as such, with some amount of leading whitespace:: | ||
47 | |||
48 | version="1.20.1" | ||
49 | |||
50 | It will rewrite all lines that look like this in the file. | ||
51 | |||
52 | The version format is:: | ||
53 | |||
54 | MAJOR.MINOR.PATCH | ||
55 | |||
56 | For more information check out the semver docs. | ||
57 | |||
58 | Version generation increments a version component by one. By default a patch | ||
59 | version is generated. Passing the ``-m`` or ``--minor`` flags to the command | ||
60 | will 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 | ||
62 | and patch versions to zero. | ||
63 | |||
64 | This command will also create a tag in the git repository of format | ||
65 | ``release-{semver}``. | ||
66 | |||
67 | git_push | ||
68 | -------- | ||
69 | This command runs a ``git push`` command to push the ``master`` branch to the | ||
70 | remote ``origin``. The command will also push tags. If your git repository | ||
71 | doesn't use these naming conventions the command will fail. | ||
72 | |||
73 | cover_tests | ||
74 | ----------- | ||
75 | This command will setup python | ||
76 | `coverage <https://pypi.python.org/pypi/coverage>`_ monitoring and invoke the | ||
77 | setuptools ``test`` target. Coverage data will be written to ``.coverage`` in | ||
78 | the same directory as the ``setup.py`` file. | ||
79 | |||
80 | This command will also generate a Cobertura coverage report as ``coverage.xml`` | ||
81 | and an HTML report in the ``htmlcov`` folder. | ||
82 | |||
83 | Failure of the tests will cause a failure of the build so it is suitable to use | ||
84 | this command as a replacement for the builtin ``test`` command. This command | ||
85 | also suppresses the system exit that the builtin ``test`` command generates so | ||
86 | other commands can be chained after this one. | ||
87 | |||
88 | pep8 | ||
89 | ---- | ||
90 | This command will run a `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_ | ||
91 | code style validation on all Python files in the project, including the | ||
92 | setup.py file. | ||
93 | |||
94 | Contributing | ||
95 | ============ | ||
96 | If 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 | ||
98 | request 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 | |||
108 | All code is reviewed before acceptance and changes may be requested to better | ||
109 | follow the conventions of the existing API. | ||
110 | |||
111 | he build system runs ``python setup.py validate`` on all supported Python | ||
112 | versions. 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 @@ | |||
1 | import os | ||
2 | import re | ||
3 | import sys | ||
4 | from io import BytesIO | ||
5 | from distutils import log | ||
6 | from distutils.cmd import Command | ||
7 | from subprocess import check_output | ||
8 | from distutils.errors import DistutilsError | ||
9 | |||
10 | |||
11 | def simple_call(cmd): | ||
12 | return check_output(cmd.split(" ")) | ||
13 | |||
14 | |||
15 | class 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 | |||
41 | class 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 | |||
104 | class 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 | |||
115 | class 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 | |||
139 | class 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 | |||
3 | from setuptools import setup, find_packages | ||
4 | |||
5 | setup( | ||
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 | ) | ||