Skip to content

Commit 4e7582b

Browse files
authored
Drop support for Python 2.7, migrate to pyproject.toml (#445)
* Drop support for Python 2.7 * Require pkgconfig >= 1.5 * Migrate to `pyproject.toml` * Require Python >= 3.7
1 parent 19af2b3 commit 4e7582b

23 files changed

+134
-250
lines changed

Diff for: README.rst

+10-4
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ binary extension for your Python.
3131

3232
If it is unable to build a binary extension, it will use cffi ABI mode
3333
instead and only needs the libvips shared library. This takes longer to
34-
start up and is typically ~20% slower in execution. You can find out how
35-
pyvips installed with ``pip show pyvips``.
34+
start up and is typically ~20% slower in execution. You can find out if
35+
API mode is being used with:
36+
37+
.. code-block:: python
38+
39+
import pyvips
40+
41+
print(pyvips.API_mode)
3642
3743
This binding passes the vips test suite cleanly and with no leaks under
38-
python2.7 - python3.11, pypy and pypy3 on Windows, macOS and Linux.
44+
python3 and pypy3 on Windows, macOS and Linux.
3945

4046
How it works
4147
------------
@@ -246,7 +252,7 @@ Update pypi package:
246252

247253
.. code-block:: shell
248254
249-
$ python3 setup.py sdist
255+
$ python3 -m build --sdist
250256
$ twine upload --repository pyvips dist/*
251257
$ git tag -a v2.2.0 -m "as uploaded to pypi"
252258
$ git push origin v2.2.0

Diff for: pyproject.toml

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
[build-system]
2+
requires = [
3+
# First version of setuptools to support pyproject.toml configuration
4+
"setuptools>=61.0.0",
5+
"wheel",
6+
# Must be kept in sync with `project.dependencies`
7+
"cffi>=1.0.0",
8+
"pkgconfig>=1.5",
9+
]
10+
build-backend = "setuptools.build_meta"
11+
12+
[project]
13+
name = "pyvips"
14+
authors = [
15+
{name = "John Cupitt", email = "[email protected]"},
16+
]
17+
description = "binding for the libvips image processing library"
18+
readme = "README.rst"
19+
keywords = [
20+
"image processing",
21+
]
22+
license = {text = "MIT"}
23+
requires-python = ">=3.7"
24+
classifiers = [
25+
"Development Status :: 5 - Production/Stable",
26+
"Environment :: Console",
27+
"Intended Audience :: Developers",
28+
"Intended Audience :: Science/Research",
29+
"Topic :: Multimedia :: Graphics",
30+
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
31+
"License :: OSI Approved :: MIT License",
32+
"Programming Language :: Python",
33+
"Programming Language :: Python :: 3",
34+
"Programming Language :: Python :: 3 :: Only",
35+
"Programming Language :: Python :: 3.7",
36+
"Programming Language :: Python :: 3.8",
37+
"Programming Language :: Python :: 3.9",
38+
"Programming Language :: Python :: 3.10",
39+
"Programming Language :: Python :: 3.11",
40+
"Programming Language :: Python :: 3.12",
41+
"Programming Language :: Python :: Implementation :: CPython",
42+
"Programming Language :: Python :: Implementation :: PyPy",
43+
]
44+
dependencies = [
45+
# Must be kept in sync with `build-system.requires`
46+
"cffi>=1.0.0",
47+
]
48+
dynamic = [
49+
"version",
50+
]
51+
52+
[project.urls]
53+
changelog = "https://github.com/libvips/pyvips/blob/master/CHANGELOG.rst"
54+
documentation = "https://libvips.github.io/pyvips/"
55+
funding = "https://opencollective.com/libvips"
56+
homepage = "https://github.com/libvips/pyvips"
57+
issues = "https://github.com/libvips/pyvips/issues"
58+
source = "https://github.com/libvips/pyvips"
59+
60+
[tool.setuptools]
61+
# We try to compile as part of install, so we can't run in a ZIP
62+
zip-safe = false
63+
include-package-data = false
64+
65+
[tool.setuptools.dynamic]
66+
version = {attr = "pyvips.version.__version__"}
67+
68+
[tool.setuptools.packages.find]
69+
exclude = [
70+
"doc*",
71+
"examples*",
72+
"tests*",
73+
]
74+
75+
[project.optional-dependencies]
76+
# All the following are used for our own testing
77+
tox = ["tox"]
78+
test = [
79+
"pytest",
80+
"pyperf",
81+
]
82+
sdist = ["build"]
83+
doc = [
84+
"sphinx",
85+
"sphinx_rtd_theme",
86+
]
87+
88+
[tool.pytest.ini_options]
89+
norecursedirs = ["tests/helpers"]

Diff for: pyvips/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# user code can override this null handler
1111
logger.addHandler(logging.NullHandler())
1212

13-
# pull in our module version number, see also setup.py
13+
# pull in our module version number
1414
from .version import __version__
1515

1616
# try to import our binary interface ... if that works, we are in API mode

Diff for: pyvips/error.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
# errors from libvips
22

3-
import sys
43
import logging
54

5+
from pathlib import Path
66
from pyvips import ffi, vips_lib, glib_lib
77

88
logger = logging.getLogger(__name__)
99

10-
_is_PY3 = sys.version_info[0] == 3
11-
12-
if _is_PY3:
13-
# pathlib is not part of Python 2 stdlib
14-
from pathlib import Path
15-
text_type = str, Path
16-
byte_type = bytes
17-
else:
18-
text_type = unicode # noqa: F821
19-
byte_type = str
20-
2110

2211
def _to_bytes(x):
2312
"""Convert to a byte string.
@@ -26,7 +15,7 @@ def _to_bytes(x):
2615
byte string. You must call this on strings you pass to libvips.
2716
2817
"""
29-
if isinstance(x, text_type):
18+
if isinstance(x, (str, Path)):
3019
# n.b. str also converts pathlib.Path objects
3120
x = str(x).encode('utf-8')
3221

@@ -44,7 +33,7 @@ def _to_string(x):
4433
x = 'NULL'
4534
else:
4635
x = ffi.string(x)
47-
if isinstance(x, byte_type):
36+
if isinstance(x, bytes):
4837
x = x.decode('utf-8')
4938

5039
return x

Diff for: pyvips/gobject.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import logging
42

53
import pyvips

Diff for: pyvips/gvalue.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
from __future__ import division
2-
from __future__ import unicode_literals
3-
41
import logging
52
import numbers
6-
import sys
73

84
import pyvips
95
from pyvips import ffi, vips_lib, gobject_lib, \
@@ -12,8 +8,6 @@
128

139
logger = logging.getLogger(__name__)
1410

15-
_is_PY2 = sys.version_info.major == 2
16-
1711

1812
class GValue(object):
1913

@@ -103,7 +97,7 @@ def to_enum(gtype, value):
10397
10498
"""
10599

106-
if isinstance(value, basestring if _is_PY2 else str): # noqa: F821
100+
if isinstance(value, str):
107101
enum_value = vips_lib.vips_enum_from_nick(b'pyvips', gtype,
108102
_to_bytes(value))
109103
if enum_value < 0:
@@ -132,7 +126,7 @@ def to_flag(gtype, value):
132126
133127
"""
134128

135-
if isinstance(value, basestring if _is_PY2 else str): # noqa: F821
129+
if isinstance(value, str):
136130
flag_value = vips_lib.vips_flags_from_nick(b'pyvips', gtype,
137131
_to_bytes(value))
138132
if flag_value < 0:

Diff for: pyvips/pyvips_build.py

+1-13
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,7 @@
99
if pkgconfig.installed('vips', '< 8.2'):
1010
raise Exception('pkg-config "vips" is too old -- need libvips 8.2 or later')
1111

12-
# pkgconfig 1.5+ has modversion ... otherwise, use a small shim
13-
try:
14-
from pkgconfig import modversion
15-
except ImportError:
16-
def modversion(package):
17-
# will need updating once we hit 8.20 :(
18-
for i in range(20, 3, -1):
19-
if pkgconfig.installed(package, '>= 8.' + str(i)):
20-
# be careful micro version is always set to 0
21-
return '8.' + str(i) + '.0'
22-
return '8.2.0'
23-
24-
major, minor, micro = [int(s) for s in modversion('vips').split('.')]
12+
major, minor, micro = [int(s) for s in pkgconfig.modversion('vips').split('.')]
2513

2614
ffibuilder = FFI()
2715

Diff for: pyvips/vconnection.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import logging
42

53
import pyvips

Diff for: pyvips/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# this is execfile()d into setup.py imported into __init__.py
1+
# this is used in pyproject.toml and imported into __init__.py
22
__version__ = '2.2.3'
33

44
__all__ = ['__version__']

Diff for: pyvips/vimage.py

+1-30
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# wrap VipsImage
22

3-
from __future__ import division
4-
53
import numbers
64
import struct
75

@@ -83,26 +81,6 @@ def _run_cmplx(fn, image):
8381
return image
8482

8583

86-
# https://stackoverflow.com/a/22409540/1480019
87-
# https://github.com/benjaminp/six/blob/33b584b2c551548021adb92a028ceaf892deb5be/six.py#L846-L861
88-
def _with_metaclass(metaclass):
89-
"""Class decorator for creating a class with a metaclass."""
90-
def wrapper(cls):
91-
orig_vars = cls.__dict__.copy()
92-
slots = orig_vars.get('__slots__')
93-
if slots is not None:
94-
if isinstance(slots, str):
95-
slots = [slots]
96-
for slots_var in slots:
97-
orig_vars.pop(slots_var)
98-
orig_vars.pop('__dict__', None)
99-
orig_vars.pop('__weakref__', None)
100-
if hasattr(cls, '__qualname__'):
101-
orig_vars['__qualname__'] = cls.__qualname__
102-
return metaclass(cls.__name__, cls.__bases__, orig_vars)
103-
return wrapper
104-
105-
10684
# decorator to set docstring
10785
def _add_doc(name):
10886
try:
@@ -258,8 +236,7 @@ def call_function(*args, **kwargs):
258236
return call_function
259237

260238

261-
@_with_metaclass(ImageType)
262-
class Image(pyvips.VipsObject):
239+
class Image(pyvips.VipsObject, metaclass=ImageType):
263240
"""Wrap a VipsImage object.
264241
265242
"""
@@ -610,12 +587,6 @@ def new_from_memory(data, width, height, bands, format):
610587
"""
611588
format_value = GValue.to_enum(GValue.format_type, format)
612589
pointer = ffi.from_buffer(data)
613-
# py3:
614-
# - memoryview has .nbytes for number of bytes in object
615-
# - len() returns number of elements in top array
616-
# py2:
617-
# - buffer has no nbytes member
618-
# - but len() gives number of bytes in object
619590
nbytes = data.nbytes if hasattr(data, 'nbytes') else len(data)
620591
vi = vips_lib.vips_image_new_from_memory(pointer,
621592
nbytes,

Diff for: pyvips/vinterpolate.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import pyvips
42
from pyvips import ffi, vips_lib, Error, _to_bytes
53

Diff for: pyvips/vobject.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# wrap VipsObject
22

3-
from __future__ import division
4-
53
import logging
64

75
import pyvips

Diff for: pyvips/voperation.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division, print_function
2-
31
import logging
42

53
import pyvips

Diff for: pyvips/vregion.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# wrap VipsRegion
22

3-
from __future__ import division
4-
53
import pyvips
64
from pyvips import ffi, glib_lib, vips_lib, Error, at_least_libvips
75

Diff for: pyvips/vsource.py

-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import logging
42

53
import pyvips
@@ -81,12 +79,6 @@ def new_from_memory(data):
8179

8280
# logger.debug('VipsSource.new_from_memory:')
8381

84-
# py3:
85-
# - memoryview has .nbytes for number of bytes in object
86-
# - len() returns number of elements in top array
87-
# py2:
88-
# - buffer has no nbytes member
89-
# - but len() gives number of bytes in object
9082
start = ffi.from_buffer(data)
9183
nbytes = data.nbytes if hasattr(data, 'nbytes') else len(data)
9284

Diff for: pyvips/vsourcecustom.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import logging
42

53
import pyvips

Diff for: pyvips/vtarget.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import logging
42

53
import pyvips

Diff for: pyvips/vtargetcustom.py

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import division
2-
31
import logging
42

53
import pyvips
@@ -34,13 +32,7 @@ def on_write(self, handler):
3432
"""
3533

3634
def interface_handler(buf):
37-
bytes_written = handler(buf)
38-
# py2 will often return None for bytes_written ... replace with
39-
# the length of the string
40-
if bytes_written is None:
41-
bytes_written = len(buf)
42-
43-
return bytes_written
35+
return handler(buf)
4436

4537
self.signal_connect("write", interface_handler)
4638

Diff for: setup.cfg

-8
This file was deleted.

0 commit comments

Comments
 (0)