From db0f1999ef64c0cf47d09406b2b6c7b4887d6dad Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 3 May 2024 11:29:42 +0800 Subject: [PATCH 1/6] Move project config to PEP621 format with SCM version number. --- .github/workflows/ci.yml | 4 +- .pre-commit-config.yaml | 6 ++ pyproject.toml | 92 ++++++++++++++++++++++++++- setup.cfg | 75 ---------------------- src/gbulb/__init__.py | 21 +++++- tests/docker-images/Makefile | 5 -- tests/docker-images/base.Dockerfile | 3 - tests/docker-images/python.Dockerfile | 29 --------- tests/test_glib_events.py | 10 +-- tox.ini | 47 ++++++++------ 10 files changed, 149 insertions(+), 143 deletions(-) delete mode 100644 setup.cfg delete mode 100644 tests/docker-images/Makefile delete mode 100644 tests/docker-images/base.Dockerfile delete mode 100644 tests/docker-images/python.Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5865d2e..a9b8966 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,10 +39,10 @@ jobs: continue-on-error: ${{ matrix.experimental }} strategy: matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev" ] + python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev" ] include: - experimental: false - - python-version: "3.12-dev" + - python-version: "3.13-dev" experimental: true steps: - name: Checkout diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2f23ae..92063b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,3 +27,9 @@ repos: rev: 7.0.0 hooks: - id: flake8 + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + # remove toml extra once Python 3.10 is no longer supported + additional_dependencies: ['.[toml]'] diff --git a/pyproject.toml b/pyproject.toml index 4ad39e3..872c5bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,70 @@ [build-system] requires = [ - "setuptools >= 46.4.0", - "wheel >= 0.32.0", + "setuptools==69.5.1", + "setuptools_scm==8.0.4", ] build-backend = "setuptools.build_meta" +[project] +dynamic = ["version"] +name = "gbulb" +description = "GLib event loop for Python asyncio" +readme = "README.rst" +requires-python = ">= 3.8" +license.text = "Apache 2.0" +authors = [ + {name="Russell Keith-Magee", email="russell@keith-magee.com"}, + {name="Nathan Hoad", email="nathan@getoffmalawn.com"} +] +maintainers = [ + {name="Russell Keith-Magee", email="russell@keith-magee.com"}, +] +keywords = [ + "gtk", + "glib", + "gnome", + "asyncio", + "tulip", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX", + "Programming Language :: Python :: 3", + "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 :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + # Dependencies required at runtime are set as ranges to ensure maximum + # compatibility with the end-user's development environment. + "pygobject >= 3.14.0", +] + +[project.optional-dependencies] +# Extras used by developers *of* gbulb are pinned to specific versions to +# ensure environment consistency. +dev = [ + "coverage[toml] == 7.5.0", + # Pre-commit 3.6.0 deprecated support for Python 3.8 + "pre-commit == 3.5.0 ; python_version < '3.9'", + "pre-commit == 3.7.0 ; python_version >= '3.9'", + "pytest == 8.1.1", + "tox == 4.15.0", +] + +[project.urls] +Homepage = "https://github.com/beeware/gbulb" +Funding = "https://beeware.org/contributing/membership/" +# Documentation = "http://gbulb.readthedocs.io/en/latest/" +Tracker = "https://github.com/beeware/gbulb/issues" +Source = "https://github.com/beeware/gbulb" + [tool.isort] profile = "black" skip_glob = [ @@ -13,9 +73,37 @@ skip_glob = [ ] multi_line_output = 3 +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = [ + # "error", # promote warnings to errors +] + +# need to ensure build directories aren't excluded from recursion +norecursedirs = [] + +[tool.setuptools_scm] +# To enable SCM versioning, we need an empty tool configuration for setuptools_scm + [tool.towncrier] directory = "changes" package = "gbulb" package_dir = "src" filename = "CHANGELOG.rst" +title_format = "{version} ({project_date})" +issue_format = "`#{issue} `__" template = "changes/template.rst" +type = [ + { directory = "feature", name = "Features", showcontent = true }, + { directory = "bugfix", name = "Bugfixes", showcontent = true }, + { directory = "removal", name = "Backward Incompatible Changes", showcontent = true }, + { directory = "doc", name = "Documentation", showcontent = true }, + { directory = "misc", name = "Misc", showcontent = false }, + ] + +[tool.codespell] +skip = '.git,*.pdf,*.svg' +# the way to make case sensitive skips of words etc +ignore-regex = '\bNd\b' +# case insensitive +# ignore-words-list = '' diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e028daf..0000000 --- a/setup.cfg +++ /dev/null @@ -1,75 +0,0 @@ -[metadata] - -name = gbulb -version = attr: gbulb.__version__ -url = https://github.com/beeware/gbulb -project_urls = - Funding = https://beeware.org/contributing/membership/ - Documentation = http://gbulb.readthedocs.io/en/latest/ - Tracker = https://github.com/beeware/gbulb/issues - Source = https://github.com/beeware/gbulb -author = Russell Keith-Magee -author_email = russell@keith-magee.com -maintainer = Russell Keith-Magee -maintainer_email = russell@keith-magee.com -classifiers = - Development Status :: 4 - Beta - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: POSIX - Programming Language :: Python :: 3 - 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 - Topic :: Software Development :: Libraries :: Python Modules -license = Apache 2.0 -license_files = LICENSE -description = GLib event loop for tulip (PEP 3156) -long_description = file: README.rst -long_description_content_type = text/x-rst; charset=UTF-8 -keywords = - gtk - glib - gnome - asyncio - tulip -platforms = linux - -[options] -zip_safe = False -packages = find: -python_requires = >= 3.7 -include_package_data = True -package_dir = - = src -install_requires = - pygobject>=3.14.0 - -[options.extras_require] -# Extras used by developers *of* gbulb are pinned to specific versions to -# ensure environment consistency. -dev = - coverage[toml] == 7.1.0 - # Pre-commit 3.0 dropped support for Python 3.7 - pre-commit == 2.21.0; python_version < "3.8" - pre-commit == 3.0.2; python_version >= "3.8" - pytest == 7.2.1 - pytest-tldr == 0.2.5 - tox == 4.4.2 - -[options.packages.find] -where = src - -[tool:pytest] -testpaths = tests - -# need to ensure build directories aren't excluded from recursion -norecursedirs = - -[flake8] -max-complexity = 25 -max-line-length = 119 -ignore = E203,E266,E501,W503 diff --git a/src/gbulb/__init__.py b/src/gbulb/__init__.py index 60d940e..3111110 100644 --- a/src/gbulb/__init__.py +++ b/src/gbulb/__init__.py @@ -1,4 +1,23 @@ from .glib_events import * # noqa: F401,F403 from .utils import * # noqa: F401,F403 -__version__ = "0.6.4" +__all__ = [ + "__version__", +] + +try: + # Read version from SCM metadata + # This will only exist in a development environment + from setuptools_scm import get_version + + # Excluded from coverage because a pure test environment (such as the one + # used by tox in CI) won't have setuptools_scm + __version__ = get_version("../..", relative_to=__file__) # pragma: no cover +except (ModuleNotFoundError, LookupError): # pragma: no-cover-if-missing-setuptools_scm + # If setuptools_scm isn't in the environment, the call to import will fail. + # If it *is* in the environment, but the code isn't a git checkout (e.g., + # it's been pip installed non-editable) the call to get_version() will fail. + # If either of these occurs, read version from the installer metadata. + from importlib.metadata import version + + __version__ = version("gbulb") diff --git a/tests/docker-images/Makefile b/tests/docker-images/Makefile deleted file mode 100644 index 6cfd88a..0000000 --- a/tests/docker-images/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -base: - docker build -t nathanhoad/gbulb-base -f base.Dockerfile .; -python: - test ${VERSION} - docker build -t nathanhoad/gbulb-python:${VERSION} -f python.Dockerfile --build-arg=PYTHON_VERSION=${VERSION} .; diff --git a/tests/docker-images/base.Dockerfile b/tests/docker-images/base.Dockerfile deleted file mode 100644 index 8aa0d64..0000000 --- a/tests/docker-images/base.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM centos:7 -RUN yum install -y openssl-devel zlib-devel gtk3-devel gobject-introspection-devel libffi-devel bzip2-devel which gcc make git libtool bzip2 -RUN git clone https://github.com/yyuu/pyenv ~/.pyenv diff --git a/tests/docker-images/python.Dockerfile b/tests/docker-images/python.Dockerfile deleted file mode 100644 index 178d78a..0000000 --- a/tests/docker-images/python.Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM nathanhoad/gbulb-base - -ARG PYTHON_VERSION -ARG GOBJECT_CHECKSUM=779effa93f4b59cdb72f4ab0128fb3fd82900bf686193b570fd3a8ce63392d54 -ARG GOBJECT_BASE_VERSION=3.14 -ARG GOBJECT_VERSION=3.14.0 - -ENV HOME=/root/ -ENV PYENV_ROOT=$HOME/.pyenv -ENV PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH - -RUN pyenv install $PYTHON_VERSION -RUN pyenv global $PYTHON_VERSION - -RUN curl -L "https://ftp.gnome.org/pub/GNOME/sources/pygobject/$GOBJECT_BASE_VERSION/pygobject-$GOBJECT_VERSION.tar.xz" -o pygobject.tar.xz -RUN echo "$GOBJECT_CHECKSUM pygobject.tar.xz" > pygobject.checksum -RUN sha256sum --check pygobject.checksum -RUN tar xvf pygobject.tar.xz - -WORKDIR pygobject-$GOBJECT_VERSION - -# pygobject enforces c90 in configure, in a "you're not getting past this" kind -# of way. From CPython 3.6.0, they (quite reasonably) moved to c99, and -# introduced some c++ style comments to really rub it in, which doesn't go well -# with gobject's c90. So this gross sed is to get us those wonderous comments. -RUN sed -i 's/-std=c90/-std=c99/g' configure -RUN ./configure --prefix="$PYENV_ROOT/versions/$PYTHON_VERSION" --enable-cairo=no -RUN make install -RUN pip install pytest diff --git a/tests/test_glib_events.py b/tests/test_glib_events.py index 8e52c10..66713c2 100644 --- a/tests/test_glib_events.py +++ b/tests/test_glib_events.py @@ -89,7 +89,7 @@ def handler(): glib_loop.call_later(0.01, os.kill, os.getpid(), signal.SIGHUP) glib_loop.run_forever() - assert called, "signal handler didnt fire" + assert called, "signal handler didn't fire" @skipIf(is_windows, "Unix signal handlers are not supported on Windows") def test_remove_signal_handler(self, glib_loop): @@ -155,7 +155,7 @@ def callback(*args): os.close(rfd) os.close(wfd) - assert called, "callback handler didnt fire" + assert called, "callback handler didn't fire" @skipIf( is_windows, "Waiting on raw file descriptors only works for sockets on Windows" @@ -181,7 +181,7 @@ def callback(*args): os.close(rfd) os.close(wfd) - assert called, "callback handler didnt fire" + assert called, "callback handler didn't fire" @skipIf( is_windows, "Waiting on raw file descriptors only works for sockets on Windows" @@ -279,7 +279,7 @@ def handler(): glib_loop.call_at(s + 0.1, handler) glib_loop.run_forever() - assert called, "call_at handler didnt fire" + assert called, "call_at handler didn't fire" def test_call_soon_no_coroutine(self, glib_loop): with pytest.raises(TypeError): @@ -360,7 +360,7 @@ def handler(): glib_loop.call_soon_threadsafe(handler) glib_loop.run_forever() - assert called, "call_soon_threadsafe handler didnt fire" + assert called, "call_soon_threadsafe handler didn't fire" class TestGLibEventLoop: diff --git a/tox.ini b/tox.ini index 1e847ac..fd8756d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,40 +1,45 @@ -# Tox (http://tox.testrun.org/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. +# Flake8 doesn't believe in pyproject.toml, so we put the configuration here. +[flake8] +# https://flake8.readthedocs.org/en/latest/ +exclude=\ + venv*/*,\ + local/*,\ + docs/*,\ + build/*,\ + tests/apps/*,\ + .eggs/*,\ + .tox/* +max-line-length = 119 +extend-ignore = + # whitespace before : + # See https://github.com/PyCQA/pycodestyle/issues/373 + E203, [tox] -envlist = towncrier-check,package,py{37,38,39,310,311,312},pypy3 +envlist = towncrier-check,py{38,39,310,311,312,313} skip_missing_interpreters = true -[testenv] +[testenv:py{38,39,310,311,312,313}] setenv = PYTHONPATH = {toxinidir}/src extras = dev commands = python -m coverage run -m pytest {posargs:-vv --color yes} -[testenv:towncrier-check] +[testenv:towncrier{,-check}] skip_install = True deps = - {[testenv:towncrier]deps} + towncrier==23.11.0 commands = - python -m towncrier.check --compare-with origin/main - -[testenv:towncrier] -skip_install = True -deps = - towncrier ~= 22.8 -commands = - towncrier build {posargs} + check : python -m towncrier.check --compare-with origin/main + !check : python -m towncrier {posargs} [testenv:package] skip_install = True +passenv = FORCE_COLOR deps = - check_manifest - build - twine + build==1.2.1 + twine==5.0.0 commands = - check-manifest -v - python -m build --sdist --wheel --outdir dist + python -m build . --outdir dist/ python -m twine check dist/* From 5289fd7922ff3eb79a2157765a50e5dfd0fb4d43 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 4 May 2024 10:26:18 +0800 Subject: [PATCH 2/6] Silence Py3.12 deprecation warnings, and adapt to 3.13 API changes. --- src/gbulb/__init__.py | 2 +- src/gbulb/glib_events.py | 136 ++++++++++++++++++++------------------ src/gbulb/transports.py | 11 ++- src/gbulb/utils.py | 14 ++-- tests/test_glib_events.py | 2 +- 5 files changed, 91 insertions(+), 74 deletions(-) diff --git a/src/gbulb/__init__.py b/src/gbulb/__init__.py index 3111110..e897d09 100644 --- a/src/gbulb/__init__.py +++ b/src/gbulb/__init__.py @@ -13,7 +13,7 @@ # Excluded from coverage because a pure test environment (such as the one # used by tox in CI) won't have setuptools_scm __version__ = get_version("../..", relative_to=__file__) # pragma: no cover -except (ModuleNotFoundError, LookupError): # pragma: no-cover-if-missing-setuptools_scm +except (ModuleNotFoundError, LookupError): # pragma: no cover # If setuptools_scm isn't in the environment, the call to import will fail. # If it *is* in the environment, but the code isn't a git checkout (e.g., # it's been pip installed non-editable) the call to get_version() will fail. diff --git a/src/gbulb/glib_events.py b/src/gbulb/glib_events.py index 0676843..64d9598 100644 --- a/src/gbulb/glib_events.py +++ b/src/gbulb/glib_events.py @@ -6,6 +6,7 @@ import socket import sys import threading +import warnings import weakref from asyncio import CancelledError, constants, events, sslproto, tasks @@ -51,92 +52,95 @@ class AbstractChildWatcher: from asyncio.unix_events import AbstractChildWatcher -class GLibChildWatcher(AbstractChildWatcher): - def __init__(self): - self._sources = {} - self._handles = {} +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) - # On windows on has to open a process handle for the given PID number - # before it's possible to use GLib's `child_watch_add` on it - if sys.platform == "win32": + class GLibChildWatcher(AbstractChildWatcher): + def __init__(self): + self._sources = {} + self._handles = {} - def _create_handle_for_pid(self, pid): - import _winapi + # On windows on has to open a process handle for the given PID number + # before it's possible to use GLib's `child_watch_add` on it + if sys.platform == "win32": - return _winapi.OpenProcess(0x00100400, 0, pid) + def _create_handle_for_pid(self, pid): + import _winapi - def _close_process_handle(self, handle): - import _winapi + return _winapi.OpenProcess(0x00100400, 0, pid) - _winapi.CloseHandle(handle) + def _close_process_handle(self, handle): + import _winapi - else: + _winapi.CloseHandle(handle) - def _create_handle_for_pid(self, pid): - return pid + else: - def _close_process_handle(self, pid): - return None + def _create_handle_for_pid(self, pid): + return pid - def attach_loop(self, loop): - # just ignored - pass + def _close_process_handle(self, pid): + return None - def add_child_handler(self, pid, callback, *args): - self.remove_child_handler(pid) + def attach_loop(self, loop): + # just ignored + pass - handle = self._create_handle_for_pid(pid) - source = GLib.child_watch_add(0, handle, self.__callback__) - self._sources[pid] = source, callback, args, handle - self._handles[handle] = pid + def add_child_handler(self, pid, callback, *args): + self.remove_child_handler(pid) - def remove_child_handler(self, pid): - try: - source, callback, args, handle = self._sources.pop(pid) - assert self._handles.pop(handle) == pid - except KeyError: - return False + handle = self._create_handle_for_pid(pid) + source = GLib.child_watch_add(0, handle, self.__callback__) + self._sources[pid] = source, callback, args, handle + self._handles[handle] = pid - self._close_process_handle(handle) - GLib.source_remove(source) - return True + def remove_child_handler(self, pid): + try: + source, callback, args, handle = self._sources.pop(pid) + assert self._handles.pop(handle) == pid + except KeyError: + return False - def close(self): - for source, callback, args, handle in self._sources.values(): self._close_process_handle(handle) GLib.source_remove(source) - self._sources = {} - self._handles = {} + return True - def __enter__(self): - return self + def close(self): + for source, callback, args, handle in self._sources.values(): + self._close_process_handle(handle) + GLib.source_remove(source) + self._sources = {} + self._handles = {} - def __exit__(self, a, b, c): - pass + def __enter__(self): + return self - def __callback__(self, handle, status): - try: - pid = self._handles.pop(handle) - source, callback, args, handle = self._sources.pop(pid) - except KeyError: - return + def __exit__(self, a, b, c): + pass - self._close_process_handle(handle) - GLib.source_remove(source) + def __callback__(self, handle, status): + try: + pid = self._handles.pop(handle) + source, callback, args, handle = self._sources.pop(pid) + except KeyError: + return - if hasattr(os, "WIFSIGNALED") and os.WIFSIGNALED(status): - returncode = -os.WTERMSIG(status) - elif hasattr(os, "WIFEXITED") and os.WIFEXITED(status): - returncode = os.WEXITSTATUS(status) + self._close_process_handle(handle) + GLib.source_remove(source) - # FIXME: Hack for adjusting invalid status returned by GLIB - # Looks like there is a bug in glib or in pygobject - if returncode > 128: - returncode = 128 - returncode - else: - returncode = status + if hasattr(os, "WIFSIGNALED") and os.WIFSIGNALED(status): + returncode = -os.WTERMSIG(status) + elif hasattr(os, "WIFEXITED") and os.WIFEXITED(status): + returncode = os.WEXITSTATUS(status) - callback(pid, returncode, *args) + # FIXME: Hack for adjusting invalid status returned by GLIB + # Looks like there is a bug in glib or in pygobject + if returncode > 128: + returncode = 128 - returncode + else: + returncode = status + + callback(pid, returncode, *args) class GLibHandle(events.Handle): @@ -383,7 +387,11 @@ async def _make_subprocess_transport( **kwargs, ): """Create subprocess transport.""" - with events.get_child_watcher() as watcher: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + watcher = events.get_child_watcher() + + with watcher: waiter = asyncio.Future(loop=self) transport = transports.SubprocessTransport( self, diff --git a/src/gbulb/transports.py b/src/gbulb/transports.py index 9e7d446..2b179a5 100644 --- a/src/gbulb/transports.py +++ b/src/gbulb/transports.py @@ -1,6 +1,7 @@ import collections import socket import subprocess +import sys from asyncio import CancelledError, InvalidStateError, base_subprocess, transports @@ -24,7 +25,10 @@ def __init__(self, loop, sock, protocol, waiter=None, extra=None, server=None): self._loop._transports[sock.fileno()] = self if self._server is not None: - self._server._attach() + if sys.version_info < (3, 13): + self._server._attach() + else: + self._server._attach(self) def transport_async_init(): self._protocol.connection_made(self) @@ -78,7 +82,10 @@ def _force_close_async(self, exc): self._sock.close() self._sock = None if self._server is not None: - self._server._detach() + if sys.version_info < (3, 13): + self._server._detach() + else: + self._server._detach(self) self._server = None diff --git a/src/gbulb/utils.py b/src/gbulb/utils.py index e0eecc0..f2bfa21 100644 --- a/src/gbulb/utils.py +++ b/src/gbulb/utils.py @@ -1,6 +1,10 @@ import asyncio +import sys import weakref +if sys.version_info < (3, 14): + from .glib_events import GLibChildWatcher + __all__ = ["install", "get_event_loop", "new_event_loop", "wait_signal"] @@ -27,12 +31,10 @@ def install(gtk=False): policy = GLibEventLoopPolicy() - # There are some libraries that use SafeChildWatcher directly (which is - # completely reasonable), so we have to ensure that it is our version. I'm - # sorry, I know this isn't great but it's basically the best that we have. - from .glib_events import GLibChildWatcher - - asyncio.SafeChildWatcher = GLibChildWatcher + if sys.version_info < (3, 14): + # There are some libraries that use SafeChildWatcher directly (which is + # completely reasonable), so we have to ensure that it is our version. + asyncio.SafeChildWatcher = GLibChildWatcher asyncio.set_event_loop_policy(policy) diff --git a/tests/test_glib_events.py b/tests/test_glib_events.py index 66713c2..04fd2ca 100644 --- a/tests/test_glib_events.py +++ b/tests/test_glib_events.py @@ -258,7 +258,7 @@ def test_time(self, glib_loop): e = glib_loop.time() diff = e - s - assert SLEEP_TIME + 0.005 >= diff >= SLEEP_TIME + assert SLEEP_TIME + 0.01 >= diff >= SLEEP_TIME def test_call_at(self, glib_loop): called = False From b2c7047e6138b163ff78b186b7d507bf1106717b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 4 May 2024 10:26:46 +0800 Subject: [PATCH 3/6] Add some basic testing environment info. --- README.rst | 35 ++++++++++++++++++++++++++++++++++- tests/Dockerfile | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/Dockerfile diff --git a/README.rst b/README.rst index 0be44c4..f6cd194 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ If you notice any differences, please report them. Requirements ------------ -- python 3.7+ +- python 3.8+ - pygobject - glib - gtk+3 (optional) @@ -129,6 +129,39 @@ actually prevent you from doing that (in accordance with PEP 3156), however in mind that enclosed loops may be started at any time by third-party code calling GLib's primitives. +Testing +------- + +Testing GBulb requires a Linux environment that has GLib and GTK development +libraries available. + +The tests folder contains a Dockerfile that defines a complete testing +environment. To use the Docker environment, run the following from the root of +the git checkout: + + $ docker buildx build --tag beeware/gbulb:latest --file ./tests/Dockerfile . + $ docker run --rm --volume $(PWD):/home/brutus/gbulb:z -it beeware/gbulb:latest + +This will drop you into an Ubuntu 24.04 shell that has Python 3.8-3.13 +installed, mounting the current working directory as `/home/brutus/gbulb`. You +can use this to create virtual environments for each Python version. + +Once you have an active virtual environment, run: + + (venv) $ pip install -e .[dev] + (venv) $ pytest + +to run the test suite. Alternatively, you can install tox, and then run: + + # To test a single Python version + (venv) $ tox -e py + + # To test Python 3.10 specifically + (venv) $ tox -e py310 + + # To test all versions + (venv) $ tox + Community --------- diff --git a/tests/Dockerfile b/tests/Dockerfile new file mode 100644 index 0000000..8ae89f0 --- /dev/null +++ b/tests/Dockerfile @@ -0,0 +1,48 @@ +FROM ubuntu:24.04 + +# Disable pip's warnings and SDL audio +ENV PIP_ROOT_USER_ACTION=ignore \ + PIP_NO_WARN_SCRIPT_LOCATION=0 \ + SDL_AUDIODRIVER=dummy + +# Run apt non-interactively; use ARG so this only applies while building the image +ARG DEBIAN_FRONTEND="noninteractive" + +# Add deadsnakes +RUN apt-get update -y && \ + apt-get install --no-install-recommends -y software-properties-common && \ + add-apt-repository ppa:deadsnakes/ppa + +# Install System python +RUN apt-get update -y && \ + apt-get install --no-install-recommends -y \ + libgirepository1.0-dev \ + gir1.2-gtk-3.0 \ + libcairo2-dev \ + build-essential \ + git \ + python3.8-dev \ + python3.8-venv \ + python3.9-dev \ + python3.9-venv \ + python3.10-dev \ + python3.10-venv \ + python3.11-dev \ + python3.11-venv \ + python3-dev \ + python3-venv \ + python3.13-dev \ + python3.13-venv \ + python3-pip + +RUN groupadd beeware && \ + useradd brutus -g beeware --home /home/brutus && \ + mkdir -p /home/brutus && chown brutus:beeware /home/brutus + +# Use the brutus user for operations in the container +USER brutus + +# Set the working directory +WORKDIR /home/brutus/gbulb + +CMD /bin/bash From c45a4e1976d6e5fea24761960601bac50a98201f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 4 May 2024 10:29:19 +0800 Subject: [PATCH 4/6] Add changenotes. --- changes/76.feature.1.rst | 1 + changes/76.feature.2.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changes/76.feature.1.rst create mode 100644 changes/76.feature.2.rst diff --git a/changes/76.feature.1.rst b/changes/76.feature.1.rst new file mode 100644 index 0000000..89008bc --- /dev/null +++ b/changes/76.feature.1.rst @@ -0,0 +1 @@ +Support for Python 3.12 was added. diff --git a/changes/76.feature.2.rst b/changes/76.feature.2.rst new file mode 100644 index 0000000..4ddcec9 --- /dev/null +++ b/changes/76.feature.2.rst @@ -0,0 +1 @@ +Support for Python 3.13 was added. From db6435657ae65828f6d9653b72a7600c40dba6c2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 4 May 2024 10:34:13 +0800 Subject: [PATCH 5/6] Add removal note on 3.7 support. --- changes/137.removal.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/137.removal.rst diff --git a/changes/137.removal.rst b/changes/137.removal.rst new file mode 100644 index 0000000..d591714 --- /dev/null +++ b/changes/137.removal.rst @@ -0,0 +1 @@ +Support for Python 3.7 was removed. From 7e88bebe4fb7f47ea7193436b0b822084424baf6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 4 May 2024 10:37:29 +0800 Subject: [PATCH 6/6] Actually test on tox -e py --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fd8756d..17ce1ca 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ extend-ignore = envlist = towncrier-check,py{38,39,310,311,312,313} skip_missing_interpreters = true -[testenv:py{38,39,310,311,312,313}] +[testenv:py{,38,39,310,311,312,313}] setenv = PYTHONPATH = {toxinidir}/src extras = dev