💥 Remove: 移除 Python 3.9 支持 (#3860)

This commit is contained in:
呵呵です
2026-02-18 00:11:36 +08:00
committed by GitHub
parent f719a6b41b
commit 63cde5da77
56 changed files with 603 additions and 1144 deletions

View File

@@ -1,10 +1,11 @@
from collections.abc import Generator
from collections.abc import Callable, Generator
from functools import wraps
import os
from pathlib import Path
import sys
import threading
from typing import TYPE_CHECKING, Callable, TypeVar
from types import EllipsisType
from typing import TYPE_CHECKING, TypeVar
from typing_extensions import ParamSpec
from nonebug import NONEBOT_INIT_KWARGS
@@ -50,12 +51,12 @@ def anyio_backend(request: pytest.FixtureRequest):
def run_once(func: Callable[P, R]) -> Callable[P, R]:
result = ...
result: R | EllipsisType = ...
@wraps(func)
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
nonlocal result
if result is not Ellipsis:
if result is not ...:
return result
result = func(*args, **kwargs)

View File

@@ -1,7 +1,7 @@
import base64
import json
import socket
from typing import TypeVar, Union
from typing import TypeVar
from werkzeug import Request, Response
from werkzeug.datastructures import MultiDict
@@ -36,7 +36,7 @@ def json_safe(string, content_type="application/octet-stream") -> str:
).decode("utf-8")
def flattern(d: "MultiDict[K, V]") -> dict[K, Union[V, list[V]]]:
def flattern(d: "MultiDict[K, V]") -> dict[K, V | list[V]]:
return {k: v[0] if len(v) == 1 else v for k, v in d.to_dict(flat=False).items()}

View File

@@ -1,4 +1,4 @@
from typing import TypeVar, Union
from typing import TypeVar
from nonebot.adapters import Bot
@@ -28,7 +28,7 @@ async def sub_bot(b: FooBot) -> FooBot:
class BarBot(Bot): ...
async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]:
async def union_bot(b: FooBot | BarBot) -> FooBot | BarBot:
return b
@@ -46,4 +46,4 @@ async def generic_bot_none(b: CB) -> CB:
return b
async def not_bot(b: Union[int, Bot]): ...
async def not_bot(b: int | Bot): ...

View File

@@ -1,4 +1,4 @@
from typing import TypeVar, Union
from typing import TypeVar
from nonebot.adapters import Event, Message
from nonebot.params import EventMessage, EventPlainText, EventToMe, EventType
@@ -29,7 +29,7 @@ async def sub_event(e: FooEvent) -> FooEvent:
class BarEvent(Event): ...
async def union_event(e: Union[FooEvent, BarEvent]) -> Union[FooEvent, BarEvent]:
async def union_event(e: FooEvent | BarEvent) -> FooEvent | BarEvent:
return e
@@ -47,7 +47,7 @@ async def generic_event_none(e: CE) -> CE:
return e
async def not_event(e: Union[int, Event]): ...
async def not_event(e: int | Event): ...
async def event_type(t: str = EventType()) -> str:

View File

@@ -1,7 +1,4 @@
from typing import Union
async def exc(e: Exception, x: Union[ValueError, TypeError]) -> Exception:
async def exc(e: Exception, x: ValueError | TypeError) -> Exception:
assert e == x
return e

View File

@@ -1,4 +1,4 @@
from typing import Any, TypeVar, Union
from typing import Any, TypeVar
from nonebot.adapters import Event
from nonebot.matcher import Matcher
@@ -36,8 +36,8 @@ class BarMatcher(Matcher): ...
async def union_matcher(
m: Union[FooMatcher, BarMatcher],
) -> Union[FooMatcher, BarMatcher]:
m: FooMatcher | BarMatcher,
) -> FooMatcher | BarMatcher:
return m
@@ -55,7 +55,7 @@ async def generic_matcher_none(m: CM) -> CM:
return m
async def not_matcher(m: Union[int, Matcher]): ...
async def not_matcher(m: int | Matcher): ...
async def receive(e: Event = Received("test")) -> Event:

View File

@@ -1,5 +1,3 @@
from typing import Optional
from nonebot.adapters import Bot, Event, Message
from nonebot.matcher import Matcher
from nonebot.params import Arg, Depends
@@ -12,11 +10,11 @@ def dependency():
async def complex_priority(
sub: int = Depends(dependency),
bot: Optional[Bot] = None,
event: Optional[Event] = None,
bot: Bot | None = None,
event: Event | None = None,
state: T_State = {},
matcher: Optional[Matcher] = None,
matcher: Matcher | None = None,
arg: Message = Arg(),
exception: Optional[Exception] = None,
exception: Exception | None = None,
default: int = 1,
): ...

View File

@@ -1,5 +1,4 @@
from contextlib import asynccontextmanager
from typing import Optional
from nonebug import App
import pytest
@@ -19,8 +18,8 @@ from utils import FakeAdapter
@pytest.mark.anyio
async def test_adapter_connect(app: App, driver: Driver):
last_connect_bot: Optional[Bot] = None
last_disconnect_bot: Optional[Bot] = None
last_connect_bot: Bot | None = None
last_disconnect_bot: Bot | None = None
def _fake_bot_connect(bot: Bot):
nonlocal last_connect_bot
@@ -75,8 +74,8 @@ async def test_adapter_connect(app: App, driver: Driver):
indirect=True,
)
def test_adapter_server(driver: Driver):
last_http_setup: Optional[HTTPServerSetup] = None
last_ws_setup: Optional[WebSocketServerSetup] = None
last_http_setup: HTTPServerSetup | None = None
last_ws_setup: WebSocketServerSetup | None = None
def _fake_setup_http_server(setup: HTTPServerSetup):
nonlocal last_http_setup
@@ -142,7 +141,7 @@ def test_adapter_server(driver: Driver):
indirect=True,
)
async def test_adapter_http_client(driver: Driver):
last_request: Optional[Request] = None
last_request: Request | None = None
async def _fake_request(request: Request):
nonlocal last_request
@@ -190,7 +189,7 @@ async def test_adapter_http_client(driver: Driver):
)
async def test_adapter_websocket_client(driver: Driver):
_fake_ws = object()
_last_request: Optional[Request] = None
_last_request: Request | None = None
@asynccontextmanager
async def _fake_websocket(setup: Request):

View File

@@ -1,4 +1,4 @@
from typing import Any, Optional
from typing import Any
import anyio
from nonebug import App
@@ -123,7 +123,7 @@ async def test_bot_called_api_hook_simple(app: App):
async def called_api_hook(
bot: Bot,
exception: Optional[Exception],
exception: Exception | None,
api: str,
data: dict[str, Any],
result: Any,
@@ -155,7 +155,7 @@ async def test_bot_called_api_hook_mock(app: App):
async def called_api_hook(
bot: Bot,
exception: Optional[Exception],
exception: Exception | None,
api: str,
data: dict[str, Any],
result: Any,
@@ -201,7 +201,7 @@ async def test_bot_called_api_hook_multi_mock(app: App):
async def called_api_hook1(
bot: Bot,
exception: Optional[Exception],
exception: Exception | None,
api: str,
data: dict[str, Any],
result: Any,
@@ -214,7 +214,7 @@ async def test_bot_called_api_hook_multi_mock(app: App):
async def called_api_hook2(
bot: Bot,
exception: Optional[Exception],
exception: Exception | None,
api: str,
data: dict[str, Any],
result: Any,

View File

@@ -1,5 +1,4 @@
import sys
from typing import Optional
from nonebug import App
import pytest
@@ -326,7 +325,7 @@ async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
event: Event,
state: T_State,
matcher: Matcher,
exception: Optional[Exception],
exception: Exception | None,
sub: int = Depends(_dependency),
default: int = 1,
):

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Annotated, Any, Optional
from typing import Annotated, Any
from pydantic import BaseModel, ValidationError
import pytest
@@ -144,7 +144,7 @@ def test_validate_json():
test3: bool
test4: dict
test5: list
test6: Optional[int]
test6: int | None
assert type_validate_json(
TestModel,

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING
from pydantic import BaseModel, Field
import pytest
@@ -16,8 +16,8 @@ class Simple(BaseModel):
class Example(BaseSettings):
if TYPE_CHECKING:
_env_file: Optional[DOTENV_TYPE] = ".env", ".env.example"
_env_nested_delimiter: Optional[str] = "__"
_env_file: DOTENV_TYPE | None = ".env", ".env.example"
_env_nested_delimiter: str | None = "__"
if PYDANTIC_V2:
model_config = SettingsConfig(
@@ -32,10 +32,10 @@ class Example(BaseSettings):
env_nested_delimiter = "__"
simple: str = ""
int_str: Union[int, str] = LegacyUnionField(default="")
int_str: int | str = LegacyUnionField(default="")
complex: list[int] = Field(default=[1])
complex_none: Optional[list[int]] = None
complex_union: Union[int, list[int]] = 1
complex_none: list[int] | None = None
complex_union: int | list[int] = 1
nested: Simple = Simple()
nested_inner: Simple = Simple()
aliased_simple: str = Field(default="", alias="alias_simple")

View File

@@ -1,6 +1,6 @@
from http.cookies import SimpleCookie
import json
from typing import Any, Optional
from typing import Any
from aiohttp import ClientSession, ClientWebSocketResponse, WSMessage, WSMsgType
import anyio
@@ -173,7 +173,7 @@ async def test_websocket_server(app: App, driver: Driver):
async def test_cross_context(app: App, driver: Driver):
assert isinstance(driver, ASGIMixin)
ws: Optional[WebSocket] = None
ws: WebSocket | None = None
ws_ready = anyio.Event()
ws_should_close = anyio.Event()
@@ -651,7 +651,7 @@ async def test_aiohttp_websocket_close_frame(msg_type: str) -> None:
def closed(self) -> bool:
return True
async def receive(self, timeout: Optional[float] = None) -> WSMessage: # noqa: ASYNC109
async def receive(self, timeout: float | None = None) -> WSMessage: # noqa: ASYNC109
return WSMessage(type=WSMsgType[msg_type], data=None, extra=None)
async with ClientSession() as session:

View File

@@ -1,5 +1,3 @@
from typing import Optional
from nonebug import App
import pytest
@@ -138,7 +136,7 @@ async def test_superuser(app: App, type: str, user_id: str, expected: bool):
],
)
async def test_user(
app: App, session_ids: tuple[str, ...], session_id: Optional[str], expected: bool
app: App, session_ids: tuple[str, ...], session_id: str | None, expected: bool
):
dependent = next(iter(USER(*session_ids).checkers))
checker = dependent.call

View File

@@ -1,8 +1,9 @@
from collections.abc import Callable
from dataclasses import asdict
from functools import wraps
from pathlib import Path
import sys
from typing import Callable, TypeVar
from typing import TypeVar
from typing_extensions import ParamSpec
import pytest

View File

@@ -1,4 +1,4 @@
from typing import Callable, Optional
from collections.abc import Callable
import pytest
@@ -103,7 +103,7 @@ from nonebot.typing import T_RuleChecker
)
def test_on(
matcher_name: str,
pre_rule_factory: Optional[Callable[[type[Event]], T_RuleChecker]],
pre_rule_factory: Callable[[type[Event]], T_RuleChecker] | None,
has_permission: bool,
):
import plugins.plugin.matchers as module

View File

@@ -1,6 +1,5 @@
import re
from re import Match
from typing import Optional, Union
from nonebug import App
import pytest
@@ -163,10 +162,10 @@ async def test_trie(app: App):
],
)
async def test_startswith(
msg: Union[str, tuple[str, ...]],
msg: str | tuple[str, ...],
ignorecase: bool,
type: str,
text: Optional[str],
text: str | None,
expected: bool,
):
test_startswith = startswith(msg, ignorecase)
@@ -203,10 +202,10 @@ async def test_startswith(
],
)
async def test_endswith(
msg: Union[str, tuple[str, ...]],
msg: str | tuple[str, ...],
ignorecase: bool,
type: str,
text: Optional[str],
text: str | None,
expected: bool,
):
test_endswith = endswith(msg, ignorecase)
@@ -243,10 +242,10 @@ async def test_endswith(
],
)
async def test_fullmatch(
msg: Union[str, tuple[str, ...]],
msg: str | tuple[str, ...],
ignorecase: bool,
type: str,
text: Optional[str],
text: str | None,
expected: bool,
):
test_fullmatch = fullmatch(msg, ignorecase)
@@ -281,7 +280,7 @@ async def test_fullmatch(
async def test_keyword(
kws: tuple[str, ...],
type: str,
text: Optional[str],
text: str | None,
expected: bool,
):
test_keyword = keyword(*kws)
@@ -324,10 +323,10 @@ async def test_keyword(
)
async def test_command(
cmds: tuple[tuple[str, ...]],
force_whitespace: Optional[Union[str, bool]],
force_whitespace: str | bool | None,
cmd: tuple[str, ...],
whitespace: Optional[str],
arg_text: Optional[str],
whitespace: str | None,
arg_text: str | None,
expected: bool,
):
test_command = command(*cmds, force_whitespace=force_whitespace)
@@ -492,9 +491,9 @@ async def test_shell_command():
async def test_regex(
pattern: str,
type: str,
text: Optional[str],
text: str | None,
expected: bool,
matched: Optional[Match[str]],
matched: Match[str] | None,
):
test_regex = regex(pattern)
dependent = next(iter(test_regex.checkers))
@@ -507,7 +506,7 @@ async def test_regex(
event = make_fake_event(_type=type, _message=message)()
state = {}
assert await dependent(event=event, state=state) == expected
result: Optional[Match[str]] = state.get(REGEX_MATCHED)
result: Match[str] | None = state.get(REGEX_MATCHED)
if matched is None:
assert result is None
else:

View File

@@ -23,7 +23,8 @@ def test_loguru_escape_tag():
def test_generic_check_issubclass():
assert generic_check_issubclass(int, (int, float))
assert not generic_check_issubclass(str, (int, float))
assert generic_check_issubclass(Union[int, float, None], (int, float))
assert generic_check_issubclass(Union[int, float, None], (int, float)) # noqa: UP007
assert generic_check_issubclass(int | float | None, (int, float))
assert generic_check_issubclass(Literal[1, 2, 3], int)
assert not generic_check_issubclass(Literal[1, 2, "3"], int)
assert generic_check_issubclass(List[int], list) # noqa: UP006

View File

@@ -1,5 +1,4 @@
from collections.abc import Iterable, Mapping
from typing import Optional, Union
from typing_extensions import override
from pydantic import create_model
@@ -60,7 +59,7 @@ class FakeMessage(Message[FakeMessageSegment]):
@staticmethod
@override
def _construct(msg: Union[str, Iterable[Mapping]]):
def _construct(msg: str | Iterable[Mapping]):
if isinstance(msg, str):
yield FakeMessageSegment.text(msg)
else:
@@ -69,21 +68,19 @@ class FakeMessage(Message[FakeMessageSegment]):
return
@override
def __add__(
self, other: Union[str, FakeMessageSegment, Iterable[FakeMessageSegment]]
):
def __add__(self, other: str | FakeMessageSegment | Iterable[FakeMessageSegment]):
other = escape_text(other) if isinstance(other, str) else other
return super().__add__(other)
def make_fake_event(
_base: Optional[type[Event]] = None,
_base: type[Event] | None = None,
_type: str = "message",
_name: str = "test",
_description: str = "test",
_user_id: Optional[str] = "test",
_session_id: Optional[str] = "test",
_message: Optional[Message] = None,
_user_id: str | None = "test",
_session_id: str | None = "test",
_message: Message | None = None,
_to_me: bool = True,
**fields,
) -> type[Event]: