Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Cleo CLI #203

Merged
merged 13 commits into from
Oct 16, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Added scooze_id to CardModel and Card. Changed the MongoDB _id to scooze_id. ([#193](https://github.com/arcavios/scooze/pull/193))
- Added AsyncScoozeApi as a way to use API endpoints in an async context (fixes Jupyter compatability) ([#199](https://github.com/arcavios/scooze/pull/199))
- Add Docker support for starting MongoDB via the CLI ([#200](https://github.com/arcavios/scooze/pull/200))
- CLI rework to be more robust ([#203](https://github.com/arcavios/scooze/pull/203))

### Changed

Expand Down
194 changes: 148 additions & 46 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ requests = "^2.31.0"
frozendict = "^2.3.8"
ijson = "^3.2.3"
docker = "^6.1.3"
cleo = "^2.0.1"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
Expand All @@ -43,7 +44,7 @@ isort = "^5.12.0"
mongomock = "^4.1.2"

[tool.poetry.scripts]
scooze = "scooze.cli:run_cli"
scooze = "scooze.console.cli:run_cli"

[tool.pytest.ini_options]
# addopts = "-m 'not slow'"
Expand Down
2 changes: 1 addition & 1 deletion src/scooze/api/bulkdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def load_batch(batch) -> int:

except FileNotFoundError:
print(file_path)
download_now = input(f"{file_type} file not found; would you like to download it now? [y/n] ") in "yY"
download_now = input(f"{file_type} file not found; would you like to download it now? [y/N] ") in "yY"
if not download_now:
print("No cards loaded into database.")
return
Expand Down
220 changes: 0 additions & 220 deletions src/scooze/cli.py

This file was deleted.

57 changes: 57 additions & 0 deletions src/scooze/console/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import annotations
iambroadband marked this conversation as resolved.
Show resolved Hide resolved

from importlib import import_module
from typing import TYPE_CHECKING

import pkg_resources
from cleo.application import Application
from cleo.loaders.factory_command_loader import FactoryCommandLoader

if TYPE_CHECKING:
from collections.abc import Callable

from cleo.commands.command import Command

# Accepted scooze CLI commands
COMMANDS = [
"delete",
"run",
# Load commands
"load cards",
"load decks",
# Setup commands
"setup docker",
"setup local",
# Teardown commands
"teardown docker",
"teardown local",
]


def load_scooze_command(name: str) -> Callable[[], Command]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the empty array [] mean in this type hint?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reading that as "the Callable it returns takes no arguments", but would be good to confirm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep it should always be Callable[[<args type list>], <return type>]

def _scooze_command_factory() -> Command:
cli_words = name.split(" ")
module = import_module("scooze.console.commands." + ".".join(cli_words))
scooze_command_class = getattr(module, "".join(c.title() for c in cli_words) + "Command")
command: Command = scooze_command_class()
return command

return _scooze_command_factory


class ScoozeApplication(Application):
def __init__(self):
pkg_name = "scooze"
super().__init__(pkg_name, f"{pkg_resources.get_distribution(pkg_name).version}")

command_loader = FactoryCommandLoader({name: load_scooze_command(name) for name in COMMANDS})
self.set_command_loader(command_loader)


def run_cli() -> int:
exit_code: int = ScoozeApplication().run()
return exit_code


if __name__ == "__main__":
run_cli()
49 changes: 49 additions & 0 deletions src/scooze/console/commands/delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import scooze.database.deck as deck_db
from cleo.commands.command import Command
from cleo.helpers import argument
from scooze.api import ScoozeApi
from scooze.catalogs import DbCollection


class DeleteCommand(Command):
name = "delete"
description = "Delete collections from the database."

arguments = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjork3 @iambroadband as this is currently written now it'll accept an arbitrary list of string args and will act accordingly if it see any of all, cards, or decks. Any other things passed in will be ignored. Do we want to print a message that other stuff passed in was ignored? It'll already say it needs valid input if none of what we expect is passed, but extra stuff isn't handled yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to print the ignored options too.

argument(
"collections",
f"Which collections to remove from the database. Can be any of: <fg=cyan>all, cards, decks</>",
multiple=True,
)
]

def handle(self):
to_delete: list[DbCollection] = []
delete_args = self.argument("collections")

if "all" in delete_args:
to_delete.extend(DbCollection.list())
else:
if "cards" in delete_args:
to_delete.append(DbCollection.CARDS)
if "decks" in delete_args:
to_delete.append(DbCollection.DECKS)

if len(to_delete) == 0:
print("No valid collections were given to delete.")

for collection in to_delete:
delete_collection(collection)


def delete_collection(coll: DbCollection):
clean = input(f"Delete existing {coll}? [y/N] ") in "yY"
if clean:
print(f"Deleting all {coll} from your local database...")
match coll:
case DbCollection.CARDS:
with ScoozeApi() as s:
s.delete_cards_all()
case DbCollection.DECKS:
# TODO(#145): Use the ScoozeApi for this
deck_db.delete_decks_all()
Loading