From 9772584d2aad15d0fd266fe30a7005fb7388c13d Mon Sep 17 00:00:00 2001 From: Benex254 Date: Tue, 6 Aug 2024 15:45:26 +0300 Subject: [PATCH] feat: lazy load all cli commands for fasster start times --- fastanime/cli/__init__.py | 19 +++---- fastanime/cli/commands/__init__.py | 40 ++++++++++++++ fastanime/cli/commands/anilist/__init__.py | 52 +++++++------------ .../cli/commands/anilist/__lazyloader__.py | 42 +++++++++++++++ fastanime/cli/commands/config.py | 2 +- 5 files changed, 110 insertions(+), 45 deletions(-) create mode 100644 fastanime/cli/commands/anilist/__lazyloader__.py diff --git a/fastanime/cli/__init__.py b/fastanime/cli/__init__.py index 3a56ecf..dd5bade 100644 --- a/fastanime/cli/__init__.py +++ b/fastanime/cli/__init__.py @@ -6,18 +6,14 @@ from .. import __version__ from ..libs.anime_provider import anime_sources from ..libs.anime_provider.allanime.constants import SERVERS_AVAILABLE from ..Utility.data import anilist_sort_normalizer -from .commands.anilist import anilist -from .commands.config import configure -from .commands.download import download -from .commands.downloads import downloads -from .commands.search import search +from .commands import LazyGroup commands = { - "search": search, - "download": download, - "anilist": anilist, - "config": configure, - "downloads": downloads, + "search": "search.search", + "download": "download.download", + "anilist": "anilist.anilist", + "config": "config.config", + "downloads": "downloads.downloads", } @@ -35,7 +31,8 @@ signal.signal(signal.SIGINT, handle_exit) @click.group( - commands=commands, + lazy_subcommands=commands, + cls=LazyGroup, help="A command line application for streaming anime that provides a complete and featureful interface", short_help="Stream Anime", ) diff --git a/fastanime/cli/commands/__init__.py b/fastanime/cli/commands/__init__.py index e69de29..c89e538 100644 --- a/fastanime/cli/commands/__init__.py +++ b/fastanime/cli/commands/__init__.py @@ -0,0 +1,40 @@ +# in lazy_group.py +import importlib + +import click + + +class LazyGroup(click.Group): + def __init__(self, *args, lazy_subcommands=None, **kwargs): + super().__init__(*args, **kwargs) + # lazy_subcommands is a map of the form: + # + # {command-name} -> {module-name}.{command-object-name} + # + self.lazy_subcommands = lazy_subcommands or {} + + def list_commands(self, ctx): + base = super().list_commands(ctx) + lazy = sorted(self.lazy_subcommands.keys()) + return base + lazy + + def get_command(self, ctx, cmd_name): # pyright:ignore + if cmd_name in self.lazy_subcommands: + return self._lazy_load(cmd_name) + return super().get_command(ctx, cmd_name) + + def _lazy_load(self, cmd_name: str): + # lazily loading a command, first get the module name and attribute name + import_path: str = self.lazy_subcommands[cmd_name] + modname, cmd_object_name = import_path.rsplit(".", 1) + # do the import + mod = importlib.import_module(f".{modname}", package="fastanime.cli.commands") + # get the Command object from that module + cmd_object = getattr(mod, cmd_object_name) + # check the result to make debugging easier + if not isinstance(cmd_object, click.BaseCommand): + raise ValueError( + f"Lazy loading of {import_path} failed by returning " + "a non-command object" + ) + return cmd_object diff --git a/fastanime/cli/commands/anilist/__init__.py b/fastanime/cli/commands/anilist/__init__.py index fe56316..7c2c420 100644 --- a/fastanime/cli/commands/anilist/__init__.py +++ b/fastanime/cli/commands/anilist/__init__.py @@ -1,45 +1,31 @@ import click from ...utils.tools import QueryDict -from .completed import completed -from .dropped import dropped -from .favourites import favourites -from .login import login -from .notifier import notifier -from .paused import paused -from .planning import planning -from .popular import popular -from .random_anime import random_anime -from .recent import recent -from .rewatching import rewatching -from .scores import scores -from .search import search -from .trending import trending -from .upcoming import upcoming -from .watching import watching +from .__lazyloader__ import LazyGroup commands = { - "trending": trending, - "recent": recent, - "search": search, - "upcoming": upcoming, - "scores": scores, - "popular": popular, - "favourites": favourites, - "random": random_anime, - "login": login, - "watching": watching, - "paused": paused, - "rewatching": rewatching, - "dropped": dropped, - "completed": completed, - "planning": planning, - "notifier": notifier, + "trending": "trending.trending", + "recent": "recent.recent", + "search": "search.search", + "upcoming": "upcoming.upcoming", + "scores": "scores.scores", + "popular": "popular.popular", + "favourites": "favourites.favourites", + "random": "random_anime.random_anime", + "login": "login.login", + "watching": "watching.watching", + "paused": "paused.paused", + "rewatching": "rewatching.rewatching", + "dropped": "dropped.dropped", + "completed": "completed.completed", + "planning": "planning.planning", + "notifier": "notifier.notifier", } @click.group( - commands=commands, + lazy_subcommands=commands, + cls=LazyGroup, invoke_without_command=True, help="A beautiful interface that gives you access to a commplete streaming experience", short_help="Access all streaming options", diff --git a/fastanime/cli/commands/anilist/__lazyloader__.py b/fastanime/cli/commands/anilist/__lazyloader__.py new file mode 100644 index 0000000..d15b569 --- /dev/null +++ b/fastanime/cli/commands/anilist/__lazyloader__.py @@ -0,0 +1,42 @@ +# in lazy_group.py +import importlib + +import click + + +class LazyGroup(click.Group): + def __init__(self, *args, lazy_subcommands=None, **kwargs): + super().__init__(*args, **kwargs) + # lazy_subcommands is a map of the form: + # + # {command-name} -> {module-name}.{command-object-name} + # + self.lazy_subcommands = lazy_subcommands or {} + + def list_commands(self, ctx): + base = super().list_commands(ctx) + lazy = sorted(self.lazy_subcommands.keys()) + return base + lazy + + def get_command(self, ctx, cmd_name): # pyright:ignore + if cmd_name in self.lazy_subcommands: + return self._lazy_load(cmd_name) + return super().get_command(ctx, cmd_name) + + def _lazy_load(self, cmd_name: str): + # lazily loading a command, first get the module name and attribute name + import_path: str = self.lazy_subcommands[cmd_name] + modname, cmd_object_name = import_path.rsplit(".", 1) + # do the import + mod = importlib.import_module( + f".{modname}", package="fastanime.cli.commands.anilist" + ) + # get the Command object from that module + cmd_object = getattr(mod, cmd_object_name) + # check the result to make debugging easier + if not isinstance(cmd_object, click.BaseCommand): + raise ValueError( + f"Lazy loading of {import_path} failed by returning " + "a non-command object" + ) + return cmd_object diff --git a/fastanime/cli/commands/config.py b/fastanime/cli/commands/config.py index 09f6753..fab6fa1 100644 --- a/fastanime/cli/commands/config.py +++ b/fastanime/cli/commands/config.py @@ -13,7 +13,7 @@ import click is_flag=True, ) # @click.pass_obj -def configure(path, desktop_entry): +def config(path, desktop_entry): import os import subprocess