From 2bfeca84509ce6cd99880fb5e5796fb4eccb4b38 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] 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 | 4 + cheroot/connections.py | 4 +- cheroot/server.py | 14 ++-- cheroot/test/_pytest_plugin.py | 26 ++----- 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 | 9 +++ docs/conf.py | 6 +- 11 files changed, 102 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..59f720aadb 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -101,6 +101,8 @@ 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 +117,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 +218,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..d874d85ce3 100644 --- a/cheroot/test/_pytest_plugin.py +++ b/cheroot/test/_pytest_plugin.py @@ -21,25 +21,11 @@ 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..1821a3722c --- /dev/null +++ b/docs/changelog-fragments.d/826.contrib.rst @@ -0,0 +1,9 @@ +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