Commit bd96d8e1 authored by Jason Madden's avatar Jason Madden

Drop setuptools/pkg_resources dep; fix unhandled exceptions from hub.handle_error in a callback.

parent 7bd2cc38
Remove support for obsolete Python versions. This is everything prior
to 3.8.
Related changes include:
- Stop using ``pkg_resources`` to find entry points (plugins).
Instead, use ``importlib.metadata``.
- Honor ``sys.unraisablehook`` when a callback function produces an
exception, and handling the exception in the hub *also* produces an
exception. In older versions, these would be simply printed.
......@@ -233,11 +233,6 @@ install_requires = greenlet_requires + CFFI_REQUIRES + [
# ultimately be published, but at this writing only the event
# interfaces are.
'zope.interface',
# setuptools is also used (via pkg_resources) for event
# notifications. It's a hard dependency of zope.interface
# anyway.
# XXX: Drop this, shift to importlib.
'setuptools',
]
# We use headers from greenlet, so it needs to be installed before we
......
......@@ -62,25 +62,11 @@ if sys.platform == 'win32':
import socket # pylint:disable=unused-import,useless-suppression
del socket
try:
# Floating point number, in number of seconds,
# like time.time
getswitchinterval = sys.getswitchinterval
setswitchinterval = sys.setswitchinterval
except AttributeError:
# Running on Python 2
_switchinterval = 0.005
def getswitchinterval():
return _switchinterval
def setswitchinterval(interval):
# Weed out None and non-numbers. This is not
# exactly exception compatible with the Python 3
# versions.
if interval > 0:
global _switchinterval
_switchinterval = interval
# Floating point number, in number of seconds,
# like time.time
getswitchinterval = sys.getswitchinterval
setswitchinterval = sys.setswitchinterval
from gevent._config import config
from gevent._hub_local import get_hub
......
......@@ -20,6 +20,10 @@ much higher-level, flexible system. If you are using one of these
systems, you generally will not want to directly modify `subscribers`.
.. versionadded:: 1.3b1
.. versionchanged:: NEXT
Now uses :mod:`importlib.metadata` instead of :mod:`pkg_resources`
to locate entry points.
"""
from __future__ import absolute_import
from __future__ import division
......@@ -71,7 +75,7 @@ from zope.interface import implementer
from zope.event import subscribers
from zope.event import notify
from pkg_resources import iter_entry_points
#: Applications may register for notification of events by appending a
#: callable to the ``subscribers`` list.
......@@ -100,7 +104,36 @@ finally:
def notify_and_call_entry_points(event):
notify(event)
for plugin in iter_entry_points(event.ENTRY_POINT_NAME):
from importlib import metadata
import sys
# This used to use the old ``pkg_resources.iter_entry_points(group,name=None)``
# API, passing it just the first argument, ``group=event.ENTRY_POINT_NAME``.
# In other words, we don't care about the ``name``.
if sys.version_info[:2] >= (3, 10):
# pylint:disable-next=unexpected-keyword-arg
# The only thing you can do with this is iterate it to get
# EntryPoint objects. (e.g., accessing by index raises a warning)
entry_points = metadata.entry_points(group=event.ENTRY_POINT_NAME)
else:
# Prior to 3.10, we have to do this all manually (keyword selection
# was introduced in 3.10; in 3.9 and before, entry_points returns a plain
# ``dict``). Using it like this is deprecated in 3.10, so to avoid warnings
# we have to write it twice.
#
# Prior to 3.9, there is no ``.module`` attribute, so if we
# needed that we'd have to look at the complete ``.value``
# attribute.
ep_dict = entry_points()
__traceback_info__ = ep_dict
# On Python 3.8, we can get duplicate EntryPoint objects; it is unclear
# why. Drop them into a set to make sure we only get one.
entry_points = set(
ep
for ep
in ep_dict.get(event.ENTRY_POINT_NAME)
)
for plugin in entry_points:
subscriber = plugin.load()
subscriber(event)
......
......@@ -30,12 +30,17 @@ from cpython.ref cimport Py_INCREF
from cpython.ref cimport Py_DECREF
from cpython.mem cimport PyMem_Malloc
from cpython.mem cimport PyMem_Free
from cpython.object cimport PyObject
from cpython.exc cimport PyErr_NormalizeException
from cpython.exc cimport PyErr_WriteUnraisable
from libc.errno cimport errno
cdef extern from "Python.h":
int Py_ReprEnter(object)
void Py_ReprLeave(object)
int PyException_SetTraceback(PyObject* ex, PyObject* tb) except *
import sys
import os
import traceback
......@@ -1387,12 +1392,15 @@ cdef public void gevent_handle_error(loop loop, object context):
# If it was set, this will clear it, and we will own
# the references.
PyErr_Fetch(&typep, &valuep, &tracebackp)
# TODO: Should we call PyErr_Normalize? There's code in
# Hub.handle_error that works around what looks like an
# unnormalized exception.
if not typep:
return
PyErr_NormalizeException(&typep, &valuep, &tracebackp)
if tracebackp:
PyException_SetTraceback(valuep, tracebackp)
# This assignment will do a Py_INCREF
# on the value. We already own the reference
# returned from PyErr_Fetch,
......@@ -1410,7 +1418,15 @@ cdef public void gevent_handle_error(loop loop, object context):
# If this method fails by raising an exception,
# cython will print it for us because we don't return a
# Python object and we don't declare an `except` clause.
loop.handle_error(context, type, value, traceback)
# Prior to Cython 3.<something>, we relied on Cython printing an
# uncaught exception here (because we don't return a Python object, and
# we have no except clause). It seems that as-of 3.0b3 at least,
# that no longer happens by default; if we want un caught, unraisable exception to be
# reported, we need to do so ourself.
try:
loop.handle_error(context, type, value, traceback)
except:
PyErr_WriteUnraisable(context)
cdef public tuple _empty_tuple = ()
......
......@@ -115,6 +115,17 @@ def get_switch_expected(fullname):
disabled_tests = [
# "test an implementation detail of thread objects" --- our implementation differs
"test_threading.ThreadTests.test_tstate_lock",
# This likes to check the number of native thread IDs using the ``native_id`` attribute,
# and the ``get_native_id()`` function. Because we're monkey-patched,
# those don't change in other threads, so it won't work.
'test_threading.ThreadTests.test_various_ops',
# Added to address CPython issue 27718; it wants functions in the signal
# module to have __module__ equal to 'signal', but obviously when we
# monkey patch they don't.
'test_signal.GenericTests.test_functions_module_attr',
# XXX: While we debug latest updates. This is leaking
'test_threading.ThreadTests.test_no_refcycle_through_target',
......
......@@ -209,9 +209,9 @@ def libev_supports_linux_iouring():
def resolver_dnspython_available():
# Try hard not to leave around junk we don't have to.
import pkg_resources
from importlib import metadata
try:
pkg_resources.get_distribution('dnspython')
except pkg_resources.DistributionNotFound:
metadata.distribution('dnspython')
except metadata.PackageNotFoundError:
return False
return True
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment