chore(deps): Update dependency aiohttp to v3.14.3 #12

Open
Renovate wants to merge 1 commit from renovate/aiohttp-3.x into master
Collaborator

This PR contains the following updates:

Package Change Age Confidence
aiohttp ==3.14.1==3.14.3 age confidence

Release Notes

aio-libs/aiohttp (aiohttp)

v3.14.3

Compare Source

===================

Bug fixes

  • Fixed the client dropping only the first Authorization, Cookie and
    Proxy-Authorization header when a redirect crossed an origin -- by :user:arshsmith1.

    Related issues and pull requests on GitHub:
    :issue:13180.

  • Fixed error message construction in the C HTTP parser -- by :user:bdraco.

    Related issues and pull requests on GitHub:
    :issue:13222.


v3.14.2

Compare Source

===================

Bug fixes

  • Fixed :py:attr:~aiohttp.web.StreamResponse.last_modified rounding a
    :class:datetime.datetime with a fractional second down.

    Related issues and pull requests on GitHub:
    :issue:5303.

  • Fixed resolving localhost on Windows to fall back without AI_ADDRCONFIG
    when the first lookup fails, so localhost still works without an active
    network.

    Related issues and pull requests on GitHub:
    :issue:5357.

  • Rejected multipart body parts whose Content-Length header is not a
    plain sequence of digits (e.g. +5, -1, 1_0), matching the
    strictness of the main request parser per :rfc:9110#section-8.6
    -- by :user:dxbjavid.

    Related issues and pull requests on GitHub:
    :issue:12794.

  • Fixed GunicornWebWorker endlessly reloading when app fails during startup -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12879.

  • Fixed some inconsistent case sensitivity on request methods -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12931.

  • Fixed IndexError: string index out of range in parse_content_disposition
    when a header parameter has an empty value (e.g. filename=).
    -- by :user:JSap0914.

    Related issues and pull requests on GitHub:
    :issue:12948.

  • Fixed the sock_read timeout being re-armed on a keep-alive connection after
    it had been returned to the pool. An idle pooled connection could be left with a
    pending read timeout that fired and poisoned it, so the next request reusing the
    connection failed immediately with :exc:aiohttp.SocketTimeoutError. The read
    timeout is now only rescheduled when resuming a transport that was actually
    paused -- by :user:daragok.

    Related issues and pull requests on GitHub:
    :issue:12953, :issue:12954.

  • Fixed the client decompressing frames when permessage-deflate was not negotiated -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12976.

  • Fixed DigestAuthMiddleware raising an IndexError on empty domain -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12983.

  • Fixed :class:~aiohttp.DigestAuthMiddleware corrupting the Digest
    challenge when a WWW-Authenticate response offered more than one
    authentication scheme -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12984.

  • Fixed client not closing cleanly after an exception -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12985.

  • Fixed control frames breaking fragmented WebSocket messages -- by :user:arshsmith1.

    Related issues and pull requests on GitHub:
    :issue:12988.

  • Fixed parse_content_disposition rejecting otherwise-valid
    Content-Disposition header values that contain optional whitespace (OWS)
    around the disposition type (e.g. "form-data ; name=\"field\"").
    The disposition type is now stripped before token validation, consistent with
    how parameter keys are already handled -- by :user:JSap0914.

    Related issues and pull requests on GitHub:
    :issue:12996.

  • Fixed an :exc:IndexError in the pure-Python HTTP parser -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:13001.

  • Fixed parsing optional whitespace in Content-Disposition -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:13002.

  • Fixed request body not being read on rejected WebSocket upgrades -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:13016.

  • Fixed :exc:LookupError (and an unguarded :exc:UnicodeDecodeError) escaping
    Content-Disposition parsing when a multipart part supplies an extended
    parameter with an unknown charset
    -- by :user:arshsmith1.

    Related issues and pull requests on GitHub:
    :issue:13042.

  • Fixed escape_quotes in the Digest authentication middleware not escaping
    backslashes, so a WWW-Authenticate challenge value containing a backslash
    could break out of its quoted-string in the generated Authorization header
    -- by :user:dxbjavid.

    Related issues and pull requests on GitHub:
    :issue:13054.

  • Fixed Python parser not rejecting a bare LF in the request line -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:13136.

  • Fixed the C HTTP parser folding the fragment into the query string for an
    origin-form request target with an empty query (e.g. /path?#frag),
    which diverged from the pure-Python parser -- by :user:GiulioDER.

    Related issues and pull requests on GitHub:
    :issue:13171.

  • Fixed the C parser reporting newer HTTP methods such as QUERY as <unknown>;
    the method table is now derived from the vendored llhttp instead of a hand-maintained count
    -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:13174.

Packaging updates and notes for downstreams

  • Upgraded llhttp to v9.4.2 -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12956.

Contributor-facing changes

  • Added admin documentation on incident response and on running reproducer code
    safely, covering security vulnerability handling and supply-chain, account, and
    CI/infrastructure compromise -- by :user:Dreamsorcerer.

    Related issues and pull requests on GitHub:
    :issue:12914.



Configuration

📅 Schedule: (in timezone Europe/Berlin)

  • Branch creation
    • "before 6am on monday"
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [aiohttp](https://github.com/aio-libs/aiohttp) | `==3.14.1` → `==3.14.3` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/aiohttp/3.14.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/aiohttp/3.14.1/3.14.3?slim=true) | --- ### Release Notes <details> <summary>aio-libs/aiohttp (aiohttp)</summary> ### [`v3.14.3`](https://github.com/aio-libs/aiohttp/blob/HEAD/CHANGES.rst#3143-2026-07-22) [Compare Source](https://github.com/aio-libs/aiohttp/compare/v3.14.2...v3.14.3) \=================== ## Bug fixes - Fixed the client dropping only the first `Authorization`, `Cookie` and `Proxy-Authorization` header when a redirect crossed an origin -- by :user:`arshsmith1`. *Related issues and pull requests on GitHub:* :issue:`13180`. - Fixed error message construction in the C HTTP parser -- by :user:`bdraco`. *Related issues and pull requests on GitHub:* :issue:`13222`. *** ### [`v3.14.2`](https://github.com/aio-libs/aiohttp/blob/HEAD/CHANGES.rst#3142-2026-07-20) [Compare Source](https://github.com/aio-libs/aiohttp/compare/v3.14.1...v3.14.2) \=================== ## Bug fixes - Fixed :py:attr:`~aiohttp.web.StreamResponse.last_modified` rounding a :class:`datetime.datetime` with a fractional second down. *Related issues and pull requests on GitHub:* :issue:`5303`. - Fixed resolving `localhost` on Windows to fall back without `AI_ADDRCONFIG` when the first lookup fails, so `localhost` still works without an active network. *Related issues and pull requests on GitHub:* :issue:`5357`. - Rejected multipart body parts whose `Content-Length` header is not a plain sequence of digits (e.g. `+5`, `-1`, `1_0`), matching the strictness of the main request parser per :rfc:`9110#section-8.6` \-- by :user:`dxbjavid`. *Related issues and pull requests on GitHub:* :issue:`12794`. - Fixed `GunicornWebWorker` endlessly reloading when app fails during startup -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12879`. - Fixed some inconsistent case sensitivity on request methods -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12931`. - Fixed `IndexError: string index out of range` in `parse_content_disposition` when a header parameter has an empty value (e.g. `filename=`). \-- by :user:`JSap0914`. *Related issues and pull requests on GitHub:* :issue:`12948`. - Fixed the `sock_read` timeout being re-armed on a keep-alive connection after it had been returned to the pool. An idle pooled connection could be left with a pending read timeout that fired and poisoned it, so the next request reusing the connection failed immediately with :exc:`aiohttp.SocketTimeoutError`. The read timeout is now only rescheduled when resuming a transport that was actually paused -- by :user:`daragok`. *Related issues and pull requests on GitHub:* :issue:`12953`, :issue:`12954`. - Fixed the client decompressing frames when `permessage-deflate` was not negotiated -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12976`. - Fixed `DigestAuthMiddleware` raising an `IndexError` on empty domain -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12983`. - Fixed :class:`~aiohttp.DigestAuthMiddleware` corrupting the `Digest` challenge when a `WWW-Authenticate` response offered more than one authentication scheme -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12984`. - Fixed client not closing cleanly after an exception -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12985`. - Fixed control frames breaking fragmented WebSocket messages -- by :user:`arshsmith1`. *Related issues and pull requests on GitHub:* :issue:`12988`. - Fixed `parse_content_disposition` rejecting otherwise-valid `Content-Disposition` header values that contain optional whitespace (OWS) around the disposition type (e.g. `"form-data ; name=\"field\""`). The disposition type is now stripped before token validation, consistent with how parameter keys are already handled -- by :user:`JSap0914`. *Related issues and pull requests on GitHub:* :issue:`12996`. - Fixed an :exc:`IndexError` in the pure-Python HTTP parser -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`13001`. - Fixed parsing optional whitespace in Content-Disposition -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`13002`. - Fixed request body not being read on rejected WebSocket upgrades -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`13016`. - Fixed :exc:`LookupError` (and an unguarded :exc:`UnicodeDecodeError`) escaping `Content-Disposition` parsing when a multipart part supplies an extended parameter with an unknown charset \-- by :user:`arshsmith1`. *Related issues and pull requests on GitHub:* :issue:`13042`. - Fixed `escape_quotes` in the Digest authentication middleware not escaping backslashes, so a `WWW-Authenticate` challenge value containing a backslash could break out of its quoted-string in the generated `Authorization` header \-- by :user:`dxbjavid`. *Related issues and pull requests on GitHub:* :issue:`13054`. - Fixed Python parser not rejecting a bare `LF` in the request line -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`13136`. - Fixed the C HTTP parser folding the fragment into the query string for an origin-form request target with an empty query (e.g. `/path?#frag`), which diverged from the pure-Python parser -- by :user:`GiulioDER`. *Related issues and pull requests on GitHub:* :issue:`13171`. - Fixed the C parser reporting newer HTTP methods such as `QUERY` as `<unknown>`; the method table is now derived from the vendored llhttp instead of a hand-maintained count \-- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`13174`. ## Packaging updates and notes for downstreams - Upgraded `llhttp` to v9.4.2 -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12956`. ## Contributor-facing changes - Added admin documentation on incident response and on running reproducer code safely, covering security vulnerability handling and supply-chain, account, and CI/infrastructure compromise -- by :user:`Dreamsorcerer`. *Related issues and pull requests on GitHub:* :issue:`12914`. *** </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Berlin) - Branch creation - "before 6am on monday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43LjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC43LjIiLCJ0YXJnZXRCcmFuY2giOiJtYXN0ZXIiLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIiwicmVub3ZhdGUiXX0=-->
chore(deps): Update dependency aiohttp to v3.14.3
Some checks failed
Build and Push Docker Image / build (pull_request) Successful in 51s
PR Tests / test (pull_request) Failing after -5m4s
ab15519b41

CI Results

Check Status
Ruff
Pylint (9.93/10)
Config
Imports
Ruff output
I001 [*] Import block is un-sorted or un-formatted
  --> src/func/twitch.py:2:1
   |
 1 |   """Twitch API client: token management, user ID lookup, stream status."""
 2 | / import asyncio
 3 | | import time
 4 | | import logging
 5 | | from typing import Dict, Optional
 6 | |
 7 | | import aiohttp
 8 | |
 9 | | from .models import StreamInfo
   | |______________________________^
10 |
11 |   log = logging.getLogger(__name__)
   |
help: Organize imports
  |
2 | import asyncio
3 + import logging
4 | import time
  - import logging
5 | from typing import Dict, Optional
  |

UP035 `typing.Dict` is deprecated, use `dict` instead
 --> src/func/twitch.py:5:1
  |
3 | import time
4 | import logging
5 | from typing import Dict, Optional
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 |
7 | import aiohttp
  |

UP045 [*] Use `X | None` for type annotations
  --> src/func/twitch.py:24:23
   |
22 |         self.client_id = client_id
23 |         self.client_secret = client_secret
24 |         self.session: Optional[aiohttp.ClientSession] = None
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25 |         self._token: Optional[str] = None
26 |         self._token_expires: float = 0
   |
help: Convert to `X | None`
   |
23 |         self.client_secret = client_secret
   -         self.session: Optional[aiohttp.ClientSession] = None
24 +         self.session: aiohttp.ClientSession | None = None
25 |         self._token: Optional[str] = None
   |

UP045 [*] Use `X | None` for type annotations
  --> src/func/twitch.py:25:22
   |
23 |         self.client_secret = client_secret
24 |         self.session: Optional[aiohttp.ClientSession] = None
25 |         self._token: Optional[str] = None
   |                      ^^^^^^^^^^^^^
26 |         self._token_expires: float = 0
   |
help: Convert to `X | None`
   |
24 |         self.session: Optional[aiohttp.ClientSession] = None
   -         self._token: Optional[str] = None
25 +         self._token: str | None = None
26 |         self._token_expires: float = 0
   |

UP006 [*] Use `dict` instead of `Dict` for type annotation
  --> src/func/twitch.py:80:45
   |
78 |             raise TwitchAPIError(f"Twitch API error: HTTP {r.status} for {url}")
79 |
80 |     async def fetch_user_ids(self, streams: Dict[str, StreamInfo]) -> None:
   |                                             ^^^^
81 |         """Resolve Twitch login names to user IDs and store them in each StreamInfo."""
82 |         await self.ensure_token()
   |
help: Replace with `dict`
   |
79 |
   -     async def fetch_user_ids(self, streams: Dict[str, StreamInfo]) -> None:
80 +     async def fetch_user_ids(self, streams: dict[str, StreamInfo]) -> None:
81 |         """Resolve Twitch login names to user IDs and store them in each StreamInfo."""
   |

UP006 [*] Use `dict` instead of `Dict` for type annotation
  --> src/func/twitch.py:90:49
   |
88 |                 log.debug("User ID for %s: %s", user["login"], user["id"])
89 |
90 |     async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]:
   |                                                 ^^^^
91 |         """Return a dict of currently live streams keyed by login name."""
92 |         await self.ensure_token()
   |
help: Replace with `dict`
   |
89 |
   -     async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]:
90 +     async def fetch_live_streams(self, streams: dict[str, StreamInfo]) -> Dict[str, dict]:
91 |         """Return a dict of currently live streams keyed by login name."""
   |

UP006 [*] Use `dict` instead of `Dict` for type annotation
  --> src/func/twitch.py:90:75
   |
88 |                 log.debug("User ID for %s: %s", user["login"], user["id"])
89 |
90 |     async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]:
   |                                                                           ^^^^
91 |         """Return a dict of currently live streams keyed by login name."""
92 |         await self.ensure_token()
   |
help: Replace with `dict`
   |
89 |
   -     async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]:
90 +     async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> dict[str, dict]:
91 |         """Return a dict of currently live streams keyed by login name."""
   |

I001 [*] Import block is un-sorted or un-formatted
  --> src/func/watcher.py:2:1
   |
 1 |   """Main orchestrator: polls Twitch and dispatches Discord webhook notifications."""
 2 | / import asyncio
 3 | | import logging
 4 | | import queue
 5 | | import random
 6 | | from datetime import datetime, timedelta
 7 | | from typing import Dict, Optional
 8 | | from zoneinfo import ZoneInfo
 9 | | import configparser
10 | |
11 | | import aiohttp
12 | | import ntplib
13 | |
14 | | from .models import StreamInfo
15 | | from .twitch import TwitchClient, TwitchAPIError
16 | | from .webhook import WebhookSender
   | |__________________________________^
17 |
18 |   log = logging.getLogger(__name__)
   |
help: Organize imports
   |
2  | import asyncio
3  + import configparser
4  | import logging
--------------------------------------------------------------------------------
9  | from zoneinfo import ZoneInfo
   - import configparser
10 |
--------------------------------------------------------------------------------
14 | from .models import StreamInfo
   - from .twitch import TwitchClient, TwitchAPIError
15 + from .twitch import TwitchAPIError, TwitchClient
16 | from .webhook import WebhookSender
   |

UP035 `typing.Dict` is deprecated, use `dict` instead
 --> src/func/watcher.py:7:1
  |
5 | import random
6 | from datetime import datetime, timedelta
7 | from typing import Dict, Optional
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 | from zoneinfo import ZoneInfo
9 | import configparser
  |

UP045 [*] Use `X | None` for type annotations
  --> src/func/watcher.py:28:70
   |
26 |     """Watches a set of Twitch streams and posts live notifications via Discord webhooks."""
27 |
28 |     def __init__(self, config: configparser.ConfigParser, log_queue: Optional[queue.Queue] = None):
   |                                                                      ^^^^^^^^^^^^^^^^^^^^^
29 |         dc = config["DISCORD"]
30 |         tw = config["TWITCH"]
   |
help: Convert to `X | None`
   |
27 |
   -     def __init__(self, config: configparser.ConfigParser, log_queue: Optional[queue.Queue] = None):
28 +     def __init__(self, config: configparser.ConfigParser, log_queue: queue.Queue | None = None):
29 |         dc = config["DISCORD"]
   |

UP006 [*] Use `dict` instead of `Dict` for type annotation
  --> src/func/watcher.py:50:23
   |
48 |         )
49 |
50 |         self.streams: Dict[str, StreamInfo] = {
   |                       ^^^^
51 |             name: StreamInfo(name=name)
52 |             for raw in tw["streams"].split(",")
   |
help: Replace with `dict`
   |
49 |
   -         self.streams: Dict[str, StreamInfo] = {
50 +         self.streams: dict[str, StreamInfo] = {
51 |             name: StreamInfo(name=name)
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/func/watcher.py:78:28
   |
76 |                     except aiohttp.ClientError as e:
77 |                         log.error("Network error: %s", e)
78 |                     except Exception as e:  # pylint: disable=broad-exception-caught
   |                            ^^^^^^^^^
79 |                         log.error("Unexpected tick error: %s", e)
80 |                     await asyncio.sleep(self.poll_interval)
   |

BLE001 Do not catch blind exception: `Exception`
   --> src/func/watcher.py:115:16
    |
113 |             offset = await loop.run_in_executor(None, _query_ntp)
114 |             return datetime.now(self.leet_tz) + timedelta(seconds=offset)
115 |         except Exception as exc:
    |                ^^^^^^^^^
116 |             log.warning("NTP query failed, using system time: %s", exc)
117 |             return datetime.now(self.leet_tz)
    |

BLE001 Do not catch blind exception: `Exception`
   --> src/func/watcher.py:133:16
    |
131 |                 accurate.strftime("%Y-%m-%d %H:%M:%S"),
132 |             )
133 |         except Exception as exc:
    |                ^^^^^^^^^
134 |             log.warning("NTP query failed at startup: %s", exc)
135 |             log.info(
    |

UP045 [*] Use `X | None` for type annotations
  --> src/func/webhook.py:17:23
   |
15 |         self.username = username
16 |         self.avatar_url = avatar_url
17 |         self.session: Optional[aiohttp.ClientSession] = None
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18 |
19 |     def _build_payload(self, content: str) -> dict:
   |
help: Convert to `X | None`
   |
16 |         self.avatar_url = avatar_url
   -         self.session: Optional[aiohttp.ClientSession] = None
17 +         self.session: aiohttp.ClientSession | None = None
18 |
   |

UP045 [*] Use `X | None` for type annotations
  --> src/func/webhook.py:27:57
   |
25 |         return payload
26 |
27 |     async def send(self, url: str, content: str, thumb: Optional[bytes] = None) -> None:
   |                                                         ^^^^^^^^^^^^^^^
28 |         """Post a message to the webhook, optionally with a thumbnail attachment."""
29 |         try:
   |
help: Convert to `X | None`
   |
26 |
   -     async def send(self, url: str, content: str, thumb: Optional[bytes] = None) -> None:
27 +     async def send(self, url: str, content: str, thumb: bytes | None = None) -> None:
28 |         """Post a message to the webhook, optionally with a thumbnail attachment."""
   |

UP045 [*] Use `X | None` for type annotations
  --> src/func/webhook.py:43:50
   |
41 |             log.error("Failed to send webhook: %s", e)
42 |
43 |     async def fetch_thumbnail(self, url: str) -> Optional[bytes]:
   |                                                  ^^^^^^^^^^^^^^^
44 |         """Download a stream thumbnail and return its raw bytes, or None on failure."""
45 |         url = url.replace("{width}", "500").replace("{height}", "281")
   |
help: Convert to `X | None`
   |
42 |
   -     async def fetch_thumbnail(self, url: str) -> Optional[bytes]:
43 +     async def fetch_thumbnail(self, url: str) -> bytes | None:
44 |         """Download a stream thumbnail and return its raw bytes, or None on failure."""
   |

EXE001 Shebang is present but file is not executable
 --> src/main.py:1:1
  |
1 | #!/usr/bin/python3
  | ^^^^^^^^^^^^^^^^^^
2 | """Entry point: configures logging and starts the StreamWatcher."""
3 | import asyncio
  |

I001 [*] Import block is un-sorted or un-formatted
 --> src/main.py:3:1
  |
1 |   #!/usr/bin/python3
2 |   """Entry point: configures logging and starts the StreamWatcher."""
3 | / import asyncio
4 | | import configparser
5 | | import logging
6 | | import os
7 | | import queue
8 | | from func.watcher import StreamWatcher
  | |______________________________________^
help: Organize imports
  |
7 | import queue
8 +
9 | from func.watcher import StreamWatcher
  |

BLE001 Do not catch blind exception: `Exception`
  --> src/main.py:21:16
   |
19 |         try:
20 |             self.queue.put_nowait(self.format(record))
21 |         except Exception:  # pylint: disable=broad-exception-caught
   |                ^^^^^^^^^
22 |             self.handleError(record)
   |

Found 20 errors.
[*] 13 fixable with the `--fix` option.
Pylint output
************* Module func.watcher
src/func/watcher.py:115:15: W0718: Catching too general exception Exception (broad-exception-caught)
src/func/watcher.py:133:15: W0718: Catching too general exception Exception (broad-exception-caught)

-----------------------------------
Your code has been rated at 9.93/10

Updated: 2026-08-02 22:57 UTC

<!-- ci-results --> ## CI Results | Check | Status | |---|---| | Ruff | ❌ | | Pylint (9.93/10) | ❌ | | Config | ✅ | | Imports | ✅ | <details><summary>Ruff output</summary> ``` I001 [*] Import block is un-sorted or un-formatted --> src/func/twitch.py:2:1 | 1 | """Twitch API client: token management, user ID lookup, stream status.""" 2 | / import asyncio 3 | | import time 4 | | import logging 5 | | from typing import Dict, Optional 6 | | 7 | | import aiohttp 8 | | 9 | | from .models import StreamInfo | |______________________________^ 10 | 11 | log = logging.getLogger(__name__) | help: Organize imports | 2 | import asyncio 3 + import logging 4 | import time - import logging 5 | from typing import Dict, Optional | UP035 `typing.Dict` is deprecated, use `dict` instead --> src/func/twitch.py:5:1 | 3 | import time 4 | import logging 5 | from typing import Dict, Optional | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | import aiohttp | UP045 [*] Use `X | None` for type annotations --> src/func/twitch.py:24:23 | 22 | self.client_id = client_id 23 | self.client_secret = client_secret 24 | self.session: Optional[aiohttp.ClientSession] = None | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 25 | self._token: Optional[str] = None 26 | self._token_expires: float = 0 | help: Convert to `X | None` | 23 | self.client_secret = client_secret - self.session: Optional[aiohttp.ClientSession] = None 24 + self.session: aiohttp.ClientSession | None = None 25 | self._token: Optional[str] = None | UP045 [*] Use `X | None` for type annotations --> src/func/twitch.py:25:22 | 23 | self.client_secret = client_secret 24 | self.session: Optional[aiohttp.ClientSession] = None 25 | self._token: Optional[str] = None | ^^^^^^^^^^^^^ 26 | self._token_expires: float = 0 | help: Convert to `X | None` | 24 | self.session: Optional[aiohttp.ClientSession] = None - self._token: Optional[str] = None 25 + self._token: str | None = None 26 | self._token_expires: float = 0 | UP006 [*] Use `dict` instead of `Dict` for type annotation --> src/func/twitch.py:80:45 | 78 | raise TwitchAPIError(f"Twitch API error: HTTP {r.status} for {url}") 79 | 80 | async def fetch_user_ids(self, streams: Dict[str, StreamInfo]) -> None: | ^^^^ 81 | """Resolve Twitch login names to user IDs and store them in each StreamInfo.""" 82 | await self.ensure_token() | help: Replace with `dict` | 79 | - async def fetch_user_ids(self, streams: Dict[str, StreamInfo]) -> None: 80 + async def fetch_user_ids(self, streams: dict[str, StreamInfo]) -> None: 81 | """Resolve Twitch login names to user IDs and store them in each StreamInfo.""" | UP006 [*] Use `dict` instead of `Dict` for type annotation --> src/func/twitch.py:90:49 | 88 | log.debug("User ID for %s: %s", user["login"], user["id"]) 89 | 90 | async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]: | ^^^^ 91 | """Return a dict of currently live streams keyed by login name.""" 92 | await self.ensure_token() | help: Replace with `dict` | 89 | - async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]: 90 + async def fetch_live_streams(self, streams: dict[str, StreamInfo]) -> Dict[str, dict]: 91 | """Return a dict of currently live streams keyed by login name.""" | UP006 [*] Use `dict` instead of `Dict` for type annotation --> src/func/twitch.py:90:75 | 88 | log.debug("User ID for %s: %s", user["login"], user["id"]) 89 | 90 | async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]: | ^^^^ 91 | """Return a dict of currently live streams keyed by login name.""" 92 | await self.ensure_token() | help: Replace with `dict` | 89 | - async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> Dict[str, dict]: 90 + async def fetch_live_streams(self, streams: Dict[str, StreamInfo]) -> dict[str, dict]: 91 | """Return a dict of currently live streams keyed by login name.""" | I001 [*] Import block is un-sorted or un-formatted --> src/func/watcher.py:2:1 | 1 | """Main orchestrator: polls Twitch and dispatches Discord webhook notifications.""" 2 | / import asyncio 3 | | import logging 4 | | import queue 5 | | import random 6 | | from datetime import datetime, timedelta 7 | | from typing import Dict, Optional 8 | | from zoneinfo import ZoneInfo 9 | | import configparser 10 | | 11 | | import aiohttp 12 | | import ntplib 13 | | 14 | | from .models import StreamInfo 15 | | from .twitch import TwitchClient, TwitchAPIError 16 | | from .webhook import WebhookSender | |__________________________________^ 17 | 18 | log = logging.getLogger(__name__) | help: Organize imports | 2 | import asyncio 3 + import configparser 4 | import logging -------------------------------------------------------------------------------- 9 | from zoneinfo import ZoneInfo - import configparser 10 | -------------------------------------------------------------------------------- 14 | from .models import StreamInfo - from .twitch import TwitchClient, TwitchAPIError 15 + from .twitch import TwitchAPIError, TwitchClient 16 | from .webhook import WebhookSender | UP035 `typing.Dict` is deprecated, use `dict` instead --> src/func/watcher.py:7:1 | 5 | import random 6 | from datetime import datetime, timedelta 7 | from typing import Dict, Optional | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 8 | from zoneinfo import ZoneInfo 9 | import configparser | UP045 [*] Use `X | None` for type annotations --> src/func/watcher.py:28:70 | 26 | """Watches a set of Twitch streams and posts live notifications via Discord webhooks.""" 27 | 28 | def __init__(self, config: configparser.ConfigParser, log_queue: Optional[queue.Queue] = None): | ^^^^^^^^^^^^^^^^^^^^^ 29 | dc = config["DISCORD"] 30 | tw = config["TWITCH"] | help: Convert to `X | None` | 27 | - def __init__(self, config: configparser.ConfigParser, log_queue: Optional[queue.Queue] = None): 28 + def __init__(self, config: configparser.ConfigParser, log_queue: queue.Queue | None = None): 29 | dc = config["DISCORD"] | UP006 [*] Use `dict` instead of `Dict` for type annotation --> src/func/watcher.py:50:23 | 48 | ) 49 | 50 | self.streams: Dict[str, StreamInfo] = { | ^^^^ 51 | name: StreamInfo(name=name) 52 | for raw in tw["streams"].split(",") | help: Replace with `dict` | 49 | - self.streams: Dict[str, StreamInfo] = { 50 + self.streams: dict[str, StreamInfo] = { 51 | name: StreamInfo(name=name) | BLE001 Do not catch blind exception: `Exception` --> src/func/watcher.py:78:28 | 76 | except aiohttp.ClientError as e: 77 | log.error("Network error: %s", e) 78 | except Exception as e: # pylint: disable=broad-exception-caught | ^^^^^^^^^ 79 | log.error("Unexpected tick error: %s", e) 80 | await asyncio.sleep(self.poll_interval) | BLE001 Do not catch blind exception: `Exception` --> src/func/watcher.py:115:16 | 113 | offset = await loop.run_in_executor(None, _query_ntp) 114 | return datetime.now(self.leet_tz) + timedelta(seconds=offset) 115 | except Exception as exc: | ^^^^^^^^^ 116 | log.warning("NTP query failed, using system time: %s", exc) 117 | return datetime.now(self.leet_tz) | BLE001 Do not catch blind exception: `Exception` --> src/func/watcher.py:133:16 | 131 | accurate.strftime("%Y-%m-%d %H:%M:%S"), 132 | ) 133 | except Exception as exc: | ^^^^^^^^^ 134 | log.warning("NTP query failed at startup: %s", exc) 135 | log.info( | UP045 [*] Use `X | None` for type annotations --> src/func/webhook.py:17:23 | 15 | self.username = username 16 | self.avatar_url = avatar_url 17 | self.session: Optional[aiohttp.ClientSession] = None | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | def _build_payload(self, content: str) -> dict: | help: Convert to `X | None` | 16 | self.avatar_url = avatar_url - self.session: Optional[aiohttp.ClientSession] = None 17 + self.session: aiohttp.ClientSession | None = None 18 | | UP045 [*] Use `X | None` for type annotations --> src/func/webhook.py:27:57 | 25 | return payload 26 | 27 | async def send(self, url: str, content: str, thumb: Optional[bytes] = None) -> None: | ^^^^^^^^^^^^^^^ 28 | """Post a message to the webhook, optionally with a thumbnail attachment.""" 29 | try: | help: Convert to `X | None` | 26 | - async def send(self, url: str, content: str, thumb: Optional[bytes] = None) -> None: 27 + async def send(self, url: str, content: str, thumb: bytes | None = None) -> None: 28 | """Post a message to the webhook, optionally with a thumbnail attachment.""" | UP045 [*] Use `X | None` for type annotations --> src/func/webhook.py:43:50 | 41 | log.error("Failed to send webhook: %s", e) 42 | 43 | async def fetch_thumbnail(self, url: str) -> Optional[bytes]: | ^^^^^^^^^^^^^^^ 44 | """Download a stream thumbnail and return its raw bytes, or None on failure.""" 45 | url = url.replace("{width}", "500").replace("{height}", "281") | help: Convert to `X | None` | 42 | - async def fetch_thumbnail(self, url: str) -> Optional[bytes]: 43 + async def fetch_thumbnail(self, url: str) -> bytes | None: 44 | """Download a stream thumbnail and return its raw bytes, or None on failure.""" | EXE001 Shebang is present but file is not executable --> src/main.py:1:1 | 1 | #!/usr/bin/python3 | ^^^^^^^^^^^^^^^^^^ 2 | """Entry point: configures logging and starts the StreamWatcher.""" 3 | import asyncio | I001 [*] Import block is un-sorted or un-formatted --> src/main.py:3:1 | 1 | #!/usr/bin/python3 2 | """Entry point: configures logging and starts the StreamWatcher.""" 3 | / import asyncio 4 | | import configparser 5 | | import logging 6 | | import os 7 | | import queue 8 | | from func.watcher import StreamWatcher | |______________________________________^ help: Organize imports | 7 | import queue 8 + 9 | from func.watcher import StreamWatcher | BLE001 Do not catch blind exception: `Exception` --> src/main.py:21:16 | 19 | try: 20 | self.queue.put_nowait(self.format(record)) 21 | except Exception: # pylint: disable=broad-exception-caught | ^^^^^^^^^ 22 | self.handleError(record) | Found 20 errors. [*] 13 fixable with the `--fix` option. ``` </details> <details><summary>Pylint output</summary> ``` ************* Module func.watcher src/func/watcher.py:115:15: W0718: Catching too general exception Exception (broad-exception-caught) src/func/watcher.py:133:15: W0718: Catching too general exception Exception (broad-exception-caught) ----------------------------------- Your code has been rated at 9.93/10 ``` </details> _Updated: 2026-08-02 22:57 UTC_
Some checks failed
Build and Push Docker Image / build (pull_request) Successful in 51s
PR Tests / test (pull_request) Failing after -5m4s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin renovate/aiohttp-3.x:renovate/aiohttp-3.x
git switch renovate/aiohttp-3.x

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch master
git merge --no-ff renovate/aiohttp-3.x
git switch renovate/aiohttp-3.x
git rebase master
git switch master
git merge --ff-only renovate/aiohttp-3.x
git switch renovate/aiohttp-3.x
git rebase master
git switch master
git merge --no-ff renovate/aiohttp-3.x
git switch master
git merge --squash renovate/aiohttp-3.x
git switch master
git merge --ff-only renovate/aiohttp-3.x
git switch master
git merge renovate/aiohttp-3.x
git push origin master
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
wirehack7/DiscordStreamBot!12
No description provided.