From 323f3b9cda30811d7fce96d220d88e79d9a356c5 Mon Sep 17 00:00:00 2001 From: julianz- <6255571+julianz-@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:00:36 -0700 Subject: [PATCH 1/3] Bump Ruff to v0.15.18 and address new lint violations - Fix D421: reword property docstrings to noun phrases - Fix ISC004: parenthesize implicit string concatenation in collections; use single strings with # noqa: LN001 where readability is better - Drop now-redundant # noqa: D401 comments on property definitions - Suppress PLW0717, RUF067, S607 pending future fixes --- .pre-commit-config.yaml | 6 +- .ruff.toml | 3 + cheroot/connections.py | 4 +- cheroot/server.py | 14 ++-- cheroot/test/_pytest_plugin.py | 52 ++++++++----- cheroot/test/test_conn.py | 27 ++----- cheroot/test/test_ssl.py | 88 ++++++++++++++-------- cheroot/test/webtest.py | 4 +- cheroot/workers/threadpool.py | 6 +- docs/changelog-fragments.d/826.contrib.rst | 12 +++ docs/conf.py | 6 +- 11 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 docs/changelog-fragments.d/826.contrib.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c95b28b108..fdc9347d77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,7 +60,7 @@ repos: - id: no_optional - repo: https://github.com/astral-sh/ruff-pre-commit.git - rev: v0.13.3 + rev: v0.15.18 hooks: - id: ruff args: @@ -68,7 +68,7 @@ repos: - --fix # NOTE: When `--fix` is used, linting should be before ruff-format - repo: https://github.com/astral-sh/ruff-pre-commit.git - rev: v0.13.3 + rev: v0.15.18 hooks: - id: ruff-format alias: ruff-format-first-pass @@ -80,7 +80,7 @@ repos: - id: add-trailing-comma - repo: https://github.com/astral-sh/ruff-pre-commit.git - rev: v0.13.3 + rev: v0.15.18 hooks: - id: ruff-format alias: ruff-format-second-pass diff --git a/.ruff.toml b/.ruff.toml index 4036b42718..28521b86f6 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -101,6 +101,7 @@ ignore = [ "PLR6104", # non-augmented-assignment # FIXME "PLR6301", # no-self-use # FIXME / noqa + "PLW0717", # too-many-statements-in-try-clause # FIXME "PLW1514", # unspecified-encoding # FIXME "PTH100", # os-path-abspath # FIXME @@ -115,6 +116,7 @@ ignore = [ "PYI024", # collections-named-tuple # FIXME "RUF005", # collection-literal-concatenation # FIXME + "RUF067", # non-empty-init-module # FIXME "RUF012", # mutable-class-default # FIXME "RUF043", # pytest-raises-ambiguous-pattern # FIXME "RUF048", # map-int-version-parsing # FIXME @@ -215,6 +217,7 @@ testing = [ "S101", # Allow use of `assert` in test files "S404", # Allow importing 'subprocess' module to testing call external tools needed by these hooks "S603", # subprocess-without-shell-equals-true + "S607", # start-process-with-partial-path # FIXME "SLF001", # Private member accessed ] diff --git a/cheroot/connections.py b/cheroot/connections.py index 98f17e42f5..9bf1d50e75 100644 --- a/cheroot/connections.py +++ b/cheroot/connections.py @@ -80,7 +80,7 @@ def __len__(self): @property def connections(self): - """Retrieve connections registered with the selector.""" + """Connections registered with the selector.""" with self._lock: mapping = self._selector.get_map() or {} for _, (_, sock_fd, _, conn) in mapping.items(): @@ -386,7 +386,7 @@ def close(self): @property def _num_connections(self): - """Return the current number of connections. + """The current number of connections. Includes all connections registered with the selector, minus one for the server socket, which is always registered diff --git a/cheroot/server.py b/cheroot/server.py index 284cf17c72..89793309aa 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -1453,19 +1453,19 @@ def get_peer_creds(self): # LRU cached on per-instance basis, see __init__ @property def peer_pid(self): - """Return the id of the connected peer process.""" + """The id of the connected peer process.""" pid, _, _ = self.get_peer_creds() return pid @property def peer_uid(self): - """Return the user id of the connected peer process.""" + """The user id of the connected peer process.""" _, uid, _ = self.get_peer_creds() return uid @property def peer_gid(self): - """Return the group id of the connected peer process.""" + """The group id of the connected peer process.""" _, _, gid = self.get_peer_creds() return gid @@ -1495,13 +1495,13 @@ def resolve_peer_creds(self): # LRU cached on per-instance basis @property def peer_user(self): - """Return the username of the connected peer process.""" + """The username of the connected peer process.""" user, _ = self.resolve_peer_creds() return user @property def peer_group(self): - """Return the group of the connected peer process.""" + """The group of the connected peer process.""" _, group = self.resolve_peer_creds() return group @@ -1750,7 +1750,7 @@ def __str__(self): @property def bind_addr(self): - """Return the interface on which to listen for connections. + """The interface on which to listen for connections. For TCP sockets, a (host, port) tuple. Host values may be any :term:`IPv4` or :term:`IPv6` address, or any valid hostname. @@ -2234,7 +2234,7 @@ def interrupt(self): @property def _stopping_for_interrupt(self): - """Return whether the server is responding to an interrupt.""" + """Whether the server is responding to an interrupt.""" return self._interrupt is _STOPPING_FOR_INTERRUPT @interrupt.setter diff --git a/cheroot/test/_pytest_plugin.py b/cheroot/test/_pytest_plugin.py index 51d22a0a05..77ac31ae83 100644 --- a/cheroot/test/_pytest_plugin.py +++ b/cheroot/test/_pytest_plugin.py @@ -21,25 +21,37 @@ def pytest_load_initial_conftests(early_config, parser, args): # * https://github.com/pytest-dev/pytest/issues/5299 early_config._inicache['filterwarnings'].extend( ( - 'ignore:Exception in thread CP Server Thread-:' - 'pytest.PytestUnhandledThreadExceptionWarning:_pytest.threadexception', - 'ignore:Exception in thread Thread-:' - 'pytest.PytestUnhandledThreadExceptionWarning:_pytest.threadexception', - 'ignore:Exception ignored in. ' - ', ' - 'providing a full reproducer with as much context and details ' - r'as possible\.$', + r'A fatal exception happened\. Setting the server interrupt flag to ConnectionResetError\(666,?\) and giving up\.\n\nPlease, report this on the Cheroot tracker at , providing a full reproducer with as much context and details as possible\.$', # noqa: LN001 ), ) @@ -840,13 +832,11 @@ def _trigger_kb_intr(_req, _resp): expected_log_entries = ( ( logging.DEBUG, - '^Got a server shutdown request while handling a connection ' - r'from .*:\d{1,5} \(simulated test handler keyboard interrupt\)$', + r'^Got a server shutdown request while handling a connection from .*:\d{1,5} \(simulated test handler keyboard interrupt\)$', # noqa: LN001 ), ( logging.DEBUG, - '^Setting the server interrupt flag to KeyboardInterrupt' - r"\('simulated test handler keyboard interrupt',?\)$", + r"^Setting the server interrupt flag to KeyboardInterrupt\('simulated test handler keyboard interrupt',?\)$", # noqa: LN001 ), ( logging.INFO, @@ -913,9 +903,7 @@ def _trigger_scary_exc(_req, _resp): expected_log_entries = ( ( logging.ERROR, - '^Unhandled error while processing an incoming connection ' - 'SillyMistake' - r"\('simulated unhandled exception 💣 in test handler',?\)$", + r"^Unhandled error while processing an incoming connection SillyMistake\('simulated unhandled exception 💣 in test handler',?\)$", # noqa: LN001 ), ( logging.INFO, @@ -997,8 +985,7 @@ def _read_request_line(self): expected_log_entries = ( ( logging.ERROR, - '^Unhandled error while processing an incoming connection ' - r'ScaryCrash\(666,?\)$', + r'^Unhandled error while processing an incoming connection ScaryCrash\(666,?\)$', # noqa: LN001 ), ( logging.INFO, diff --git a/cheroot/test/test_ssl.py b/cheroot/test/test_ssl.py index 1859e6bc28..f6a91ab758 100644 --- a/cheroot/test/test_ssl.py +++ b/cheroot/test/test_ssl.py @@ -480,51 +480,75 @@ def test_tls_client_auth( # noqa: C901, WPS213 # FIXME expected_substrings += ( ( "bad handshake: SysCallError(10054, 'WSAECONNRESET')", - "('Connection aborted.', " - 'OSError("(10054, \'WSAECONNRESET\')"))', - "('Connection aborted.', " - 'OSError("(10054, \'WSAECONNRESET\')",))', - "('Connection aborted.', " - 'error("(10054, \'WSAECONNRESET\')",))', - "('Connection aborted.', " - 'ConnectionResetError(10054, ' - "'An existing connection was forcibly closed " - "by the remote host', None, 10054, None))", - "('Connection aborted.', " - 'error(10054, ' - "'An existing connection was forcibly closed " - "by the remote host'))", + ( + "('Connection aborted.', " + 'OSError("(10054, \'WSAECONNRESET\')"))' + ), + ( + "('Connection aborted.', " + 'OSError("(10054, \'WSAECONNRESET\')",))' + ), + ( + "('Connection aborted.', " + 'error("(10054, \'WSAECONNRESET\')",))' + ), + ( + "('Connection aborted.', " + 'ConnectionResetError(10054, ' + "'An existing connection was forcibly closed " + "by the remote host', None, 10054, None))" + ), + ( + "('Connection aborted.', " + 'error(10054, ' + "'An existing connection was forcibly closed " + "by the remote host'))" + ), ) if IS_WINDOWS else ( - "('Connection aborted.', " - 'OSError("(104, \'ECONNRESET\')"))', - "('Connection aborted.', " - 'OSError("(104, \'ECONNRESET\')",))', + ( + "('Connection aborted.', " + 'OSError("(104, \'ECONNRESET\')"))' + ), + ( + "('Connection aborted.', " + 'OSError("(104, \'ECONNRESET\')",))' + ), "('Connection aborted.', error(\"(104, 'ECONNRESET')\",))", - "('Connection aborted.', " - "ConnectionResetError(104, 'Connection reset by peer'))", - "('Connection aborted.', " - "error(104, 'Connection reset by peer'))", + ( + "('Connection aborted.', " + "ConnectionResetError(104, 'Connection reset by peer'))" + ), + ( + "('Connection aborted.', " + "error(104, 'Connection reset by peer'))" + ), ) if (IS_GITHUB_ACTIONS_WORKFLOW and IS_LINUX) else ( - "('Connection aborted.', " - "BrokenPipeError(32, 'Broken pipe'))", + ( + "('Connection aborted.', " + "BrokenPipeError(32, 'Broken pipe'))" + ), ) ) if PY310_PLUS: # FIXME: Figure out what's happening and correct the problem expected_substrings += ( - 'SSLError(SSLEOFError(8, ' - "'EOF occurred in violation of protocol (_ssl.c:", + ( + 'SSLError(SSLEOFError(8, ' + "'EOF occurred in violation of protocol (_ssl.c:" + ), ) if IS_GITHUB_ACTIONS_WORKFLOW and IS_WINDOWS and PY310_PLUS: expected_substrings += ( - "('Connection aborted.', " - 'RemoteDisconnected(' - "'Remote end closed connection without response'))", + ( + "('Connection aborted.', " + 'RemoteDisconnected(' + "'Remote end closed connection without response'))" + ), ) assert any(e in err_text for e in expected_substrings) @@ -662,8 +686,10 @@ def test_ssl_env( # noqa: C901 # FIXME pytest.xfail( '\n'.join( ( - 'Sometimes this test fails due to ' - 'a socket.socket ResourceWarning:', + ( + 'Sometimes this test fails due to ' + 'a socket.socket ResourceWarning:' + ), msg, ), ), diff --git a/cheroot/test/webtest.py b/cheroot/test/webtest.py index fbdd86dc6d..146c670585 100644 --- a/cheroot/test/webtest.py +++ b/cheroot/test/webtest.py @@ -120,7 +120,7 @@ class WebCase(unittest.TestCase): @property def _Conn(self): - """Return HTTPConnection or HTTPSConnection based on self.scheme. + """HTTPConnection or HTTPSConnection based on self.scheme. * from :py:mod:`python:http.client`. """ @@ -305,7 +305,7 @@ def _handlewebError(self, msg): # noqa: C901 # FIXME sys.stdout.flush() @property - def status_code(self): # noqa: D401; irrelevant for properties + def status_code(self): """Integer HTTP status code.""" return int(self.status[:3]) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 31bc004b4f..c827a567aa 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -299,8 +299,8 @@ def start(self): self.grow(self.min) @property - def idle(self): # noqa: D401; irrelevant for properties - """Number of worker threads which are idle. Read-only.""" # noqa: D401 + def idle(self): + """Number of worker threads which are idle. Read-only.""" idles = len([t for t in self._threads if t.conn is None]) return max(idles - len(self._pending_shutdowns), 0) @@ -432,5 +432,5 @@ def _clear_threads(self): @property def qsize(self): - """Return the queue size.""" + """The queue size.""" return self._queue.qsize() diff --git a/docs/changelog-fragments.d/826.contrib.rst b/docs/changelog-fragments.d/826.contrib.rst new file mode 100644 index 0000000000..cbdda176cb --- /dev/null +++ b/docs/changelog-fragments.d/826.contrib.rst @@ -0,0 +1,12 @@ +Bumped Ruff to v0.15.18, which introduces new rules: + +1. Property docstrings must now use noun phrases rather than verb phrases + (D421, e.g. "The queue size." not "Return the queue size."), +2. Implicit string concatenation inside collection literals must be + parenthesized (ISC004). + +Three further new rules are suppressed pending fixes: too many statements +in a try clause (PLW0717), non-empty ``__init__`` modules (RUF067), and +starting a process with a partial executable path (S607) + +-- by :user:`julianz-`. diff --git a/docs/conf.py b/docs/conf.py index a8c4ecebed..be8181ae1c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -107,10 +107,8 @@ r'https://github\.com/cherrypy/cherrypy/tree', # Has an ephemeral anchor (line-range) but actual HTML has separate per- # line anchors. - r'https://github\.com' - r'/python/cpython/blob/c39b52f/Lib/poplib\.py#L297-L302', - r'https://github\.com' - r'/python/cpython/blob/c39b52f/Lib/poplib\.py#user-content-L297-L302', + r'https://github\.com/python/cpython/blob/c39b52f/Lib/poplib\.py#L297-L302', # noqa: LN001 + r'https://github\.com/python/cpython/blob/c39b52f/Lib/poplib\.py#user-content-L297-L302', # noqa: LN001 r'^https://img\.shields\.io/matrix', # these are rate-limited r'^https://matrix\.to/#', # these render fully on front-end from anchors r'^https://stackoverflow\.com/', # these generate HTTP 403 Forbidden From 7e8a57bb8d07cf7703913659451c2ea427aa1b63 Mon Sep 17 00:00:00 2001 From: julianz- <6255571+julianz-@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:40:30 -0700 Subject: [PATCH 2/3] Move PLW0717 to per-file ignores instead of global ignore Suppresses too-many-statements-in-try-clause only in the files that actually trigger it, rather than globally. Test files are also covered. Global suppression masked the rule entirely; per-file ignores leave it active elsewhere and make the FIXMEs more targeted. --- .ruff.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.ruff.toml b/.ruff.toml index 28521b86f6..8d757898ab 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -101,7 +101,6 @@ ignore = [ "PLR6104", # non-augmented-assignment # FIXME "PLR6301", # no-self-use # FIXME / noqa - "PLW0717", # too-many-statements-in-try-clause # FIXME "PLW1514", # unspecified-encoding # FIXME "PTH100", # os-path-abspath # FIXME @@ -208,12 +207,19 @@ testing = [ [lint.per-file-ignores] +# Too many statements in try clause — FIXME: narrow these try blocks +"cheroot/connections.py" = ["PLW0717"] +"cheroot/server.py" = ["PLW0717"] +"cheroot/ssl/pyopenssl.py" = ["PLW0717"] +"cheroot/wsgi.py" = ["PLW0717"] + # Exceptions for test files "cheroot/test/**.py" = [ "ARG002", # Allow unused arguments in instance methods (required for test stubs) "ARG004", # unused-static-method-argument (hit in WSGI test apps) "PLC2701", # Allow importing internal files needed for testing # "PLR6301", # Allow 'self' parameter in method definitions (required for test stubs) + "PLW0717", # too-many-statements-in-try-clause # FIXME "S101", # Allow use of `assert` in test files "S404", # Allow importing 'subprocess' module to testing call external tools needed by these hooks "S603", # subprocess-without-shell-equals-true From 027fabb7f94364f49c7a7c0da131f1515aa8388c Mon Sep 17 00:00:00 2001 From: julianz- <6255571+julianz-@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:56:28 -0700 Subject: [PATCH 3/3] Fix line length violations and remove unnecessary PLW0717 suppression from test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace # noqa: LN001 suppressions with properly split strings wrapped in parentheses to satisfy ISC004. Remove PLW0717 from the test file ignore list — test files have no violations so the suppression was unnecessary. --- .ruff.toml | 1 - cheroot/test/test_conn.py | 37 ++++++++++++++++++---- docs/changelog-fragments.d/826.contrib.rst | 4 +-- docs/conf.py | 10 ++++-- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index 8d757898ab..3714489e49 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -219,7 +219,6 @@ testing = [ "ARG004", # unused-static-method-argument (hit in WSGI test apps) "PLC2701", # Allow importing internal files needed for testing # "PLR6301", # Allow 'self' parameter in method definitions (required for test stubs) - "PLW0717", # too-many-statements-in-try-clause # FIXME "S101", # Allow use of `assert` in test files "S404", # Allow importing 'subprocess' module to testing call external tools needed by these hooks "S603", # subprocess-without-shell-equals-true diff --git a/cheroot/test/test_conn.py b/cheroot/test/test_conn.py index 9230a7bf78..c129309a8c 100644 --- a/cheroot/test/test_conn.py +++ b/cheroot/test/test_conn.py @@ -191,7 +191,8 @@ def testing_server(raw_testing_server, monkeypatch): continue assert c_msg in raw_testing_server.error_log.ignored_msgs, ( - f"Found error in the error log: message = '{c_msg}', level = '{c_level}'\n{c_traceback}" # noqa: LN001 + f"Found error in the error log: message = '{c_msg}', " + f"level = '{c_level}'\n{c_traceback}" ) @@ -788,11 +789,21 @@ def _read_request_line(self): (logging.WARNING, r'^socket\.error 666$'), ( logging.INFO, - r'^Got a connection error while handling a connection from .*:\d{1,5} \(666\)', # noqa: LN001 + ( + r'^Got a connection error while handling a connection ' + r'from .*:\d{1,5} \(666\)' + ), ), ( logging.CRITICAL, - r'A fatal exception happened\. Setting the server interrupt flag to ConnectionResetError\(666,?\) and giving up\.\n\nPlease, report this on the Cheroot tracker at , providing a full reproducer with as much context and details as possible\.$', # noqa: LN001 + ( + r'A fatal exception happened\. Setting the server interrupt flag ' + r'to ConnectionResetError\(666,?\) and giving up\.\n\nPlease, ' + r'report this on the Cheroot tracker at ' + r', ' + r'providing a full reproducer with as much context and details ' + r'as possible\.$' + ), ), ) @@ -832,11 +843,17 @@ def _trigger_kb_intr(_req, _resp): expected_log_entries = ( ( logging.DEBUG, - r'^Got a server shutdown request while handling a connection from .*:\d{1,5} \(simulated test handler keyboard interrupt\)$', # noqa: LN001 + ( + r'^Got a server shutdown request while handling a connection ' + r'from .*:\d{1,5} \(simulated test handler keyboard interrupt\)$' + ), ), ( logging.DEBUG, - r"^Setting the server interrupt flag to KeyboardInterrupt\('simulated test handler keyboard interrupt',?\)$", # noqa: LN001 + ( + r'^Setting the server interrupt flag to KeyboardInterrupt' + r"\('simulated test handler keyboard interrupt',?\)$" + ), ), ( logging.INFO, @@ -903,7 +920,10 @@ def _trigger_scary_exc(_req, _resp): expected_log_entries = ( ( logging.ERROR, - r"^Unhandled error while processing an incoming connection SillyMistake\('simulated unhandled exception 💣 in test handler',?\)$", # noqa: LN001 + ( + r'^Unhandled error while processing an incoming connection ' + r"SillyMistake\('simulated unhandled exception 💣 in test handler',?\)$" + ), ), ( logging.INFO, @@ -985,7 +1005,10 @@ def _read_request_line(self): expected_log_entries = ( ( logging.ERROR, - r'^Unhandled error while processing an incoming connection ScaryCrash\(666,?\)$', # noqa: LN001 + ( + r'^Unhandled error while processing an incoming connection ' + r'ScaryCrash\(666,?\)$' + ), ), ( logging.INFO, diff --git a/docs/changelog-fragments.d/826.contrib.rst b/docs/changelog-fragments.d/826.contrib.rst index cbdda176cb..a58af94103 100644 --- a/docs/changelog-fragments.d/826.contrib.rst +++ b/docs/changelog-fragments.d/826.contrib.rst @@ -7,6 +7,6 @@ Bumped Ruff to v0.15.18, which introduces new rules: Three further new rules are suppressed pending fixes: too many statements in a try clause (PLW0717), non-empty ``__init__`` modules (RUF067), and -starting a process with a partial executable path (S607) +starting a process with a partial executable path (S607). --- by :user:`julianz-`. +-- by :user:`julianz-` diff --git a/docs/conf.py b/docs/conf.py index be8181ae1c..81149c06cb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -107,8 +107,14 @@ r'https://github\.com/cherrypy/cherrypy/tree', # Has an ephemeral anchor (line-range) but actual HTML has separate per- # line anchors. - r'https://github\.com/python/cpython/blob/c39b52f/Lib/poplib\.py#L297-L302', # noqa: LN001 - r'https://github\.com/python/cpython/blob/c39b52f/Lib/poplib\.py#user-content-L297-L302', # noqa: LN001 + ( + r'https://github\.com' + r'/python/cpython/blob/c39b52f/Lib/poplib\.py#L297-L302' + ), + ( + r'https://github\.com' + r'/python/cpython/blob/c39b52f/Lib/poplib\.py#user-content-L297-L302' + ), r'^https://img\.shields\.io/matrix', # these are rate-limited r'^https://matrix\.to/#', # these render fully on front-end from anchors r'^https://stackoverflow\.com/', # these generate HTTP 403 Forbidden