diff --git a/src/interface/api.py b/src/interface/api.py index a3765b1b8..b90b01dc0 100644 --- a/src/interface/api.py +++ b/src/interface/api.py @@ -4,6 +4,8 @@ from __future__ import ( import inspect import re +import codecs + import fastapi import pydantic import starlette @@ -132,12 +134,17 @@ class Interface(BaseInterface): else ... # required ) - if param.name in paths: - param_default = fastapi.Path(param_default, **param_kwargs) + if param_kwargs.pop("file", False): + new_param = param.replace( + annotation=fastapi.UploadFile, + default=param_default + ) + elif param.name in paths: + new_param = param.replace(default=fastapi.Path(param_default, **param_kwargs)) else: - param_default = fastapi.Query(param_default, **param_kwargs) + new_param = param.replace(default=fastapi.Query(param_default, **param_kwargs)) - override_params.append(param.replace(default=param_default)) + override_params.append(new_param) def hook_results(*args, **kwargs): new_kwargs = {} @@ -147,6 +154,10 @@ class Interface(BaseInterface): if isinstance(value, pydantic.BaseModel): # Turn pydantic model back to individual kwargs new_kwargs = value.dict() | new_kwargs + elif isinstance(value, starlette.datastructures.UploadFile): + # views expects a opened file (fastapi UploadFile is a bytes SpooledTemporaryFile) + new_kwargs[name] = codecs.iterdecode(value.file, 'utf-8') + opened_files.append(name) else: new_kwargs[name] = value @@ -158,6 +169,10 @@ class Interface(BaseInterface): raise fastapi.exceptions.RequestValidationError([ErrorWrapper(ValueError(e.strerror), ("query", "test"))]) except: raise + finally: + # I guess we need to close the opened file + for kwarg_name in opened_files: + kwargs[kwarg_name].file.close() route_func = override_function( func, diff --git a/src/interface/cli.py b/src/interface/cli.py index 0e597c03a..555943f49 100644 --- a/src/interface/cli.py +++ b/src/interface/cli.py @@ -17,6 +17,7 @@ from yunohost.interface.base import ( get_params_doc, override_function, ) +from yunohost.utils.error import YunohostValidationError def parse_cli_command(command: str) -> tuple[str, list[str]]: @@ -53,31 +54,37 @@ class Interface(BaseInterface): command, args = parse_cli_command(command_def) for param in params: + param_default = ( + param.default + if not param.default == param.empty + else ... # required + ) + param_kwargs = local_data.get(param.name, {}) param_kwargs["help"] = params_doc.get(param.name, None) if param_kwargs.pop("deprecated", False): param_kwargs["rich_help_panel"] = "Deprecated Options" - if param.name not in args and not param_kwargs.get("hidden", False): - param_kwargs["prompt"] = True - - if param.name == "password": - param_kwargs["confirmation_prompt"] = True - param_kwargs["hide_input"] = True + if param_kwargs.get("prompt", False): + if param.name == "password": + param_kwargs["confirmation_prompt"] = True + param_kwargs["hide_input"] = True # Populate default param value with typer.Argument|Option - param_default = ( - param.default - if not param.default == param.empty - else ... # required - ) - if param.name in args: - param_default = typer.Argument(param_default, **param_kwargs) + if param_kwargs.pop("file", False): + new_param = param.replace( + annotation=typer.FileText, + default=param_default + ) + elif param.kind == param.VAR_POSITIONAL: + new_param = param + elif param.name in args: + new_param = param.replace(default=typer.Argument(param_default, **param_kwargs)) else: - param_default = typer.Option(param_default, **param_kwargs) + new_param = param.replace(default=typer.Option(param_default, **param_kwargs)) - override_params.append(param.replace(default=param_default)) + override_params.append(new_param) def hook_results(*args, **kwargs): try: