diff options
author | Mike Crute <mike@crute.us> | 2020-06-09 01:07:31 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2020-06-09 02:25:32 +0000 |
commit | f6714a399003e660d310172391dfb762ea306b00 (patch) | |
tree | 9eaae019ca5dcbb7b6e23ac775e066fa990a3084 | |
parent | e42c95b978a83a1ba48020b6d84218f2f17b5db4 (diff) | |
download | pydora-f6714a399003e660d310172391dfb762ea306b00.tar.bz2 pydora-f6714a399003e660d310172391dfb762ea306b00.tar.xz pydora-f6714a399003e660d310172391dfb762ea306b00.zip |
Convert build system to tox
This is the current modern way to build, package, and test python code.
-rw-r--r-- | .github/workflows/build.yml | 6 | ||||
-rw-r--r-- | .github/workflows/release.yml | 23 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | CONTRIBUTING.rst | 2 | ||||
-rw-r--r-- | pandora/__init__.py | 1 | ||||
-rw-r--r-- | setup.cfg | 82 | ||||
-rwxr-xr-x | setup.py | 206 |
7 files changed, 114 insertions, 209 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f63125..f3833be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml | |||
@@ -13,6 +13,7 @@ jobs: | |||
13 | os: | 13 | os: |
14 | - ubuntu-latest | 14 | - ubuntu-latest |
15 | - macos-latest | 15 | - macos-latest |
16 | |||
16 | python-version: | 17 | python-version: |
17 | - 3.5 | 18 | - 3.5 |
18 | - 3.6 | 19 | - 3.6 |
@@ -26,5 +27,6 @@ jobs: | |||
26 | with: | 27 | with: |
27 | python-version: ${{ matrix.python-version }} | 28 | python-version: ${{ matrix.python-version }} |
28 | 29 | ||
29 | - run: pip install -U pip setuptools wheel mock nose pytest | 30 | - run: | |
30 | - run: python setup.py release | 31 | pip install tox |
32 | tox -e tests,release | ||
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f47c613 --- /dev/null +++ b/.github/workflows/release.yml | |||
@@ -0,0 +1,23 @@ | |||
1 | name: release | ||
2 | |||
3 | on: | ||
4 | push: | ||
5 | tags: | ||
6 | - 'release-*' | ||
7 | |||
8 | jobs: | ||
9 | build: | ||
10 | runs-on: ubuntu-latest | ||
11 | |||
12 | steps: | ||
13 | - uses: actions/checkout@v2 | ||
14 | |||
15 | - uses: actions/setup-python@v2 | ||
16 | with: | ||
17 | python-version: 3.8 | ||
18 | |||
19 | - env: | ||
20 | TWINE_PASSWORD: ${{ secrets.PYPI_API_KEY }} | ||
21 | run: | | ||
22 | pip install tox | ||
23 | tox -e tests,release,upload | ||
@@ -2,9 +2,8 @@ | |||
2 | .coverage/ | 2 | .coverage/ |
3 | .eggs/ | 3 | .eggs/ |
4 | .py3/ | 4 | .py3/ |
5 | .sandbox_name | 5 | .tox/ |
6 | build/ | 6 | build/ |
7 | coverage.xml | ||
8 | dist/ | 7 | dist/ |
9 | htmlcov/ | 8 | htmlcov/ |
10 | pydora.egg-info/ | 9 | pydora.egg-info/ |
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7e95437..2671c63 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst | |||
@@ -16,5 +16,5 @@ your changes. To have the best experience contributing, please: | |||
16 | All code is reviewed before acceptance and changes may be requested to better | 16 | All code is reviewed before acceptance and changes may be requested to better |
17 | follow the conventions of the existing API. | 17 | follow the conventions of the existing API. |
18 | 18 | ||
19 | The build system runs ``python setup.py release`` on all supported Python | 19 | The build system runs ``tox -e tests,release`` on all supported Python |
20 | versions. You can, and should, run this on your pull request before submitting. | 20 | versions. You can, and should, run this on your pull request before submitting. |
diff --git a/pandora/__init__.py b/pandora/__init__.py index e69de29..159d48b 100644 --- a/pandora/__init__.py +++ b/pandora/__init__.py | |||
@@ -0,0 +1 @@ | |||
__version__ = "2.0.1" | |||
diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a6c5c6d --- /dev/null +++ b/setup.cfg | |||
@@ -0,0 +1,82 @@ | |||
1 | [metadata] | ||
2 | name = pydora | ||
3 | version = attr: pandora.__version__ | ||
4 | description = Python wrapper for Pandora API | ||
5 | long_description = file: README.rst | ||
6 | author = Mike Crute | ||
7 | author_email = mike@crute.us | ||
8 | url = https://github.com/mcrute/pydora | ||
9 | classifiers = | ||
10 | Development Status :: 5 - Production/Stable | ||
11 | Environment :: Console | ||
12 | Intended Audience :: Developers | ||
13 | Intended Audience :: End Users/Desktop | ||
14 | License :: OSI Approved :: MIT License | ||
15 | Operating System :: OS Independent | ||
16 | Programming Language :: Python :: 3 | ||
17 | Programming Language :: Python :: 3.5 | ||
18 | Programming Language :: Python :: 3.6 | ||
19 | Programming Language :: Python :: 3.7 | ||
20 | Programming Language :: Python :: 3.8 | ||
21 | Programming Language :: Python :: 3 :: Only | ||
22 | Topic :: Internet :: WWW/HTTP | ||
23 | Topic :: Multimedia :: Sound/Audio :: Players | ||
24 | |||
25 | [options] | ||
26 | packages = find: | ||
27 | python_requires = >=3.5 | ||
28 | |||
29 | install_requires = | ||
30 | requests >=2, <3 | ||
31 | blowfish >=0.6.1, <1.0 | ||
32 | |||
33 | [options.packages.find] | ||
34 | exclude = | ||
35 | tests | ||
36 | tests.* | ||
37 | |||
38 | [options.entry_points] | ||
39 | distutils.commands = | ||
40 | pydora = pydora.player:main | ||
41 | pydora-configure = pydora.configure:main | ||
42 | |||
43 | [testenv:format] | ||
44 | deps = | ||
45 | black | ||
46 | |||
47 | commands = | ||
48 | black -l 79 -t py35 pandora/ pydora/ tests/ setup.py | ||
49 | |||
50 | [testenv:tests] | ||
51 | deps = | ||
52 | pytest | ||
53 | black | ||
54 | flake8 >=3.3 | ||
55 | coverage >=4.1, <5 | ||
56 | |||
57 | commands = | ||
58 | black --check -l 79 -t py35 pandora/ pydora/ tests/ setup.py | ||
59 | flake8 --statistics --ignore=E231 pandora/ pydora/ tests/ setup.py | ||
60 | coverage run --source='pandora/,pydora/' -m pytest | ||
61 | coverage report --fail-under 100 -m --include='pandora/*' | ||
62 | coverage report -m --include='pydora/*' | ||
63 | coverage html --include='pandora/*,pydora/*' | ||
64 | |||
65 | [testenv:release] | ||
66 | deps = | ||
67 | wheel | ||
68 | |||
69 | commands = | ||
70 | python setup.py sdist bdist_wheel | ||
71 | |||
72 | [testenv:upload] | ||
73 | skip_install = true | ||
74 | |||
75 | deps = | ||
76 | twine | ||
77 | |||
78 | passenv = | ||
79 | TWINE_PASSWORD | ||
80 | |||
81 | commands = | ||
82 | twine upload -u __token__ --non-interactive --skip-existing dist/* | ||
@@ -1,208 +1,6 @@ | |||
1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
2 | 2 | ||
3 | import os | 3 | import setuptools |
4 | import math | ||
5 | import shutil | ||
6 | import subprocess | ||
7 | from distutils import log | ||
8 | from distutils.core import Command | ||
9 | from setuptools.command.test import test | ||
10 | from distutils.errors import DistutilsError | ||
11 | from setuptools import setup, find_packages | ||
12 | 4 | ||
13 | 5 | ||
14 | class Release(Command): | 6 | setuptools.setup() |
15 | |||
16 | user_options = [] | ||
17 | description = "build and test package with linting" | ||
18 | |||
19 | def initialize_options(self): | ||
20 | pass | ||
21 | |||
22 | def finalize_options(self): | ||
23 | pass | ||
24 | |||
25 | def run(self): | ||
26 | self.run_command("test") | ||
27 | |||
28 | # Flake8 should examine tests too | ||
29 | self.distribution.packages = find_packages() | ||
30 | self.run_command("flake8") | ||
31 | |||
32 | |||
33 | class TestsWithCoverage(test): | ||
34 | |||
35 | description = "run unit tests with coverage" | ||
36 | |||
37 | coverage_goal = 100 | ||
38 | missed_branches_goal = 0 | ||
39 | partial_branches_goal = 0 | ||
40 | |||
41 | def initialize_options(self): | ||
42 | super().initialize_options() | ||
43 | self.missed_coverage_goals = False | ||
44 | |||
45 | def enforce_coverage_goals(self, rel_path, analysis): | ||
46 | # There is no coverage goal for the player package, just the API | ||
47 | if os.path.split(rel_path)[0] == "pydora": | ||
48 | return | ||
49 | |||
50 | coverage_percent = math.ceil(analysis.numbers.pc_covered) | ||
51 | if coverage_percent != self.coverage_goal: | ||
52 | self.missed_coverage_goals = True | ||
53 | self.announce( | ||
54 | "Coverage: {!r} coverage is {}%, goal is {}%".format( | ||
55 | rel_path, coverage_percent, self.coverage_goal | ||
56 | ), | ||
57 | log.ERROR, | ||
58 | ) | ||
59 | |||
60 | missed_branches = analysis.numbers.n_missing_branches | ||
61 | if missed_branches != self.missed_branches_goal: | ||
62 | self.missed_coverage_goals = True | ||
63 | self.announce( | ||
64 | "Coverage: {!r} missed branch count is {}, goal is {}".format( | ||
65 | rel_path, missed_branches, self.missed_branches_goal | ||
66 | ), | ||
67 | log.ERROR, | ||
68 | ) | ||
69 | |||
70 | partially_covered_branches = analysis.numbers.n_partial_branches | ||
71 | if partially_covered_branches != self.partial_branches_goal: | ||
72 | self.missed_coverage_goals = True | ||
73 | self.announce( | ||
74 | "Coverage: {!r} partial branch count is {}, goal is {}".format( | ||
75 | rel_path, | ||
76 | partially_covered_branches, | ||
77 | self.partial_branches_goal, | ||
78 | ), | ||
79 | log.ERROR, | ||
80 | ) | ||
81 | |||
82 | def run(self): | ||
83 | from coverage import Coverage | ||
84 | |||
85 | cov = Coverage(source=self.distribution.packages, branch=True) | ||
86 | |||
87 | cov.start() | ||
88 | super().run() | ||
89 | cov.stop() | ||
90 | |||
91 | # Save HTML report for debugging missed coverage | ||
92 | cov.html_report() | ||
93 | |||
94 | # Print coverage report to console for CI log | ||
95 | cov.report() | ||
96 | |||
97 | for rep in cov._get_file_reporters(): | ||
98 | self.enforce_coverage_goals(rep.relname, cov._analyze(rep)) | ||
99 | |||
100 | if self.missed_coverage_goals: | ||
101 | raise DistutilsError("Project missed coverage goals") | ||
102 | |||
103 | |||
104 | class PyPiReleaseCommand(Command): | ||
105 | |||
106 | user_options = [] | ||
107 | description = "build and release artifacts to pypi" | ||
108 | |||
109 | def initialize_options(self): | ||
110 | pass | ||
111 | |||
112 | def finalize_options(self): | ||
113 | pass | ||
114 | |||
115 | def venv_run(self, cmd, *args): | ||
116 | subprocess.check_call((os.path.join(".release/py3/bin", cmd),) + args) | ||
117 | |||
118 | def make_release_tree(self): | ||
119 | if not os.path.exists(".release"): | ||
120 | log.info("Creating temp release tree") | ||
121 | os.mkdir(".release") | ||
122 | |||
123 | def configure_environment(self): | ||
124 | log.info("Configuring release environment") | ||
125 | subprocess.check_call(["python3", "-m", "venv", ".release/py3"]) | ||
126 | self.venv_run( | ||
127 | "pip", "install", "-U", "pip", "setuptools", "virtualenv", "twine" | ||
128 | ) | ||
129 | |||
130 | def build_py3_artifact(self): | ||
131 | log.info("Building Python 3 Artifact") | ||
132 | self.venv_run( | ||
133 | "python", | ||
134 | "setup.py", | ||
135 | "release", | ||
136 | "bdist_wheel", | ||
137 | "--python-tag", | ||
138 | "py3", | ||
139 | ) | ||
140 | |||
141 | def build_sdist_artifact(self): | ||
142 | log.info("Building Source Dist Artifact") | ||
143 | self.venv_run("python", "setup.py", "sdist") | ||
144 | |||
145 | def upload_artifacts(self): | ||
146 | log.info("Uploading artifacts to PyPi") | ||
147 | self.venv_run("twine", "upload", "dist/*") | ||
148 | |||
149 | def cleanup(self): | ||
150 | log.info("Cleaning up temp release tree") | ||
151 | shutil.rmtree(".release") | ||
152 | |||
153 | def run(self): | ||
154 | try: | ||
155 | self.make_release_tree() | ||
156 | self.configure_environment() | ||
157 | self.build_py3_artifact() | ||
158 | self.build_sdist_artifact() | ||
159 | self.upload_artifacts() | ||
160 | finally: | ||
161 | self.cleanup() | ||
162 | |||
163 | |||
164 | setup( | ||
165 | name="pydora", | ||
166 | version="2.0.0", | ||
167 | description="Python wrapper for Pandora API", | ||
168 | long_description=open("README.rst", "r").read(), | ||
169 | author="Mike Crute", | ||
170 | author_email="mike@crute.us", | ||
171 | url="https://github.com/mcrute/pydora", | ||
172 | test_suite="tests.discover_suite", | ||
173 | packages=find_packages(exclude=["tests", "tests.*"]), | ||
174 | cmdclass={ | ||
175 | "test": TestsWithCoverage, | ||
176 | "pypi_release": PyPiReleaseCommand, | ||
177 | "release": Release, | ||
178 | }, | ||
179 | entry_points={ | ||
180 | "console_scripts": [ | ||
181 | "pydora = pydora.player:main", | ||
182 | "pydora-configure = pydora.configure:main", | ||
183 | ], | ||
184 | }, | ||
185 | setup_requires=[ | ||
186 | "wheel", | ||
187 | "flake8>=3.3", | ||
188 | "setuptools>=36.0.1", | ||
189 | "coverage>=4.1,<5", | ||
190 | ], | ||
191 | install_requires=["requests>=2,<3", "blowfish>=0.6.1,<1.0",], | ||
192 | python_requires=">=3.5", | ||
193 | classifiers=[ | ||
194 | "Development Status :: 5 - Production/Stable", | ||
195 | "Environment :: Console", | ||
196 | "Intended Audience :: Developers", | ||
197 | "Intended Audience :: End Users/Desktop", | ||
198 | "License :: OSI Approved :: MIT License", | ||
199 | "Operating System :: OS Independent", | ||
200 | "Programming Language :: Python :: 3", | ||
201 | "Programming Language :: Python :: 3.5", | ||
202 | "Programming Language :: Python :: 3.6", | ||
203 | "Programming Language :: Python :: 3.7", | ||
204 | "Programming Language :: Python :: 3 :: Only", | ||
205 | "Topic :: Internet :: WWW/HTTP", | ||
206 | "Topic :: Multimedia :: Sound/Audio :: Players", | ||
207 | ], | ||
208 | ) | ||