Skip to content

Commit

Permalink
Avoid new dependencies that will likely break installs
Browse files Browse the repository at this point in the history
See this explanation:
#1578 (comment)
  • Loading branch information
mnaberez committed Mar 24, 2023
1 parent ca54549 commit 19c68f5
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 54 deletions.
11 changes: 2 additions & 9 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
4.3.0.dev0 (Next Release)
-------------------------

- The installation requirements have changed because Setuptools 67.5.0
deprecated the use of ``pkg_resources``, which Supervisor used to load
its plugins. The ``setuptools`` package is no longer a runtime dependency
of Supervisor. On Python < 3.8 where ``importlib.metadata`` is not
available in stdlib, Supervisor now requires the PyPI package
``importlib-metadata``. Additionally, on Python < 3.7 where
``importlib.resources`` is not available in stdlib, Supervisor now requires
the PyPI package ``importlib-resources``. These new dependencies have been
added as conditional requirements in ``setup.py``. Patch by Ofek Lev.
- On Python 3.8 and later, ``setuptools`` is no longer a runtime
dependency. Patch by Ofek Lev.

- ``supervisorctl`` now reads extra files included via the ``[include]``
section in ``supervisord.conf`` like ``supervisord`` does. This allows
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
elif (3, 0) < py_version < (3, 4):
raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later')

# setuptools is required as a runtime dependency only on
# Python < 3.8. See the comments in supervisor/compat.py.
requires = [
"importlib-metadata; python_version < '3.8'",
"importlib-resources; python_version < '3.7'",
"setuptools; python_version < '3.8'",
]
tests_require = []

tests_require = []
testing_extras = tests_require + [
'pytest',
'pytest-cov',
Expand Down
57 changes: 42 additions & 15 deletions supervisor/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,24 +150,51 @@ def is_text_stream(stream):
except ImportError: # pragma: no cover
from HTMLParser import HTMLParser

# Begin importlib/setuptools compatibility code

# Supervisor used pkg_resources (a part of setuptools) to load package
# resources for 15 years, until setuptools 67.5.0 (2023-03-05) deprecated
# the use of pkg_resources. On Python 3.8 or later, Supervisor now uses
# importlib (part of Python 3 stdlib). Unfortunately, on Python < 3.8,
# Supervisor needs to use pkg_resources despite its deprecation. The PyPI
# backport packages "importlib-resources" and "importlib-metadata" couldn't
# be added as dependencies to Supervisor because they require even more
# dependencies that would likely cause some Supervisor installs to fail.
from warnings import filterwarnings as _fw
_fw("ignore", message="pkg_resources is deprecated as an API")

try: # pragma: no cover
import importlib.metadata as importlib_metadata
from importlib.metadata import EntryPoint as _EntryPoint

def import_spec(spec):
return _EntryPoint(None, spec, None).load()

except ImportError: # pragma: no cover
# fall back to PyPI backport if not in stdlib
import importlib_metadata
from pkg_resources import EntryPoint as _EntryPoint

def import_spec(spec):
ep = _EntryPoint.parse("x=" + spec)
if hasattr(ep, 'resolve'):
# this is available on setuptools >= 10.2
return ep.resolve()
else:
# this causes a DeprecationWarning on setuptools >= 11.3
return ep.load(False)

try: # pragma: no cover
import importlib.resources as importlib_resources
except ImportError: # pragma: no cover
# fall back to PyPI backport if not in stdlib
import importlib_resources
import importlib.resources as _importlib_resources

if hasattr(importlib_resources, "files"):
def resource_file(package, path): # pragma: no cover
return str(importlib_resources.files(package).joinpath(path))
if hasattr(_importlib_resources, "files"):
def resource_filename(package, path):
return str(_importlib_resources.files(package).joinpath(path))

else: # pragma: no cover
# fall back to deprecated .path if .files is not available
def resource_file(package, path): # pragma: no cover
with importlib_resources.path(package, '__init__.py') as p:
return str(p.parent.joinpath(path))
else:
# fall back to deprecated .path if .files is not available
def resource_filename(package, path):
with _importlib_resources.path(package, '__init__.py') as p:
return str(p.parent.joinpath(path))

except ImportError: # pragma: no cover
from pkg_resources import resource_filename

# End importlib/setuptools compatibility code
4 changes: 2 additions & 2 deletions supervisor/confecho.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import sys
from supervisor.compat import as_string
from supervisor.compat import resource_file
from supervisor.compat import resource_filename


def main(out=sys.stdout):
with open(resource_file(__package__, 'skel/sample.conf'), 'r') as f:
with open(resource_filename(__package__, 'skel/sample.conf'), 'r') as f:
out.write(as_string(f.read()))
4 changes: 2 additions & 2 deletions supervisor/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from supervisor.compat import xmlrpclib
from supervisor.compat import StringIO
from supervisor.compat import basestring
from supervisor.compat import importlib_metadata
from supervisor.compat import import_spec

from supervisor.medusa import asyncore_25 as asyncore

Expand Down Expand Up @@ -391,7 +391,7 @@ def get_plugins(self, parser, factory_key, section_prefix):

def import_spec(self, spec):
"""On failure, raises either AttributeError or ImportError"""
return importlib_metadata.EntryPoint(None, spec, None).load()
return import_spec(spec)

def read_include_config(self, fp, parser, expansions):
if parser.has_section('include'):
Expand Down
46 changes: 23 additions & 23 deletions supervisor/tests/test_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import signal
import sys
import unittest
from supervisor.compat import resource_file
from supervisor.compat import resource_filename
from supervisor.compat import xmlrpclib
from supervisor.xmlrpc import SupervisorTransport

Expand All @@ -25,7 +25,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self):
passed to the child without the percent sign being mangled."""
key = "SUPERVISOR_TEST_1441B"
val = "foo_%s_%_%%_%%%_%2_bar"
filename = resource_file(__package__, 'fixtures/issue-291a.conf')
filename = resource_filename(__package__, 'fixtures/issue-291a.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
try:
os.environ[key] = val
Expand All @@ -38,7 +38,7 @@ def test_issue_291a_percent_signs_in_original_env_are_preserved(self):
def test_issue_550(self):
"""When an environment variable is set in the [supervisord] section,
it should be put into the environment of the subprocess."""
filename = resource_file(__package__, 'fixtures/issue-550.conf')
filename = resource_filename(__package__, 'fixtures/issue-550.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -54,7 +54,7 @@ def test_issue_550(self):
def test_issue_565(self):
"""When a log file has Unicode characters in it, 'supervisorctl
tail -f name' should still work."""
filename = resource_file(__package__, 'fixtures/issue-565.conf')
filename = resource_filename(__package__, 'fixtures/issue-565.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -71,7 +71,7 @@ def test_issue_565(self):
def test_issue_638(self):
"""When a process outputs something on its stdout or stderr file
descriptor that is not valid UTF-8, supervisord should not crash."""
filename = resource_file(__package__, 'fixtures/issue-638.conf')
filename = resource_filename(__package__, 'fixtures/issue-638.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -90,7 +90,7 @@ def test_issue_638(self):
def test_issue_663(self):
"""When Supervisor is run on Python 3, the eventlistener protocol
should work."""
filename = resource_file(__package__, 'fixtures/issue-663.conf')
filename = resource_filename(__package__, 'fixtures/issue-663.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -102,7 +102,7 @@ def test_issue_664(self):
"""When a subprocess name has Unicode characters, 'supervisord'
should not send incomplete XML-RPC responses and 'supervisorctl
status' should work."""
filename = resource_file(__package__, 'fixtures/issue-664.conf')
filename = resource_filename(__package__, 'fixtures/issue-664.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -121,7 +121,7 @@ def test_issue_664(self):
def test_issue_733(self):
"""When a subprocess enters the FATAL state, a one-line eventlistener
can be used to signal supervisord to shut down."""
filename = resource_file(__package__, 'fixtures/issue-733.conf')
filename = resource_filename(__package__, 'fixtures/issue-733.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -130,7 +130,7 @@ def test_issue_733(self):
supervisord.expect(pexpect.EOF)

def test_issue_835(self):
filename = resource_file(__package__, 'fixtures/issue-835.conf')
filename = resource_filename(__package__, 'fixtures/issue-835.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -146,7 +146,7 @@ def test_issue_835(self):
transport.connection.close()

def test_issue_836(self):
filename = resource_file(__package__, 'fixtures/issue-836.conf')
filename = resource_filename(__package__, 'fixtures/issue-836.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -169,7 +169,7 @@ def test_issue_836(self):
def test_issue_986_command_string_with_double_percent(self):
"""A percent sign can be used in a command= string without being
expanded if it is escaped by a second percent sign."""
filename = resource_file(__package__, 'fixtures/issue-986.conf')
filename = resource_filename(__package__, 'fixtures/issue-986.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -178,7 +178,7 @@ def test_issue_986_command_string_with_double_percent(self):
def test_issue_1054(self):
"""When run on Python 3, the 'supervisorctl avail' command
should work."""
filename = resource_file(__package__, 'fixtures/issue-1054.conf')
filename = resource_filename(__package__, 'fixtures/issue-1054.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -196,7 +196,7 @@ def test_issue_1170a(self):
"""When the [supervisord] section has a variable defined in
environment=, that variable should be able to be used in an
%(ENV_x) expansion in a [program] section."""
filename = resource_file(__package__, 'fixtures/issue-1170a.conf')
filename = resource_filename(__package__, 'fixtures/issue-1170a.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -207,7 +207,7 @@ def test_issue_1170b(self):
environment=, and a variable by the same name is defined in
enviroment= of a [program] section, the one in the [program]
section should be used."""
filename = resource_file(__package__, 'fixtures/issue-1170b.conf')
filename = resource_filename(__package__, 'fixtures/issue-1170b.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -218,7 +218,7 @@ def test_issue_1170c(self):
environment=, and a variable by the same name is defined in
enviroment= of an [eventlistener] section, the one in the
[eventlistener] section should be used."""
filename = resource_file(__package__, 'fixtures/issue-1170c.conf')
filename = resource_filename(__package__, 'fixtures/issue-1170c.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -229,7 +229,7 @@ def test_issue_1224(self):
then the non-rotating logger will be used to avoid an
IllegalSeekError in the case that the user has configured a
non-seekable file like /dev/stdout."""
filename = resource_file(__package__, 'fixtures/issue-1224.conf')
filename = resource_filename(__package__, 'fixtures/issue-1224.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -238,7 +238,7 @@ def test_issue_1224(self):
def test_issue_1231a(self):
"""When 'supervisorctl tail -f name' is run and the log contains
unicode, it should not fail."""
filename = resource_file(__package__, 'fixtures/issue-1231a.conf')
filename = resource_filename(__package__, 'fixtures/issue-1231a.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -255,7 +255,7 @@ def test_issue_1231a(self):
def test_issue_1231b(self):
"""When 'supervisorctl tail -f name' is run and the log contains
unicode, it should not fail."""
filename = resource_file(__package__, 'fixtures/issue-1231b.conf')
filename = resource_filename(__package__, 'fixtures/issue-1231b.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand Down Expand Up @@ -289,7 +289,7 @@ def test_issue_1231b(self):
def test_issue_1231c(self):
"""When 'supervisorctl tail -f name' is run and the log contains
unicode, it should not fail."""
filename = resource_file(__package__, 'fixtures/issue-1231c.conf')
filename = resource_filename(__package__, 'fixtures/issue-1231c.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand Down Expand Up @@ -331,7 +331,7 @@ def test_issue_1298(self):
"""When the output of 'supervisorctl tail -f worker' is piped such as
'supervisor tail -f worker | grep something', 'supervisorctl' should
not crash."""
filename = resource_file(__package__, 'fixtures/issue-1298.conf')
filename = resource_filename(__package__, 'fixtures/issue-1298.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand Down Expand Up @@ -365,7 +365,7 @@ def test_issue_1418_pidproxy_cmd_with_args(self):
def test_issue_1483a_identifier_default(self):
"""When no identifier is supplied on the command line or in the config
file, the default is used."""
filename = resource_file(__package__, 'fixtures/issue-1483a.conf')
filename = resource_filename(__package__, 'fixtures/issue-1483a.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -384,7 +384,7 @@ def test_issue_1483a_identifier_default(self):
def test_issue_1483b_identifier_from_config_file(self):
"""When the identifier is supplied in the config file only, that
identifier is used instead of the default."""
filename = resource_file(__package__, 'fixtures/issue-1483b.conf')
filename = resource_filename(__package__, 'fixtures/issue-1483b.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename]
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand All @@ -403,7 +403,7 @@ def test_issue_1483b_identifier_from_config_file(self):
def test_issue_1483c_identifier_from_command_line(self):
"""When an identifier is supplied in both the config file and on the
command line, the one from the command line is used."""
filename = resource_file(__package__, 'fixtures/issue-1483c.conf')
filename = resource_filename(__package__, 'fixtures/issue-1483c.conf')
args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line']
supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')
self.addCleanup(supervisord.kill, signal.SIGINT)
Expand Down

0 comments on commit 19c68f5

Please sign in to comment.