Skip to content

Commit 09fe027

Browse files
committed
fix #17
1 parent 6c48be3 commit 09fe027

File tree

9 files changed

+354
-99
lines changed

9 files changed

+354
-99
lines changed

django_routines/__init__.py

+43-8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@
5050
Command = t.Union["ManagementCommand", "SystemCommand"]
5151

5252

53+
def to_symbol(name: str) -> str:
54+
return name.lstrip("-").replace("-", "_")
55+
56+
57+
def to_cli_option(name: str) -> str:
58+
return f'--{to_symbol(name).replace("_", "-")}'
59+
60+
5361
@dataclass
5462
class _RoutineCommand(ABC):
5563
"""
@@ -88,6 +96,9 @@ class _RoutineCommand(ABC):
8896
# process, or the result object returned by subprocess.run() if run in a subprocess.
8997
# """
9098

99+
def __post_init__(self):
100+
self.switches = tuple([to_symbol(switch) for switch in self.switches])
101+
91102
@property
92103
@abstractmethod
93104
def kind(self) -> str:
@@ -192,6 +203,12 @@ class Routine:
192203
If true run each of the commands in a subprocess.
193204
"""
194205

206+
def __post_init__(self):
207+
self.name = to_symbol(self.name)
208+
self.switch_helps = {
209+
to_symbol(switch): hlp for switch, hlp in self.switch_helps.items()
210+
}
211+
195212
def __len__(self):
196213
return len(self.commands)
197214

@@ -290,7 +307,22 @@ def routine(
290307
for command in commands:
291308
routine.add(command)
292309

293-
settings[ROUTINE_SETTING][name] = routine.to_dict()
310+
settings[ROUTINE_SETTING][routine.name] = routine.to_dict()
311+
312+
313+
def _get_routine(routine_name: str, routines: t.Dict[str, t.Any]) -> Routine:
314+
"""
315+
Routine may undergo some normalization, we account for that here when trying
316+
to fetch them.
317+
"""
318+
try:
319+
routine_name = to_symbol(routine_name)
320+
return routines[routine_name]
321+
except KeyError:
322+
for name, routine in routines.items():
323+
if to_symbol(name).lower() == routine_name.lower():
324+
return routine
325+
raise
294326

295327

296328
def _add_command(
@@ -304,11 +336,10 @@ def _add_command(
304336
settings = sys._getframe(2).f_globals # noqa: WPS437
305337
settings[ROUTINE_SETTING] = settings.get(ROUTINE_SETTING, {}) or {}
306338
routine_dict = settings[ROUTINE_SETTING]
307-
routine_obj = (
308-
Routine.from_dict(routine_dict[routine])
309-
if routine in routine_dict
310-
else Routine(routine, "", [])
311-
)
339+
try:
340+
routine_obj = Routine.from_dict(_get_routine(routine, routine_dict))
341+
except KeyError:
342+
routine_obj = Routine(routine, "", [])
312343
extra = {"options": options} if command_type is ManagementCommand else {}
313344
new_cmd = routine_obj.add(
314345
command_type(
@@ -398,8 +429,12 @@ def get_routine(name: str) -> Routine:
398429

399430
try:
400431
if not settings.configured:
401-
Routine.from_dict(sys._getframe(1).f_globals.get(ROUTINE_SETTING, {})[name]) # noqa: WPS437
402-
return Routine.from_dict(getattr(settings, ROUTINE_SETTING, {})[name])
432+
Routine.from_dict(
433+
_get_routine(name, sys._getframe(1).f_globals.get(ROUTINE_SETTING, {}))
434+
) # noqa: WPS437
435+
return Routine.from_dict(
436+
_get_routine(name, getattr(settings, ROUTINE_SETTING, {}))
437+
)
403438
except TypeError as err:
404439
raise ImproperlyConfigured(
405440
f"{ROUTINE_SETTING} routine {name} is malformed."

django_routines/management/commands/routine.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
SystemCommand,
2020
get_routine,
2121
routines,
22+
to_cli_option,
23+
to_symbol,
2224
)
2325

2426
if sys.version_info < (3, 9):
@@ -36,7 +38,7 @@
3638
width = Console().width
3739

3840
COMMAND_TMPL = """
39-
def {routine}(
41+
def {routine_func}(
4042
self,
4143
ctx: typer.Context,
4244
subprocess: Annotated[bool, typer.Option("{subprocess_opt}", help="{subprocess_help}", show_default=False)] = {subprocess},
@@ -52,7 +54,7 @@ def {routine}(
5254
) else None
5355
if not ctx.invoked_subcommand:
5456
return self._run_routine(subprocess=subprocess)
55-
return self.{routine}
57+
return self.{routine_func}
5658
"""
5759

5860

@@ -254,11 +256,16 @@ def _list(self) -> None:
254256
else ""
255257
)
256258
switches_str += ", ".join(
257-
click.style(switch, fg="yellow")
259+
click.style(to_cli_option(switch).lstrip("-"), fg="yellow")
258260
for switch in (command.switches or [])
259261
)
260262
else:
261-
switches_str += ", ".join(command.switches or [])
263+
switches_str += ", ".join(
264+
[
265+
to_cli_option(switch).lstrip("-")
266+
for switch in (command.switches or [])
267+
]
268+
)
262269

263270
opt_str = f" ({opt_str})" if opt_str else ""
264271
self.secho(f"[{priority}] {cmd_str}{opt_str}{switches_str}")
@@ -268,15 +275,18 @@ def _list(self) -> None:
268275
switches = routine.switches
269276
switch_args = ", ".join(
270277
[
271-
f"{switch}: Annotated[bool, typer.Option('--{switch}', help='{routine.switch_helps.get(switch, '')}')] = False"
278+
f"{to_symbol(switch)}: Annotated[bool, typer.Option('{to_cli_option(switch)}', help='{routine.switch_helps.get(switch, '')}')] = False"
272279
for switch in switches
273280
]
274281
)
275282
add_switches = ""
276283
for switch in switches:
277-
add_switches += f'\n if all or {switch}: self.switches.append("{switch}")'
284+
add_switches += (
285+
f'\n if all or {to_symbol(switch)}: self.switches.append("{switch}")'
286+
)
278287

279288
cmd_code = COMMAND_TMPL.format(
289+
routine_func=to_symbol(routine.name),
280290
routine=routine.name,
281291
switch_args=switch_args,
282292
add_switches=add_switches,
@@ -305,7 +315,7 @@ def _list(self) -> None:
305315
cmd_str += f" ({opt_str})"
306316
switches_str = " | " if command.switches else ""
307317
switches_str += ", ".join(
308-
f"{'[yellow]' if use_rich else ''}{switch}{'[/yellow]' if use_rich else ''}"
318+
f"{'[yellow]' if use_rich else ''}{to_cli_option(switch).lstrip("-")}{'[/yellow]' if use_rich else ''}"
309319
for switch in (command.switches or [])
310320
)
311321
command_strings.append(f"[{priority}] {cmd_str}{switches_str}")
@@ -322,8 +332,11 @@ def _list(self) -> None:
322332
f"{lb}{routine.help_text}\n{ruler}{lb}{'' if use_rich else lb}{cmd_strings}\n"
323333
)
324334
grp = Command.group(
325-
help=help_txt, short_help=routine.help_text, invoke_without_command=True
326-
)(locals()[routine.name])
335+
name=routine.name.replace("_", "-"),
336+
help=help_txt,
337+
short_help=routine.help_text,
338+
invoke_without_command=True,
339+
)(locals()[to_symbol(routine.name)])
327340

328341
@grp.command(name="list", help=_("List the commands that will be run."))
329342
def list(self):

doc/source/changelog.rst

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
Change Log
33
==========
44

5+
v1.1.1
6+
======
7+
8+
* `Allow hyphens (-) in switches. <https://github.com/bckohan/django-routines/issues/17>`_
9+
510
v1.1.0
611
======
712

doc/source/index.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ In our settings file we may define these routines like this:
3131
.. literalinclude:: ../../examples/readme.py
3232
:caption: settings.py
3333
:linenos:
34-
:lines: 2-33
34+
:lines: 2-41
3535

3636
The routine command will read our settings file and generate two subcommands, one called deploy
3737
and one called package:
@@ -85,7 +85,7 @@ work with the native configuration format which is a dictionary structure define
8585
.. literalinclude:: ../../examples/readme_dict.py
8686
:caption: settings.py
8787
:linenos:
88-
:lines: 2-33
88+
:lines: 2-39
8989

9090

9191
.. _priority:

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-routines"
3-
version = "1.1.0"
3+
version = "1.1.1"
44
description = "Define named groups of management commands in Django settings files for batched execution."
55
authors = ["Brian Kohan <bckohan@gmail.com>"]
66
license = "MIT"

tests/settings.py

+12
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,15 @@
112112
RoutineCommand(("does_not_exist",)),
113113
RoutineCommand(("track", "1")),
114114
)
115+
116+
routine(
117+
"--test-hyphen",
118+
"Test that hyphens dont mess everything up.",
119+
RoutineCommand(("track", "1"), switches=["--hyphen-ok", "hyphen-ok-prefix"]),
120+
RoutineCommand(("track", "2")),
121+
RoutineCommand(("track", "3"), switches=["hyphen-ok"]),
122+
RoutineCommand(("track", "4")),
123+
RoutineCommand(("track", "5"), switches=("hyphen-ok", "--hyphen-ok_prefix")),
124+
hyphen_ok="Test hyphen.",
125+
hyphen_ok_prefix="Test hyphen with -- prefix.",
126+
)

tests/test_core.py

+113-8
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,61 @@ def test_list(self, no_color=True):
411411
],
412412
)
413413

414+
def test_hyphen_list(self, no_color=True):
415+
if no_color:
416+
command = ("routine", "--no-color", "test-hyphen")
417+
else:
418+
command = ("routine", "--force-color", "test-hyphen")
419+
420+
out = StringIO()
421+
call_command(*command, "--all", "list", stdout=out)
422+
plan = self.lines(out.getvalue(), no_color=no_color)
423+
self.assertEqual(
424+
plan,
425+
[
426+
"[0] track 1 | hyphen-ok, hyphen-ok-prefix",
427+
"[0] track 2",
428+
"[0] track 3 | hyphen-ok",
429+
"[0] track 4",
430+
"[0] track 5 | hyphen-ok, hyphen-ok-prefix",
431+
],
432+
)
433+
434+
out = StringIO()
435+
call_command(*command, "list", stdout=out)
436+
plan = self.lines(out.getvalue(), no_color=no_color)
437+
self.assertEqual(
438+
plan,
439+
["[0] track 2", "[0] track 4"],
440+
)
441+
442+
out = StringIO()
443+
call_command(*command, "--hyphen-ok", "list", stdout=out)
444+
plan = self.lines(out.getvalue(), no_color=no_color)
445+
self.assertEqual(
446+
plan,
447+
[
448+
"[0] track 1 | hyphen-ok, hyphen-ok-prefix",
449+
"[0] track 2",
450+
"[0] track 3 | hyphen-ok",
451+
"[0] track 4",
452+
"[0] track 5 | hyphen-ok, hyphen-ok-prefix",
453+
],
454+
)
455+
456+
out = StringIO()
457+
call_command(*command, "--hyphen-ok-prefix", "list", stdout=out)
458+
plan = self.lines(out.getvalue(), no_color=no_color)
459+
self.assertEqual(
460+
plan,
461+
[
462+
"[0] track 1 | hyphen-ok, hyphen-ok-prefix",
463+
"[0] track 2",
464+
"[0] track 4",
465+
"[0] track 5 | hyphen-ok, hyphen-ok-prefix",
466+
],
467+
)
468+
414469
def test_list_color(self):
415470
self.test_list(no_color=False)
416471

@@ -460,9 +515,10 @@ def test_subprocess(self):
460515
│ --skip-checks Skip system checks. │
461516
╰──────────────────────────────────────────────────────────────────────────────╯
462517
╭─ Commands ───────────────────────────────────────────────────────────────────╮
463-
│ bad Bad command test routine │
464-
│ deploy Deploy the site application into production. │
465-
│ test Test Routine 1 │
518+
│ bad Bad command test routine │
519+
│ deploy Deploy the site application into production. │
520+
│ test Test Routine 1 │
521+
│ test-hyphen Test that hyphens dont mess everything up. │
466522
╰──────────────────────────────────────────────────────────────────────────────╯
467523
"""
468524

@@ -547,9 +603,10 @@ def test_helps_rich(self):
547603
--help Show this message and exit.
548604
549605
Commands:
550-
bad Bad command test routine
551-
deploy Deploy the site application into production.
552-
test Test Routine 1
606+
bad Bad command test routine
607+
deploy Deploy the site application into production.
608+
test Test Routine 1
609+
test-hyphen Test that hyphens dont mess everything up.
553610
"""
554611

555612
routine_test_help_no_rich = """
@@ -608,7 +665,6 @@ def test_helps_no_rich(self):
608665

609666
def test_settings_format(self):
610667
routines = getattr(settings, ROUTINE_SETTING)
611-
612668
self.assertEqual(
613669
routines["bad"],
614670
{
@@ -650,7 +706,7 @@ def test_settings_format(self):
650706
"kind": "management",
651707
"options": {},
652708
"priority": 0,
653-
"switches": ["prepare"],
709+
"switches": ("prepare",),
654710
},
655711
{
656712
"command": ("migrate",),
@@ -762,6 +818,55 @@ def test_settings_format(self):
762818
"subprocess": False,
763819
},
764820
)
821+
self.assertEqual(
822+
routines["test_hyphen"],
823+
{
824+
"commands": [
825+
{
826+
"command": ("track", "1"),
827+
"kind": "management",
828+
"options": {},
829+
"priority": 0,
830+
"switches": ("hyphen_ok", "hyphen_ok_prefix"),
831+
},
832+
{
833+
"command": ("track", "2"),
834+
"kind": "management",
835+
"options": {},
836+
"priority": 0,
837+
"switches": (),
838+
},
839+
{
840+
"command": ("track", "3"),
841+
"kind": "management",
842+
"options": {},
843+
"priority": 0,
844+
"switches": ("hyphen_ok",),
845+
},
846+
{
847+
"command": ("track", "4"),
848+
"kind": "management",
849+
"options": {},
850+
"priority": 0,
851+
"switches": (),
852+
},
853+
{
854+
"command": ("track", "5"),
855+
"kind": "management",
856+
"options": {},
857+
"priority": 0,
858+
"switches": ("hyphen_ok", "hyphen_ok_prefix"),
859+
},
860+
],
861+
"help_text": "Test that hyphens dont mess everything up.",
862+
"name": "test_hyphen",
863+
"subprocess": False,
864+
"switch_helps": {
865+
"hyphen_ok": "Test hyphen.",
866+
"hyphen_ok_prefix": "Test hyphen with -- prefix.",
867+
},
868+
},
869+
)
765870

766871

767872
class Test(CoreTests, TestCase):

0 commit comments

Comments
 (0)