Added additional Ruff rule sets and fixed all violations.
CI / Formatting (push) Successful in 11s
CI / Linting (push) Successful in 13s
CI / Tests (Python 3.12) (push) Successful in 25s
CI / Tests (Python 3.13) (push) Successful in 25s
CI / Tests (Python 3.14) (push) Successful in 24s
CI / Type Checking (push) Successful in 25s
CI / Formatting (push) Successful in 11s
CI / Linting (push) Successful in 13s
CI / Tests (Python 3.12) (push) Successful in 25s
CI / Tests (Python 3.13) (push) Successful in 25s
CI / Tests (Python 3.14) (push) Successful in 24s
CI / Type Checking (push) Successful in 25s
This commit is contained in:
@@ -324,7 +324,7 @@ class Config:
|
|||||||
logger.debug(f"Loading configuration from: {self.config_path.absolute()}")
|
logger.debug(f"Loading configuration from: {self.config_path.absolute()}")
|
||||||
if self.config_path.exists():
|
if self.config_path.exists():
|
||||||
try:
|
try:
|
||||||
with open(self.config_path) as f:
|
with self.config_path.open() as f:
|
||||||
self._data = yaml.safe_load(f) or {}
|
self._data = yaml.safe_load(f) or {}
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
logger.error(f"Failed to parse config file: {e}")
|
logger.error(f"Failed to parse config file: {e}")
|
||||||
@@ -385,7 +385,7 @@ class Config:
|
|||||||
"""Write current configuration to the YAML file."""
|
"""Write current configuration to the YAML file."""
|
||||||
logger.debug(f"Saving configuration to: {self.config_path.absolute()}")
|
logger.debug(f"Saving configuration to: {self.config_path.absolute()}")
|
||||||
try:
|
try:
|
||||||
with open(self.config_path, "w") as f:
|
with self.config_path.open("w") as f:
|
||||||
yaml.safe_dump(
|
yaml.safe_dump(
|
||||||
self._ordered_data(), f, default_flow_style=False, sort_keys=False
|
self._ordered_data(), f, default_flow_style=False, sort_keys=False
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -273,7 +273,9 @@ def _parse_placeholder_date(date_str: str) -> datetime | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dt = datetime.strptime(date_part, "%b %d %Y %I:%M:%S %p")
|
# Intentionally naive: tz abbreviation is resolved separately via
|
||||||
|
# TIMEZONE_OFFSETS since strptime's %z only handles numeric offsets.
|
||||||
|
dt = datetime.strptime(date_part, "%b %d %Y %I:%M:%S %p") # noqa: DTZ007
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -369,19 +371,17 @@ async def _evaluate_count(
|
|||||||
(counter_name, delta, delta),
|
(counter_name, delta, delta),
|
||||||
)
|
)
|
||||||
return str(result)
|
return str(result)
|
||||||
else:
|
try:
|
||||||
try:
|
value = int(modifier_str)
|
||||||
value = int(modifier_str)
|
except ValueError as e:
|
||||||
except ValueError as e:
|
raise PlaceholderError(
|
||||||
raise PlaceholderError(
|
"Invalid $(count): modifier must be an integer (e.g., +5, -1, 0)"
|
||||||
"Invalid $(count): modifier must be an integer (e.g., +5, -1, 0)"
|
) from e
|
||||||
) from e
|
result = await ctx.storage.fetch_value(
|
||||||
result = await ctx.storage.fetch_value(
|
"INSERT OR REPLACE INTO counters (name, value) VALUES (?, ?) RETURNING value",
|
||||||
"INSERT OR REPLACE INTO counters (name, value) VALUES (?, ?) "
|
(counter_name, value),
|
||||||
"RETURNING value",
|
)
|
||||||
(counter_name, value),
|
return str(result)
|
||||||
)
|
|
||||||
return str(result)
|
|
||||||
|
|
||||||
|
|
||||||
async def _evaluate_getcount(
|
async def _evaluate_getcount(
|
||||||
@@ -429,7 +429,7 @@ async def _evaluate_rand(
|
|||||||
raise PlaceholderError(
|
raise PlaceholderError(
|
||||||
"Invalid $(rand): arguments must be integers, e.g., $(rand 1 100)"
|
"Invalid $(rand): arguments must be integers, e.g., $(rand 1 100)"
|
||||||
) from e
|
) from e
|
||||||
return str(random.randint(min(start, stop), max(start, stop)))
|
return str(random.randint(min(start, stop), max(start, stop))) # noqa: S311
|
||||||
|
|
||||||
|
|
||||||
async def _evaluate_countdown(
|
async def _evaluate_countdown(
|
||||||
@@ -455,8 +455,7 @@ async def _evaluate_countdown(
|
|||||||
seconds = int(delta.total_seconds())
|
seconds = int(delta.total_seconds())
|
||||||
if seconds > 0:
|
if seconds > 0:
|
||||||
return _format_duration(seconds)
|
return _format_duration(seconds)
|
||||||
else:
|
return "0 seconds"
|
||||||
return "0 seconds"
|
|
||||||
|
|
||||||
|
|
||||||
type PlaceholderHandler = Callable[
|
type PlaceholderHandler = Callable[
|
||||||
|
|||||||
@@ -186,10 +186,9 @@ def _parse_placeholder(
|
|||||||
)
|
)
|
||||||
if found_close:
|
if found_close:
|
||||||
return PlaceholderNode(name, child_nodes), new_pos
|
return PlaceholderNode(name, child_nodes), new_pos
|
||||||
else:
|
# Unclosed placeholder, degrade to literal. Return None so
|
||||||
# Unclosed placeholder, degrade to literal. Return None so
|
# the caller emits "$(" as literal and re-scans the rest.
|
||||||
# the caller emits "$(" as literal and re-scans the rest.
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
# The character after the name is something unexpected (e.g. another $).
|
# The character after the name is something unexpected (e.g. another $).
|
||||||
# Treat as unclosed/invalid -- degrade.
|
# Treat as unclosed/invalid -- degrade.
|
||||||
|
|||||||
@@ -401,16 +401,16 @@ class RouteDispatcher:
|
|||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
return web.Response(status=204) # No Content.
|
return web.Response(status=204) # No Content.
|
||||||
elif isinstance(result, web.StreamResponse):
|
if isinstance(result, web.StreamResponse):
|
||||||
return result
|
return result
|
||||||
elif isinstance(result, dict):
|
if isinstance(result, dict):
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
else: # pragma: no branch — defensive against untyped handlers
|
# pragma: no branch — defensive against untyped handlers
|
||||||
mod_logger.error( # type: ignore[unreachable]
|
mod_logger.error( # type: ignore[unreachable]
|
||||||
f"Route handler '{route_info.full_path}' returned "
|
f"Route handler '{route_info.full_path}' returned "
|
||||||
f"unsupported type: {type(result).__name__}"
|
f"unsupported type: {type(result).__name__}"
|
||||||
)
|
)
|
||||||
return web.Response(status=500)
|
return web.Response(status=500)
|
||||||
|
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
await module_ctx.storage._rollback()
|
await module_ctx.storage._rollback()
|
||||||
|
|||||||
+41
-6
@@ -68,16 +68,49 @@ extend-exclude = ["owlbot/_version.py"] # auto-generated by hatch-vcs
|
|||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = [
|
select = [
|
||||||
|
# Core
|
||||||
"F", # Pyflakes
|
"F", # Pyflakes
|
||||||
"E", # pycodestyle errors
|
"E", # pycodestyle errors
|
||||||
"W", # pycodestyle warnings
|
"W", # pycodestyle warnings
|
||||||
"I", # isort
|
"N", # pep8-naming
|
||||||
"UP", # pyupgrade
|
|
||||||
"B", # flake8-bugbear
|
|
||||||
"SIM", # flake8-simplify
|
|
||||||
"TCH", # flake8-type-checking
|
|
||||||
"RUF", # Ruff-specific rules
|
|
||||||
"D", # pydocstyle
|
"D", # pydocstyle
|
||||||
|
"I", # isort
|
||||||
|
"ICN", # flake8-import-conventions
|
||||||
|
|
||||||
|
# Correctness & bugs
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"ASYNC", # flake8-async
|
||||||
|
"DTZ", # flake8-datetimez
|
||||||
|
"RSE", # flake8-raise
|
||||||
|
"RET", # flake8-return
|
||||||
|
"A", # flake8-builtins
|
||||||
|
"PIE", # flake8-pie
|
||||||
|
|
||||||
|
# Modernization & simplification
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"FLY", # flynt (f-string conversion)
|
||||||
|
"PTH", # flake8-use-pathlib
|
||||||
|
|
||||||
|
# Performance
|
||||||
|
"PERF", # Perflint
|
||||||
|
|
||||||
|
# Security
|
||||||
|
"S", # flake8-bandit
|
||||||
|
|
||||||
|
# Code hygiene
|
||||||
|
"T10", # flake8-debugger
|
||||||
|
"T20", # flake8-print
|
||||||
|
"ERA", # eradicate
|
||||||
|
"PGH", # pygrep-hooks
|
||||||
|
"TCH", # flake8-type-checking
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
"PT", # flake8-pytest-style
|
||||||
|
|
||||||
|
# Ruff-specific
|
||||||
|
"RUF", # Ruff-specific rules
|
||||||
]
|
]
|
||||||
ignore = [
|
ignore = [
|
||||||
"D203", # incompatible with D211 (no blank line before class docstring)
|
"D203", # incompatible with D211 (no blank line before class docstring)
|
||||||
@@ -86,6 +119,8 @@ ignore = [
|
|||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"owlbot/__init__.py" = ["E402"] # constants defined before imports intentionally
|
"owlbot/__init__.py" = ["E402"] # constants defined before imports intentionally
|
||||||
|
"owlbot/__main__.py" = ["T201"] # CLI entry point uses print() for user output
|
||||||
|
"tests/**" = ["S101", "S311"] # assert is standard for pytest; random is fine in tests
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source = ["owlbot"]
|
source = ["owlbot"]
|
||||||
|
|||||||
+16
-19
@@ -117,7 +117,7 @@ class TestOnCommandDecorator:
|
|||||||
"""Tests the @on_command decorator from owlbot.api.commands."""
|
"""Tests the @on_command decorator from owlbot.api.commands."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"name,aliases,auth,mod,cooldown",
|
("name", "aliases", "auth", "mod", "cooldown"),
|
||||||
[
|
[
|
||||||
pytest.param("ping", None, False, False, 0, id="defaults"),
|
pytest.param("ping", None, False, False, 0, id="defaults"),
|
||||||
pytest.param("cmd", ["c", "cm"], False, False, 0, id="with-aliases"),
|
pytest.param("cmd", ["c", "cm"], False, False, 0, id="with-aliases"),
|
||||||
@@ -284,12 +284,11 @@ class TestCommandRegistry:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
registry.register("ping", handler, module_name="mod_a")
|
registry.register("ping", handler, module_name="mod_a")
|
||||||
with pytest.raises(ValueError) as exc_info:
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=r"Command trigger 'ping' conflicts with existing command 'ping'",
|
||||||
|
):
|
||||||
registry.register("ping", handler, module_name="mod_b")
|
registry.register("ping", handler, module_name="mod_b")
|
||||||
assert (
|
|
||||||
str(exc_info.value)
|
|
||||||
== "Command trigger 'ping' conflicts with existing command 'ping'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_register_conflict_alias_with_name(self) -> None:
|
def test_register_conflict_alias_with_name(self) -> None:
|
||||||
"""Alias that conflicts with an existing command name raises ValueError."""
|
"""Alias that conflicts with an existing command name raises ValueError."""
|
||||||
@@ -299,12 +298,11 @@ class TestCommandRegistry:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
registry.register("ping", handler, module_name="mod_a")
|
registry.register("ping", handler, module_name="mod_a")
|
||||||
with pytest.raises(ValueError) as exc_info:
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=r"Command trigger 'ping' conflicts with existing command 'ping'",
|
||||||
|
):
|
||||||
registry.register("other", handler, aliases=["ping"], module_name="mod_b")
|
registry.register("other", handler, aliases=["ping"], module_name="mod_b")
|
||||||
assert (
|
|
||||||
str(exc_info.value)
|
|
||||||
== "Command trigger 'ping' conflicts with existing command 'ping'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_register_conflict_alias_with_alias(self) -> None:
|
def test_register_conflict_alias_with_alias(self) -> None:
|
||||||
"""Alias that conflicts with an existing alias raises ValueError."""
|
"""Alias that conflicts with an existing alias raises ValueError."""
|
||||||
@@ -314,12 +312,11 @@ class TestCommandRegistry:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
registry.register("cmd1", handler, aliases=["c"], module_name="mod_a")
|
registry.register("cmd1", handler, aliases=["c"], module_name="mod_a")
|
||||||
with pytest.raises(ValueError) as exc_info:
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=r"Command trigger 'c' conflicts with existing command 'cmd1'",
|
||||||
|
):
|
||||||
registry.register("cmd2", handler, aliases=["c"], module_name="mod_b")
|
registry.register("cmd2", handler, aliases=["c"], module_name="mod_b")
|
||||||
assert (
|
|
||||||
str(exc_info.value)
|
|
||||||
== "Command trigger 'c' conflicts with existing command 'cmd1'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_exists_true(self) -> None:
|
def test_exists_true(self) -> None:
|
||||||
"""Registered command returns True."""
|
"""Registered command returns True."""
|
||||||
@@ -447,7 +444,7 @@ class TestCommandRegistry:
|
|||||||
assert registry.unregister_by_module("no_such") == 0
|
assert registry.unregister_by_module("no_such") == 0
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"message,prefix,expected",
|
("message", "prefix", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("!ping", "!", ("ping", ""), id="simple-command"),
|
pytest.param("!ping", "!", ("ping", ""), id="simple-command"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -921,7 +918,7 @@ class TestModuleCommands:
|
|||||||
assert mod_cmds["mod_a"].module_commands == {}
|
assert mod_cmds["mod_a"].module_commands == {}
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"owner,other_setup,call_module,trigger,expected",
|
("owner", "other_setup", "call_module", "trigger", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("mod_a", None, "mod_a", "ping", True, id="own-command"),
|
pytest.param("mod_a", None, "mod_a", "ping", True, id="own-command"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -1020,7 +1017,7 @@ class TestCommandContext:
|
|||||||
return cmd_ctx, module_ctx, cmd_event, event_ctx
|
return cmd_ctx, module_ctx, cmd_event, event_ctx
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"prop,use_is",
|
("prop", "use_is"),
|
||||||
[
|
[
|
||||||
pytest.param("module_name", False, id="module-name"),
|
pytest.param("module_name", False, id="module-name"),
|
||||||
pytest.param("storage", True, id="storage"),
|
pytest.param("storage", True, id="storage"),
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class TestParseTimestamp:
|
|||||||
"""Exercises _parse_timestamp() with various input formats."""
|
"""Exercises _parse_timestamp() with various input formats."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"ts,expected",
|
("ts", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(None, None, id="none-input"),
|
pytest.param(None, None, id="none-input"),
|
||||||
pytest.param("", None, id="empty-string"),
|
pytest.param("", None, id="empty-string"),
|
||||||
@@ -221,7 +221,7 @@ class TestUser:
|
|||||||
assert user.scopes == []
|
assert user.scopes == []
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"scopes,expected",
|
("scopes", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(["MODERATOR"], True, id="has-moderator"),
|
pytest.param(["MODERATOR"], True, id="has-moderator"),
|
||||||
pytest.param(["OTHER"], False, id="other-scope"),
|
pytest.param(["OTHER"], False, id="other-scope"),
|
||||||
@@ -255,7 +255,7 @@ class TestChatEvent:
|
|||||||
assert event.timestamp == datetime(2026, 1, 15, 12, 0, tzinfo=UTC)
|
assert event.timestamp == datetime(2026, 1, 15, 12, 0, tzinfo=UTC)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"raw,expected",
|
("raw", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("<p>hello</p>", "hello", id="strips-p-tags"),
|
pytest.param("<p>hello</p>", "hello", id="strips-p-tags"),
|
||||||
pytest.param("hello", "hello", id="no-tags"),
|
pytest.param("hello", "hello", id="no-tags"),
|
||||||
@@ -518,7 +518,7 @@ class TestParseEvent:
|
|||||||
"""Exercises the top-level parse_event() dispatcher."""
|
"""Exercises the top-level parse_event() dispatcher."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"type_str,expected_type,expected_class",
|
("type_str", "expected_type", "expected_class"),
|
||||||
[
|
[
|
||||||
pytest.param("CHAT", EventType.CHAT, ChatEvent, id="chat"),
|
pytest.param("CHAT", EventType.CHAT, ChatEvent, id="chat"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -592,7 +592,7 @@ class TestLogEvent:
|
|||||||
"""Exercises log_event() output via caplog."""
|
"""Exercises log_event() output via caplog."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"event_type,event,expected_message",
|
("event_type", "event", "expected_message"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
EventType.CHAT,
|
EventType.CHAT,
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class TestOnEventDecorator:
|
|||||||
"""Tests the @on_event decorator from owlbot.api.events."""
|
"""Tests the @on_event decorator from owlbot.api.events."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"event_types,priority,expected_types,expected_priority",
|
("event_types", "priority", "expected_types", "expected_priority"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(EventType.CHAT,),
|
(EventType.CHAT,),
|
||||||
@@ -173,7 +173,7 @@ class TestPriority:
|
|||||||
"""Tests the Priority IntEnum from owlbot.api.events."""
|
"""Tests the Priority IntEnum from owlbot.api.events."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"member,value",
|
("member", "value"),
|
||||||
[
|
[
|
||||||
pytest.param(Priority.HIGHEST, 100, id="highest"),
|
pytest.param(Priority.HIGHEST, 100, id="highest"),
|
||||||
pytest.param(Priority.HIGH, 75, id="high"),
|
pytest.param(Priority.HIGH, 75, id="high"),
|
||||||
@@ -294,7 +294,7 @@ class TestEventRegistry:
|
|||||||
assert registry.get_handler_module(handler) is None
|
assert registry.get_handler_module(handler) is None
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"setup_types,expected_return,expected_remaining",
|
("setup_types", "expected_return", "expected_remaining"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(EventType.CHAT,),
|
(EventType.CHAT,),
|
||||||
@@ -795,7 +795,7 @@ class TestCommandDispatchPhase:
|
|||||||
"""Tests Phase 2 (command dispatch) in EventDispatcher.dispatch()."""
|
"""Tests Phase 2 (command dispatch) in EventDispatcher.dispatch()."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"event_type,event_factory,expected_calls",
|
("event_type", "event_factory", "expected_calls"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
EventType.CHAT,
|
EventType.CHAT,
|
||||||
@@ -945,7 +945,7 @@ class TestModuleEvents:
|
|||||||
assert mod_events.module_events == {}
|
assert mod_events.module_events == {}
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"register_on,unregister_from,expected",
|
("register_on", "unregister_from", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("mod_a", "mod_a", True, id="own-handler"),
|
pytest.param("mod_a", "mod_a", True, id="own-handler"),
|
||||||
pytest.param("mod_b", "mod_a", False, id="other-modules-handler"),
|
pytest.param("mod_b", "mod_a", False, id="other-modules-handler"),
|
||||||
@@ -1028,7 +1028,7 @@ class TestEventContext:
|
|||||||
"""Tests EventContext from owlbot.api.context."""
|
"""Tests EventContext from owlbot.api.context."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"prop,use_is",
|
("prop", "use_is"),
|
||||||
[
|
[
|
||||||
pytest.param("module_name", False, id="module-name"),
|
pytest.param("module_name", False, id="module-name"),
|
||||||
pytest.param("storage", True, id="storage"),
|
pytest.param("storage", True, id="storage"),
|
||||||
|
|||||||
+17
-17
@@ -70,7 +70,7 @@ class TestSimpleSubstitution:
|
|||||||
"""Basic happy-path usage of placeholders and plain text."""
|
"""Basic happy-path usage of placeholders and plain text."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,kwargs,expected",
|
("template", "kwargs", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"Just a plain message.", {}, "Just a plain message.", id="plain-text"
|
"Just a plain message.", {}, "Just a plain message.", id="plain-text"
|
||||||
@@ -154,7 +154,7 @@ class TestArgPlaceholders:
|
|||||||
"""Positional arguments $(1) through $(9)."""
|
"""Positional arguments $(1) through $(9)."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,args,expected",
|
("template", "args", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"$(1)$(2)$(3)$(4)$(5)$(6)$(7)$(8)$(9)",
|
"$(1)$(2)$(3)$(4)$(5)$(6)$(7)$(8)$(9)",
|
||||||
@@ -186,7 +186,7 @@ class TestArgPlaceholders:
|
|||||||
assert await process(template, placeholder_storage, args=args) == expected
|
assert await process(template, placeholder_storage, args=args) == expected
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,args,expected",
|
("template", "args", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("$(0)", ["first"], "$(0)", id="index-zero"),
|
pytest.param("$(0)", ["first"], "$(0)", id="index-zero"),
|
||||||
pytest.param("$(10)", ["a"] * 10, "$(10)", id="index-ten"),
|
pytest.param("$(10)", ["a"] * 10, "$(10)", id="index-ten"),
|
||||||
@@ -205,7 +205,7 @@ class TestArgPlaceholders:
|
|||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,args,expected",
|
("template", "args", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"$(1 extra)",
|
"$(1 extra)",
|
||||||
@@ -269,7 +269,7 @@ class TestNamedCounters:
|
|||||||
assert row["value"] == 5
|
assert row["value"] == 5
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"counter_values,template,expected,db_name,db_value",
|
("counter_values", "template", "expected", "db_name", "db_value"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
{"deaths": 10},
|
{"deaths": 10},
|
||||||
@@ -310,7 +310,7 @@ class TestNamedCounters:
|
|||||||
assert row["value"] == db_value
|
assert row["value"] == db_value
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"counter_values,template,expected",
|
("counter_values", "template", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param({}, "$(count deaths +5)", "5", id="plus-five"),
|
pytest.param({}, "$(count deaths +5)", "5", id="plus-five"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -425,7 +425,7 @@ class TestCounterNameValidation:
|
|||||||
"""Counter names are lowercased and must match ^[a-z0-9_]+$."""
|
"""Counter names are lowercased and must match ^[a-z0-9_]+$."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,expected,db_name,db_value",
|
("template", "expected", "db_name", "db_value"),
|
||||||
[
|
[
|
||||||
pytest.param("$(count Deaths)", "1", "deaths", 1, id="uppercase-name"),
|
pytest.param("$(count Deaths)", "1", "deaths", 1, id="uppercase-name"),
|
||||||
pytest.param("$(count 123)", "1", "123", 1, id="numeric-name"),
|
pytest.param("$(count 123)", "1", "123", 1, id="numeric-name"),
|
||||||
@@ -509,7 +509,7 @@ class TestRand:
|
|||||||
assert result == f"Alice rolled a {expected_roll}!"
|
assert result == f"Alice rolled a {expected_roll}!"
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,expected",
|
("template", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("$(rand 5 5)", "5", id="equal-bounds"),
|
pytest.param("$(rand 5 5)", "5", id="equal-bounds"),
|
||||||
pytest.param("$(rand 0 0)", "0", id="zero-bounds"),
|
pytest.param("$(rand 0 0)", "0", id="zero-bounds"),
|
||||||
@@ -606,7 +606,7 @@ class TestCountdownCountup:
|
|||||||
assert await process(template, placeholder_storage) == "0 seconds"
|
assert await process(template, placeholder_storage) == "0 seconds"
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"placeholder,bad_date",
|
("placeholder", "bad_date"),
|
||||||
[
|
[
|
||||||
pytest.param("countdown", "not a real date", id="gibberish"),
|
pytest.param("countdown", "not a real date", id="gibberish"),
|
||||||
pytest.param("countdown", "monday", id="weekday-only"),
|
pytest.param("countdown", "monday", id="weekday-only"),
|
||||||
@@ -640,7 +640,7 @@ class TestCountdownCountup:
|
|||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"seconds,expected",
|
("seconds", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(0, "0 seconds", id="zero"),
|
pytest.param(0, "0 seconds", id="zero"),
|
||||||
pytest.param(1, "1 second", id="one-second"),
|
pytest.param(1, "1 second", id="one-second"),
|
||||||
@@ -775,7 +775,7 @@ class TestNesting:
|
|||||||
assert result == "$(test)"
|
assert result == "$(test)"
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,args",
|
("template", "args"),
|
||||||
[
|
[
|
||||||
pytest.param("$(rand $(1) $(2))", [], id="no-args"),
|
pytest.param("$(rand $(1) $(2))", [], id="no-args"),
|
||||||
pytest.param("$(rand $(1) $(2))", ["5"], id="one-arg"),
|
pytest.param("$(rand $(1) $(2))", ["5"], id="one-arg"),
|
||||||
@@ -805,7 +805,7 @@ class TestEscaping:
|
|||||||
"""Escape sequences and backslash boundary conditions."""
|
"""Escape sequences and backslash boundary conditions."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,kwargs,expected",
|
("template", "kwargs", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
r"Use \$(user) to insert your name.",
|
r"Use \$(user) to insert your name.",
|
||||||
@@ -856,7 +856,7 @@ class TestCaseInsensitivity:
|
|||||||
"""Placeholder names are lowercased before handler lookup."""
|
"""Placeholder names are lowercased before handler lookup."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,kwargs,expected",
|
("template", "kwargs", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("$(USER)", {}, "Alice", id="all-upper-user"),
|
pytest.param("$(USER)", {}, "Alice", id="all-upper-user"),
|
||||||
pytest.param("$(User)", {}, "Alice", id="title-case-user"),
|
pytest.param("$(User)", {}, "Alice", id="title-case-user"),
|
||||||
@@ -986,7 +986,7 @@ class TestParserBoundaries:
|
|||||||
"""Parser edge cases around incomplete or unusual $( sequences."""
|
"""Parser edge cases around incomplete or unusual $( sequences."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,expected",
|
("template", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("$(rand 1", "$(rand 1", id="unclosed-rand"),
|
pytest.param("$(rand 1", "$(rand 1", id="unclosed-rand"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@@ -1017,7 +1017,7 @@ class TestUnknownPlaceholders:
|
|||||||
"""Unknown placeholder names pass through unchanged."""
|
"""Unknown placeholder names pass through unchanged."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,expected",
|
("template", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param("$(madeup stuff)", "$(madeup stuff)", id="with-args"),
|
pytest.param("$(madeup stuff)", "$(madeup stuff)", id="with-args"),
|
||||||
pytest.param("$(banana)", "$(banana)", id="bare-name"),
|
pytest.param("$(banana)", "$(banana)", id="bare-name"),
|
||||||
@@ -1043,7 +1043,7 @@ class TestEvaluationSafety:
|
|||||||
"""Verify that resolved text is never re-parsed as placeholders."""
|
"""Verify that resolved text is never re-parsed as placeholders."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,kwargs,expected",
|
("template", "kwargs", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"$(1)", {"args": ["$(user)"]}, "$(user)", id="arg-contains-user"
|
"$(1)", {"args": ["$(user)"]}, "$(user)", id="arg-contains-user"
|
||||||
@@ -1093,7 +1093,7 @@ class TestRuntimeErrors:
|
|||||||
"""A PlaceholderError cancels the entire render and returns the message."""
|
"""A PlaceholderError cancels the entire render and returns the message."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"template,expected_error",
|
("template", "expected_error"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"$(rand 1)",
|
"$(rand 1)",
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class TestFetchOne:
|
|||||||
"""fetch_one() returns a Row or None."""
|
"""fetch_one() returns a Row or None."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"accessor, expected",
|
("accessor", "expected"),
|
||||||
[
|
[
|
||||||
pytest.param(lambda row: row["name"], "a", id="key-access"),
|
pytest.param(lambda row: row["name"], "a", id="key-access"),
|
||||||
pytest.param(lambda row: row[1], "a", id="index-access"),
|
pytest.param(lambda row: row[1], "a", id="index-access"),
|
||||||
@@ -288,7 +288,7 @@ class TestTransaction:
|
|||||||
self, storage_with_table: ModuleStorage
|
self, storage_with_table: ModuleStorage
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Writes inside transaction() are discarded if an exception is raised."""
|
"""Writes inside transaction() are discarded if an exception is raised."""
|
||||||
with pytest.raises(RuntimeError, match="boom"):
|
with pytest.raises(RuntimeError, match="boom"): # noqa: PT012
|
||||||
async with storage_with_table._checkout(), storage_with_table.transaction():
|
async with storage_with_table._checkout(), storage_with_table.transaction():
|
||||||
await storage_with_table.execute(
|
await storage_with_table.execute(
|
||||||
"INSERT INTO items (name, value) VALUES (?, ?)",
|
"INSERT INTO items (name, value) VALUES (?, ?)",
|
||||||
@@ -335,7 +335,7 @@ class TestStorageError:
|
|||||||
"""Invalid SQL raises StorageError wrapping the underlying error."""
|
"""Invalid SQL raises StorageError wrapping the underlying error."""
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"method, args",
|
("method", "args"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"execute",
|
"execute",
|
||||||
@@ -372,7 +372,7 @@ class TestStorageError:
|
|||||||
await getattr(storage, method)(*args)
|
await getattr(storage, method)(*args)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"method, args",
|
("method", "args"),
|
||||||
[
|
[
|
||||||
pytest.param("execute", ("SELECT 1",), id="execute"),
|
pytest.param("execute", ("SELECT 1",), id="execute"),
|
||||||
pytest.param("fetch_one", ("SELECT 1",), id="fetch-one"),
|
pytest.param("fetch_one", ("SELECT 1",), id="fetch-one"),
|
||||||
|
|||||||
Reference in New Issue
Block a user