Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support for Python 2.7, migrate to pyproject.toml #445

Merged
merged 5 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = "jcupitt@gmail.com"},
]
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