From 479ad2d0e7c80efa0884578aa0f6e0bf29fac942 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 6 Apr 2018 11:14:07 -0500 Subject: [PATCH 01/21] Add pxd to allow cimport from other cython modules --- src/quicktions.pxd | 12 ++++++++++++ src/quicktions.pyx | 7 ++----- 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 src/quicktions.pxd diff --git a/src/quicktions.pxd b/src/quicktions.pxd new file mode 100644 index 0000000..9b2040b --- /dev/null +++ b/src/quicktions.pxd @@ -0,0 +1,12 @@ + +cpdef _gcd(a, b) + +cdef class Fraction: + cdef _numerator + cdef _denominator + cdef Py_hash_t _hash + + cpdef limit_denominator(self, max_denominator=*) + cpdef conjugate(self) + cdef _eq(a, b) + cdef _richcmp(self, other, int op) diff --git a/src/quicktions.pyx b/src/quicktions.pyx index 8a22503..fa1c348 100644 --- a/src/quicktions.pyx +++ b/src/quicktions.pyx @@ -220,9 +220,6 @@ cdef class Fraction: Fraction(147, 100) """ - cdef _numerator - cdef _denominator - cdef Py_hash_t _hash def __cinit__(self, numerator=0, denominator=None, *, bint _normalize=True): cdef Fraction value @@ -364,7 +361,7 @@ cdef class Fraction: else: return cls(digits, pow10(-exp)) - def limit_denominator(self, max_denominator=1000000): + cpdef limit_denominator(self, max_denominator=1000000): """Closest Fraction to self with denominator at most max_denominator. >>> Fraction('3.141592653589793').limit_denominator(10) @@ -592,7 +589,7 @@ cdef class Fraction: "Real numbers have no imaginary component." return 0 - def conjugate(self): + cpdef conjugate(self): """Conjugate is a no-op for Reals.""" return +self From 4ab0b49911e0c1255a182529abd527fda234c6e0 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 6 Apr 2018 11:19:20 -0500 Subject: [PATCH 02/21] Add test case for cimporting from another cython module --- src/test_fractions.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test_fractions.py b/src/test_fractions.py index 2a5eea5..f4d15de 100644 --- a/src/test_fractions.py +++ b/src/test_fractions.py @@ -10,6 +10,7 @@ from __future__ import division +import os from decimal import Decimal import math import numbers @@ -745,11 +746,47 @@ def test_pi_digits(self): self.assertEqual(ff.numerator, qf.numerator) self.assertEqual(ff.denominator, qf.denominator) +class CImportTest(unittest.TestCase): + + def setUp(self): + self.build_test_module() + + def tearDown(self): + self.remove_test_module() + + def build_test_module(self): + self.module_code = '\n'.join([ + 'from quicktions cimport Fraction', + 'def get_fraction():', + ' return Fraction(1, 2)', + ]) + base_path = os.path.abspath(os.path.dirname(__file__)) + self.module_name = 'quicktions_importtest' + self.module_filename = os.path.join(base_path, '.'.join([self.module_name, 'pyx'])) + with open(self.module_filename, 'w') as f: + f.write(self.module_code) + + def remove_test_module(self): + if os.path.exists(self.module_filename): + os.remove(self.module_filename) + + def test_cimport(self): + self.build_test_module() + import pyximport + self.py_importer, self.pyx_importer = pyximport.install() + + from quicktions_importtest import get_fraction + + self.assertEqual(get_fraction(), F(1,2)) + + pyximport.uninstall(self.py_importer, self.pyx_importer) + def test_main(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(GcdTest)) suite.addTest(unittest.makeSuite(FractionTest)) + suite.addTest(unittest.makeSuite(CImportTest)) import doctest suite.addTest(doctest.DocTestSuite('quicktions')) return suite From 2a10e919ddc8e3aa79c55d28d5f0f2764f113623 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 6 Apr 2018 13:27:51 -0500 Subject: [PATCH 03/21] Rename top-level src directory so it can be imported as a package --- .gitignore | 4 ++-- MANIFEST.in | 2 +- quicktions/__init__.py | 1 + {src => quicktions}/quicktions.pxd | 0 {src => quicktions}/quicktions.pyx | 0 {src => quicktions}/test_fractions.py | 2 +- setup.py | 8 +++++--- tox.ini | 4 ++-- 8 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 quicktions/__init__.py rename {src => quicktions}/quicktions.pxd (100%) rename {src => quicktions}/quicktions.pyx (100%) rename {src => quicktions}/test_fractions.py (99%) diff --git a/.gitignore b/.gitignore index 3e14355..495b9ad 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,8 @@ __pycache__ /build /dist -src/*.c -src/*.html +quicktions/*.c +quicktions/*.html MANIFEST .tox diff --git a/MANIFEST.in b/MANIFEST.in index 07a4ed4..6f9a9ff 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include MANIFEST.in LICENSE *.rst include setup.py *.yml tox.ini *.cmd *.txt -recursive-include src *.py *.pyx *.pxd *.c *.html +recursive-include quicktions *.py *.pyx *.pxd *.c *.html recursive-include benchmark *.py telco-bench.b diff --git a/quicktions/__init__.py b/quicktions/__init__.py new file mode 100644 index 0000000..3e23dfe --- /dev/null +++ b/quicktions/__init__.py @@ -0,0 +1 @@ +from .quicktions import Fraction, _gcd diff --git a/src/quicktions.pxd b/quicktions/quicktions.pxd similarity index 100% rename from src/quicktions.pxd rename to quicktions/quicktions.pxd diff --git a/src/quicktions.pyx b/quicktions/quicktions.pyx similarity index 100% rename from src/quicktions.pyx rename to quicktions/quicktions.pyx diff --git a/src/test_fractions.py b/quicktions/test_fractions.py similarity index 99% rename from src/test_fractions.py rename to quicktions/test_fractions.py index f4d15de..4211ab2 100644 --- a/src/test_fractions.py +++ b/quicktions/test_fractions.py @@ -775,7 +775,7 @@ def test_cimport(self): import pyximport self.py_importer, self.pyx_importer = pyximport.install() - from quicktions_importtest import get_fraction + from quicktions.quicktions_importtest import get_fraction self.assertEqual(get_fraction(), F(1,2)) diff --git a/setup.py b/setup.py index 3c2211c..4990f22 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ ext_modules = [ - Extension("quicktions", ["src/quicktions.pyx"]), + Extension("quicktions", ["quicktions/quicktions.pyx"]), ] try: @@ -43,7 +43,7 @@ ext_module.sources[:] = [m.replace('.pyx', '.c') for m in ext_module.sources] -with open('src/quicktions.pyx') as f: +with open('quicktions/quicktions.pyx') as f: version = re.search("__version__\s*=\s*'([^']+)'", f.read(2048)).group(1) with open('README.rst') as f: @@ -65,7 +65,9 @@ #bugtrack_url="https://github.com/scoder/quicktions/issues", ext_modules=ext_modules, - package_dir={'': 'src'}, + packages=['quicktions'], + package_data={'quicktions':['*.pxd']}, + include_package_data=True, classifiers=[ "Development Status :: 6 - Mature", diff --git a/tox.ini b/tox.ini index f1e2d83..21fa63b 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ platform = linux: linux darwin: darwin passenv = * -commands = coverage run --parallel-mode -m pytest src/test_fractions.py --capture=no --strict {posargs} +commands = coverage run --parallel-mode -m pytest quicktions/test_fractions.py --capture=no --strict {posargs} coverage combine - coverage report -m --include=src/test_fractions.py + coverage report -m --include=quicktions/test_fractions.py {windows,linux}: codecov From 0a6e8e24d435551677e69699430a547909ca594a Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 09:44:47 -0600 Subject: [PATCH 04/21] Modify extension build methods to match layout --- setup.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 4990f22..3a30c08 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ +import os import sys import re @@ -9,10 +10,14 @@ from distutils.core import setup, Extension - -ext_modules = [ - Extension("quicktions", ["quicktions/quicktions.pyx"]), -] +try: + from Cython.Build import cythonize + import Cython.Compiler.Options as cython_options + cython_options.annotate = True + cython_available = True +except ImportError: + cython_available = False + cython = None try: sys.argv.remove("--with-profile") @@ -21,27 +26,21 @@ else: enable_profiling = True +ext_modules = None try: sys.argv.remove("--with-cython") except ValueError: cythonize = None else: - try: - from Cython.Build import cythonize - import Cython.Compiler.Options as cython_options - cython_options.annotate = True - except ImportError: - cythonize = None - else: + if cython_available: compiler_directives = {} if enable_profiling: compiler_directives['profile'] = True - ext_modules = cythonize(ext_modules, compiler_directives=compiler_directives) - -if cythonize is None: - for ext_module in ext_modules: - ext_module.sources[:] = [m.replace('.pyx', '.c') for m in ext_module.sources] - + ext_modules = cythonize('quicktions/*.pyx', compiler_directives=compiler_directives) +if ext_modules is None: + ext_modules = [ + Extension("quicktions", [os.path.join("quicktions", "quicktions.c")]), + ] with open('quicktions/quicktions.pyx') as f: version = re.search("__version__\s*=\s*'([^']+)'", f.read(2048)).group(1) From 278516cd184be48a11be422fac9e2fccff4c4a23 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 10:12:55 -0600 Subject: [PATCH 05/21] Add language level directive to pxd and unittest cython module --- src/quicktions.pxd | 2 ++ src/test_fractions.py | 1 + 2 files changed, 3 insertions(+) diff --git a/src/quicktions.pxd b/src/quicktions.pxd index 9b2040b..a8d993b 100644 --- a/src/quicktions.pxd +++ b/src/quicktions.pxd @@ -1,3 +1,5 @@ +# cython: language_level=3str +## cython: profile=True cpdef _gcd(a, b) diff --git a/src/test_fractions.py b/src/test_fractions.py index 2174e75..c7a73d5 100644 --- a/src/test_fractions.py +++ b/src/test_fractions.py @@ -756,6 +756,7 @@ def tearDown(self): def build_test_module(self): self.module_code = '\n'.join([ + '# cython: language_level=3str', 'from quicktions cimport Fraction', 'def get_fraction():', ' return Fraction(1, 2)', From 18f73897c5074629a40439e13eb32ae90ff8ce57 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:27:57 -0600 Subject: [PATCH 06/21] Update paths in Makefile --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index a6f0c0a..8fb2b1c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ PYTHON?=python -VERSION?=$(shell sed -ne "s|^__version__\s*=\s*'\([^']*\)'.*|\1|p" src/quicktions.pyx) +PKG_ROOT?=quicktions +VERSION?=$(shell sed -ne "s|^__version__\s*=\s*'\([^']*\)'.*|\1|p" $(PKG_ROOT)/quicktions.pyx) PACKAGE=quicktions WITH_CYTHON := $(shell python -c 'from Cython.Build import cythonize' 2>/dev/null && echo "--with-cython") @@ -19,13 +20,13 @@ dist/$(PACKAGE)-$(VERSION).tar.gz: $(PYTHON) setup.py sdist $(WITH_CYTHON) test: local - PYTHONPATH=src $(PYTHON) src/test_fractions.py + PYTHONPATH=$(PKG_ROOT) $(PYTHON) $(PKG_ROOT)/test_fractions.py clean: - rm -fr build src/*.so + rm -fr build $(PKG_ROOT)/*.so realclean: clean - rm -fr src/*.c src/*.html + rm -fr $(PKG_ROOT)/*.c $(PKG_ROOT)/*.html wheel_manylinux: wheel_manylinux64 wheel_manylinux32 From 46daa88b606e335be3694c6b384e4907a2176460 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:28:33 -0600 Subject: [PATCH 07/21] Add language directive to pxd --- quicktions/quicktions.pxd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quicktions/quicktions.pxd b/quicktions/quicktions.pxd index 9b2040b..a8d993b 100644 --- a/quicktions/quicktions.pxd +++ b/quicktions/quicktions.pxd @@ -1,3 +1,5 @@ +# cython: language_level=3str +## cython: profile=True cpdef _gcd(a, b) From 3be2c4edd9bff14bc5da598351cf3882ea87f62c Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:29:08 -0600 Subject: [PATCH 08/21] Add module-level pxd --- quicktions/__init__.pxd | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 quicktions/__init__.pxd diff --git a/quicktions/__init__.pxd b/quicktions/__init__.pxd new file mode 100644 index 0000000..58f7e18 --- /dev/null +++ b/quicktions/__init__.pxd @@ -0,0 +1,2 @@ +from .quicktions import Fraction, _gcd +from quicktions.quicktions cimport Fraction, _gcd From 13b03b5da963fee0773b402a9e11b2e706dea11e Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:30:00 -0600 Subject: [PATCH 09/21] Add language level to pyximport, handle extension teardown properly --- quicktions/test_fractions.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/quicktions/test_fractions.py b/quicktions/test_fractions.py index 010c9f5..4c5bfa3 100644 --- a/quicktions/test_fractions.py +++ b/quicktions/test_fractions.py @@ -760,20 +760,22 @@ def build_test_module(self): 'def get_fraction():', ' return Fraction(1, 2)', ]) - base_path = os.path.abspath(os.path.dirname(__file__)) + self.base_path = os.path.abspath(os.path.dirname(__file__)) self.module_name = 'quicktions_importtest' - self.module_filename = os.path.join(base_path, '.'.join([self.module_name, 'pyx'])) + self.module_filename = os.path.join(self.base_path, '.'.join([self.module_name, 'pyx'])) with open(self.module_filename, 'w') as f: f.write(self.module_code) def remove_test_module(self): - if os.path.exists(self.module_filename): - os.remove(self.module_filename) + for fn in os.listdir(self.base_path): + if not fn.startswith(self.module_name): + continue + os.remove(os.path.join(self.base_path, fn)) def test_cimport(self): self.build_test_module() import pyximport - self.py_importer, self.pyx_importer = pyximport.install() + self.py_importer, self.pyx_importer = pyximport.install(inplace=True, language_level=3) from quicktions.quicktions_importtest import get_fraction From 32fe9eae1557e8901abb64b92a3536efd3d7c505 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:48:56 -0600 Subject: [PATCH 10/21] Import docstrings from quicktions.pyx at package level --- quicktions/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quicktions/__init__.py b/quicktions/__init__.py index 3e23dfe..25b622f 100644 --- a/quicktions/__init__.py +++ b/quicktions/__init__.py @@ -1 +1,2 @@ from .quicktions import Fraction, _gcd +from .quicktions import __doc__ From 53e12506121178fff2001670f9eb2fcc3ace9c60 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:49:38 -0600 Subject: [PATCH 11/21] Handle py2 imports properly --- quicktions/test_fractions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quicktions/test_fractions.py b/quicktions/test_fractions.py index 4c5bfa3..283d2ff 100644 --- a/quicktions/test_fractions.py +++ b/quicktions/test_fractions.py @@ -11,6 +11,7 @@ from __future__ import division import os +import sys from decimal import Decimal import math import numbers @@ -25,6 +26,7 @@ F = quicktions.Fraction gcd = quicktions._gcd +PY2 = sys.version_info.major == 2 class DummyFloat(object): """Dummy float class for testing comparisons with Fractions""" @@ -777,7 +779,10 @@ def test_cimport(self): import pyximport self.py_importer, self.pyx_importer = pyximport.install(inplace=True, language_level=3) - from quicktions.quicktions_importtest import get_fraction + if PY2: + from quicktions_importtest import get_fraction + else: + from quicktions.quicktions_importtest import get_fraction self.assertEqual(get_fraction(), F(1,2)) From 08ac125dcf0cfe2a104affb0a5270453d9e04bf2 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Mon, 12 Nov 2018 13:56:59 -0600 Subject: [PATCH 12/21] Compatibility fix for py2.6 --- quicktions/test_fractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quicktions/test_fractions.py b/quicktions/test_fractions.py index 283d2ff..4c31eef 100644 --- a/quicktions/test_fractions.py +++ b/quicktions/test_fractions.py @@ -26,7 +26,7 @@ F = quicktions.Fraction gcd = quicktions._gcd -PY2 = sys.version_info.major == 2 +PY2 = sys.version_info[0] == 2 class DummyFloat(object): """Dummy float class for testing comparisons with Fractions""" From 6de8ea429cce26e7aca78f8b5e75b68311a70bda Mon Sep 17 00:00:00 2001 From: nocarryr Date: Tue, 22 Jan 2019 11:35:38 -0600 Subject: [PATCH 13/21] Use pre-built wheel to install and run tests against --- .travis.yml | 5 +++-- Makefile | 1 + src/test_fractions.py | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c802e0..96fe068 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,11 @@ language: python install: - pip install -r requirements-appveyor.txt tox-travis - python setup.py build_ext --inplace --with-cython - + - python setup.py sdist bdist_wheel + - make clean + - pip install --no-index --find-links=dist/ quicktions script: - tox - - python setup.py sdist bdist_wheel # the following is stolen from https://github.com/joerick/pyinstrument_cext/blob/master/.travis.yml # uncomment to push wheels automatically to pypi for tagged releases only (requires TWINE_PASSWORD to be set) # - | diff --git a/Makefile b/Makefile index a6f0c0a..562306f 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ test: local clean: rm -fr build src/*.so + rm -r src/quicktions.egg-info realclean: clean rm -fr src/*.c src/*.html diff --git a/src/test_fractions.py b/src/test_fractions.py index 3312563..d42aaf4 100644 --- a/src/test_fractions.py +++ b/src/test_fractions.py @@ -22,6 +22,7 @@ from pickle import dumps, loads import quicktions +assert os.path.dirname(quicktions.__file__) != os.path.dirname(__file__) F = quicktions.Fraction gcd = quicktions._gcd From 5cb185ac486d9358330e74dacfe51d3c2c8e8266 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Wed, 23 Jan 2019 16:33:20 -0600 Subject: [PATCH 14/21] Move wrapper code and typedefs into pxd --- src/quicktions.pxd | 25 +++++++++++++++++++++++++ src/quicktions.pyx | 18 ------------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/quicktions.pxd b/src/quicktions.pxd index a8d993b..ecb9a2d 100644 --- a/src/quicktions.pxd +++ b/src/quicktions.pxd @@ -1,7 +1,32 @@ # cython: language_level=3str ## cython: profile=True +cdef extern from *: + """ + #if PY_VERSION_HEX < 0x030500F0 || !CYTHON_COMPILING_IN_CPYTHON + #define _PyLong_GCD(a, b) (NULL) + #endif + """ + # CPython 3.5+ has a fast PyLong GCD implementation that we can use. + int PY_VERSION_HEX + int IS_CPYTHON "CYTHON_COMPILING_IN_CPYTHON" + _PyLong_GCD(a, b) + +ctypedef unsigned long long ullong +ctypedef unsigned long ulong +ctypedef unsigned int uint + +ctypedef fused cunumber: + ullong + ulong + uint + cpdef _gcd(a, b) +cdef ullong _abs(long long x) +cdef cunumber _igcd(cunumber a, cunumber b) +cdef cunumber _ibgcd(cunumber a, cunumber b) +cdef _py_gcd(ullong a, ullong b) +cdef _gcd_fallback(a, b) cdef class Fraction: cdef _numerator diff --git a/src/quicktions.pyx b/src/quicktions.pyx index 96abb04..3f8f45d 100644 --- a/src/quicktions.pyx +++ b/src/quicktions.pyx @@ -70,16 +70,6 @@ cdef pow10(Py_ssize_t i): # Half-private GCD implementation. -cdef extern from *: - """ - #if PY_VERSION_HEX < 0x030500F0 || !CYTHON_COMPILING_IN_CPYTHON - #define _PyLong_GCD(a, b) (NULL) - #endif - """ - # CPython 3.5+ has a fast PyLong GCD implementation that we can use. - int PY_VERSION_HEX - int IS_CPYTHON "CYTHON_COMPILING_IN_CPYTHON" - _PyLong_GCD(a, b) cpdef _gcd(a, b): @@ -94,14 +84,6 @@ cpdef _gcd(a, b): return _PyLong_GCD(a, b) -ctypedef unsigned long long ullong -ctypedef unsigned long ulong -ctypedef unsigned int uint - -ctypedef fused cunumber: - ullong - ulong - uint cdef ullong _abs(long long x): From b7d99f49255bbb326e4fbe103c51f2ee9c00cdb3 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 25 Jan 2019 11:15:54 -0600 Subject: [PATCH 15/21] Move to src layout with subdirectory. Move test module to project root With quicktions nested under 'src/', setuptools can create a proper distribution/wheel. Moving test_fractions.py was needed to avoid import errors from namespace collisions. --- .gitignore | 4 ++-- MANIFEST.in | 3 ++- Makefile | 4 ++-- setup.py | 9 ++++++--- {quicktions => src/quicktions}/__init__.pxd | 0 {quicktions => src/quicktions}/__init__.py | 0 {quicktions => src/quicktions}/quicktions.pxd | 0 {quicktions => src/quicktions}/quicktions.pyx | 0 quicktions/test_fractions.py => test_fractions.py | 5 +---- tox.ini | 4 ++-- 10 files changed, 15 insertions(+), 14 deletions(-) rename {quicktions => src/quicktions}/__init__.pxd (100%) rename {quicktions => src/quicktions}/__init__.py (100%) rename {quicktions => src/quicktions}/quicktions.pxd (100%) rename {quicktions => src/quicktions}/quicktions.pyx (100%) rename quicktions/test_fractions.py => test_fractions.py (99%) diff --git a/.gitignore b/.gitignore index 495b9ad..4e239dc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,8 @@ __pycache__ /build /dist -quicktions/*.c -quicktions/*.html +src/quicktions/*.c +src/quicktions/*.html MANIFEST .tox diff --git a/MANIFEST.in b/MANIFEST.in index 6f9a9ff..5274f8d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include MANIFEST.in LICENSE *.rst include setup.py *.yml tox.ini *.cmd *.txt -recursive-include quicktions *.py *.pyx *.pxd *.c *.html +include test_fractions.py +recursive-include src *.py *.pyx *.pxd *.c *.html recursive-include benchmark *.py telco-bench.b diff --git a/Makefile b/Makefile index 6efbe21..9a96c14 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON?=python -PKG_ROOT?=quicktions +PKG_ROOT?=src/quicktions VERSION?=$(shell sed -ne "s|^__version__\s*=\s*'\([^']*\)'.*|\1|p" $(PKG_ROOT)/quicktions.pyx) PACKAGE=quicktions WITH_CYTHON := $(shell python -c 'from Cython.Build import cythonize' 2>/dev/null && echo "--with-cython") @@ -24,7 +24,7 @@ test: local clean: rm -fr build $(PKG_ROOT)/*.so - rm -r $(PKG_ROOT)/quicktions.egg-info + rm -r src/quicktions.egg-info realclean: clean rm -fr $(PKG_ROOT)/*.c $(PKG_ROOT)/*.html diff --git a/setup.py b/setup.py index 3a30c08..bedefa9 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,8 @@ cython_available = False cython = None +PKG_ROOT = os.path.join('src', 'quicktions') + try: sys.argv.remove("--with-profile") except ValueError: @@ -36,13 +38,13 @@ compiler_directives = {} if enable_profiling: compiler_directives['profile'] = True - ext_modules = cythonize('quicktions/*.pyx', compiler_directives=compiler_directives) + ext_modules = cythonize(os.path.join(PKG_ROOT, '*.pyx'), compiler_directives=compiler_directives) if ext_modules is None: ext_modules = [ - Extension("quicktions", [os.path.join("quicktions", "quicktions.c")]), + Extension("quicktions.quicktions", [os.path.join(PKG_ROOT, "quicktions.c")]), ] -with open('quicktions/quicktions.pyx') as f: +with open(os.path.join(PKG_ROOT, 'quicktions.pyx')) as f: version = re.search("__version__\s*=\s*'([^']+)'", f.read(2048)).group(1) with open('README.rst') as f: @@ -64,6 +66,7 @@ #bugtrack_url="https://github.com/scoder/quicktions/issues", ext_modules=ext_modules, + package_dir={'':'src'}, packages=['quicktions'], package_data={'quicktions':['*.pxd']}, include_package_data=True, diff --git a/quicktions/__init__.pxd b/src/quicktions/__init__.pxd similarity index 100% rename from quicktions/__init__.pxd rename to src/quicktions/__init__.pxd diff --git a/quicktions/__init__.py b/src/quicktions/__init__.py similarity index 100% rename from quicktions/__init__.py rename to src/quicktions/__init__.py diff --git a/quicktions/quicktions.pxd b/src/quicktions/quicktions.pxd similarity index 100% rename from quicktions/quicktions.pxd rename to src/quicktions/quicktions.pxd diff --git a/quicktions/quicktions.pyx b/src/quicktions/quicktions.pyx similarity index 100% rename from quicktions/quicktions.pyx rename to src/quicktions/quicktions.pyx diff --git a/quicktions/test_fractions.py b/test_fractions.py similarity index 99% rename from quicktions/test_fractions.py rename to test_fractions.py index 433c211..91b0cd3 100644 --- a/quicktions/test_fractions.py +++ b/test_fractions.py @@ -832,10 +832,7 @@ def test_cimport(self): import pyximport self.py_importer, self.pyx_importer = pyximport.install(inplace=True, language_level=3) - if PY2: - from quicktions_importtest import get_fraction - else: - from quicktions.quicktions_importtest import get_fraction + from quicktions_importtest import get_fraction self.assertEqual(get_fraction(), F(1,2)) diff --git a/tox.ini b/tox.ini index 7c28fd3..79f9559 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ platform = linux: linux darwin: darwin passenv = * -commands = coverage run --parallel-mode -m pytest quicktions/test_fractions.py --capture=no --strict {posargs} +commands = coverage run --parallel-mode -m pytest test_fractions.py --capture=no --strict {posargs} coverage combine - coverage report -m --include=quicktions/test_fractions.py + coverage report -m --include=test_fractions.py {windows,linux}: codecov From f8894e9f5febe56b16a9237bf7688fa2ea0dcdb1 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 25 Jan 2019 12:12:39 -0600 Subject: [PATCH 16/21] Remove unused imports and assertions --- test_fractions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test_fractions.py b/test_fractions.py index 91b0cd3..e3da3cf 100644 --- a/test_fractions.py +++ b/test_fractions.py @@ -11,7 +11,6 @@ from __future__ import division import os -import sys from decimal import Decimal import math import numbers @@ -23,11 +22,9 @@ from pickle import dumps, loads import quicktions -assert os.path.dirname(quicktions.__file__) != os.path.dirname(__file__) F = quicktions.Fraction gcd = quicktions._gcd -PY2 = sys.version_info[0] == 2 class DummyFloat(object): """Dummy float class for testing comparisons with Fractions""" From fad41afac14cc6fcd97d5c36f5e8355b2027f932 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 25 Jan 2019 12:14:27 -0600 Subject: [PATCH 17/21] Correct paths for "make test" --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9a96c14..e41b8b2 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ dist/$(PACKAGE)-$(VERSION).tar.gz: $(PYTHON) setup.py sdist $(WITH_CYTHON) test: local - PYTHONPATH=$(PKG_ROOT) $(PYTHON) $(PKG_ROOT)/test_fractions.py + PYTHONPATH=$(PKG_ROOT) $(PYTHON) test_fractions.py clean: rm -fr build $(PKG_ROOT)/*.so From dde765464fc5ec0b7d1f5bf3bb516fdd75707531 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 25 Jan 2019 12:24:11 -0600 Subject: [PATCH 18/21] Add comment explaining wheel installation step in travis config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 96fe068..72360cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ install: - python setup.py build_ext --inplace --with-cython - python setup.py sdist bdist_wheel - make clean + # Install quicktions from the wheel built above (into site-packages) - pip install --no-index --find-links=dist/ quicktions script: - tox From efcca1567f8cdefeae5ef4e4bf92721071511717 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 25 Jan 2019 12:31:30 -0600 Subject: [PATCH 19/21] Make __version__ and __all__ variables available at the package level --- src/quicktions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quicktions/__init__.py b/src/quicktions/__init__.py index 25b622f..6428b97 100644 --- a/src/quicktions/__init__.py +++ b/src/quicktions/__init__.py @@ -1,2 +1,2 @@ from .quicktions import Fraction, _gcd -from .quicktions import __doc__ +from .quicktions import __doc__, __version__, __all__ From 0536e1e8bb6eab0b63e8ce0d406d1e2370f6c783 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 15 Feb 2019 13:13:09 -0600 Subject: [PATCH 20/21] Place Cython-related tests into separate module --- test_fractions.py | 39 ------------------------------ test_quicktions.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ tox.ini | 2 +- 3 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 test_quicktions.py diff --git a/test_fractions.py b/test_fractions.py index e3da3cf..b7c576d 100644 --- a/test_fractions.py +++ b/test_fractions.py @@ -797,50 +797,11 @@ def test_pi_digits(self): self.assertEqual(ff.numerator, qf.numerator) self.assertEqual(ff.denominator, qf.denominator) -class CImportTest(unittest.TestCase): - - def setUp(self): - self.build_test_module() - - def tearDown(self): - self.remove_test_module() - - def build_test_module(self): - self.module_code = '\n'.join([ - '# cython: language_level=3str', - 'from quicktions cimport Fraction', - 'def get_fraction():', - ' return Fraction(1, 2)', - ]) - self.base_path = os.path.abspath(os.path.dirname(__file__)) - self.module_name = 'quicktions_importtest' - self.module_filename = os.path.join(self.base_path, '.'.join([self.module_name, 'pyx'])) - with open(self.module_filename, 'w') as f: - f.write(self.module_code) - - def remove_test_module(self): - for fn in os.listdir(self.base_path): - if not fn.startswith(self.module_name): - continue - os.remove(os.path.join(self.base_path, fn)) - - def test_cimport(self): - self.build_test_module() - import pyximport - self.py_importer, self.pyx_importer = pyximport.install(inplace=True, language_level=3) - - from quicktions_importtest import get_fraction - - self.assertEqual(get_fraction(), F(1,2)) - - pyximport.uninstall(self.py_importer, self.pyx_importer) - def test_main(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(GcdTest)) suite.addTest(unittest.makeSuite(FractionTest)) - suite.addTest(unittest.makeSuite(CImportTest)) import doctest suite.addTest(doctest.DocTestSuite('quicktions')) return suite diff --git a/test_quicktions.py b/test_quicktions.py new file mode 100644 index 0000000..9397101 --- /dev/null +++ b/test_quicktions.py @@ -0,0 +1,60 @@ +import os +import unittest + +import quicktions +F = quicktions.Fraction +gcd = quicktions._gcd + +class CImportTest(unittest.TestCase): + + def setUp(self): + self.build_test_module() + + def tearDown(self): + self.remove_test_module() + + def build_test_module(self): + self.module_code = '\n'.join([ + '# cython: language_level=3str', + 'from quicktions cimport Fraction', + 'def get_fraction():', + ' return Fraction(1, 2)', + ]) + self.base_path = os.path.abspath(os.path.dirname(__file__)) + self.module_name = 'quicktions_importtest' + self.module_filename = os.path.join(self.base_path, '.'.join([self.module_name, 'pyx'])) + with open(self.module_filename, 'w') as f: + f.write(self.module_code) + + def remove_test_module(self): + for fn in os.listdir(self.base_path): + if not fn.startswith(self.module_name): + continue + os.remove(os.path.join(self.base_path, fn)) + + def test_cimport(self): + self.build_test_module() + import pyximport + self.py_importer, self.pyx_importer = pyximport.install(inplace=True, language_level=3) + + from quicktions_importtest import get_fraction + + self.assertEqual(get_fraction(), F(1,2)) + + pyximport.uninstall(self.py_importer, self.pyx_importer) + + + +def test_main(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(CImportTest)) + return suite + +def main(): + suite = test_main() + runner = unittest.TextTestRunner(sys.stdout, verbosity=2) + result = runner.run(suite) + sys.exit(not result.wasSuccessful()) + +if __name__ == '__main__': + main() diff --git a/tox.ini b/tox.ini index 79f9559..378863d 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ platform = linux: linux darwin: darwin passenv = * -commands = coverage run --parallel-mode -m pytest test_fractions.py --capture=no --strict {posargs} +commands = coverage run --parallel-mode -m pytest --capture=no --strict {posargs} coverage combine coverage report -m --include=test_fractions.py {windows,linux}: codecov From 5a477cc9ae9684fc26fe88dc2cfe175aba1f04f7 Mon Sep 17 00:00:00 2001 From: nocarryr Date: Fri, 15 Feb 2019 13:15:29 -0600 Subject: [PATCH 21/21] Use Cythonize module instead of pyximport to compile test modules --- test_quicktions.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/test_quicktions.py b/test_quicktions.py index 9397101..18a7ed6 100644 --- a/test_quicktions.py +++ b/test_quicktions.py @@ -1,6 +1,9 @@ import os +import glob import unittest +from Cython.Build import Cythonize + import quicktions F = quicktions.Fraction gcd = quicktions._gcd @@ -8,42 +11,38 @@ class CImportTest(unittest.TestCase): def setUp(self): - self.build_test_module() + self.module_files = [] def tearDown(self): - self.remove_test_module() + for fn in self.module_files: + if os.path.exists(fn): + os.remove(fn) def build_test_module(self): - self.module_code = '\n'.join([ + module_code = '\n'.join([ '# cython: language_level=3str', 'from quicktions cimport Fraction', 'def get_fraction():', ' return Fraction(1, 2)', ]) - self.base_path = os.path.abspath(os.path.dirname(__file__)) - self.module_name = 'quicktions_importtest' - self.module_filename = os.path.join(self.base_path, '.'.join([self.module_name, 'pyx'])) - with open(self.module_filename, 'w') as f: - f.write(self.module_code) - - def remove_test_module(self): - for fn in os.listdir(self.base_path): - if not fn.startswith(self.module_name): - continue - os.remove(os.path.join(self.base_path, fn)) + base_path = os.path.abspath(os.path.dirname(__file__)) + module_name = 'quicktions_importtest' + module_filename = os.path.join(base_path, '.'.join([module_name, 'pyx'])) + with open(module_filename, 'w') as f: + f.write(module_code) + + Cythonize.main(['-i', module_filename]) + + for fn in glob.glob(os.path.join(base_path, '.'.join([module_name, '*']))): + self.module_files.append(os.path.abspath(fn)) def test_cimport(self): self.build_test_module() - import pyximport - self.py_importer, self.pyx_importer = pyximport.install(inplace=True, language_level=3) from quicktions_importtest import get_fraction self.assertEqual(get_fraction(), F(1,2)) - pyximport.uninstall(self.py_importer, self.pyx_importer) - - def test_main(): suite = unittest.TestSuite()