Skip to content

Commit 65c0caa

Browse files
committed
Modernize and improve setup.py
The current setup/CI configuration has rotted a bit, especially with respect to the control of requirements. This is a modernization pass to fix that and make some improvements: 1) Various requirements are now specified directly in setup.py through "extras_require", so to install the dev dependencies it's sufficient to run `$ pip install -e .[dev]` 2) When building or installing unreleased versions of asyncpg, the dev version is generated automatically with setuptools_scm. 3) When cythonization is necessary, the correct version of Cython is now used automatically through the use of setup_requires.
1 parent 0ddfa46 commit 65c0caa

14 files changed

+174
-52
lines changed

.ci/appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ branches:
2626

2727
install:
2828
- "%PYTHON% -m pip install --upgrade pip wheel setuptools"
29-
- "%PYTHON% -m pip install --upgrade -r .ci/requirements-win.txt"
3029

3130
build_script:
32-
- "%PYTHON% setup.py build_ext --inplace"
31+
- "%PYTHON% setup.py build_ext --inplace --cython-always"
3332

3433
test_script:
3534
- "%PYTHON% setup.py test"
@@ -41,6 +40,7 @@ artifacts:
4140
- path: dist\*
4241

4342
deploy_script:
43+
- "%PYTHON% -m pip install --upgrade -r .ci/requirements-publish.txt"
4444
- ps: |
4545
if ($env:appveyor_repo_branch -eq 'releases') {
4646
$PACKAGE_VERSION = & "$env:PYTHON" ".ci/package-version.py"

.ci/requirements-publish.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tinys3
2+
twine

.ci/requirements-win.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

.ci/requirements.txt

Lines changed: 0 additions & 5 deletions
This file was deleted.

.ci/travis-build-docs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ if [[ "${BUILD}" != *docs* ]]; then
77
exit 0
88
fi
99

10-
pip install -r docs/requirements.txt
10+
pip install -U .[dev]
1111
make htmldocs SPHINXOPTS="-q -W -j4"

.ci/travis-install.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
88
eval "$(pyenv init -)"
99
fi
1010

11-
pip install --upgrade pip wheel
12-
pip install --upgrade setuptools
13-
pip install --upgrade -r .ci/requirements.txt
11+
pip install --upgrade setuptools pip wheel
12+
pip install --upgrade -e .[dev]

.ci/travis-publish-docs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
1313
exit 0
1414
fi
1515

16-
pip install -r docs/requirements.txt
16+
pip install -U .[dev]
1717
make htmldocs
1818

1919
git config --global user.email "[email protected]"

.ci/travis-release.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ if [ -z "${TRAVIS_TAG}" ]; then
77
exit 0
88
fi
99

10+
pip install -U ".ci/requirements-publish.txt"
1011

1112
PACKAGE_VERSION=$(python ".ci/package-version.py")
1213
PYPI_VERSION=$(python ".ci/pypi-check.py" "${PYMODULE}")

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ docs/_build
3131
*,cover
3232
.coverage
3333
/.pytest_cache/
34+
/.eggs

Makefile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ compile:
2020

2121

2222
debug:
23-
$(PYTHON) setup.py build_ext --inplace --debug \
24-
--cython-always \
25-
--cython-annotate \
26-
--cython-directives="linetrace=True" \
27-
--define ASYNCPG_DEBUG,CYTHON_TRACE,CYTHON_TRACE_NOGIL
23+
ASYNCPG_DEBUG=1 $(PYTHON) setup.py build_ext --inplace
2824

2925

3026
test:

asyncpg/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,20 @@
1515
__all__ = ('connect', 'create_pool', 'Record', 'Connection') + \
1616
exceptions.__all__ # NOQA
1717

18-
__version__ = '0.16.0.dev1'
18+
# The rules of changing __version__:
19+
#
20+
# In a release revision, __version__ must be set to 'x.y.z',
21+
# and the release revision tagged with the 'vx.y.z' tag.
22+
# For example, asyncpg release 0.15.0 should have
23+
# __version__ set to '0.15.0', and tagged with 'v0.15.0'.
24+
#
25+
# In between releases, __version__ must be set to
26+
# 'x.y+1.0.dev0', so asyncpg revisions between 0.15.0 and
27+
# 0.16.0 should have __version__ set to '0.16.0.dev0' in
28+
# the source.
29+
#
30+
# Source and wheel distributions built from development
31+
# snapshots will automatically include the git revision
32+
# in __version__, for example: '0.16.0.dev22+ge06ad03'
33+
34+
__version__ = '0.16.0.dev0'

docs/installation.rst

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,27 @@ If you want to build **asyncpg** from a Git checkout you will need:
2929
* CPython header files. These can usually be obtained by installing
3030
the relevant Python development package: **python3-dev** on Debian/Ubuntu,
3131
**python3-devel** on RHEL/Fedora.
32-
* Cython version 0.24 or later. The easiest way to install it to use
33-
virtualenv and pip, however a system package should also suffice.
34-
* GNU make
3532

36-
Once the above requirements are satisfied, run:
33+
Once the above requirements are satisfied, run the following command
34+
in the root of the source checkout:
3735

3836
.. code-block:: bash
3937
40-
$ make
38+
$ pip install -e .
4139
42-
At this point you can run the usual ``setup.py`` commands or
43-
``pip install -e .`` to install the newly built version.
40+
A debug build containing more runtime checks can be created by setting
41+
the ``ASYNCPG_DEBUG`` environment variable when building:
4442

45-
.. note::
43+
.. code-block:: bash
4644
47-
A debug build can be created by running ``make debug``.
45+
$ env ASYNCPG_DEBUG=1 pip install -e .
4846
4947
5048
Running tests
5149
-------------
5250

53-
To execute the testsuite simply run:
51+
To execute the testsuite run:
5452

5553
.. code-block:: bash
5654
57-
$ make test
55+
$ python setup.py test

requirements-dev.txt

Lines changed: 0 additions & 4 deletions
This file was deleted.

setup.py

Lines changed: 137 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,43 @@
55
# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0
66

77

8+
import sys
9+
10+
if sys.version_info < (3, 5):
11+
raise RuntimeError('asyncpg requires Python 3.5 or greater')
12+
813
import os
914
import os.path
15+
import pathlib
1016
import platform
11-
import sys
17+
import re
18+
19+
# We use vanilla build_ext, to avoid importing Cython via
20+
# the setuptools version.
21+
from distutils import extension as distutils_extension
22+
from distutils.command import build_ext as distutils_build_ext
1223

1324
import setuptools
14-
from setuptools.command import build_ext as _build_ext
25+
from setuptools.command import build_py as setuptools_build_py
26+
from setuptools.command import sdist as setuptools_sdist
1527

1628

17-
if sys.version_info < (3, 5):
18-
raise RuntimeError('asyncpg requires Python 3.5 or greater')
29+
CYTHON_DEPENDENCY = 'Cython==0.28.3'
30+
31+
EXTRA_DEPENDENCIES = {
32+
# Dependencies required to develop asyncpg.
33+
'dev': [
34+
CYTHON_DEPENDENCY,
35+
'flake8~=3.5.0',
36+
'pytest~=3.0.7',
37+
'uvloop>=0.8.0;platform_system!="Windows"',
38+
# Docs
39+
'Sphinx~=1.7.3',
40+
'sphinxcontrib-asyncio~=0.2.0',
41+
'sphinx_rtd_theme~=0.2.4',
42+
]
43+
}
44+
1945

2046
CFLAGS = ['-O2']
2147
LDFLAGS = []
@@ -24,8 +50,44 @@
2450
CFLAGS.extend(['-fsigned-char', '-Wall', '-Wsign-compare', '-Wconversion'])
2551

2652

27-
class build_ext(_build_ext.build_ext):
28-
user_options = _build_ext.build_ext.user_options + [
53+
class VersionMixin:
54+
55+
def _fix_version(self, filename):
56+
# Replace asyncpg.__version__ with the actual version
57+
# of the distribution (possibly inferred from git).
58+
59+
with open(str(filename)) as f:
60+
content = f.read()
61+
62+
version_re = r"(.*__version__\s*=\s*)'[^']+'(.*)"
63+
repl = r"\1'{}'\2".format(self.distribution.metadata.version)
64+
content = re.sub(version_re, repl, content)
65+
66+
with open(str(filename), 'w') as f:
67+
f.write(content)
68+
69+
70+
class sdist(setuptools_sdist.sdist, VersionMixin):
71+
72+
def make_release_tree(self, base_dir, files):
73+
super().make_release_tree(base_dir, files)
74+
self._fix_version(pathlib.Path(base_dir) / 'asyncpg' / '__init__.py')
75+
76+
77+
class build_py(setuptools_build_py.build_py, VersionMixin):
78+
79+
def build_module(self, module, module_file, package):
80+
outfile, copied = super().build_module(module, module_file, package)
81+
82+
if module == '__init__' and package == 'asyncpg':
83+
self._fix_version(outfile)
84+
85+
return outfile, copied
86+
87+
88+
class build_ext(distutils_build_ext.build_ext):
89+
90+
user_options = distutils_build_ext.build_ext.user_options + [
2991
('cython-always', None,
3092
'run cythonize() even if .c files are present'),
3193
('cython-annotate', None,
@@ -42,9 +104,17 @@ def initialize_options(self):
42104
return
43105

44106
super(build_ext, self).initialize_options()
45-
self.cython_always = False
46-
self.cython_annotate = None
47-
self.cython_directives = None
107+
108+
if os.environ.get('ASYNCPG_DEBUG'):
109+
self.cython_always = True
110+
self.cython_annotate = True
111+
self.cython_directives = "linetrace=True"
112+
self.define = 'ASYNCPG_DEBUG,CYTHON_TRACE,CYTHON_TRACE_NOGIL'
113+
self.debug = True
114+
else:
115+
self.cython_always = False
116+
self.cython_annotate = None
117+
self.cython_directives = None
48118

49119
def finalize_options(self):
50120
# finalize_options() may be called multiple times on the
@@ -72,15 +142,25 @@ def finalize_options(self):
72142
need_cythonize = True
73143

74144
if need_cythonize:
145+
import pkg_resources
146+
147+
# Double check Cython presence in case setup_requires
148+
# didn't go into effect (most likely because someone
149+
# imported Cython before setup_requires injected the
150+
# correct egg into sys.path.
75151
try:
76152
import Cython
77153
except ImportError:
78154
raise RuntimeError(
79-
'please install Cython to compile asyncpg from source')
155+
'please install {} to compile asyncpg from source'.format(
156+
CYTHON_DEPENDENCY))
80157

81-
if Cython.__version__ < '0.24':
158+
cython_dep = pkg_resources.Requirement.parse(CYTHON_DEPENDENCY)
159+
if Cython.__version__ not in cython_dep:
82160
raise RuntimeError(
83-
'asyncpg requires Cython version 0.24 or greater')
161+
'asyncpg requires {}, got Cython=={}'.format(
162+
CYTHON_DEPENDENCY, Cython.__version__
163+
))
84164

85165
from Cython.Build import cythonize
86166

@@ -103,12 +183,14 @@ def finalize_options(self):
103183
super(build_ext, self).finalize_options()
104184

105185

106-
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f:
186+
_ROOT = pathlib.Path(__file__).parent
187+
188+
189+
with open(str(_ROOT / 'README.rst')) as f:
107190
readme = f.read()
108191

109192

110-
with open(os.path.join(
111-
os.path.dirname(__file__), 'asyncpg', '__init__.py')) as f:
193+
with open(str(_ROOT / 'asyncpg' / '__init__.py')) as f:
112194
for line in f:
113195
if line.startswith('__version__ ='):
114196
_, _, version = line.partition('=')
@@ -119,6 +201,41 @@ def finalize_options(self):
119201
'unable to read the version from asyncpg/__init__.py')
120202

121203

204+
extra_setup_kwargs = {}
205+
setup_requires = []
206+
207+
if (_ROOT / '.git').is_dir():
208+
# This is a git checkout, use setuptools_scm to
209+
# generage a precise version.
210+
def version_scheme(v):
211+
from setuptools_scm import version
212+
213+
if v.exact:
214+
fv = v.format_with("{tag}")
215+
if fv != VERSION:
216+
raise RuntimeError(
217+
'asyncpg.__version__ does not match the git tag')
218+
else:
219+
fv = v.format_next_version(
220+
version.guess_next_simple_semver,
221+
retain=version.SEMVER_MINOR)
222+
223+
if not fv.startswith(VERSION[:-1]):
224+
raise RuntimeError(
225+
'asyncpg.__version__ does not match the git tag')
226+
227+
return fv
228+
229+
setup_requires.append('setuptools_scm')
230+
extra_setup_kwargs['use_scm_version'] = {
231+
'version_scheme': version_scheme
232+
}
233+
234+
if not (_ROOT / 'asyncpg' / 'protocol' / 'protocol.c').exists():
235+
# No Cython output, require Cython to build.
236+
setup_requires.append(CYTHON_DEPENDENCY)
237+
238+
122239
setuptools.setup(
123240
name='asyncpg',
124241
version=VERSION,
@@ -145,13 +262,16 @@ def finalize_options(self):
145262
provides=['asyncpg'],
146263
include_package_data=True,
147264
ext_modules=[
148-
setuptools.Extension(
265+
distutils_extension.Extension(
149266
"asyncpg.protocol.protocol",
150267
["asyncpg/protocol/record/recordobj.c",
151268
"asyncpg/protocol/protocol.pyx"],
152269
extra_compile_args=CFLAGS,
153270
extra_link_args=LDFLAGS)
154271
],
155-
cmdclass={'build_ext': build_ext},
272+
cmdclass={'build_ext': build_ext, 'build_py': build_py, 'sdist': sdist},
156273
test_suite='tests.suite',
274+
extras_require=EXTRA_DEPENDENCIES,
275+
setup_requires=setup_requires,
276+
**extra_setup_kwargs
157277
)

0 commit comments

Comments
 (0)