0

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?"

  • At the very least, I wouldn't bother providing any type hints at all for `BaseCommand.action`: they don't provide any information. Changing the signature of a method you are overriding is problematic whether you type-hint it or not, because (for example) `Echo.action` is not compatible with code like `for x in some_list_of_base_command_objects: x.action(....)`, because no matter what you replace `...` with, it might be invalid for any given `x`. (If `x` is an instance of `Echo`, it needs to be a string. If `x` is an instance of `Foo`--to make up another base class--it has to be an int, etc.) – chepner Nov 19 '22 at 20:32
  • I've just realize, that I can add star parameter right before my main parameters (like `self, *, param_1, param_2`)` to make them be keyword-only, and it will work correct, but I still think, that `**kwargs: Any` in abstract BaseCommand class is not good type hint. So, the question remains. – ALittleMoron Nov 19 '22 at 20:36
  • @chepnet I understand it, but in my code I don't call these actions manually. I just regiter them in my Typer app object. It make input validation and pass all needed parameters by itself, so I will not have any problems with incompatibility of action methods. – ALittleMoron Nov 19 '22 at 20:40
  • The type checker doesn't just care about what you *are* doing, but what you *might* do. – chepner Nov 19 '22 at 20:41
  • If you want something compliant, `Echo.action` must also take `**kwargs: Any` as its argument(s). It can then take specific action for `kwargs['echo']` inside the function and ignore or raise on any other argument. – chepner Nov 19 '22 at 21:03

0 Answers0