Skip to content

Commit

Permalink
Drop support for Python 2.7, migrate to pyproject.toml (#445)
Browse files Browse the repository at this point in the history
* Drop support for Python 2.7

* Require pkgconfig >= 1.5

* Migrate to `pyproject.toml`

* Require Python >= 3.7
  • Loading branch information
kleisauke authored Sep 21, 2024
1 parent 19af2b3 commit 4e7582b
Show file tree
Hide file tree
Showing 23 changed files with 134 additions and 250 deletions.
14 changes: 10 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@ binary extension for your Python.

If it is unable to build a binary extension, it will use cffi ABI mode
instead and only needs the libvips shared library. This takes longer to
start up and is typically ~20% slower in execution. You can find out how
pyvips installed with ``pip show pyvips``.
start up and is typically ~20% slower in execution. You can find out if
API mode is being used with:

.. code-block:: python
import pyvips
print(pyvips.API_mode)
This binding passes the vips test suite cleanly and with no leaks under
python2.7 - python3.11, pypy and pypy3 on Windows, macOS and Linux.
python3 and pypy3 on Windows, macOS and Linux.

How it works
------------
Expand Down Expand Up @@ -246,7 +252,7 @@ Update pypi package:

.. code-block:: shell
$ python3 setup.py sdist
$ python3 -m build --sdist
$ twine upload --repository pyvips dist/*
$ git tag -a v2.2.0 -m "as uploaded to pypi"
$ git push origin v2.2.0
Expand Down
89 changes: 89 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
[build-system]
requires = [
# First version of setuptools to support pyproject.toml configuration
"setuptools>=61.0.0",
"wheel",
# Must be kept in sync with `project.dependencies`
"cffi>=1.0.0",
"pkgconfig>=1.5",
]
build-backend = "setuptools.build_meta"

[project]
name = "pyvips"
authors = [
{name = "John Cupitt", email = "[email protected]"},
]
description = "binding for the libvips image processing library"
readme = "README.rst"
keywords = [
"image processing",
]
license = {text = "MIT"}
requires-python = ">=3.7"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Topic :: Multimedia :: Graphics",
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
# Must be kept in sync with `build-system.requires`
"cffi>=1.0.0",
]
dynamic = [
"version",
]

[project.urls]
changelog = "https://github.com/libvips/pyvips/blob/master/CHANGELOG.rst"
documentation = "https://libvips.github.io/pyvips/"
funding = "https://opencollective.com/libvips"
homepage = "https://github.com/libvips/pyvips"
issues = "https://github.com/libvips/pyvips/issues"
source = "https://github.com/libvips/pyvips"

[tool.setuptools]
# We try to compile as part of install, so we can't run in a ZIP
zip-safe = false
include-package-data = false

[tool.setuptools.dynamic]
version = {attr = "pyvips.version.__version__"}

[tool.setuptools.packages.find]
exclude = [
"doc*",
"examples*",
"tests*",
]

[project.optional-dependencies]
# All the following are used for our own testing
tox = ["tox"]
test = [
"pytest",
"pyperf",
]
sdist = ["build"]
doc = [
"sphinx",
"sphinx_rtd_theme",
]

[tool.pytest.ini_options]
norecursedirs = ["tests/helpers"]
2 changes: 1 addition & 1 deletion pyvips/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# user code can override this null handler
logger.addHandler(logging.NullHandler())

# pull in our module version number, see also setup.py
# pull in our module version number
from .version import __version__

# try to import our binary interface ... if that works, we are in API mode
Expand Down
17 changes: 3 additions & 14 deletions pyvips/error.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
# errors from libvips

import sys
import logging

from pathlib import Path
from pyvips import ffi, vips_lib, glib_lib

logger = logging.getLogger(__name__)

_is_PY3 = sys.version_info[0] == 3

if _is_PY3:
# pathlib is not part of Python 2 stdlib
from pathlib import Path
text_type = str, Path
byte_type = bytes
else:
text_type = unicode # noqa: F821
byte_type = str


def _to_bytes(x):
"""Convert to a byte string.
Expand All @@ -26,7 +15,7 @@ def _to_bytes(x):
byte string. You must call this on strings you pass to libvips.
"""
if isinstance(x, text_type):
if isinstance(x, (str, Path)):
# n.b. str also converts pathlib.Path objects
x = str(x).encode('utf-8')

Expand All @@ -44,7 +33,7 @@ def _to_string(x):
x = 'NULL'
else:
x = ffi.string(x)
if isinstance(x, byte_type):
if isinstance(x, bytes):
x = x.decode('utf-8')

return x
Expand Down
2 changes: 0 additions & 2 deletions pyvips/gobject.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import logging

import pyvips
Expand Down
10 changes: 2 additions & 8 deletions pyvips/gvalue.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
from __future__ import division
from __future__ import unicode_literals

import logging
import numbers
import sys

import pyvips
from pyvips import ffi, vips_lib, gobject_lib, \
Expand All @@ -12,8 +8,6 @@

logger = logging.getLogger(__name__)

_is_PY2 = sys.version_info.major == 2


class GValue(object):

Expand Down Expand Up @@ -103,7 +97,7 @@ def to_enum(gtype, value):
"""

if isinstance(value, basestring if _is_PY2 else str): # noqa: F821
if isinstance(value, str):
enum_value = vips_lib.vips_enum_from_nick(b'pyvips', gtype,
_to_bytes(value))
if enum_value < 0:
Expand Down Expand Up @@ -132,7 +126,7 @@ def to_flag(gtype, value):
"""

if isinstance(value, basestring if _is_PY2 else str): # noqa: F821
if isinstance(value, str):
flag_value = vips_lib.vips_flags_from_nick(b'pyvips', gtype,
_to_bytes(value))
if flag_value < 0:
Expand Down
14 changes: 1 addition & 13 deletions pyvips/pyvips_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,7 @@
if pkgconfig.installed('vips', '< 8.2'):
raise Exception('pkg-config "vips" is too old -- need libvips 8.2 or later')

# pkgconfig 1.5+ has modversion ... otherwise, use a small shim
try:
from pkgconfig import modversion
except ImportError:
def modversion(package):
# will need updating once we hit 8.20 :(
for i in range(20, 3, -1):
if pkgconfig.installed(package, '>= 8.' + str(i)):
# be careful micro version is always set to 0
return '8.' + str(i) + '.0'
return '8.2.0'

major, minor, micro = [int(s) for s in modversion('vips').split('.')]
major, minor, micro = [int(s) for s in pkgconfig.modversion('vips').split('.')]

ffibuilder = FFI()

Expand Down
2 changes: 0 additions & 2 deletions pyvips/vconnection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import logging

import pyvips
Expand Down
2 changes: 1 addition & 1 deletion pyvips/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# this is execfile()d into setup.py imported into __init__.py
# this is used in pyproject.toml and imported into __init__.py
__version__ = '2.2.3'

__all__ = ['__version__']
31 changes: 1 addition & 30 deletions pyvips/vimage.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# wrap VipsImage

from __future__ import division

import numbers
import struct

Expand Down Expand Up @@ -83,26 +81,6 @@ def _run_cmplx(fn, image):
return image


# https://stackoverflow.com/a/22409540/1480019
# https://github.com/benjaminp/six/blob/33b584b2c551548021adb92a028ceaf892deb5be/six.py#L846-L861
def _with_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
if hasattr(cls, '__qualname__'):
orig_vars['__qualname__'] = cls.__qualname__
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper


# decorator to set docstring
def _add_doc(name):
try:
Expand Down Expand Up @@ -258,8 +236,7 @@ def call_function(*args, **kwargs):
return call_function


@_with_metaclass(ImageType)
class Image(pyvips.VipsObject):
class Image(pyvips.VipsObject, metaclass=ImageType):
"""Wrap a VipsImage object.
"""
Expand Down Expand Up @@ -610,12 +587,6 @@ def new_from_memory(data, width, height, bands, format):
"""
format_value = GValue.to_enum(GValue.format_type, format)
pointer = ffi.from_buffer(data)
# py3:
# - memoryview has .nbytes for number of bytes in object
# - len() returns number of elements in top array
# py2:
# - buffer has no nbytes member
# - but len() gives number of bytes in object
nbytes = data.nbytes if hasattr(data, 'nbytes') else len(data)
vi = vips_lib.vips_image_new_from_memory(pointer,
nbytes,
Expand Down
2 changes: 0 additions & 2 deletions pyvips/vinterpolate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import pyvips
from pyvips import ffi, vips_lib, Error, _to_bytes

Expand Down
2 changes: 0 additions & 2 deletions pyvips/vobject.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# wrap VipsObject

from __future__ import division

import logging

import pyvips
Expand Down
2 changes: 0 additions & 2 deletions pyvips/voperation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division, print_function

import logging

import pyvips
Expand Down
2 changes: 0 additions & 2 deletions pyvips/vregion.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# wrap VipsRegion

from __future__ import division

import pyvips
from pyvips import ffi, glib_lib, vips_lib, Error, at_least_libvips

Expand Down
8 changes: 0 additions & 8 deletions pyvips/vsource.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import logging

import pyvips
Expand Down Expand Up @@ -81,12 +79,6 @@ def new_from_memory(data):

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

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

Expand Down
2 changes: 0 additions & 2 deletions pyvips/vsourcecustom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import logging

import pyvips
Expand Down
2 changes: 0 additions & 2 deletions pyvips/vtarget.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import logging

import pyvips
Expand Down
10 changes: 1 addition & 9 deletions pyvips/vtargetcustom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import division

import logging

import pyvips
Expand Down Expand Up @@ -34,13 +32,7 @@ def on_write(self, handler):
"""

def interface_handler(buf):
bytes_written = handler(buf)
# py2 will often return None for bytes_written ... replace with
# the length of the string
if bytes_written is None:
bytes_written = len(buf)

return bytes_written
return handler(buf)

self.signal_connect("write", interface_handler)

Expand Down
8 changes: 0 additions & 8 deletions setup.cfg

This file was deleted.

Loading

0 comments on commit 4e7582b

Please sign in to comment.