Skip to content

Commit 9023c77

Browse files
committed
Add a command lazy loading system
1 parent de55578 commit 9023c77

File tree

6 files changed

+141
-6
lines changed

6 files changed

+141
-6
lines changed

cleo/application.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from .io.outputs.output import Output
3333
from .io.outputs.output import Verbosity
3434
from .io.outputs.stream_output import StreamOutput
35+
from .loaders.command_loader import CommandLoader
3536
from .terminal import Terminal
3637
from .ui.ui import UI
3738

@@ -67,6 +68,8 @@ def __init__(self, name: str = "console", version: str = "") -> None:
6768
# TODO: signals support
6869
self._event_dispatcher = None
6970

71+
self._command_loader: Optional[CommandLoader] = None
72+
7073
@property
7174
def name(self) -> str:
7275
return self._name
@@ -139,6 +142,9 @@ def set_version(self, version: str) -> None:
139142
def set_ui(self, ui: UI) -> None:
140143
self._ui = ui
141144

145+
def set_command_loader(self, command_loader: CommandLoader) -> None:
146+
self._command_loader = command_loader
147+
142148
def auto_exits(self, auto_exits: bool = True) -> None:
143149
self._auto_exit = auto_exits
144150

@@ -203,8 +209,15 @@ def get(self, name: str) -> Command:
203209
def has(self, name: str) -> bool:
204210
self._init()
205211

206-
# TODO: Command loader
207-
return name in self._commands
212+
if name in self._commands:
213+
return True
214+
215+
if not self._command_loader:
216+
return False
217+
218+
return self._command_loader.has(name) and self.add(
219+
self._command_loader.get(name)
220+
)
208221

209222
def get_namespaces(self) -> List[str]:
210223
namespaces = []
@@ -245,7 +258,11 @@ def find(self, name: str) -> Command:
245258
if self.has(name):
246259
return self.get(name)
247260

248-
all_commands = [
261+
all_commands = []
262+
if self._command_loader:
263+
all_commands += self._command_loader.names
264+
265+
all_commands += [
249266
name for name, command in self._commands.items() if not command.hidden
250267
]
251268

@@ -255,16 +272,30 @@ def all(self, namespace: Optional[str] = None) -> Dict[str, Command]:
255272
self._init()
256273

257274
if namespace is None:
258-
# TODO: Command loader
259-
return self._commands.copy()
275+
commands = self._commands.copy()
276+
if not self._command_loader:
277+
return commands
278+
279+
for name in self._command_loader.names:
280+
if name not in commands and self.has(name):
281+
commands[name] = self.get(name)
282+
283+
return commands
260284

261285
commands = {}
262286

263287
for name, command in self._commands.items():
264288
if namespace == self.extract_namespace(name, name.count(" ") + 1):
265289
commands[name] = command
266290

267-
# TODO: Command loader
291+
if self._command_loader:
292+
for name in self._command_loader.names:
293+
if (
294+
name not in commands
295+
and namespace == self.extract_namespace(name, name.count(" ") + 1)
296+
and self.has(name)
297+
):
298+
commands[name] = self.get(name)
268299

269300
return commands
270301

cleo/loaders/__init__.py

Whitespace-only changes.

cleo/loaders/command_loader.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import List
2+
3+
from cleo.commands.command import Command
4+
5+
6+
class CommandLoader:
7+
@property
8+
def names(self) -> List[str]:
9+
"""
10+
All registered command names.
11+
"""
12+
raise NotImplementedError()
13+
14+
def get(self, name: str) -> Command:
15+
"""
16+
Loads a command.
17+
"""
18+
raise NotImplementedError()
19+
20+
def has(self, name: str) -> bool:
21+
"""
22+
Checks whether a command exists or not.
23+
"""
24+
raise NotImplementedError()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from typing import Callable
2+
from typing import Dict
3+
from typing import List
4+
5+
from cleo.commands.command import Command
6+
7+
from ..exceptions import CommandNotFoundException
8+
from .command_loader import CommandLoader
9+
10+
11+
class FactoryCommandLoader(CommandLoader):
12+
"""
13+
A simple command loader using factories to instantiate commands lazily.
14+
"""
15+
16+
def __init__(self, factories: Dict[str, Callable]) -> None:
17+
self._factories = factories
18+
19+
@property
20+
def names(self) -> List[str]:
21+
return list(self._factories.keys())
22+
23+
def has(self, name: str) -> bool:
24+
return name in self._factories
25+
26+
def get(self, name: str) -> Command:
27+
if name not in self._factories:
28+
raise CommandNotFoundException(name)
29+
30+
factory = self._factories[name]
31+
32+
return factory()

tests/loaders/__init__.py

Whitespace-only changes.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pytest
2+
3+
from cleo.commands.command import Command
4+
from cleo.exceptions import CommandNotFoundException
5+
from cleo.loaders.factory_command_loader import FactoryCommandLoader
6+
7+
8+
def command(name: str) -> Command:
9+
command_ = Command()
10+
command_.name = name
11+
12+
return command_
13+
14+
15+
def test_has():
16+
loader = FactoryCommandLoader(
17+
{"foo": lambda: command("foo"), "bar": lambda: command("bar")}
18+
)
19+
20+
assert loader.has("foo")
21+
assert loader.has("bar")
22+
assert not loader.has("baz")
23+
24+
25+
def test_get():
26+
loader = FactoryCommandLoader(
27+
{"foo": lambda: command("foo"), "bar": lambda: command("bar")}
28+
)
29+
30+
assert isinstance(loader.get("foo"), Command)
31+
assert isinstance(loader.get("bar"), Command)
32+
33+
34+
def test_get_invalid_command_raises_error():
35+
loader = FactoryCommandLoader(
36+
{"foo": lambda: command("foo"), "bar": lambda: command("bar")}
37+
)
38+
39+
with pytest.raises(CommandNotFoundException):
40+
loader.get("baz")
41+
42+
43+
def test_names():
44+
loader = FactoryCommandLoader(
45+
{"foo": lambda: command("foo"), "bar": lambda: command("bar")}
46+
)
47+
48+
assert loader.names == ["foo", "bar"]

0 commit comments

Comments
 (0)