Repository: pauldmccarthy/indexed_gzip (language: python)

Issue:
Incompatible with Cython 3 due to division semantics
Cython 3.0.0 was released, and `indexed_gzip` is not quite compatible.

```
=================================== FAILURES ===================================
________________________________ test_read_all _________________________________
[gw7] linux -- Python 3.12.0 /usr/bin/python3

testfile = '/builddir/build/BUILD/indexed_gzip-1.7.1/ctest_zran_15801045_False.gz'
nelems = 15801045, use_mmap = False

    def test_read_all(testfile, nelems, use_mmap):
        for no_fds in (True, False):
>           ctest_zran.test_read_all(testfile, no_fds, nelems, use_mmap)

../../BUILDROOT/python-indexed_gzip-1.7.1-5.fc39.x86_64/usr/lib64/python3.12/site-packages/indexed_gzip/tests/test_zran.py:86:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   nelemsp = nbytes / 8.
E   TypeError: 'float' object cannot be interpreted as an integer

indexed_gzip/tests/ctest_zran.pyx:799: TypeError
__________________________ test_seek_then_read_block ___________________________
[gw2] linux -- Python 3.12.0 /usr/bin/python3

testfile = '/builddir/build/BUILD/indexed_gzip-1.7.1/ctest_zran_15249998_False.gz'
nelems = 15249998, niters = 1000, seed = 497514082, use_mmap = False

    @pytest.mark.slow_test
    def test_seek_then_read_block(testfile, nelems, niters, seed, use_mmap):
        for no_fds in (True, False):
>           ctest_zran.test_seek_then_read_block(
                testfile, no_fds, nelems, niters, seed, use_mmap
            )

../../BUILDROOT/python-indexed_gzip-1.7.1-5.fc39.x86_64/usr/lib64/python3.12/site-packages/indexed_gzip/tests/test_zran.py:91:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
indexed_gzip/tests/ctest_zran.pyx:817: in indexed_gzip.tests.ctest_zran.test_seek_then_read_block
    with open(testfile, 'rb') as pyfid:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   nelemsp = nbytes / 8.
E   TypeError: 'float' object cannot be interpreted as an integer

indexed_gzip/tests/ctest_zran.pyx:858: TypeError
---------------------------- Captured stdout setup -----------------------------
Seed for random number generator: 497514082
----------------------------- Captured stdout call -----------------------------
0 / 1000: reading 3257996 elements from 9244320 ...
```

The obvious issue here is that Cython 3 [aligns division semantics with Python 3](https://cython.readthedocs.io/en/latest/src/changes.html#improved-fidelity-to-python-semantics), so `x / y` always produces a Python `float` even where `x` and `y` are both Python integers. This could be rewritten as `x // y` to produce an integer.

I could offer a PR for the particular cases showing up in the test failures, but it might be best for someone more familiar with the code to review uses of the `/` operator throughout the various `.pyx` files.


Discussion / hints:

Fix patch:
diff --git a/.ci/build_dev_indexed_gzip.sh b/.ci/build_dev_indexed_gzip.sh
index 07548fd1..bbf5778f 100755
--- a/.ci/build_dev_indexed_gzip.sh
+++ b/.ci/build_dev_indexed_gzip.sh
@@ -13,4 +13,4 @@ source $thisdir/activate_env.sh "$envdir"
 # modules - see setup.py
 export INDEXED_GZIP_TESTING=1
 
-python setup.py develop
+pip install -e .
diff --git a/.ci/build_wheels.sh b/.ci/build_wheels.sh
index d50071d9..f6aa1c6b 100755
--- a/.ci/build_wheels.sh
+++ b/.ci/build_wheels.sh
@@ -41,12 +41,5 @@ chmod a+x testcmd
 
 export CIBW_TEST_COMMAND="bash {project}/testcmd {project}"
 
-# cibuildwheel 2 doesn't support py27, but cibuildwheel
-# 1 doesn't suppport py310. So we do two builds.
 python -m pip install cibuildwheel
 python -m cibuildwheel --output-dir ./dist
-
-# Disable py27 builds on windows
-export CIBW_BUILD="cp27-mac* cp27-*linux*"
-python -m pip install cibuildwheel==1.*
-python -m cibuildwheel --allow-empty --output-dir ./dist
diff --git a/.ci/install_32bit_dependencies.sh b/.ci/install_32bit_dependencies.sh
index 5a381e49..8e6979cd 100755
--- a/.ci/install_32bit_dependencies.sh
+++ b/.ci/install_32bit_dependencies.sh
@@ -5,21 +5,25 @@
 export DEBIAN_FRONTEND="noninteractive"
 export TZ="Europe/London"
 
-apt-get install -y                 \
-        build-essential            \
-        software-properties-common \
-        zlib1g                     \
+apt-get install -y      \
+        build-essential \
+        wget gpg        \
+        zlib1g          \
         zlib1g-dev
 
-if [ "$PYTHON_VERSION" == "2.7" ]; then
-  PACKAGES="python-pip python-virtualenv"
-else
-  PACKAGES="python3-pip python${PYTHON_VERSION}-venv"
-fi
+# software-properties-common (which provides the
+# add-apt-repository command) cannot be installed on
+# i386/20.04 due to broken package specifications.
+# So here we add the deadsnakes repository by hand.
+echo "deb https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/ focal main"        > /etc/apt/sources.list.d//deadsnakes-ubuntu-ppa-focal.list
+echo "# deb-src https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu/ focal main" >> /etc/apt/sources.list.d//deadsnakes-ubuntu-ppa-focal.list
+
+wget -O - "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xF23C5A6CF475977595C89F51BA6932366A755776" | \
+  gpg --batch --yes --dearmor --output "/etc/apt/trusted.gpg.d/deadsnakes-ubuntu-ppa.gpg"
 
-add-apt-repository -y ppa:deadsnakes/ppa
 apt-get update     -y
-apt-get install    -y               \
-        python"$PYTHON_VERSION"     \
-        python"$PYTHON_VERSION"-dev \
-        $PACKAGES
+apt-get install    -y                \
+        python"$PYTHON_VERSION"      \
+        python"$PYTHON_VERSION"-dev  \
+        python${PYTHON_VERSION}-venv \
+        python3-pip
diff --git a/.github/workflows/master.yaml b/.github/workflows/master.yaml
index 7055c883..165cb498 100644
--- a/.github/workflows/master.yaml
+++ b/.github/workflows/master.yaml
@@ -23,11 +23,8 @@ jobs:
     strategy:
       matrix:
         os:             ["ubuntu-latest", "macos-latest", "windows-latest"]
-        python-version: ["2.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
         extra-args:     ["", "--concat"]
-        exclude:
-          - os:             windows-latest
-            python-version: 2.7
 
     env:
       PLATFORM:       ${{ matrix.os }}
@@ -101,7 +98,7 @@ jobs:
 
     env:
       PLATFORM:       ${{ matrix.os }}
-      PYTHON_VERSION: 3.8
+      PYTHON_VERSION: 3.10
       TEST_SUITE:     ${{ matrix.test-suite }}
       EXTRA_ARGS:     ${{ matrix.extra-args }}
       NELEMS:         ${{ matrix.nelems }}
@@ -130,46 +127,6 @@ jobs:
         run:  bash  ./.ci/run_tests.sh "$ENV_DIR"
 
 
-  # Nibabel integration run on all OSes,
-  # and different versions of nibabel
-  test-nibabel-integration:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os:      ["ubuntu-latest", "macos-latest", "windows-latest"]
-        nibabel: ["2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "3.0", "3.1", "3.2"]
-    env:
-      PLATFORM:       ${{ matrix.os }}
-      PYTHON_VERSION: "3.8"
-      TEST_SUITE:     nibabel_test
-      NIBABEL:        ${{ matrix.nibabel }}
-      # earlier versions of nibabel require
-      # an older version of numpy
-      NUMPY:          ${{ (matrix.nibabel <= 2.3) && '1.17' || '1.20' }}
-      ENV_DIR:        ./test.env
-
-    steps:
-      - uses: actions/checkout@v2
-      - name: Set up Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: "3.8"
-          architecture:   x64
-      - name: Create test environment
-        run:  bash ./.ci/create_test_env.sh "$ENV_DIR"
-      - name: Log test environment
-        run: |
-          source ./.ci/activate_env.sh "$ENV_DIR"
-          python -V
-          pip freeze
-      - name: Download zlib
-        if:   ${{ matrix.os == 'windows-latest' }}
-        run:  bash ./.ci/download_zlib.sh
-      - name: Build indexed_gzip
-        run:  bash ./.ci/build_dev_indexed_gzip.sh "$ENV_DIR"
-      - name: Run tests
-        run:  bash  ./.ci/run_tests.sh "$ENV_DIR"
-
   # zran tests run only on ubuntu
   test-zran:
     runs-on: "ubuntu-latest"
@@ -282,10 +239,10 @@ jobs:
   # Tests on 32 bit platform
   test-32bit:
     runs-on: ubuntu-latest
-    container: i386/ubuntu:18.04
+    container: i386/ubuntu:20.04
     strategy:
       matrix:
-        python-version: ["2.7", "3.8"]
+        python-version: "3.9"
         extra-args:     ["", "--concat"]
 
     env:
diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml
index 684088e1..8d2a6d14 100644
--- a/.github/workflows/pull_request.yaml
+++ b/.github/workflows/pull_request.yaml
@@ -14,11 +14,8 @@ jobs:
     strategy:
       matrix:
         os:             ["ubuntu-latest", "macos-latest", "windows-latest"]
-        python-version: ["2.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
         extra-args:     ["", "--concat"]
-        exclude:
-          - os:             windows-latest
-            python-version: 2.7
 
     env:
       PLATFORM:       ${{ matrix.os }}
@@ -53,10 +50,10 @@ jobs:
   # Quick tests on 32 bit platform
   test-32bit:
     runs-on: ubuntu-latest
-    container: i386/ubuntu:18.04
+    container: i386/ubuntu:20.04
     strategy:
       matrix:
-        python-version: ["2.7", "3.8"]
+        python-version: ["3.9"]
         extra-args:     ["", "--concat"]
 
     env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be307e54..98c133ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
 # `indexed_gzip` changelog
 
 
+## 1.8.0 (July 24th 2023)
+
+
+* Compatibility fixes for Python 3.12 and Cython 3.0.0 (#126, #127).
+* Removed support for Python 2.7 (#127).
+
+
 ## 1.7.1 (March 31st 2023)
 
 
@@ -8,7 +15,6 @@
   which do not implement `fileno()` (#118).
 
 
-
 ## 1.7.0 (September 12th 2022)
 
 
diff --git a/MANIFEST.in b/MANIFEST.in
index 6711a299..eb43e9b2 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,8 +1,4 @@
-include                        LICENSE
-include                        README.md
-include                        conftest.py
-recursive-include indexed_gzip *.py
 recursive-include indexed_gzip *.pyx
 recursive-include indexed_gzip *.pxd
 recursive-include indexed_gzip *.c
-recursive-include indexed_gzip *.h
+recursive-include indexed_gzip *.h
\ No newline at end of file
diff --git a/README.md b/README.md
index 99a61cef..4aba1bee 100644
--- a/README.md
+++ b/README.md
@@ -310,6 +310,7 @@ Many thanks to the following contributors (listed chronologically):
  - Alexander Gorban (@alexgorban) Fix memory leak (#82, #83).
  - Maximilian Knespel (@mxmlnkn) Change default read buffer size to
    improve performance (#90).
+ - Ben Beasley (@musicinmybrain) Python 3.12 compatibility (#126).
 
 
 ## License
diff --git a/compat/stdint.h b/compat/stdint.h
deleted file mode 100644
index 7177fbce..00000000
--- a/compat/stdint.h
+++ /dev/null
@@ -1,247 +0,0 @@
-﻿// ISO C9x  compliant stdint.h for Microsoft Visual Studio
-// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
-// 
-//  Copyright (c) 2006-2008 Alexander Chemeris
-// 
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-// 
-//   1. Redistributions of source code must retain the above copyright notice,
-//      this list of conditions and the following disclaimer.
-// 
-//   2. Redistributions in binary form must reproduce the above copyright
-//      notice, this list of conditions and the following disclaimer in the
-//      documentation and/or other materials provided with the distribution.
-// 
-//   3. The name of the author may be used to endorse or promote products
-//      derived from this software without specific prior written permission.
-// 
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// 
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef _MSC_VER // [
-#error "Use this header only with Microsoft Visual C++ compilers!"
-#endif // _MSC_VER ]
-
-#ifndef _MSC_STDINT_H_ // [
-#define _MSC_STDINT_H_
-
-#if _MSC_VER > 1000
-#pragma once
-#endif
-
-#include <limits.h>
-
-// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
-// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
-// or compiler give many errors like this:
-//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
-#ifdef __cplusplus
-extern "C" {
-#endif
-#  include <wchar.h>
-#ifdef __cplusplus
-}
-#endif
-
-// Define _W64 macros to mark types changing their size, like intptr_t.
-#ifndef _W64
-#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
-#     define _W64 __w64
-#  else
-#     define _W64
-#  endif
-#endif
-
-
-// 7.18.1 Integer types
-
-// 7.18.1.1 Exact-width integer types
-
-// Visual Studio 6 and Embedded Visual C++ 4 doesn't
-// realize that, e.g. char has the same size as __int8
-// so we give up on __intX for them.
-#if (_MSC_VER < 1300)
-   typedef signed char       int8_t;
-   typedef signed short      int16_t;
-   typedef signed int        int32_t;
-   typedef unsigned char     uint8_t;
-   typedef unsigned short    uint16_t;
-   typedef unsigned int      uint32_t;
-#else
-   typedef signed __int8     int8_t;
-   typedef signed __int16    int16_t;
-   typedef signed __int32    int32_t;
-   typedef unsigned __int8   uint8_t;
-   typedef unsigned __int16  uint16_t;
-   typedef unsigned __int32  uint32_t;
-#endif
-typedef signed __int64       int64_t;
-typedef unsigned __int64     uint64_t;
-
-
-// 7.18.1.2 Minimum-width integer types
-typedef int8_t    int_least8_t;
-typedef int16_t   int_least16_t;
-typedef int32_t   int_least32_t;
-typedef int64_t   int_least64_t;
-typedef uint8_t   uint_least8_t;
-typedef uint16_t  uint_least16_t;
-typedef uint32_t  uint_least32_t;
-typedef uint64_t  uint_least64_t;
-
-// 7.18.1.3 Fastest minimum-width integer types
-typedef int8_t    int_fast8_t;
-typedef int16_t   int_fast16_t;
-typedef int32_t   int_fast32_t;
-typedef int64_t   int_fast64_t;
-typedef uint8_t   uint_fast8_t;
-typedef uint16_t  uint_fast16_t;
-typedef uint32_t  uint_fast32_t;
-typedef uint64_t  uint_fast64_t;
-
-// 7.18.1.4 Integer types capable of holding object pointers
-#ifdef _WIN64 // [
-   typedef signed __int64    intptr_t;
-   typedef unsigned __int64  uintptr_t;
-#else // _WIN64 ][
-   typedef _W64 signed int   intptr_t;
-   typedef _W64 unsigned int uintptr_t;
-#endif // _WIN64 ]
-
-// 7.18.1.5 Greatest-width integer types
-typedef int64_t   intmax_t;
-typedef uint64_t  uintmax_t;
-
-
-// 7.18.2 Limits of specified-width integer types
-
-#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
-
-// 7.18.2.1 Limits of exact-width integer types
-#define INT8_MIN     ((int8_t)_I8_MIN)
-#define INT8_MAX     _I8_MAX
-#define INT16_MIN    ((int16_t)_I16_MIN)
-#define INT16_MAX    _I16_MAX
-#define INT32_MIN    ((int32_t)_I32_MIN)
-#define INT32_MAX    _I32_MAX
-#define INT64_MIN    ((int64_t)_I64_MIN)
-#define INT64_MAX    _I64_MAX
-#define UINT8_MAX    _UI8_MAX
-#define UINT16_MAX   _UI16_MAX
-#define UINT32_MAX   _UI32_MAX
-#define UINT64_MAX   _UI64_MAX
-
-// 7.18.2.2 Limits of minimum-width integer types
-#define INT_LEAST8_MIN    INT8_MIN
-#define INT_LEAST8_MAX    INT8_MAX
-#define INT_LEAST16_MIN   INT16_MIN
-#define INT_LEAST16_MAX   INT16_MAX
-#define INT_LEAST32_MIN   INT32_MIN
-#define INT_LEAST32_MAX   INT32_MAX
-#define INT_LEAST64_MIN   INT64_MIN
-#define INT_LEAST64_MAX   INT64_MAX
-#define UINT_LEAST8_MAX   UINT8_MAX
-#define UINT_LEAST16_MAX  UINT16_MAX
-#define UINT_LEAST32_MAX  UINT32_MAX
-#define UINT_LEAST64_MAX  UINT64_MAX
-
-// 7.18.2.3 Limits of fastest minimum-width integer types
-#define INT_FAST8_MIN    INT8_MIN
-#define INT_FAST8_MAX    INT8_MAX
-#define INT_FAST16_MIN   INT16_MIN
-#define INT_FAST16_MAX   INT16_MAX
-#define INT_FAST32_MIN   INT32_MIN
-#define INT_FAST32_MAX   INT32_MAX
-#define INT_FAST64_MIN   INT64_MIN
-#define INT_FAST64_MAX   INT64_MAX
-#define UINT_FAST8_MAX   UINT8_MAX
-#define UINT_FAST16_MAX  UINT16_MAX
-#define UINT_FAST32_MAX  UINT32_MAX
-#define UINT_FAST64_MAX  UINT64_MAX
-
-// 7.18.2.4 Limits of integer types capable of holding object pointers
-#ifdef _WIN64 // [
-#  define INTPTR_MIN   INT64_MIN
-#  define INTPTR_MAX   INT64_MAX
-#  define UINTPTR_MAX  UINT64_MAX
-#else // _WIN64 ][
-#  define INTPTR_MIN   INT32_MIN
-#  define INTPTR_MAX   INT32_MAX
-#  define UINTPTR_MAX  UINT32_MAX
-#endif // _WIN64 ]
-
-// 7.18.2.5 Limits of greatest-width integer types
-#define INTMAX_MIN   INT64_MIN
-#define INTMAX_MAX   INT64_MAX
-#define UINTMAX_MAX  UINT64_MAX
-
-// 7.18.3 Limits of other integer types
-
-#ifdef _WIN64 // [
-#  define PTRDIFF_MIN  _I64_MIN
-#  define PTRDIFF_MAX  _I64_MAX
-#else  // _WIN64 ][
-#  define PTRDIFF_MIN  _I32_MIN
-#  define PTRDIFF_MAX  _I32_MAX
-#endif  // _WIN64 ]
-
-#define SIG_ATOMIC_MIN  INT_MIN
-#define SIG_ATOMIC_MAX  INT_MAX
-
-#ifndef SIZE_MAX // [
-#  ifdef _WIN64 // [
-#     define SIZE_MAX  _UI64_MAX
-#  else // _WIN64 ][
-#     define SIZE_MAX  _UI32_MAX
-#  endif // _WIN64 ]
-#endif // SIZE_MAX ]
-
-// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
-#ifndef WCHAR_MIN // [
-#  define WCHAR_MIN  0
-#endif  // WCHAR_MIN ]
-#ifndef WCHAR_MAX // [
-#  define WCHAR_MAX  _UI16_MAX
-#endif  // WCHAR_MAX ]
-
-#define WINT_MIN  0
-#define WINT_MAX  _UI16_MAX
-
-#endif // __STDC_LIMIT_MACROS ]
-
-
-// 7.18.4 Limits of other integer types
-
-#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
-
-// 7.18.4.1 Macros for minimum-width integer constants
-
-#define INT8_C(val)  val##i8
-#define INT16_C(val) val##i16
-#define INT32_C(val) val##i32
-#define INT64_C(val) val##i64
-
-#define UINT8_C(val)  val##ui8
-#define UINT16_C(val) val##ui16
-#define UINT32_C(val) val##ui32
-#define UINT64_C(val) val##ui64
-
-// 7.18.4.2 Macros for greatest-width integer constants
-#define INTMAX_C   INT64_C
-#define UINTMAX_C  UINT64_C
-
-#endif // __STDC_CONSTANT_MACROS ]
-
-
-#endif // _MSC_STDINT_H_ ]
diff --git a/indexed_gzip/__init__.py b/indexed_gzip/__init__.py
index 16712503..db925db4 100644
--- a/indexed_gzip/__init__.py
+++ b/indexed_gzip/__init__.py
@@ -19,4 +19,4 @@
 """
 
 
-__version__ = '1.7.1'
+__version__ = '1.8.0'
diff --git a/pyproject.toml b/pyproject.toml
index 80cc0357..5b4357c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,54 @@
 [build-system]
-requires = ["setuptools >= 40.8.0", "cython", "numpy"]
+requires      = ["setuptools >= 40.8.0", "cython", "numpy"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name            = "indexed_gzip"
+dynamic         = ["version"]
+description     = "Fast random access of gzip files in Python"
+readme          = {file = "README.md", content-type="text/markdown"}
+license         = {text = "zlib"}
+requires-python = ">=3"
+maintainers     = [{name = "Paul McCarthy", email = "pauldmccarthy@gmail.com"}]
+classifiers     = [
+    "Development Status :: 3 - Alpha",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: zlib/libpng License",
+    "Programming Language :: C",
+    "Programming Language :: Cython",
+    "Programming Language :: Python :: 2",
+    "Programming Language :: Python :: 3",
+    "Topic :: System :: Archiving :: Compression",
+]
+
+[project.urls]
+"Repository" = "https://github.com/pauldmccarthy/indexed_gzip"
+
+[project.optional-dependencies]
+test = [
+  "pytest",
+  "numpy",
+  "nibabel",
+  "coverage",
+  "pytest-cov"
+]
+
+[tool.setuptools.packages.find]
+include = ["indexed_gzip*"]
+
+[tool.setuptools.dynamic]
+version = {attr = "indexed_gzip.__version__"}
+
 
 [tool.pytest.ini_options]
 testpaths = ["indexed_gzip/tests"]
-addopts = "-v"
+addopts = "-v --cov=indexed_gzip"
 markers = [
     "zran_test:         Test the zran.c library",
     "indexed_gzip_test: Test the indexed_gzip library",
     "nibabel_test:      Test interaction between indexed_gzip and nibabel/numpy",
     "slow_test:         Test which takes a long time"]
+
+[tool.coverage.run]
+include = ["indexed_gzip/*"]
+omit    = ["indexed_gzip/tests/*"]
\ No newline at end of file
diff --git a/setup.py b/setup.py
index fbcea2ee..e43112d1 100644
--- a/setup.py
+++ b/setup.py
@@ -76,22 +76,17 @@ def run(self):
 
 
 # Platform information
-python2   = sys.version_info[0] == 2
-noc99     = python2 or (sys.version_info[0] == 3 and sys.version_info[1] <= 4)
+noc99     = sys.version_info[0] == 3 and sys.version_info[1] <= 4
 windows   = sys.platform.startswith("win")
 testing   = 'INDEXED_GZIP_TESTING' in os.environ
+thisdir   = op.dirname(__file__)
 
 # compile ZLIB source?
 ZLIB_HOME = os.environ.get("ZLIB_HOME", None)
-
-# Load README description
-readme = op.join(op.dirname(__file__), 'README.md')
-if python2:
-    openreadme = ft.partial(open, readme, 'rt')
-else:
-    openreadme = ft.partial(open, readme, 'rt', encoding='utf-8')
-with openreadme() as f:
-    readme = f.read().strip()
+# setuptools may complain about
+# absolute paths in some circumstances
+if ZLIB_HOME is not None:
+    ZLIB_HOME = op.relpath(ZLIB_HOME, thisdir)
 
 # If cython is present, we'll compile
 # the pyx files from scratch. Otherwise,
@@ -145,11 +140,6 @@ def run(self):
     if ZLIB_HOME is None:
         libs.append('zlib')
 
-    # For stdint.h which is not included in the old Visual C
-    # compiler used for Python 2
-    if python2:
-        include_dirs.append('compat')
-
     # Some C functions might not be present when compiling against
     # older versions of python
     if noc99:
@@ -219,46 +209,8 @@ def run(self):
 if have_cython:
     extensions = cythonize(extensions, compiler_directives=compiler_directives)
 
-
-# find the version number
-def readVersion():
-    version  = {}
-    initfile = op.join(op.dirname(__file__), 'indexed_gzip', '__init__.py')
-    with open(initfile, 'rt') as f:
-        for line in f:
-            if line.startswith('__version__'):
-                exec(line, version)
-                break
-    return version.get('__version__')
-
-
 setup(
     name='indexed_gzip',
-    packages=['indexed_gzip', 'indexed_gzip.tests'],
-    version=readVersion(),
-    author='Paul McCarthy',
-    author_email='pauldmccarthy@gmail.com',
-    description='Fast random access of gzip files in Python',
-    long_description=readme,
-    long_description_content_type='text/markdown',
-    url='https://github.com/pauldmccarthy/indexed_gzip',
-    license='zlib',
-
-    classifiers=[
-        'Development Status :: 3 - Alpha',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: zlib/libpng License',
-        'Programming Language :: C',
-        'Programming Language :: Cython',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 3',
-        'Topic :: System :: Archiving :: Compression',
-    ],
-
     cmdclass={'clean' : Clean},
-
     ext_modules=extensions,
-
-    tests_require=['pytest', 'numpy', 'nibabel'],
-    test_suite='tests',
 )


Test patch:
diff --git a/.ci/build_test_wheel.sh b/.ci/build_test_wheel.sh
index d2c4cd11..e0b696b2 100755
--- a/.ci/build_test_wheel.sh
+++ b/.ci/build_test_wheel.sh
@@ -9,8 +9,8 @@ thisdir=$(cd $(dirname "$0") && pwd)
 
 source $thisdir/activate_env.sh "$envdir"
 
-pip install twine
+pip install --upgrade pip wheel setuptools twine build
 
-python setup.py bdist_wheel
+python -m build
 
 twine check dist/*
diff --git a/.ci/create_test_env.sh b/.ci/create_test_env.sh
index 791b08c8..a53fc360 100755
--- a/.ci/create_test_env.sh
+++ b/.ci/create_test_env.sh
@@ -28,14 +28,12 @@ if [[ "$USING_OS_PYTHON" != "1" ]]; then
   pip install virtualenv
 fi
 
-if [[ "$PYTHON_VERSION" == "2.7" ]]; then
-  virtualenv "$envdir"
-elif [[ "$USING_OS_PYTHON" == "1" ]]; then
+if [[ "$USING_OS_PYTHON" == "1" ]]; then
   python"$PYTHON_VERSION" -m venv "$envdir"
 else
   python -m venv "$envdir"
 fi
 
 source $thisdir/activate_env.sh "$envdir"
-pip install wheel setuptools
-pip install --prefer-binary cython pytest "$NUMPY" "$NIBABEL"
+pip install wheel setuptools build
+pip install --prefer-binary cython pytest coverage pytest-cov "$NUMPY" "$NIBABEL"
diff --git a/indexed_gzip/tests/ctest_indexed_gzip.pyx b/indexed_gzip/tests/ctest_indexed_gzip.pyx
index 1c22848c..d9c59de6 100644
--- a/indexed_gzip/tests/ctest_indexed_gzip.pyx
+++ b/indexed_gzip/tests/ctest_indexed_gzip.pyx
@@ -1158,7 +1158,7 @@ def test_multiproc_serialise():
 
         gzf = igzip.IndexedGzipFile(fname)
 
-        size    = len(data) / 16
+        size    = int(len(data) / 16)
         offsets = np.arange(0, len(data), size)
         func    = ft.partial(_mpfunc, gzf, size * 4)
 
diff --git a/indexed_gzip/tests/ctest_zran.pyx b/indexed_gzip/tests/ctest_zran.pyx
index cac4f12d..55732cf2 100644
--- a/indexed_gzip/tests/ctest_zran.pyx
+++ b/indexed_gzip/tests/ctest_zran.pyx
@@ -796,7 +796,7 @@ def test_read_all(testfile, no_fds, nelems, use_mmap):
 
         zran.zran_free(&index)
 
-    nelemsp = nbytes / 8.
+    nelemsp = int(nbytes / 8)
     data    = np.PyArray_SimpleNewFromData(1, &nelemsp,  np.NPY_UINT64, buffer)
 
     assert check_data_valid(data, 0)
@@ -855,7 +855,7 @@ def test_seek_then_read_block(testfile, no_fds, nelems, niters, seed, use_mmap):
                 print('  should be: {}'.format((se + readelems) * 8))
                 raise
 
-            nelemsp = nbytes / 8.
+            nelemsp = int(nbytes / 8)
             data    = np.PyArray_SimpleNewFromData(1, &nelemsp,  np.NPY_UINT64, buffer)
 
             assert check_data_valid(data, se, se + readelems)