Skip to content

Commit ad008a2

Browse files
committed
fix(cli): add **kwargs forward compatibility and edge case tests
- Add **kwargs to AliasedGroup.add_command and _patched_add_command to ensure forward compatibility with future click API changes - Add comprehensive edge case tests: - Alias collision behavior (last-wins semantics) - Non-aliased command lookup verification - New rich-click child with plain click parent scenario - Command name not added to own alias mapping
1 parent 8bb7bb8 commit ad008a2

2 files changed

Lines changed: 140 additions & 2 deletions

File tree

advanced_alchemy/utils/cli_tools.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ def add_command(
8181
cmd: click.Command,
8282
name: Optional[str] = None,
8383
aliases: Optional[Iterable[str]] = None,
84+
**kwargs: Any,
8485
) -> None:
85-
super().add_command(cmd, name)
86+
super().add_command(cmd, name, **kwargs)
8687
command_name = name or cmd.name
8788
if command_name is None:
8889
return
@@ -129,8 +130,9 @@ def _patched_add_command(
129130
cmd: click.Command,
130131
name: Optional[str] = None,
131132
aliases: Optional[Iterable[str]] = None,
133+
**kwargs: Any,
132134
) -> None:
133-
_original_add_command(self, cmd, name)
135+
_original_add_command(self, cmd, name, **kwargs)
134136
if not hasattr(self, "_alias_mapping"):
135137
self._alias_mapping = {} # type: ignore[attr-defined]
136138
command_name = name or cmd.name

tests/unit/test_utils/test_click.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,139 @@ def child_group() -> None:
178178

179179
assert resolved is not None
180180
assert resolved.name == "database"
181+
182+
183+
def test_alias_collision_last_wins(monkeypatch: pytest.MonkeyPatch) -> None:
184+
"""When two commands register the same alias, last registration wins."""
185+
mod = _reload_utils_click(monkeypatch, "none")
186+
187+
@mod.group(name="main")
188+
def main_group() -> None:
189+
pass
190+
191+
@mod.command(name="command-one", aliases=("c",))
192+
def cmd_one() -> None:
193+
pass
194+
195+
@mod.command(name="command-two", aliases=("c",))
196+
def cmd_two() -> None:
197+
pass
198+
199+
main_group.add_command(cmd_one)
200+
main_group.add_command(cmd_two)
201+
202+
ctx = base_click.Context(main_group)
203+
resolved = main_group.get_command(ctx, "c")
204+
205+
# Last registered command with alias "c" wins
206+
assert resolved is not None
207+
assert resolved.name == "command-two"
208+
209+
210+
def test_non_aliased_command_lookup_still_works(monkeypatch: pytest.MonkeyPatch) -> None:
211+
"""Ensure patching doesn't break normal command lookup without aliases."""
212+
mod = _reload_utils_click(monkeypatch, "none")
213+
214+
@mod.group(name="main")
215+
def main_group() -> None:
216+
pass
217+
218+
@mod.command(name="simple")
219+
def simple_cmd() -> None:
220+
pass
221+
222+
@mod.command(name="another", aliases=("a",))
223+
def another_cmd() -> None:
224+
pass
225+
226+
main_group.add_command(simple_cmd)
227+
main_group.add_command(another_cmd)
228+
229+
ctx = base_click.Context(main_group)
230+
231+
# Non-aliased command should resolve by name
232+
simple_resolved = main_group.get_command(ctx, "simple")
233+
assert simple_resolved is not None
234+
assert simple_resolved.name == "simple"
235+
236+
# Aliased command should resolve by both name and alias
237+
another_by_name = main_group.get_command(ctx, "another")
238+
another_by_alias = main_group.get_command(ctx, "a")
239+
assert another_by_name is not None
240+
assert another_by_alias is not None
241+
assert another_by_name.name == "another"
242+
assert another_by_alias.name == "another"
243+
244+
245+
def test_new_rich_click_child_with_plain_click_parent(monkeypatch: pytest.MonkeyPatch) -> None:
246+
"""New rich-click child group added to plain click parent group.
247+
248+
This validates the integration scenario where a child group using
249+
new rich-click (with native alias support) is added to a parent group
250+
that was created with plain click.
251+
"""
252+
mod = _reload_utils_click(monkeypatch, "new")
253+
254+
# Create parent with plain click (simulating framework CLI)
255+
@base_click.group(name="main")
256+
def parent_group() -> None:
257+
pass
258+
259+
# Create child with new rich-click (native aliases)
260+
@mod.group(name="database", aliases=("db",))
261+
def child_group() -> None:
262+
pass
263+
264+
parent_group.add_command(child_group)
265+
266+
ctx = base_click.Context(parent_group)
267+
268+
# Should resolve both by name and alias
269+
by_name = parent_group.get_command(ctx, "database")
270+
by_alias = parent_group.get_command(ctx, "db")
271+
272+
assert by_name is not None
273+
assert by_name.name == "database"
274+
assert by_alias is not None
275+
assert by_alias.name == "database"
276+
277+
278+
def test_command_name_not_added_to_own_alias_mapping(monkeypatch: pytest.MonkeyPatch) -> None:
279+
"""A command's own name is not added to the alias mapping.
280+
281+
This ensures that looking up a command by its canonical name works
282+
through normal click resolution, not through alias mapping.
283+
"""
284+
mod = _reload_utils_click(monkeypatch, "none")
285+
286+
@mod.group(name="main")
287+
def main_group() -> None:
288+
pass
289+
290+
@mod.command(name="status", aliases=("st", "stat"))
291+
def status_cmd() -> None:
292+
pass
293+
294+
main_group.add_command(status_cmd)
295+
296+
ctx = base_click.Context(main_group)
297+
298+
# Command resolves by canonical name (through normal click)
299+
by_name = main_group.get_command(ctx, "status")
300+
# And by aliases (through alias mapping)
301+
by_alias_st = main_group.get_command(ctx, "st")
302+
by_alias_stat = main_group.get_command(ctx, "stat")
303+
304+
assert by_name is not None
305+
assert by_alias_st is not None
306+
assert by_alias_stat is not None
307+
assert by_name.name == "status"
308+
assert by_alias_st.name == "status"
309+
assert by_alias_stat.name == "status"
310+
311+
# The alias mapping should NOT contain the command's own name
312+
# This is why we do `self._alias_mapping.pop(command_name, None)`
313+
alias_mapping = getattr(main_group, "_alias_mapping", {})
314+
assert "status" not in alias_mapping
315+
assert "st" in alias_mapping
316+
assert "stat" in alias_mapping

0 commit comments

Comments
 (0)