I work on my pet-project on FastAPI and I want to write django-like manager (manage.py with its commands). I use Typer for this CLI, and I have the following structure of my management sub-module in my app:
...
./management
├── __init__.py
├── app.py
├── collector.py
└── commands
├── __init__.py
├── base.py
├── echo.py
└── runserver.py
So, I have app.py
as builder for management CLI app, collector.py
as importer of my commands (to not make manual import every time, when I create new command. It use import_module from importlib
) and sub-sub-module commands
, that contains all my commands and their interface. All commands should implement 1 method action
, so my question is the following: "How could I specify abstract BaseCommand class with action method, if this method will have not a specific amount of parameters per child class? (like, echo
has just echo string parameter, and runserver
has port as int, host as string and some other parameters)". **kwargs
is not a solution, because Pylance mark it as error. For example, for the following command implementation:
# base.py
class BaseCommand(ABC):
@abstractmethod
def action(self, **kwargs: Any) -> Any:
pass
# echo.py
class Echo(BaseCommand):
def action(self, echo: str):
"""Common echo command. Just """
print(echo)
Pylance shows me the following error:
Method "action" overrides class "BaseCommand" in an incompatible manner
Positional parameter count mismatch; base method has 2, but override has 2
So, if I have incorrect CLI app structure, an additional question is the following: "How should I structure my app?"
In order to persuade Pyright (and hence Pylance) that what you're doing is legitimate you need to do to things:
Echo.action
keyword only (to fit with **kwargs
in the parent)Echo.action
(also to fit with **kwargs
in the parent)The result of these two changes is this:
class BaseCommand(ABC):
@abstractmethod
def action(self, **kwargs) -> Any:
pass
class Echo(BaseCommand):
def action(self, *, echo: str, **kwargs):
"""Common echo command. Just """
print(echo)
The first *
in action
says: "everything after this is keyword-only".
If you don't like the fact that your IDE tells you that kwargs
are unused in Echo.action
you can replace **kwargs
with **_
.
This can be slightly simplified if you are allowed to change the definition of BaseCommand.action
:
class BaseCommand(ABC):
@abstractmethod
# Allow positional args in the abstractmethod
def action(self, *args, **kwargs) -> Any:
pass
class Echo(BaseCommand):
# Then you don't need `, *,` in the child
def action(self, echo: str, **kwargs):
"""Common echo command. Just """
print(echo)