From 202cce8a0da41307e61b75756f09f81298ec65a2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 13 Sep 2024 11:16:19 +0200 Subject: [PATCH] refactor: type hint cli context --- frappe/commands/__init__.py | 4 +-- frappe/commands/gettext.py | 11 ++++--- frappe/commands/scheduler.py | 17 +++++----- frappe/commands/site.py | 61 +++++++++++++++++++----------------- frappe/commands/translate.py | 19 +++++------ frappe/commands/utils.py | 59 +++++++++++++++++----------------- frappe/utils/bench_helper.py | 12 ++++++- 7 files changed, 101 insertions(+), 82 deletions(-) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 9d9f61cdd4..0fafa63a1b 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -20,13 +20,13 @@ click.disable_unicode_literals_warning = True def pass_context(f): @wraps(f) def _func(ctx, *args, **kwargs): - profile = ctx.obj["profile"] + profile = ctx.obj.profile if profile: pr = cProfile.Profile() pr.enable() try: - ret = f(frappe._dict(ctx.obj), *args, **kwargs) + ret = f(ctx.obj, *args, **kwargs) except ( frappe.exceptions.SiteNotSpecifiedError, frappe.exceptions.IncorrectSitePath, diff --git a/frappe/commands/gettext.py b/frappe/commands/gettext.py index 4eec98e500..04d4d7bbf3 100644 --- a/frappe/commands/gettext.py +++ b/frappe/commands/gettext.py @@ -2,12 +2,13 @@ import click from frappe.commands import pass_context from frappe.exceptions import SiteNotSpecifiedError +from frappe.utils.bench_helper import CliCtxObj @click.command("generate-pot-file", help="Translation: generate POT file") @click.option("--app", help="Only generate for this app. eg: frappe") @pass_context -def generate_pot_file(context, app: str | None = None): +def generate_pot_file(context: CliCtxObj, app: str | None = None): from frappe.gettext.translate import generate_pot if not app: @@ -26,7 +27,7 @@ def generate_pot_file(context, app: str | None = None): ) @click.option("--locale", help="Compile transaltions only for this locale. eg: de") @pass_context -def compile_translations(context, app: str | None = None, locale: str | None = None, force=False): +def compile_translations(context: CliCtxObj, app: str | None = None, locale: str | None = None, force=False): from frappe.gettext.translate import compile_translations as _compile_translations if not app: @@ -39,7 +40,7 @@ def compile_translations(context, app: str | None = None, locale: str | None = N @click.option("--app", help="Only migrate for this app. eg: frappe") @click.option("--locale", help="Compile translations only for this locale. eg: de") @pass_context -def csv_to_po(context, app: str | None = None, locale: str | None = None): +def csv_to_po(context: CliCtxObj, app: str | None = None, locale: str | None = None): from frappe.gettext.translate import migrate if not app: @@ -56,7 +57,7 @@ You might want to run generate-pot-file first.""", @click.option("--app", help="Only update for this app. eg: frappe") @click.option("--locale", help="Update PO files only for this locale. eg: de") @pass_context -def update_po_files(context, app: str | None = None, locale: str | None = None): +def update_po_files(context: CliCtxObj, app: str | None = None, locale: str | None = None): from frappe.gettext.translate import update_po if not app: @@ -69,7 +70,7 @@ def update_po_files(context, app: str | None = None, locale: str | None = None): @click.argument("locale", nargs=1) @click.option("--app", help="Only create for this app. eg: frappe") @pass_context -def create_po_file(context, locale: str, app: str | None = None): +def create_po_file(context: CliCtxObj, locale: str, app: str | None = None): """Create PO file for lang code""" from frappe.gettext.translate import new_po diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index 07d725d451..14772e8740 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -5,12 +5,13 @@ import click import frappe from frappe.commands import get_site, pass_context from frappe.exceptions import SiteNotSpecifiedError +from frappe.utils.bench_helper import CliCtxObj @click.command("trigger-scheduler-event", help="Trigger a scheduler event") @click.argument("event") @pass_context -def trigger_scheduler_event(context, event): +def trigger_scheduler_event(context: CliCtxObj, event): import frappe.utils.scheduler exit_code = 0 @@ -35,7 +36,7 @@ def trigger_scheduler_event(context, event): @click.command("enable-scheduler") @pass_context -def enable_scheduler(context): +def enable_scheduler(context: CliCtxObj): "Enable scheduler" import frappe.utils.scheduler @@ -54,7 +55,7 @@ def enable_scheduler(context): @click.command("disable-scheduler") @pass_context -def disable_scheduler(context): +def disable_scheduler(context: CliCtxObj): "Disable scheduler" import frappe.utils.scheduler @@ -77,7 +78,7 @@ def disable_scheduler(context): @click.option("--format", "-f", default="text", type=click.Choice(["json", "text"]), help="Output format") @click.option("--verbose", "-v", is_flag=True, help="Verbose output") @pass_context -def scheduler(context, state: str, format: str, verbose: bool = False, site: str | None = None): +def scheduler(context: CliCtxObj, state: str, format: str, verbose: bool = False, site: str | None = None): """Control scheduler state.""" import frappe from frappe.utils.scheduler import is_scheduler_inactive, toggle_scheduler @@ -111,7 +112,7 @@ def scheduler(context, state: str, format: str, verbose: bool = False, site: str @click.option("--site", help="site name") @click.argument("state", type=click.Choice(["on", "off"])) @pass_context -def set_maintenance_mode(context, state, site=None): +def set_maintenance_mode(context: CliCtxObj, state, site=None): """Put the site in maintenance mode for upgrades.""" from frappe.installer import update_site_config @@ -129,7 +130,7 @@ def set_maintenance_mode(context, state, site=None): @click.command("doctor") # Passing context always gets a site and if there is no use site it breaks @click.option("--site", help="site name") @pass_context -def doctor(context, site=None): +def doctor(context: CliCtxObj, site=None): "Get diagnostic info about background workers" from frappe.utils.doctor import doctor as _doctor @@ -141,7 +142,7 @@ def doctor(context, site=None): @click.command("show-pending-jobs") @click.option("--site", help="site name") @pass_context -def show_pending_jobs(context, site=None): +def show_pending_jobs(context: CliCtxObj, site=None): "Get diagnostic info about background jobs" from frappe.utils.doctor import pending_jobs as _pending_jobs @@ -231,7 +232,7 @@ def start_worker_pool(queue, quiet=False, num_workers=2, burst=False): @click.command("ready-for-migration") @click.option("--site", help="site name") @pass_context -def ready_for_migration(context, site=None): +def ready_for_migration(context: CliCtxObj, site=None): from frappe.utils.doctor import any_job_pending if not site: diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 460c1d77ba..00a6a153fa 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -12,6 +12,7 @@ import frappe from frappe.commands import get_site, pass_context from frappe.exceptions import SiteNotSpecifiedError from frappe.utils import CallbackManager +from frappe.utils.bench_helper import CliCtxObj @click.command("new-site") @@ -164,7 +165,7 @@ def new_site( @click.option("--encryption-key", help="Backup encryption key") @pass_context def restore( - context, + context: CliCtxObj, sql_file_path, encryption_key=None, db_root_username=None, @@ -364,7 +365,7 @@ def restore_backup( @click.option("--verbose", "-v", is_flag=True) @click.option("--encryption-key", help="Backup encryption key") @pass_context -def partial_restore(context, sql_file_path, verbose, encryption_key=None): +def partial_restore(context: CliCtxObj, sql_file_path, verbose, encryption_key=None): from frappe.installer import is_partial, partial_restore from frappe.utils.backups import decrypt_backup, get_or_generate_backup_encryption_key @@ -427,7 +428,9 @@ def partial_restore(context, sql_file_path, verbose, encryption_key=None): @click.option("--db-root-password", "--mariadb-root-password", help="Root password for MariaDB or PostgreSQL") @click.option("--yes", is_flag=True, default=False, help="Pass --yes to skip confirmation") @pass_context -def reinstall(context, admin_password=None, db_root_username=None, db_root_password=None, yes=False): +def reinstall( + context: CliCtxObj, admin_password=None, db_root_username=None, db_root_password=None, yes=False +): "Reinstall site ie. wipe all data and start over" site = get_site(context) _reinstall(site, admin_password, db_root_username, db_root_password, yes, verbose=context.verbose) @@ -476,7 +479,7 @@ def _reinstall( @click.argument("apps", nargs=-1) @click.option("--force", is_flag=True, default=False) @pass_context -def install_app(context, apps, force=False): +def install_app(context: CliCtxObj, apps, force=False): "Install a new app to site, supports multiple apps" from frappe.installer import install_app as _install_app from frappe.utils.synchronization import filelock @@ -514,7 +517,7 @@ def install_app(context, apps, force=False): @click.command("list-apps") @click.option("--format", "-f", type=click.Choice(["text", "json"]), default="text") @pass_context -def list_apps(context, format): +def list_apps(context: CliCtxObj, format): """ List apps in site. """ @@ -560,7 +563,7 @@ def list_apps(context, format): help="Column to index. Multiple columns will create multi-column index in given order. To create a multiple, single column index, execute the command multiple times.", ) @pass_context -def add_db_index(context, doctype, column): +def add_db_index(context: CliCtxObj, doctype, column): "Adds a new DB index and creates a property setter to persist it." from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -594,7 +597,7 @@ def add_db_index(context, doctype, column): @click.option("--password") @click.option("--send-welcome-email", default=False, is_flag=True) @pass_context -def add_system_manager(context, email, first_name, last_name, send_welcome_email, password): +def add_system_manager(context: CliCtxObj, email, first_name, last_name, send_welcome_email, password): "Add a new system manager to a site" import frappe.utils.user @@ -620,7 +623,7 @@ def add_system_manager(context, email, first_name, last_name, send_welcome_email @click.option("--send-welcome-email", default=False, is_flag=True) @pass_context def add_user_for_sites( - context, email, first_name, last_name, user_type, send_welcome_email, password, add_role + context: CliCtxObj, email, first_name, last_name, user_type, send_welcome_email, password, add_role ): "Add user to a site" import frappe.utils.user @@ -640,7 +643,7 @@ def add_user_for_sites( @click.command("disable-user") @click.argument("email") @pass_context -def disable_user(context, email): +def disable_user(context: CliCtxObj, email): """Disable a user account on site.""" site = get_site(context) with frappe.init_site(site): @@ -655,7 +658,7 @@ def disable_user(context, email): @click.option("--skip-failing", is_flag=True, help="Skip patches that fail to run") @click.option("--skip-search-index", is_flag=True, help="Skip search indexing for web documents") @pass_context -def migrate(context, skip_failing=False, skip_search_index=False): +def migrate(context: CliCtxObj, skip_failing=False, skip_search_index=False): "Run patches, sync schema and rebuild files/translations" from traceback_with_variables import activate_by_import @@ -686,7 +689,7 @@ def migrate_to(): @click.argument("module") @click.option("--force", is_flag=True) @pass_context -def run_patch(context, module, force): +def run_patch(context: CliCtxObj, module, force): "Run a particular patch" import frappe.modules.patch_handler @@ -706,7 +709,7 @@ def run_patch(context, module, force): @click.argument("doctype") @click.argument("docname") @pass_context -def reload_doc(context, module, doctype, docname): +def reload_doc(context: CliCtxObj, module, doctype, docname): "Reload schema for a DocType" for site in context.sites: try: @@ -723,7 +726,7 @@ def reload_doc(context, module, doctype, docname): @click.command("reload-doctype") @click.argument("doctype") @pass_context -def reload_doctype(context, doctype): +def reload_doctype(context: CliCtxObj, doctype): "Reload schema for a DocType" for site in context.sites: try: @@ -739,7 +742,7 @@ def reload_doctype(context, doctype): @click.command("add-to-hosts") @pass_context -def add_to_hosts(context): +def add_to_hosts(context: CliCtxObj): "Add site to hosts" for site in context.sites: frappe.commands.popen(f"echo 127.0.0.1\t{site} | sudo tee -a /etc/hosts") @@ -799,7 +802,7 @@ def use(site, sites_path="."): @click.option("--old-backup-metadata", default=False, is_flag=True, help="Use older backup metadata") @pass_context def backup( - context, + context: CliCtxObj, with_files=False, backup_path=None, backup_path_db=None, @@ -878,7 +881,7 @@ def backup( @click.command("remove-from-installed-apps") @click.argument("app") @pass_context -def remove_from_installed_apps(context, app): +def remove_from_installed_apps(context: CliCtxObj, app): "Remove app from site's installed-apps list" ensure_app_not_frappe(app) from frappe.installer import remove_from_installed_apps @@ -907,7 +910,7 @@ def remove_from_installed_apps(context, app): @click.option("--no-backup", help="Do not backup the site", is_flag=True, default=False) @click.option("--force", help="Force remove app from site", is_flag=True, default=False) @pass_context -def uninstall(context, app, dry_run, yes, no_backup, force): +def uninstall(context: CliCtxObj, app, dry_run, yes, no_backup, force): "Remove app and linked modules from site" ensure_app_not_frappe(app) from frappe.installer import remove_app @@ -1030,7 +1033,7 @@ def move(dest_dir, site): @click.argument("password", required=False) @click.option("--logout-all-sessions", help="Log out from all sessions", is_flag=True, default=False) @pass_context -def set_password(context, user, password=None, logout_all_sessions=False): +def set_password(context: CliCtxObj, user, password=None, logout_all_sessions=False): "Set password for a user on a site" if not context.sites: raise SiteNotSpecifiedError @@ -1043,7 +1046,7 @@ def set_password(context, user, password=None, logout_all_sessions=False): @click.argument("admin-password", required=False) @click.option("--logout-all-sessions", help="Log out from all sessions", is_flag=True, default=False) @pass_context -def set_admin_password(context, admin_password=None, logout_all_sessions=False): +def set_admin_password(context: CliCtxObj, admin_password=None, logout_all_sessions=False): "Set Administrator password for a site" if not context.sites: raise SiteNotSpecifiedError @@ -1077,7 +1080,7 @@ def set_user_password(site, user, password, logout_all_sessions=False): @click.command("set-last-active-for-user") @click.option("--user", help="Setup last active date for user") @pass_context -def set_last_active_for_user(context, user=None): +def set_last_active_for_user(context: CliCtxObj, user=None): "Set users last active date to current datetime" from frappe.core.doctype.user.user import get_system_users from frappe.utils import now_datetime @@ -1106,7 +1109,7 @@ def set_last_active_for_user(context, user=None): @click.option("--docname") @click.option("--after-commit") @pass_context -def publish_realtime(context, event, message, room, user, doctype, docname, after_commit): +def publish_realtime(context: CliCtxObj, event, message, room, user, doctype, docname, after_commit): "Publish realtime event from bench" from frappe import publish_realtime @@ -1134,7 +1137,7 @@ def publish_realtime(context, event, message, room, user, doctype, docname, afte @click.argument("site", required=False) @click.option("--user", required=False, help="Login as user") @pass_context -def browse(context, site, user=None): +def browse(context: CliCtxObj, site, user=None): """Opens the site on web browser""" from frappe.auth import CookieManager, LoginManager @@ -1175,7 +1178,7 @@ def browse(context, site, user=None): @click.command("start-recording") @pass_context -def start_recording(context): +def start_recording(context: CliCtxObj): """Start Frappe Recorder.""" import frappe.recorder @@ -1189,7 +1192,7 @@ def start_recording(context): @click.command("stop-recording") @pass_context -def stop_recording(context): +def stop_recording(context: CliCtxObj): """Stop Frappe Recorder.""" import frappe.recorder @@ -1210,7 +1213,7 @@ def stop_recording(context): help="Use the auth token present in ngrok's config.", ) @pass_context -def start_ngrok(context, bind_tls, use_default_authtoken): +def start_ngrok(context: CliCtxObj, bind_tls, use_default_authtoken): """Start a ngrok tunnel to your local development server.""" from pyngrok import ngrok @@ -1270,7 +1273,7 @@ def build_search_index(context): @click.option("--days", type=int, help="Keep records for days") @click.option("--no-backup", is_flag=True, default=False, help="Do not backup the table") @pass_context -def clear_log_table(context, doctype, days, no_backup): +def clear_log_table(context: CliCtxObj, doctype, days, no_backup): """If any logtype table grows too large then clearing it with DELETE query is not feasible in reasonable time. This command copies recent data to new table and replaces current table with new smaller table. @@ -1323,7 +1326,7 @@ def clear_log_table(context, doctype, days, no_backup): default=False, ) @pass_context -def trim_database(context, dry_run, format, no_backup, yes=False): +def trim_database(context: CliCtxObj, dry_run, format, no_backup, yes=False): """Remove database tables for deleted DocTypes.""" if not context.sites: raise SiteNotSpecifiedError @@ -1426,7 +1429,7 @@ def get_standard_tables(): @click.option("--format", "-f", default="table", type=click.Choice(["json", "table"]), help="Output format") @click.option("--no-backup", is_flag=True, default=False, help="Do not backup the site") @pass_context -def trim_tables(context, dry_run, format, no_backup): +def trim_tables(context: CliCtxObj, dry_run, format, no_backup): """Remove columns from tables where fields are deleted from doctypes.""" if not context.sites: raise SiteNotSpecifiedError @@ -1511,7 +1514,7 @@ def ensure_app_not_frappe(app: str) -> None: @click.argument("patch_name") @click.option("--yes", "-y", is_flag=True, default=False, help="Pass --yes to skip confirmation") @pass_context -def bypass_patch(context, patch_name: str, yes: bool): +def bypass_patch(context: CliCtxObj, patch_name: str, yes: bool): """Bypass a patch permanently instead of migrating using the --skip-failing flag.""" from frappe.modules.patch_handler import update_patch_log diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index daa6affdf3..8b2ccea446 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -2,12 +2,13 @@ import click from frappe.commands import get_site, pass_context from frappe.exceptions import SiteNotSpecifiedError +from frappe.utils.bench_helper import CliCtxObj # translation @click.command("build-message-files") @pass_context -def build_message_files(context): +def build_message_files(context: CliCtxObj): "Build message files for translation" import frappe.translate @@ -23,18 +24,18 @@ def build_message_files(context): @click.command("new-language") # , help="Create lang-code.csv for given app") -@pass_context @click.argument("lang_code") # , help="Language code eg. en") @click.argument("app") # , help="App name eg. frappe") -def new_language(context, lang_code, app): +@pass_context +def new_language(context: CliCtxObj, lang_code, app): """Create lang-code.csv for given app""" import frappe.translate - if not context["sites"]: + if not context.sites: raise Exception("--site is required") # init site - frappe.init(site=context["sites"][0]) + frappe.init(site=context.sites[0]) frappe.connect() frappe.translate.write_translations_file(app, lang_code) @@ -48,7 +49,7 @@ def new_language(context, lang_code, app): @click.argument("untranslated_file") @click.option("--all", default=False, is_flag=True, help="Get all message strings") @pass_context -def get_untranslated(context, lang, untranslated_file, app="_ALL_APPS", all=None): +def get_untranslated(context: CliCtxObj, lang, untranslated_file, app="_ALL_APPS", all=None): "Get untranslated strings for language" import frappe.translate @@ -67,7 +68,7 @@ def get_untranslated(context, lang, untranslated_file, app="_ALL_APPS", all=None @click.argument("untranslated_file") @click.argument("translated-file") @pass_context -def update_translations(context, lang, untranslated_file, translated_file, app="_ALL_APPS"): +def update_translations(context: CliCtxObj, lang, untranslated_file, translated_file, app="_ALL_APPS"): "Update translated strings" import frappe.translate @@ -84,7 +85,7 @@ def update_translations(context, lang, untranslated_file, translated_file, app=" @click.argument("lang") @click.argument("path") @pass_context -def import_translations(context, lang, path): +def import_translations(context: CliCtxObj, lang, path): "Update translated strings" import frappe.translate @@ -101,7 +102,7 @@ def import_translations(context, lang, path): @click.argument("source-app") @click.argument("target-app") @pass_context -def migrate_translations(context, source_app, target_app): +def migrate_translations(context: CliCtxObj, source_app, target_app): "Migrate target-app-specific translations from source-app to target-app" import frappe.translate diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index ba327e1513..485942cda1 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -12,6 +12,7 @@ from frappe.commands import get_site, pass_context from frappe.coverage import CodeCoverage from frappe.exceptions import SiteNotSpecifiedError from frappe.utils import cint, update_progress_bar +from frappe.utils.bench_helper import CliCtxObj EXTRA_ARGS_CTX = {"ignore_unknown_options": True, "allow_extra_args": True} @@ -117,7 +118,7 @@ def watch(apps=None): @click.command("clear-cache") @pass_context -def clear_cache(context): +def clear_cache(context: CliCtxObj): "Clear cache, doctype cache and defaults" import frappe.sessions from frappe.website.utils import clear_website_cache @@ -136,7 +137,7 @@ def clear_cache(context): @click.command("clear-website-cache") @pass_context -def clear_website_cache(context): +def clear_website_cache(context: CliCtxObj): "Clear website cache" from frappe.website.utils import clear_website_cache @@ -154,7 +155,7 @@ def clear_website_cache(context): @click.command("destroy-all-sessions") @click.option("--reason") @pass_context -def destroy_all_sessions(context, reason=None): +def destroy_all_sessions(context: CliCtxObj, reason=None): "Clear sessions of all users (logs them out)" import frappe.sessions @@ -173,7 +174,7 @@ def destroy_all_sessions(context, reason=None): @click.command("show-config") @click.option("--format", "-f", type=click.Choice(["text", "json"]), default="text") @pass_context -def show_config(context, format): +def show_config(context: CliCtxObj, format): "Print configuration file to STDOUT in speified format" if not context.sites: @@ -223,7 +224,7 @@ def show_config(context, format): @click.command("reset-perms") @pass_context -def reset_perms(context): +def reset_perms(context: CliCtxObj): "Reset permissions for all doctypes" from frappe.permissions import reset_perms @@ -249,7 +250,7 @@ def reset_perms(context): @click.option("--kwargs") @click.option("--profile", is_flag=True, default=False) @pass_context -def execute(context, method, args=None, kwargs=None, profile=False): +def execute(context: CliCtxObj, method, args=None, kwargs=None, profile=False): "Execute a function" for site in context.sites: ret = "" @@ -312,7 +313,7 @@ def execute(context, method, args=None, kwargs=None, profile=False): @click.command("add-to-email-queue") @click.argument("email-path") @pass_context -def add_to_email_queue(context, email_path): +def add_to_email_queue(context: CliCtxObj, email_path): "Add an email to the Email Queue" site = get_site(context) @@ -331,7 +332,7 @@ def add_to_email_queue(context, email_path): @click.argument("doctype") @click.argument("docname") @pass_context -def export_doc(context, doctype, docname): +def export_doc(context: CliCtxObj, doctype, docname): "Export a single document to csv" import frappe.modules @@ -351,7 +352,7 @@ def export_doc(context, doctype, docname): @click.argument("path") @click.option("--name", help="Export only one document") @pass_context -def export_json(context, doctype, path, name=None): +def export_json(context: CliCtxObj, doctype, path, name=None): "Export doclist as json to the given path, use '-' as name for Singles." from frappe.core.doctype.data_import.data_import import export_json @@ -370,7 +371,7 @@ def export_json(context, doctype, path, name=None): @click.argument("doctype") @click.argument("path") @pass_context -def export_csv(context, doctype, path): +def export_csv(context: CliCtxObj, doctype, path): "Export data import template with data for DocType" from frappe.core.doctype.data_import.data_import import export_csv @@ -388,7 +389,7 @@ def export_csv(context, doctype, path): @click.command("export-fixtures") @click.option("--app", default=None, help="Export fixtures of a specific app") @pass_context -def export_fixtures(context, app=None): +def export_fixtures(context: CliCtxObj, app=None): "Export fixtures" from frappe.utils.fixtures import export_fixtures @@ -406,7 +407,7 @@ def export_fixtures(context, app=None): @click.command("import-doc") @click.argument("path") @pass_context -def import_doc(context, path, force=False): +def import_doc(context: CliCtxObj, path, force=False): "Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported" from frappe.core.doctype.data_import.data_import import import_doc @@ -449,7 +450,9 @@ def import_doc(context, path, force=False): @click.option("--submit-after-import", default=False, is_flag=True, help="Submit document after importing it") @click.option("--mute-emails", default=True, is_flag=True, help="Mute emails during import") @pass_context -def data_import(context, file_path, doctype, import_type=None, submit_after_import=False, mute_emails=True): +def data_import( + context: CliCtxObj, file_path, doctype, import_type=None, submit_after_import=False, mute_emails=True +): "Import documents in bulk from CSV or XLSX using data import" from frappe.core.doctype.data_import.data_import import import_file @@ -465,7 +468,7 @@ def data_import(context, file_path, doctype, import_type=None, submit_after_impo @click.argument("doctype") @click.argument("path") @pass_context -def bulk_rename(context, doctype, path): +def bulk_rename(context: CliCtxObj, doctype, path): "Rename multiple records via CSV file" from frappe.model.rename_doc import bulk_rename from frappe.utils.csvutils import read_csv_content @@ -486,7 +489,7 @@ def bulk_rename(context, doctype, path): @click.command("db-console", context_settings=EXTRA_ARGS_CTX) @click.argument("extra_args", nargs=-1) @pass_context -def database(context, extra_args): +def database(context: CliCtxObj, extra_args): """ Enter into the Database console for given site. """ @@ -498,7 +501,7 @@ def database(context, extra_args): @click.command("mariadb", context_settings=EXTRA_ARGS_CTX) @click.argument("extra_args", nargs=-1) @pass_context -def mariadb(context, extra_args): +def mariadb(context: CliCtxObj, extra_args): """ Enter into mariadb console for a given site. """ @@ -511,7 +514,7 @@ def mariadb(context, extra_args): @click.command("postgres", context_settings=EXTRA_ARGS_CTX) @click.argument("extra_args", nargs=-1) @pass_context -def postgres(context, extra_args): +def postgres(context: CliCtxObj, extra_args): """ Enter into postgres console for a given site. """ @@ -549,7 +552,7 @@ def _enter_console(extra_args=None): @click.command("jupyter") @pass_context -def jupyter(context): +def jupyter(context: CliCtxObj): """Start an interactive jupyter notebook""" installed_packages = ( r.split("==", 1)[0] @@ -615,7 +618,7 @@ def store_logs(terminal: "InteractiveShellEmbed") -> None: @click.command("console") @click.option("--autoreload", is_flag=True, help="Reload changes to code automatically") @pass_context -def console(context, autoreload=False): +def console(context: CliCtxObj, autoreload=False): "Start ipython console for a site" site = get_site(context) frappe.init(site) @@ -681,7 +684,7 @@ def console(context, autoreload=False): ) @click.option("--failfast", is_flag=True, default=False, help="Exit on first failure occurred") @pass_context -def transform_database(context, table, engine, row_format, failfast): +def transform_database(context: CliCtxObj, table, engine, row_format, failfast): "Transform site database through given parameters" site = get_site(context) check_table = [] @@ -768,7 +771,7 @@ def transform_database(context, table, engine, row_format, failfast): ) @pass_context def run_tests( - context, + context: CliCtxObj, app=None, module=None, doctype=None, @@ -846,7 +849,7 @@ def run_tests( @click.option("--dry-run", is_flag=True, default=False, help="Dont actually run tests") @pass_context def run_parallel_tests( - context, + context: CliCtxObj, app, build_number, total_builds, @@ -889,7 +892,7 @@ def run_parallel_tests( @click.option("--ci-build-id") @pass_context def run_ui_tests( - context, + context: CliCtxObj, app, headless=False, parallel=True, @@ -975,7 +978,7 @@ def run_ui_tests( @click.option("--with-coverage", is_flag=True, default=False) @pass_context def serve( - context, + context: CliCtxObj, port=None, profile=False, proxy=False, @@ -1012,7 +1015,7 @@ def serve( @click.option("--args", help="arguments like `?cmd=test&key=value` or `/api/request/method?..`") @click.option("--path", help="path to request JSON") @pass_context -def request(context, args=None, path=None): +def request(context: CliCtxObj, args=None, path=None): "Run a request as an admin" import frappe.api import frappe.handler @@ -1073,7 +1076,7 @@ def create_patch(): @click.option("-g", "--global", "global_", is_flag=True, default=False, help="Set value in bench config") @click.option("-p", "--parse", is_flag=True, default=False, help="Evaluate as Python Object") @pass_context -def set_config(context, key, value, global_=False, parse=False): +def set_config(context: CliCtxObj, key, value, global_=False, parse=False): "Insert/Update a value in site_config.json" from frappe.installer import update_site_config @@ -1149,7 +1152,7 @@ def get_version(output): @click.command("rebuild-global-search") @click.option("--static-pages", is_flag=True, default=False, help="Rebuild global search for static pages") @pass_context -def rebuild_global_search(context, static_pages=False): +def rebuild_global_search(context: CliCtxObj, static_pages=False): """Setup help table in the current site (called after migrate)""" from frappe.utils.global_search import ( add_route_to_global_search, @@ -1186,7 +1189,7 @@ def rebuild_global_search(context, static_pages=False): @click.command("list-sites") @click.option("--json", "output_json", is_flag=True, help="Output in JSON format") @pass_context -def list_sites(context, output_json=False): +def list_sites(context: CliCtxObj, output_json=False): "List all the sites in current bench" site_dir = os.getcwd() sites = [ diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py index bbcdb00e5d..a4bc89b0d6 100644 --- a/frappe/utils/bench_helper.py +++ b/frappe/utils/bench_helper.py @@ -5,6 +5,7 @@ import os import sys import traceback import warnings +from dataclasses import dataclass from textwrap import dedent import click @@ -54,6 +55,15 @@ def FrappeClickWrapper(cls, handler): return Cls +# for type hints +@dataclass +class CliCtxObj: + sites: list[str] + force: bool + profile: bool + verbose: bool + + def handle_exception(cmd, info_name, exc): tb = sys.exc_info()[2] while tb.tb_next: @@ -123,7 +133,7 @@ def get_app_group(app: str) -> click.Group: @click.option("--force", is_flag=True, default=False, help="Force") @click.pass_context def app_group(ctx, site=False, force=False, verbose=False, profile=False): - ctx.obj = {"sites": get_sites(site), "force": force, "verbose": verbose, "profile": profile} + ctx.obj = CliCtxObj(sites=get_sites(site), force=force, verbose=verbose, profile=profile) if ctx.info_name == "frappe": ctx.info_name = ""