From 63f1ae1c2a5ab09660509acc29ff8ee0ae5b40c0 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 13 Aug 2025 23:47:43 -0700 Subject: [PATCH 1/7] typing: from static methods to class methods --- tabcmd/commands/auth/login_command.py | 6 +++--- tabcmd/commands/auth/logout_command.py | 6 +++--- .../datasources_and_workbooks_command.py | 11 ++++++----- .../datasources_workbooks_views_url_parser.py | 9 +++++---- .../datasources_and_workbooks/delete_command.py | 8 ++++---- .../datasources_and_workbooks/export_command.py | 15 ++++++++------- .../datasources_and_workbooks/get_url_command.py | 6 +++--- .../datasources_and_workbooks/publish_command.py | 14 ++++++++------ .../runschedule_command.py | 6 +++--- .../commands/extracts/create_extracts_command.py | 13 +++++++------ .../commands/extracts/decrypt_extracts_command.py | 8 ++++---- .../commands/extracts/delete_extracts_command.py | 13 +++++++------ .../commands/extracts/encrypt_extracts_command.py | 8 ++++---- tabcmd/commands/extracts/extracts.py | 5 +++-- .../extracts/reencrypt_extracts_command.py | 8 ++++---- .../commands/extracts/refresh_extracts_command.py | 15 ++++++++------- tabcmd/commands/group/create_group_command.py | 6 +++--- tabcmd/commands/group/delete_group_command.py | 8 ++++---- tabcmd/commands/project/create_project_command.py | 11 ++++++----- tabcmd/commands/project/delete_project_command.py | 10 +++++----- .../commands/project/publish_samples_command.py | 6 +++--- tabcmd/commands/site/create_site_command.py | 8 ++++---- tabcmd/commands/site/delete_site_command.py | 8 ++++---- tabcmd/commands/site/edit_site_command.py | 8 ++++---- tabcmd/commands/site/list_command.py | 6 +++--- tabcmd/commands/site/list_sites_command.py | 8 ++++---- tabcmd/commands/user/add_users_command.py | 14 +++++++++----- tabcmd/commands/user/create_site_users.py | 9 +++++---- tabcmd/commands/user/create_users_command.py | 6 +++--- tabcmd/commands/user/delete_site_users_command.py | 6 +++--- tabcmd/commands/user/remove_users_command.py | 6 +++--- tabcmd/execution/parent_parser.py | 3 +-- tabcmd/tabcmd.py | 3 ++- 33 files changed, 146 insertions(+), 131 deletions(-) diff --git a/tabcmd/commands/auth/login_command.py b/tabcmd/commands/auth/login_command.py index d042f724..8d0e9dbf 100644 --- a/tabcmd/commands/auth/login_command.py +++ b/tabcmd/commands/auth/login_command.py @@ -17,9 +17,9 @@ def define_args(parser): # just uses global options pass - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() session.create_session(args, logger) diff --git a/tabcmd/commands/auth/logout_command.py b/tabcmd/commands/auth/logout_command.py index b15f8246..f5848e60 100644 --- a/tabcmd/commands/auth/logout_command.py +++ b/tabcmd/commands/auth/logout_command.py @@ -17,9 +17,9 @@ def define_args(parser): # has no options pass - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() session.end_session_and_clear_data() diff --git a/tabcmd/commands/datasources_and_workbooks/datasources_and_workbooks_command.py b/tabcmd/commands/datasources_and_workbooks/datasources_and_workbooks_command.py index 4b1e2f31..d7628e61 100644 --- a/tabcmd/commands/datasources_and_workbooks/datasources_and_workbooks_command.py +++ b/tabcmd/commands/datasources_and_workbooks/datasources_and_workbooks_command.py @@ -5,6 +5,7 @@ from tabcmd.commands.constants import Errors from tabcmd.commands.server import Server from tabcmd.execution.localize import _ +RequestOptionsType = TSC.ExcelRequestOptions | TSC.CSVRequestOptions | TSC.PDFRequestOptions | TSC.ImageRequestOptions class DatasourcesAndWorkbooks(Server): @@ -13,7 +14,7 @@ class DatasourcesAndWorkbooks(Server): """ def __init__(self, args): - super().__init__(args) + pass @staticmethod def get_view_url_from_names(wb_name, view_name): @@ -62,7 +63,7 @@ def get_ds_by_content_url(logger, server, datasource_content_url) -> TSC.Datasou return matching_datasources[0] @staticmethod - def apply_values_from_url_params(logger, request_options: TSC.RequestOptions, url) -> None: + def apply_values_from_url_params(logger, request_options: RequestOptionsType, url) -> None: logger.debug(url) try: if "?" in url: @@ -86,7 +87,7 @@ def apply_values_from_url_params(logger, request_options: TSC.RequestOptions, ur # this is called from within from_url_params, for each view_filter value @staticmethod - def apply_encoded_filter_value(logger, request_options, value): + def apply_encoded_filter_value(logger, request_options: RequestOptionsType, value): # the REST API doesn't appear to have the option to disambiguate with "Parameters." value = value.replace("Parameters.", "") # the filter values received from the url are already url encoded. tsc will encode them again. @@ -99,7 +100,7 @@ def apply_encoded_filter_value(logger, request_options, value): # from apply_options, which expects an un-encoded input, # or from apply_url_params via apply_encoded_filter_value which decodes the input @staticmethod - def apply_filter_value(logger, request_options: TSC.RequestOptions, value: str) -> None: + def apply_filter_value(logger, request_options: RequestOptionsType, value: str) -> None: logger.debug("handling filter param {}".format(value)) data_filter = value.split("=") # we should export the _DataExportOptions class from tsc @@ -108,7 +109,7 @@ def apply_filter_value(logger, request_options: TSC.RequestOptions, value: str) # this is called from within from_url_params, for each param value # expects either ImageRequestOptions or PDFRequestOptions @staticmethod - def apply_options_in_url(logger, request_options: TSC.RequestOptions, value: str) -> None: + def apply_options_in_url(logger, request_options: RequestOptionsType, value: str) -> None: logger.debug("handling url option {}".format(value)) setting = value.split("=") if len(setting) != 2: diff --git a/tabcmd/commands/datasources_and_workbooks/datasources_workbooks_views_url_parser.py b/tabcmd/commands/datasources_and_workbooks/datasources_workbooks_views_url_parser.py index e5d3e4db..3048efad 100644 --- a/tabcmd/commands/datasources_and_workbooks/datasources_workbooks_views_url_parser.py +++ b/tabcmd/commands/datasources_and_workbooks/datasources_workbooks_views_url_parser.py @@ -1,5 +1,6 @@ import os +import tableauserverclient as TSC from uuid import UUID from tabcmd.commands.constants import Errors @@ -14,7 +15,7 @@ class DatasourcesWorkbooksAndViewsUrlParser(Server): """ def __init__(self, args): - super().__init__(args) + pass @staticmethod def get_view_url_from_names(wb_name, view_name): @@ -179,12 +180,12 @@ def get_url_item_and_item_type_from_view_url(logger, url, server): @staticmethod def get_content_and_server_content_type_from_url(logger, server, view_content_url, custom_view_id): - item = DatasourcesAndWorkbooks.get_view_by_content_url(logger, server, view_content_url) + item: TSC.ViewItem | TSC.CustomViewItem = DatasourcesAndWorkbooks.get_view_by_content_url(logger, server, view_content_url) server_content_type = server.views if custom_view_id: - custom_view_item = DatasourcesAndWorkbooks.get_custom_view_by_id(logger, server, custom_view_id) - if custom_view_item.view.id != item.id: + custom_view_item: TSC.CustomViewItem = DatasourcesAndWorkbooks.get_custom_view_by_id(logger, server, custom_view_id) + if not custom_view_item.view or custom_view_item.view.id != item.id: Errors.exit_with_error(logger, "Invalid custom view URL provided") server_content_type = server.custom_views item = custom_view_item diff --git a/tabcmd/commands/datasources_and_workbooks/delete_command.py b/tabcmd/commands/datasources_and_workbooks/delete_command.py index 3e82e081..71085c74 100644 --- a/tabcmd/commands/datasources_and_workbooks/delete_command.py +++ b/tabcmd/commands/datasources_and_workbooks/delete_command.py @@ -27,9 +27,9 @@ def define_args(delete_parser): set_project_r_arg(group) set_parent_project_arg(group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -75,4 +75,4 @@ def run_command(args): server.datasources.delete(item_to_delete.id) logger.info(_("common.output.succeeded")) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) diff --git a/tabcmd/commands/datasources_and_workbooks/export_command.py b/tabcmd/commands/datasources_and_workbooks/export_command.py index ac1ed1b3..9881bd44 100644 --- a/tabcmd/commands/datasources_and_workbooks/export_command.py +++ b/tabcmd/commands/datasources_and_workbooks/export_command.py @@ -13,6 +13,7 @@ pagesize = TSC.PDFRequestOptions.PageType # type alias for brevity pageorientation = TSC.PDFRequestOptions.Orientation imageresolution = TSC.ImageRequestOptions.Resolution +RequestOptionsType = TSC.ExcelRequestOptions | TSC.CSVRequestOptions | TSC.PDFRequestOptions | TSC.ImageRequestOptions ImageResolutionStandard = "standard" @@ -81,9 +82,9 @@ def define_args(export_parser): it to a file. This command can also export just the data used for a view_name """ - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -97,7 +98,7 @@ def run_command(args): if not view_content_url and not wb_content_url: view_example = "/workbook_name/view_name" message = "{} [{}]".format( - _("export.errors.requires_workbook_view_param").format(__class__.__name__), view_example + _("export.errors.requires_workbook_view_param").format("ExportCommand"), view_example ) Errors.exit_with_error(logger, message) @@ -127,7 +128,7 @@ def run_command(args): default_filename = "{}.png".format(export_item.name) except TSC.ServerResponseError as e: - Errors.exit_with_error(logger, _("publish.errors.unexpected_server_response").format(""), e) + Errors.exit_with_error(logger, _("publish.errors.unexpected_server_response").format(""), exception=e) except Exception as e: Errors.exit_with_error(logger, exception=e) try: @@ -138,10 +139,10 @@ def run_command(args): ExportCommand.save_to_file(logger, output, save_name) except Exception as e: - Errors.exit_with_error(logger, "Error saving to file", e) + Errors.exit_with_error(logger, "Error saving to file", exception=e) @staticmethod - def apply_filters_from_args(request_options: TSC.RequestOptions, args, logger=None) -> None: + def apply_filters_from_args(request_options: RequestOptionsType, args, logger=None) -> None: if args.filter: logger.debug("filter = {}".format(args.filter)) params = args.filter.split("&") diff --git a/tabcmd/commands/datasources_and_workbooks/get_url_command.py b/tabcmd/commands/datasources_and_workbooks/get_url_command.py index c0ea0b72..fb5cb385 100644 --- a/tabcmd/commands/datasources_and_workbooks/get_url_command.py +++ b/tabcmd/commands/datasources_and_workbooks/get_url_command.py @@ -31,14 +31,14 @@ def define_args(get_url_parser): # tabcmd get "/views/Finance/InvestmentGrowth.png?:size=640,480" -f growth.png # tabcmd get "/views/Finance/InvestmentGrowth.png?:refresh=yes" -f growth.png - @staticmethod - def run_command(args): + @classmethod + def run_command(cls, args): # A view can be returned in PDF, PNG, or CSV (summary data only) format. # A Tableau workbook is returned as a TWB if it connects to a datasource/live connection, # or a TWBX if it uses an extract. # A Tableau datasource is returned as a TDS if it connects to a live connection, # or a TDSX if it uses an extract. - logger = log(__class__.__name__, args.logging_level) + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/commands/datasources_and_workbooks/publish_command.py b/tabcmd/commands/datasources_and_workbooks/publish_command.py index 6388cc38..df11987b 100644 --- a/tabcmd/commands/datasources_and_workbooks/publish_command.py +++ b/tabcmd/commands/datasources_and_workbooks/publish_command.py @@ -33,9 +33,9 @@ def define_args(publish_parser): set_append_replace_option(group) set_parent_project_arg(group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -50,22 +50,24 @@ def run_command(args): project_id = dest_project.id except Exception as exc: logger.error(exc.__str__()) - Errors.exit_with_error(logger, _("publish.errors.server_resource_not_found"), exc) + Errors.exit_with_error(logger, _("publish.errors.server_resource_not_found"), exception=exc) publish_mode = PublishCommand.get_publish_mode(args, logger) - connection = TSC.models.ConnectionItem() + connection: TSC.models.ConnectionItem | None = None if args.db_username: + connection = TSC.models.ConnectionItem() connection.connection_credentials = TSC.models.ConnectionCredentials( args.db_username, args.db_password, embed=args.save_db_password ) elif args.oauth_username: + connection = TSC.models.ConnectionItem() connection.connection_credentials = TSC.models.ConnectionCredentials( args.oauth_username, None, embed=False, oauth=args.save_oauth ) else: - logger.debug("No db-username or oauth-username found in command") connection = None + logger.debug("No db-username or oauth-username found in command") if connection: connections = list() diff --git a/tabcmd/commands/datasources_and_workbooks/runschedule_command.py b/tabcmd/commands/datasources_and_workbooks/runschedule_command.py index 3f39d499..e6685d0f 100644 --- a/tabcmd/commands/datasources_and_workbooks/runschedule_command.py +++ b/tabcmd/commands/datasources_and_workbooks/runschedule_command.py @@ -18,9 +18,9 @@ def define_args(runschedule_parser): group = runschedule_parser.add_argument_group(title=RunSchedule.name) group.add_argument("schedule", help="Name of the schedule to run") - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/commands/extracts/create_extracts_command.py b/tabcmd/commands/extracts/create_extracts_command.py index 16f3b282..8378aa0f 100644 --- a/tabcmd/commands/extracts/create_extracts_command.py +++ b/tabcmd/commands/extracts/create_extracts_command.py @@ -26,9 +26,9 @@ def define_args(create_extract_parser): set_project_arg(group) set_parent_project_arg(group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -39,9 +39,10 @@ def run_command(args): ) try: item = Extracts.get_wb_or_ds_for_extracts(args, logger, server) + job: TSC.JobItem if args.datasource: logger.info(_("createextracts.for.datasource").format(args.datasource)) - job: TSC.JobItem = server.datasources.create_extract(item, encrypt=args.encrypt) + job = server.datasources.create_extract(item, encrypt=args.encrypt) else: if not args.include_all and not args.embedded_datasources: @@ -51,7 +52,7 @@ def run_command(args): ) logger.info(_("createextracts.for.workbook_name").format(args.workbook)) - job: TSC.JobItem = server.workbooks.create_extract( + job = server.workbooks.create_extract( item, encrypt=args.encrypt, includeAll=args.include_all, @@ -62,7 +63,7 @@ def run_command(args): if args.continue_if_exists and Errors.is_resource_conflict(e): logger.info(_("errors.xmlapi.already_exists").format(_("tabcmd.content_type.extract"), args.name)) return - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) logger.info(_("common.output.job_queued_success")) logger.debug("Extract creation queued with JobID: {}".format(job.id)) diff --git a/tabcmd/commands/extracts/decrypt_extracts_command.py b/tabcmd/commands/extracts/decrypt_extracts_command.py index cb47d7fa..8f7eae99 100644 --- a/tabcmd/commands/extracts/decrypt_extracts_command.py +++ b/tabcmd/commands/extracts/decrypt_extracts_command.py @@ -19,9 +19,9 @@ def define_args(decrypt_extract_parser): group = decrypt_extract_parser.add_argument_group(title=DecryptExtracts.name) group.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -30,7 +30,7 @@ def run_command(args): logger.info(_("decryptextracts.status").format(args.site_name)) job = server.sites.decrypt_extracts(site_item.id) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) logger.info(_("common.output.job_queued_success")) logger.debug("Extract decryption queued with JobID: {}".format(job.id)) diff --git a/tabcmd/commands/extracts/delete_extracts_command.py b/tabcmd/commands/extracts/delete_extracts_command.py index ab32a458..e9b2981f 100644 --- a/tabcmd/commands/extracts/delete_extracts_command.py +++ b/tabcmd/commands/extracts/delete_extracts_command.py @@ -25,17 +25,18 @@ def define_args(delete_extract_parser): set_project_arg(group) set_parent_project_arg(group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) try: item = Extracts.get_wb_or_ds_for_extracts(args, logger, server) + job: TSC.JobItem if args.datasource: logger.info(_("deleteextracts.for.datasource").format(args.datasource)) - job: TSC.JobItem = server.datasources.delete_extract(item) + job = server.datasources.delete_extract(item) else: if not args.include_all and not args.embedded_datasources: Errors.exit_with_error( @@ -43,12 +44,12 @@ def run_command(args): _("extracts.workbook.errors.requires_datasources_or_include_all").format("deleteextracts"), ) logger.info(_("deleteextracts.for.workbook_name").format(args.workbook)) - job: TSC.JobItem = server.workbooks.delete_extract( + job = server.workbooks.delete_extract( item, includeAll=args.include_all, datasources=args.embedded_datasources ) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) logger.info(_("common.output.job_queued_success")) logger.debug("Extract deletion queued with JobID: {}".format(job.id)) diff --git a/tabcmd/commands/extracts/encrypt_extracts_command.py b/tabcmd/commands/extracts/encrypt_extracts_command.py index 0454d0ea..42d70cbd 100644 --- a/tabcmd/commands/extracts/encrypt_extracts_command.py +++ b/tabcmd/commands/extracts/encrypt_extracts_command.py @@ -21,9 +21,9 @@ def define_args(encrypt_extract_parser): group = encrypt_extract_parser.add_argument_group(title=EncryptExtracts.name) group.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -32,7 +32,7 @@ def run_command(args): logger.info(_("encryptextracts.status").format(site_item.name)) job = server.sites.encrypt_extracts(site_item.id) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) logger.info(_("common.output.job_queued_success")) logger.debug("Extract encryption queued with JobID: {}".format(job.id)) diff --git a/tabcmd/commands/extracts/extracts.py b/tabcmd/commands/extracts/extracts.py index 60c63c00..3d2cdedd 100644 --- a/tabcmd/commands/extracts/extracts.py +++ b/tabcmd/commands/extracts/extracts.py @@ -18,10 +18,11 @@ def get_wb_or_ds_for_extracts(args, logger, server): return datasource elif args.workbook or args.url: + workbook_item: TSC.WorkbookItem if args.workbook: - workbook_item: TSC.WorkbookItem = Server.get_workbook_item(logger, server, args.workbook, container) + workbook_item = Server.get_workbook_item(logger, server, args.workbook, container) else: - workbook_item: TSC.WorkbookItem = DatasourcesAndWorkbooks.get_wb_by_content_url( + workbook_item = DatasourcesAndWorkbooks.get_wb_by_content_url( logger, server, args.url ) logger.info(_("export.status").format(workbook_item.name)) diff --git a/tabcmd/commands/extracts/reencrypt_extracts_command.py b/tabcmd/commands/extracts/reencrypt_extracts_command.py index e8eb363a..3026fff9 100644 --- a/tabcmd/commands/extracts/reencrypt_extracts_command.py +++ b/tabcmd/commands/extracts/reencrypt_extracts_command.py @@ -21,9 +21,9 @@ def define_args(reencrypt_extract_parser): group = reencrypt_extract_parser.add_argument_group(title=ReencryptExtracts.name) group.add_argument("site_name", metavar="site-name", help=_("editsite.options.site-name")) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -32,7 +32,7 @@ def run_command(args): logger.info(_("reencryptextracts.status").format(site_item.name)) job = server.sites.encrypt_extracts(site_item.id) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) logger.info(_("common.output.job_queued_success")) logger.debug("Extract re-encryption queued with JobID: {}".format(job.id)) diff --git a/tabcmd/commands/extracts/refresh_extracts_command.py b/tabcmd/commands/extracts/refresh_extracts_command.py index 4286e61f..ab978978 100644 --- a/tabcmd/commands/extracts/refresh_extracts_command.py +++ b/tabcmd/commands/extracts/refresh_extracts_command.py @@ -24,9 +24,9 @@ def define_args(refresh_extract_parser): set_parent_project_arg(group) set_sync_wait_options(group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -38,15 +38,16 @@ def run_command(args): try: item = Extracts.get_wb_or_ds_for_extracts(args, logger, server) + job: TSC.JobItem if args.datasource: logger.info(_("refreshextracts.status_refreshed").format(_("content_type.datasource"), args.datasource)) - job: TSC.JobItem = server.datasources.refresh(item.id, incremental_refresh) + job = server.datasources.refresh(item.id, incremental_refresh) else: - job: TSC.JobItem = server.workbooks.refresh(item.id, incremental_refresh) + job = server.workbooks.refresh(item.id, incremental_refresh) logger.info(_("refreshextracts.status_refreshed").format(_("content_type.workbook"), args.workbook)) except Exception as e: - Errors.exit_with_error(logger, _("refreshextracts.errors.error"), e) + Errors.exit_with_error(logger, _("refreshextracts.errors.error"), exception=e) logger.info(_("common.output.job_queued_success")) logger.debug("Extract refresh queued with JobID: {}".format(job.id)) @@ -59,4 +60,4 @@ def run_command(args): logger.info("Job completed: ") logger.info(job_done) except Exception as je: - Errors.exit_with_error(logger, je) + Errors.exit_with_error(logger, exception=je) diff --git a/tabcmd/commands/group/create_group_command.py b/tabcmd/commands/group/create_group_command.py index 36d7c5f8..ff04039f 100644 --- a/tabcmd/commands/group/create_group_command.py +++ b/tabcmd/commands/group/create_group_command.py @@ -20,9 +20,9 @@ def define_args(create_group_parser): args_group = create_group_parser.add_argument_group(title=CreateGroupCommand.name) args_group.add_argument("name") - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/commands/group/delete_group_command.py b/tabcmd/commands/group/delete_group_command.py index c1fca0b8..bc3f1f49 100644 --- a/tabcmd/commands/group/delete_group_command.py +++ b/tabcmd/commands/group/delete_group_command.py @@ -20,9 +20,9 @@ def define_args(delete_group_parser): args_group = delete_group_parser.add_argument_group(title=DeleteGroupCommand.name) args_group.add_argument("name") - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -33,4 +33,4 @@ def run_command(args): server.groups.delete(group_id) logger.info(_("common.output.succeeded")) except Exception as e: - Errors.exit_with_error(logger, _("errors.bad_request.detail.generic_delete_groups_error"), e) + Errors.exit_with_error(logger, _("errors.bad_request.detail.generic_delete_groups_error"), exception=e) diff --git a/tabcmd/commands/project/create_project_command.py b/tabcmd/commands/project/create_project_command.py index ef40875f..e5096061 100644 --- a/tabcmd/commands/project/create_project_command.py +++ b/tabcmd/commands/project/create_project_command.py @@ -25,9 +25,9 @@ def define_args(create_project_parser): set_parent_project_arg(create_project_parser) set_description_arg(create_project_parser) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -36,9 +36,10 @@ def run_command(args): if args.parent_project_path: try: logger.debug(_("tabcmd.find.parent_project").format(args.parent_project_path)) + # use none for the readable name because we are just looking for the parent path parent = Server.get_project_by_name_and_parent_path(logger, server, None, args.parent_project_path) except Exception as exc: - Errors.exit_with_error(logger, exc) + Errors.exit_with_error(logger, exception=exc) readable_name = "{0}/{1}".format(args.parent_project_path, args.project_name) parent_id = parent.id logger.debug("parent project = `{0}`, id = {1}".format(args.parent_project_path, parent_id)) @@ -54,6 +55,6 @@ def run_command(args): logger.info(_("errors.xmlapi.already_exists").format(_("content_type.project"), args.project_name)) logger.info(_("common.output.succeeded")) else: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) return project_item diff --git a/tabcmd/commands/project/delete_project_command.py b/tabcmd/commands/project/delete_project_command.py index b107406c..550b15b1 100644 --- a/tabcmd/commands/project/delete_project_command.py +++ b/tabcmd/commands/project/delete_project_command.py @@ -22,9 +22,9 @@ def define_args(delete_project_parser): args_group.add_argument("project_name", metavar="project-name", help=_("createproject.options.name")) set_parent_project_arg(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -37,7 +37,7 @@ def run_command(args): logger, server, args.project_name, args.parent_project_path ) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) project_id = project.id try: @@ -45,4 +45,4 @@ def run_command(args): server.projects.delete(project_id) logger.info(_("common.output.succeeded")) except Exception as e: - Errors.exit_with_error(logger, "tabcmd.result.failure.delete.project", e) + Errors.exit_with_error(logger, "tabcmd.result.failure.delete.project", exception=e) diff --git a/tabcmd/commands/project/publish_samples_command.py b/tabcmd/commands/project/publish_samples_command.py index a8532934..273575b2 100644 --- a/tabcmd/commands/project/publish_samples_command.py +++ b/tabcmd/commands/project/publish_samples_command.py @@ -26,9 +26,9 @@ def define_args(publish_samples_parser): ) set_parent_project_arg(args_group) # args.parent_project_name - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/commands/site/create_site_command.py b/tabcmd/commands/site/create_site_command.py index f684ba1f..947534d9 100644 --- a/tabcmd/commands/site/create_site_command.py +++ b/tabcmd/commands/site/create_site_command.py @@ -22,9 +22,9 @@ def define_args(create_site_parser): args_group.add_argument("new_site_name", metavar="site-name", help=_("editsite.options.site-name")) set_common_site_args(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -46,4 +46,4 @@ def run_command(args): if Errors.is_resource_conflict(e) and args.continue_if_exists: logger.info(_("createsite.errors.site_name_already_exists").format(args.new_site_name)) return - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) diff --git a/tabcmd/commands/site/delete_site_command.py b/tabcmd/commands/site/delete_site_command.py index 672d0141..6404d4b2 100644 --- a/tabcmd/commands/site/delete_site_command.py +++ b/tabcmd/commands/site/delete_site_command.py @@ -20,9 +20,9 @@ def define_args(delete_site_parser): args_group = delete_site_parser.add_argument_group(title=DeleteSiteCommand.name) args_group.add_argument("site_name_to_delete", metavar="site-name", help=_("tabcmd.options.delete_site.name")) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -33,4 +33,4 @@ def run_command(args): server.sites.delete(target_site_id) logger.info(_("tabcmd.deletesite.success").format(args.site_name_to_delete)) except Exception as e: - Errors.exit_with_error(logger, _("tabcmd.deletesite.error"), e) + Errors.exit_with_error(logger, _("tabcmd.deletesite.error"), exception=e) diff --git a/tabcmd/commands/site/edit_site_command.py b/tabcmd/commands/site/edit_site_command.py index ab6451ed..8b92a8ae 100644 --- a/tabcmd/commands/site/edit_site_command.py +++ b/tabcmd/commands/site/edit_site_command.py @@ -25,9 +25,9 @@ def define_args(edit_site_parser): set_common_site_args(args_group) set_site_status_arg(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -47,4 +47,4 @@ def run_command(args): logger.info(_("common.output.succeeded")) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) diff --git a/tabcmd/commands/site/list_command.py b/tabcmd/commands/site/list_command.py index 66c5aac0..dd8aecf5 100644 --- a/tabcmd/commands/site/list_command.py +++ b/tabcmd/commands/site/list_command.py @@ -21,8 +21,8 @@ def define_args(list_parser): ) args_group.add_argument("-d", "--details", action="store_true", help=_("tabcmd.options.include_details")) - @staticmethod - def run_command(args): + @classmethod + def run_command(cls, args): logger = log(__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() @@ -55,4 +55,4 @@ def run_command(args): logger.info(_("tabcmd.listing.label.name").format(item.name)) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) diff --git a/tabcmd/commands/site/list_sites_command.py b/tabcmd/commands/site/list_sites_command.py index 646a41db..6f204483 100644 --- a/tabcmd/commands/site/list_sites_command.py +++ b/tabcmd/commands/site/list_sites_command.py @@ -21,9 +21,9 @@ def define_args(list_site_parser): group = list_site_parser.add_argument_group(title=ListSiteCommand.name) set_site_detail_option(group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -35,4 +35,4 @@ def run_command(args): if args.get_extract_encryption_mode: logger.info("EXTRACTENCRYPTION: {}".format(site.extract_encryption_mode)) except Exception as e: - Errors.exit_with_error(logger, e) + Errors.exit_with_error(logger, exception=e) diff --git a/tabcmd/commands/user/add_users_command.py b/tabcmd/commands/user/add_users_command.py index be3d5d58..3e591087 100644 --- a/tabcmd/commands/user/add_users_command.py +++ b/tabcmd/commands/user/add_users_command.py @@ -3,7 +3,7 @@ from tabcmd.execution.localize import _ from tabcmd.execution.logger_config import log from .user_data import UserCommand - +import sys class AddUserCommand(UserCommand): """ @@ -20,13 +20,17 @@ def define_args(add_user_parser): set_users_file_arg(args_group) set_completeness_options(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) logger.info(_("addusers.status").format(args.users.name, args.name)) - UserCommand.act_on_users(logger, server, "added", server.groups.add_user, args) + try: + UserCommand.act_on_users(logger, server, "added", server.groups.add_user, args) + except Exception as e: + logger.error(e.__cause__) + sys.exit(1) diff --git a/tabcmd/commands/user/create_site_users.py b/tabcmd/commands/user/create_site_users.py index 98772b20..d9229b72 100644 --- a/tabcmd/commands/user/create_site_users.py +++ b/tabcmd/commands/user/create_site_users.py @@ -26,9 +26,9 @@ def define_args(create_site_users_parser): set_completeness_options(args_group) UserCommand.set_auth_arg(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) @@ -59,9 +59,10 @@ def run_command(args): if Errors.is_resource_conflict(e) and args.continue_if_exists: logger.debug(_("createsite.errors.site_name_already_exists").format(user_obj.name)) else: + logger.debug(type(e)) number_of_errors += 1 logger.debug(number_of_errors) - error_list.append(e.summary + ": " + e.detail) + error_list.append(e.__class__.__name__) # + ": " + e.__cause__ or "Unknown") logger.debug(error_list) logger.info(_("session.monitorjob.percent_complete").format(100)) logger.info(_("importcsvsummary.line.processed").format(number_of_users_listed)) diff --git a/tabcmd/commands/user/create_users_command.py b/tabcmd/commands/user/create_users_command.py index f44ee333..ab2a92a2 100644 --- a/tabcmd/commands/user/create_users_command.py +++ b/tabcmd/commands/user/create_users_command.py @@ -26,9 +26,9 @@ def define_args(create_users_parser): set_completeness_options(args_group) UserCommand.set_auth_arg(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/commands/user/delete_site_users_command.py b/tabcmd/commands/user/delete_site_users_command.py index 6c9c112c..4b23594b 100644 --- a/tabcmd/commands/user/delete_site_users_command.py +++ b/tabcmd/commands/user/delete_site_users_command.py @@ -23,9 +23,9 @@ def define_args(delete_site_users_parser): set_users_file_positional(args_group) set_completeness_options(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/commands/user/remove_users_command.py b/tabcmd/commands/user/remove_users_command.py index d9b86adc..c4e9c558 100644 --- a/tabcmd/commands/user/remove_users_command.py +++ b/tabcmd/commands/user/remove_users_command.py @@ -20,9 +20,9 @@ def define_args(remove_users_parser): set_users_file_arg(args_group) set_completeness_options(args_group) - @staticmethod - def run_command(args): - logger = log(__class__.__name__, args.logging_level) + @classmethod + def run_command(cls, args): + logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) session = Session() server = session.create_session(args, logger) diff --git a/tabcmd/execution/parent_parser.py b/tabcmd/execution/parent_parser.py index 495f8ba2..14f55506 100644 --- a/tabcmd/execution/parent_parser.py +++ b/tabcmd/execution/parent_parser.py @@ -192,10 +192,9 @@ def include_help(self): class Help: - parser = None # This needs to have access to the parser when it gets called def __init__(self, _parser: ParentParser): - self.parser = _parser + self.parser: ParentParser = _parser def run_command(self, args): logger = log(__name__, "info") diff --git a/tabcmd/tabcmd.py b/tabcmd/tabcmd.py index 70b8b63d..44904041 100644 --- a/tabcmd/tabcmd.py +++ b/tabcmd/tabcmd.py @@ -17,11 +17,12 @@ def main(): print("Keyboard Interrupt: exiting") sys.exit(1) except Exception as e: + traceback_info = f"at line {e.__traceback__.tb_lineno} of {__file__}" if e.__traceback__ else "traceback unavailable" sys.stderr.writelines( [ "ERROR\n", "Unhandled exception: {}\n".format(type(e).__name__), - f"at line {e.__traceback__.tb_lineno} of {__file__}: {e}\n", + f"{traceback_info}: {e}\n", ] ) sys.exit(1) From 112654eb22f419a1b0a9992bb1a7dc15a9524032 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 13 Aug 2025 23:48:58 -0700 Subject: [PATCH 2/7] type fixes: session.py --- tabcmd/commands/auth/session.py | 77 +++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index cdf0634e..cce15d8f 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -13,7 +13,7 @@ from tabcmd.execution.localize import _ from tabcmd.execution.logger_config import log -from typing import Dict, Any +from typing import Dict, Any, Optional class Session: @@ -28,31 +28,31 @@ class Session: PASSWORD_CRED_TYPE = "password" def __init__(self): - self.username = None + self.username: Optional[str] = None # we don't store the password - self.user_id = None - self.auth_token = None - self.token_name = None - self.token_value = None - self.password_file = None - self.token_file = None - self.site_name = None # The site name, e.g 'alpodev' - self.site_id = None # The site id, e.g 'abcd-1234-1234-1244-1234' - self.server_url = None - self.last_command = None # for when we have to renew the session then re-try - self.last_login_using = None - - self.no_prompt = False - self.certificate = None - self.no_certcheck = False - self.no_proxy = False - self.proxy = None - self.timeout = None - - self.logging_level = "info" + self.user_id: Optional[str] = None + self.auth_token: Optional[str] = None + self.token_name: Optional[str] = None + self.token_value: Optional[str] = None + self.password_file: Optional[str] = None + self.token_file: Optional[str] = None + self.site_name: Optional[str] = None # The site name, e.g 'alpodev' + self.site_id: Optional[str] = None # The site id, e.g 'abcd-1234-1234-1244-1234' + self.server_url: Optional[str] = None + self.last_command: Optional[str] = None # for when we have to renew the session then re-try + self.last_login_using: Optional[str] = None + + self.no_prompt: bool = False + self.certificate: Optional[str] = None + self.no_certcheck: bool = False + self.no_proxy: bool = False + self.proxy: Optional[str] = None + self.timeout: Optional[int] = None + + self.logging_level: str = "info" self.logger = log(__name__, self.logging_level) # instantiate here mostly for tests self._read_from_json() - self.tableau_server = None # this one is an object that doesn't get persisted in the file + self.tableau_server: Optional[TSC.Server] = None # this one is an object that doesn't get persisted in the file # called before we connect to the server # generally, we don't want to overwrite stored data with nulls @@ -201,6 +201,10 @@ def _open_connection_with_opts(self) -> TSC.Server: return tableau_server def _verify_server_connection_unauthed(self): + if not self.tableau_server: + Errors.exit_with_error(self.logger, "No server connection available") + + assert self.tableau_server is not None # Type hint for mypy try: self.tableau_server.use_server_version() except requests.exceptions.ReadTimeout as timeout_error: @@ -222,7 +226,12 @@ def _create_new_connection(self) -> TSC.Server: try: self.tableau_server = self._open_connection_with_opts() except Exception as e: - Errors.exit_with_error(self.logger, "Failed to connect to server", e) + Errors.exit_with_error(self.logger, "Failed to connect to server", exception=e) + + if not self.tableau_server: + Errors.exit_with_error(self.logger, "Failed to create server connection") + + assert self.tableau_server is not None # Type hint for mypy return self.tableau_server def _read_existing_state(self): @@ -246,7 +255,7 @@ def _print_server_info(self): def _validate_existing_signin(self): # when do these two messages show up? self.logger.info(_("session.auto_site_login")) try: - if self.tableau_server and self.tableau_server.is_signed_in(): + if self.tableau_server and self.tableau_server.is_signed_in() and self.user_id: server_user = self.tableau_server.users.get_by_id(self.user_id).name if not self.username: self.logger.info("Fetched user details from server") @@ -261,8 +270,12 @@ def _validate_existing_signin(self): # server connection created, not yet logged in def _sign_in(self, tableau_auth) -> TSC.Server: - self.logger.debug(_("session.login") + self.server_url) + if not self.tableau_server: + Errors.exit_with_error(self.logger, "No server connection available for sign in") + + self.logger.debug(_("session.login") + (self.server_url or "")) self.logger.debug(_("listsites.output").format("", self.username or self.token_name, self.site_name)) + assert self.tableau_server is not None # Type hint for mypy try: self.tableau_server.auth.sign_in(tableau_auth) # it's the same call for token or user-pass except Exception as e: @@ -298,7 +311,7 @@ def create_session(self, args, logger): self._read_existing_state() self._update_session_data(args) self.logging_level = args.logging_level or self.logging_level - self.logger = logger or log(__class__.__name__, self.logging_level) + self.logger = logger or log(self.__class__.__name__, self.logging_level) credentials = None if args.password or args.password_file: @@ -360,8 +373,8 @@ def _clear_data(self): self.tableau_server = None self.certificate = None - self.no_certcheck = None - self.no_proxy = None + self.no_certcheck = False + self.no_proxy = False self.proxy = None self.timeout = None @@ -440,13 +453,13 @@ def _save_session_to_json(self): except Exception as e: self._wipe_bad_json(e, "Failed to save session file") - def _save_file(self, data): + def _save_file(self, data: Dict[str, Any]) -> None: file_path = self._get_file_path() with open(str(file_path), "w") as f: json.dump(data, f) - def _serialize_for_save(self): - data = {"tableau_auth": []} + def _serialize_for_save(self) -> Dict[str, Any]: + data: Dict[str, Any] = {"tableau_auth": []} data["tableau_auth"].append( { "auth_token": self.auth_token, From 4457b1e1ca74f1cf3668a01685144ae1e3226d45 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 13 Aug 2025 23:51:15 -0700 Subject: [PATCH 3/7] typing: parser tests --- tests/parsers/common_setup.py | 44 ++++++++++++------- tests/parsers/test_login_parser.py | 4 +- tests/parsers/test_logout_parser.py | 2 +- tests/parsers/test_parser_add_user.py | 6 +-- tests/parsers/test_parser_create_extracts.py | 2 +- tests/parsers/test_parser_create_group.py | 2 +- tests/parsers/test_parser_create_project.py | 2 +- tests/parsers/test_parser_create_site.py | 2 +- .../parsers/test_parser_create_site_users.py | 2 +- tests/parsers/test_parser_create_user.py | 2 +- tests/parsers/test_parser_decrypt_extracts.py | 2 +- tests/parsers/test_parser_delete.py | 2 +- tests/parsers/test_parser_delete_extracts.py | 2 +- tests/parsers/test_parser_delete_group.py | 2 +- tests/parsers/test_parser_delete_project.py | 2 +- tests/parsers/test_parser_delete_site.py | 2 +- tests/parsers/test_parser_delete_site_user.py | 2 +- tests/parsers/test_parser_edit_site.py | 2 +- tests/parsers/test_parser_encrypt_extracts.py | 2 +- tests/parsers/test_parser_export.py | 2 +- tests/parsers/test_parser_get_url.py | 4 +- tests/parsers/test_parser_list_sites.py | 2 +- tests/parsers/test_parser_publish.py | 2 +- tests/parsers/test_parser_publish_samples.py | 2 +- .../parsers/test_parser_reencrypt_extracts.py | 2 +- tests/parsers/test_parser_refresh_extracts.py | 2 +- tests/parsers/test_parser_remove_user.py | 2 +- tests/parsers/test_parser_runschedule.py | 2 +- 28 files changed, 58 insertions(+), 48 deletions(-) diff --git a/tests/parsers/common_setup.py b/tests/parsers/common_setup.py index 619d4805..90583f18 100644 --- a/tests/parsers/common_setup.py +++ b/tests/parsers/common_setup.py @@ -1,33 +1,43 @@ +import unittest +from unittest import mock from tabcmd.execution import parent_parser from collections import namedtuple +import argparse +from typing import Optional, Type, Any +__all__ = ['ParserTest', 'initialize_test_pieces', 'mock_command_action', 'encoding'] encoding = "utf-8-sig" -def mock_command_action(): +def mock_command_action() -> None: print("a mockery!") # basically replaces tabcmd_controller:initialize_parsers -def initialize_test_pieces(commandname, command_object): +def initialize_test_pieces(commandname: str, command_object: Type[Any]) -> argparse.ArgumentParser: manager = parent_parser.ParentParser() - parser = manager.get_root_parser() - mock_command = namedtuple("TestObject", "name, run_command, description, define_args") - mock_command.name = commandname - mock_command.run_command = mock_command_action - mock_command.description = "mock help text" - mock_command.define_args = command_object.define_args + parser_under_test = manager.get_root_parser() + MockCommand = namedtuple("MockCommand", "name, run_command, description, define_args") + mock_command = MockCommand( + name=commandname, + run_command=mock_command_action, + description="mock help text", + define_args=command_object.define_args + ) manager.include(mock_command) - return parser + return parser_under_test -""" - base test cases for each parser: - has_required_arguments - (maybe) missing required arguments - has optional arguments - bad mix of optional arguments - has unknown arguments - """ +class ParserTest(unittest.TestCase): + parser_under_test: argparse.ArgumentParser + + """ + base test cases for each parser: + has_required_arguments + (maybe) missing required arguments + has optional arguments + bad mix of optional arguments + has unknown arguments + """ \ No newline at end of file diff --git a/tests/parsers/test_login_parser.py b/tests/parsers/test_login_parser.py index 2276ef45..5c7cef5d 100644 --- a/tests/parsers/test_login_parser.py +++ b/tests/parsers/test_login_parser.py @@ -4,11 +4,11 @@ from tabcmd.commands.auth.login_command import LoginCommand from .common_setup import * -commandname = "login" +commandname: str = "login" @mock.patch("sys.argv", None) -class LoginParserTest(unittest.TestCase): +class LoginParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, LoginCommand) diff --git a/tests/parsers/test_logout_parser.py b/tests/parsers/test_logout_parser.py index 5d59eab7..4c1aaee5 100644 --- a/tests/parsers/test_logout_parser.py +++ b/tests/parsers/test_logout_parser.py @@ -8,7 +8,7 @@ commandname = "logout" -class LogoutParserTest(unittest.TestCase): +class LogoutParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, LogoutCommand) diff --git a/tests/parsers/test_parser_add_user.py b/tests/parsers/test_parser_add_user.py index fe57f669..5b07b14f 100644 --- a/tests/parsers/test_parser_add_user.py +++ b/tests/parsers/test_parser_add_user.py @@ -7,7 +7,7 @@ commandname = "addusers" -class AddUsersParserTest(unittest.TestCase): +class AddUsersParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, AddUserCommand) @@ -26,8 +26,8 @@ def test_add_users_parser_users_file(self): with mock.patch("builtins.open", mock.mock_open(read_data="test")) as open_file: mock_args = [commandname, "group-name", "--users", "users.csv"] args = self.parser_under_test.parse_args(mock_args) - self.assertEqual(args.name, "group-name"), args - open_file.assert_called_with("users.csv", "r", -1, encoding, None), args + self.assertEqual(args.name, "group-name") + open_file.assert_called_with("users.csv", "r", -1, encoding, None) @mock.patch("builtins.open") def test_add_user_parser_missing_group_name(self, filereader): diff --git a/tests/parsers/test_parser_create_extracts.py b/tests/parsers/test_parser_create_extracts.py index 8a6d3f49..94ca7a7e 100644 --- a/tests/parsers/test_parser_create_extracts.py +++ b/tests/parsers/test_parser_create_extracts.py @@ -6,7 +6,7 @@ commandname = "createextracts" -class CreateExtractsParserTest(unittest.TestCase): +class CreateExtractsParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, CreateExtracts) diff --git a/tests/parsers/test_parser_create_group.py b/tests/parsers/test_parser_create_group.py index de05934d..db9500aa 100644 --- a/tests/parsers/test_parser_create_group.py +++ b/tests/parsers/test_parser_create_group.py @@ -6,7 +6,7 @@ commandname = "creategroup" -class CreateGroupParserTest(unittest.TestCase): +class CreateGroupParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, CreateGroupCommand) diff --git a/tests/parsers/test_parser_create_project.py b/tests/parsers/test_parser_create_project.py index b02319c6..1a7551f6 100644 --- a/tests/parsers/test_parser_create_project.py +++ b/tests/parsers/test_parser_create_project.py @@ -6,7 +6,7 @@ commandname = "createproject" -class CreateProjectParserTest(unittest.TestCase): +class CreateProjectParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, CreateProjectCommand) diff --git a/tests/parsers/test_parser_create_site.py b/tests/parsers/test_parser_create_site.py index ebe08813..5d4a50fb 100644 --- a/tests/parsers/test_parser_create_site.py +++ b/tests/parsers/test_parser_create_site.py @@ -6,7 +6,7 @@ commandname = "createsite" -class CreateSiteParserTest(unittest.TestCase): +class CreateSiteParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, CreateSiteCommand) diff --git a/tests/parsers/test_parser_create_site_users.py b/tests/parsers/test_parser_create_site_users.py index 3fa4dc04..93a51db2 100644 --- a/tests/parsers/test_parser_create_site_users.py +++ b/tests/parsers/test_parser_create_site_users.py @@ -7,7 +7,7 @@ commandname = "createsiteusers" -class CreateSiteUsersParserTest(unittest.TestCase): +class CreateSiteUsersParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, CreateSiteUsersCommand) diff --git a/tests/parsers/test_parser_create_user.py b/tests/parsers/test_parser_create_user.py index 7b67f24f..d4a533a1 100644 --- a/tests/parsers/test_parser_create_user.py +++ b/tests/parsers/test_parser_create_user.py @@ -7,7 +7,7 @@ commandname = "createusers" -class CreateUsersTest(unittest.TestCase): +class CreateUsersTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, CreateUsersCommand) diff --git a/tests/parsers/test_parser_decrypt_extracts.py b/tests/parsers/test_parser_decrypt_extracts.py index 1fdd5dc2..f90558c7 100644 --- a/tests/parsers/test_parser_decrypt_extracts.py +++ b/tests/parsers/test_parser_decrypt_extracts.py @@ -6,7 +6,7 @@ commandname = "decryptextracts" -class DecryptExtractsParserTest(unittest.TestCase): +class DecryptExtractsParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, DecryptExtracts) diff --git a/tests/parsers/test_parser_delete.py b/tests/parsers/test_parser_delete.py index 9d6900d0..edf93ea3 100644 --- a/tests/parsers/test_parser_delete.py +++ b/tests/parsers/test_parser_delete.py @@ -6,7 +6,7 @@ commandname = "delete" -class DeleteParserTest(unittest.TestCase): +class DeleteParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, DeleteCommand) diff --git a/tests/parsers/test_parser_delete_extracts.py b/tests/parsers/test_parser_delete_extracts.py index 1d71d187..2d12dbf8 100644 --- a/tests/parsers/test_parser_delete_extracts.py +++ b/tests/parsers/test_parser_delete_extracts.py @@ -6,7 +6,7 @@ commandname = "deleteextracts" -class DeleteExtractsParserTest(unittest.TestCase): +class DeleteExtractsParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, DeleteExtracts) diff --git a/tests/parsers/test_parser_delete_group.py b/tests/parsers/test_parser_delete_group.py index 368e3cce..865c7495 100644 --- a/tests/parsers/test_parser_delete_group.py +++ b/tests/parsers/test_parser_delete_group.py @@ -6,7 +6,7 @@ commandname = "deletegroup" -class DeleteGroupParserTestT(unittest.TestCase): +class DeleteGroupParserTestT(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, DeleteGroupCommand) diff --git a/tests/parsers/test_parser_delete_project.py b/tests/parsers/test_parser_delete_project.py index 5ecf43bc..3cb9e07c 100644 --- a/tests/parsers/test_parser_delete_project.py +++ b/tests/parsers/test_parser_delete_project.py @@ -6,7 +6,7 @@ commandname = "deleteproject" -class DeleteProjectParserTest(unittest.TestCase): +class DeleteProjectParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, DeleteProjectCommand) diff --git a/tests/parsers/test_parser_delete_site.py b/tests/parsers/test_parser_delete_site.py index 2d9562f7..26aa1f61 100644 --- a/tests/parsers/test_parser_delete_site.py +++ b/tests/parsers/test_parser_delete_site.py @@ -6,7 +6,7 @@ commandname = "deletesite" -class DeleteSiteParserTest(unittest.TestCase): +class DeleteSiteParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, DeleteSiteCommand) diff --git a/tests/parsers/test_parser_delete_site_user.py b/tests/parsers/test_parser_delete_site_user.py index c0d81b7e..e0de0c4f 100644 --- a/tests/parsers/test_parser_delete_site_user.py +++ b/tests/parsers/test_parser_delete_site_user.py @@ -7,7 +7,7 @@ commandname = "deletesiteusers" -class DeleteSiteUsersParserTest(unittest.TestCase): +class DeleteSiteUsersParserTest(ParserTest): csv = ("testname", "testpassword", "test", "test", "test", "test") @classmethod diff --git a/tests/parsers/test_parser_edit_site.py b/tests/parsers/test_parser_edit_site.py index 447deecf..98121e70 100644 --- a/tests/parsers/test_parser_edit_site.py +++ b/tests/parsers/test_parser_edit_site.py @@ -6,7 +6,7 @@ commandname = "editsites" -class EditSiteParserTest(unittest.TestCase): +class EditSiteParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, EditSiteCommand) diff --git a/tests/parsers/test_parser_encrypt_extracts.py b/tests/parsers/test_parser_encrypt_extracts.py index e87f8f2a..3162d688 100644 --- a/tests/parsers/test_parser_encrypt_extracts.py +++ b/tests/parsers/test_parser_encrypt_extracts.py @@ -6,7 +6,7 @@ commandname = "encryptextracts" -class EncryptExtractsParserTest(unittest.TestCase): +class EncryptExtractsParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, EncryptExtracts) diff --git a/tests/parsers/test_parser_export.py b/tests/parsers/test_parser_export.py index f90999a9..585f500c 100644 --- a/tests/parsers/test_parser_export.py +++ b/tests/parsers/test_parser_export.py @@ -6,7 +6,7 @@ commandname = "export" -class ExportParserTest(unittest.TestCase): +class ExportParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, ExportCommand) diff --git a/tests/parsers/test_parser_get_url.py b/tests/parsers/test_parser_get_url.py index 5467e236..3989cf7e 100644 --- a/tests/parsers/test_parser_get_url.py +++ b/tests/parsers/test_parser_get_url.py @@ -6,13 +6,13 @@ commandname = "listsites" -class GetUrlParserTest(unittest.TestCase): +class GetUrlParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, GetUrl) def test_get_url_parser_file(self): - mock_args = vars(argparse.Namespace(filename="helloworld")) + mock_args = [commandname, "--filename", "helloworld"] with self.assertRaises(SystemExit): args = self.parser_under_test.parse_args(mock_args) diff --git a/tests/parsers/test_parser_list_sites.py b/tests/parsers/test_parser_list_sites.py index 71b01234..03d86bc2 100644 --- a/tests/parsers/test_parser_list_sites.py +++ b/tests/parsers/test_parser_list_sites.py @@ -6,7 +6,7 @@ commandname = "listsites" -class ListSitesParserTest(unittest.TestCase): +class ListSitesParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, ListSiteCommand) diff --git a/tests/parsers/test_parser_publish.py b/tests/parsers/test_parser_publish.py index 65aaa20d..b26f911d 100644 --- a/tests/parsers/test_parser_publish.py +++ b/tests/parsers/test_parser_publish.py @@ -7,7 +7,7 @@ commandname = "Publish" -class PublishParserTest(unittest.TestCase): +class PublishParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, PublishCommand) diff --git a/tests/parsers/test_parser_publish_samples.py b/tests/parsers/test_parser_publish_samples.py index 43c4062a..e2b4a9c1 100644 --- a/tests/parsers/test_parser_publish_samples.py +++ b/tests/parsers/test_parser_publish_samples.py @@ -6,7 +6,7 @@ commandname = "publishsamples" -class PublishSamplesParserTest(unittest.TestCase): +class PublishSamplesParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, PublishSamplesCommand) diff --git a/tests/parsers/test_parser_reencrypt_extracts.py b/tests/parsers/test_parser_reencrypt_extracts.py index 79d82cda..01a91f6e 100644 --- a/tests/parsers/test_parser_reencrypt_extracts.py +++ b/tests/parsers/test_parser_reencrypt_extracts.py @@ -6,7 +6,7 @@ commandname = "reencryptextracts" -class ReencryptExtractsParserTest(unittest.TestCase): +class ReencryptExtractsParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, ReencryptExtracts) diff --git a/tests/parsers/test_parser_refresh_extracts.py b/tests/parsers/test_parser_refresh_extracts.py index a74ac760..2335fbfd 100644 --- a/tests/parsers/test_parser_refresh_extracts.py +++ b/tests/parsers/test_parser_refresh_extracts.py @@ -6,7 +6,7 @@ commandname = "refreshextracts" -class RefreshExtractsParserTest(unittest.TestCase): +class RefreshExtractsParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, RefreshExtracts) diff --git a/tests/parsers/test_parser_remove_user.py b/tests/parsers/test_parser_remove_user.py index 6441354a..f47666d6 100644 --- a/tests/parsers/test_parser_remove_user.py +++ b/tests/parsers/test_parser_remove_user.py @@ -7,7 +7,7 @@ commandname = "removeusers" -class RemoveUsersParserTest(unittest.TestCase): +class RemoveUsersParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, RemoveUserCommand) diff --git a/tests/parsers/test_parser_runschedule.py b/tests/parsers/test_parser_runschedule.py index 445dfb58..73a4bd90 100644 --- a/tests/parsers/test_parser_runschedule.py +++ b/tests/parsers/test_parser_runschedule.py @@ -6,7 +6,7 @@ commandname = "runschedule" -class RunScheduleParserTest(unittest.TestCase): +class RunScheduleParserTest(ParserTest): @classmethod def setUpClass(cls): cls.parser_under_test = initialize_test_pieces(commandname, RunSchedule) From 4677e5c31b0afee8852575263821ee4da64afc5f Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 14 Aug 2025 15:27:11 -0700 Subject: [PATCH 4/7] Update test_session.py --- tests/commands/test_session.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/commands/test_session.py b/tests/commands/test_session.py index 3267efe2..d62853b5 100644 --- a/tests/commands/test_session.py +++ b/tests/commands/test_session.py @@ -154,9 +154,9 @@ def test__create_new_token_credential_succeeds_new_token(self, mock_pass): def test__create_new_username_credential_succeeds_new_password(self, mock_pass): test_password = "pword1" - active_session = Session() + active_session: Session = Session() active_session.username = "user" - active_session.site = "" + active_session.site_id = "" auth = active_session._create_new_credential(test_password, Session.PASSWORD_CRED_TYPE) assert auth is not None @@ -489,21 +489,6 @@ def test_timeout_as_integer_stored_char(self): assert result == 0 -class TimeoutIntegrationTest(unittest.TestCase): - def test_connection_times_out(self): - test_args = Namespace(**vars(args_to_mock)) - new_session = Session() - test_args.timeout = 2 - test_args.username = "u" - test_args.password = "p" - - test_args.server = "https://nothere.com" - with self.assertRaises(SystemExit): - new_session.create_session(test_args, None) - - # should test connection doesn't time out? - - class ConnectionOptionsTest(unittest.TestCase): def test_user_agent(self): mock_session = Session() @@ -549,6 +534,20 @@ def test_timeout(self): """ + +class TimeoutIntegrationTest(unittest.TestCase): + def test_connection_times_out(self): + test_args = Namespace(**vars(args_to_mock)) + new_session = Session() + test_args.timeout = 2 + test_args.username = "u" + test_args.password = "p" + + test_args.server = "https://nothere.com" + with self.assertRaises(SystemExit): + new_session.create_session(test_args, None) + + class CookieTests(unittest.TestCase): def test_no_file_if_no_cookie(self): From 408ce05a2304bc7f6dfabd4f6f475be6213af9c6 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 14 Aug 2025 15:27:32 -0700 Subject: [PATCH 5/7] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3bdce842..cc5c6a65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ required-version = 22 target-version = ['py39', 'py310', 'py311'] extend-exclude = '^/bin/*' [tool.mypy] +check_untyped_defs = true disable_error_code = [ 'misc', 'import' From 5cee61ecec1b805efed6d32977006c9e5bb94dd3 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 14 Aug 2025 15:53:31 -0700 Subject: [PATCH 6/7] type fixes c:\dev\tabcmd>mypy tests tests\commands\test_projects_utils.py:22: error: Argument 1 to "_parse_project_path_to_list" of "Server" has incompatible type "None"; expected "str" [arg-type] tests\commands\test_user_utils.py:76: error: Incompatible types in assignment (expression has type "UserItem | None", variable has type "UserItem") [assignment] tests\commands\test_user_utils.py:84: error: Incompatible types in assignment (expression has type "UserItem | None", variable has type "UserItem") [assignment] tests\e2e\language_tests.py:208: error: "OnlineCommandTest" has no attribute "_get_workbook" [attr-defined] tests\e2e\tests_integration.py:68: error: Name "logger" is not defined [name-defined] tests\e2e\tests_integration.py:99: error: Name "logger" is not defined [name-defined] tests\e2e\tests_integration.py:133: error: Name "__class__" is not defined [name-defined] tests\commands\test_session.py:206: error: Argument 1 to "_allow_prompt" has incompatible type "Namespace"; expected "Session" [arg-type] tests\commands\test_session.py:211: error: Argument 1 to "_allow_prompt" has incompatible type "Namespace"; expected "Session" [arg-type] tests\commands\test_session.py:216: error: Argument 1 to "_allow_prompt" has incompatible type "Namespace"; expected "Session" [arg-type] Found 10 errors in 5 files (checked 51 source files) --- tabcmd/commands/server.py | 2 +- tests/commands/test_run_commands.py | 8 ++++---- tests/commands/test_session.py | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tabcmd/commands/server.py b/tabcmd/commands/server.py index 19136141..530092b7 100644 --- a/tabcmd/commands/server.py +++ b/tabcmd/commands/server.py @@ -141,7 +141,7 @@ def get_filename_extension_if_tableau_type(logger, filename): ) @staticmethod - def get_project_by_name_and_parent_path(logger, server, project_name: str, parent_path: str) -> TSC.ProjectItem: + def get_project_by_name_and_parent_path(logger, server, project_name: Optional[str], parent_path: Optional[str]) -> TSC.ProjectItem: logger.debug(_("content_type.project") + ":{0}, {1}".format(parent_path, project_name)) if not parent_path: if not project_name: diff --git a/tests/commands/test_run_commands.py b/tests/commands/test_run_commands.py index 2a6ff820..bd301a9e 100644 --- a/tests/commands/test_run_commands.py +++ b/tests/commands/test_run_commands.py @@ -4,7 +4,7 @@ import tableauserverclient as TSC from tabcmd.commands.auth import login_command, logout_command -from tabcmd.commands.datasources_and_workbooks import delete_command, export_command, get_url_command, publish_command +from tabcmd.commands.datasources_and_workbooks import delete_command, export_command, get_url_command, publish_command, runschedule_command from tabcmd.commands.extracts import ( create_extracts_command, delete_extracts_command, @@ -267,7 +267,7 @@ def test_create_group_already_exists(self, mock_session, mock_server): mock_args.continue_if_exists = True mock_args.name = "name" - mock_server.groups.create.return_value = TSC.ServerResponseError(409, "already exists", "detail") + mock_server.groups.create.return_value = TSC.ServerResponseError("409", "already exists", "detail") create_group_command.CreateGroupCommand.run_command(mock_args) mock_session.assert_called() @@ -277,7 +277,7 @@ def test_create_project_already_exists(self, mock_session, mock_server): mock_args.project_name = "repeat" mock_args.parent_project_path = "" mock_args.description = "none" - mock_server.projects.create.return_value = TSC.ServerResponseError(409, "already exists", "detail") + mock_server.projects.create.return_value = TSC.ServerResponseError("409", "already exists", "detail") create_project_command.CreateProjectCommand.run_command(mock_args) mock_session.assert_called() @@ -289,7 +289,7 @@ def test_create_site_already_exists(self, mock_session, mock_server): mock_args.site_admin_user_management = None mock_args.user_quota = None mock_args.storage_quota = None - mock_server.sites.create.return_value = TSC.ServerResponseError(409, "already exists", "detail") + mock_server.sites.create.return_value = TSC.ServerResponseError("409", "already exists", "detail") create_site_command.CreateSiteCommand.run_command(mock_args) mock_session.assert_called() diff --git a/tests/commands/test_session.py b/tests/commands/test_session.py index d62853b5..2b0811c7 100644 --- a/tests/commands/test_session.py +++ b/tests/commands/test_session.py @@ -175,7 +175,7 @@ def test__create_new_token_credential_succeeds_from_self(self, mock_pass): def test__create_new_username_credential_succeeds_from_self(self, mock_pass): active_session = Session() active_session.username = "user3" - active_session.site = "" + active_session.site_id = "" auth = active_session._create_new_credential(None, Session.PASSWORD_CRED_TYPE) assert mock_pass.has_been_called() assert auth is not None @@ -202,6 +202,7 @@ def test__create_new_username_credential_succeeds_from_args(self, mock_pass): class PromptingTests(unittest.TestCase): def test_show_prompt_if_user_didnt_say(self): test_args = Namespace(**vars(args_to_mock)) + mock_session = Session() assert Session._allow_prompt(test_args) is True, test_args def test_show_prompt_if_user_said_yes(self): From a2a11aee27713225016038a36bac46303e14e089 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 24 Sep 2025 12:40:49 -0700 Subject: [PATCH 7/7] fix type errors in /tests output with fixes > mypy tests Success: no issues found in 51 source files --- tests/commands/test_geturl_utils.py | 2 +- tests/commands/test_projects_utils.py | 1 - tests/commands/test_session.py | 11 ++++++++--- tests/commands/test_user_utils.py | 8 +++++--- tests/e2e/language_tests.py | 6 ++++++ tests/e2e/tests_integration.py | 2 +- 6 files changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/commands/test_geturl_utils.py b/tests/commands/test_geturl_utils.py index b5e8dd54..627f8ffa 100644 --- a/tests/commands/test_geturl_utils.py +++ b/tests/commands/test_geturl_utils.py @@ -178,7 +178,7 @@ def test_parse_get_url_to_view_parts_with_spaces(self): DatasourcesWorkbooksAndViewsUrlParser.parse_get_view_url_to_view_and_custom_view_parts(mock_logger, url) def test_parse_get_url_to_view_parts_without_slashes(self): - url = "views\wb name\\view-name" + url = "views\\wb name\\view-name" with self.assertRaises(SystemExit): DatasourcesWorkbooksAndViewsUrlParser.parse_get_view_url_to_view_and_custom_view_parts(mock_logger, url) diff --git a/tests/commands/test_projects_utils.py b/tests/commands/test_projects_utils.py index 5e92a10d..ef434c5c 100644 --- a/tests/commands/test_projects_utils.py +++ b/tests/commands/test_projects_utils.py @@ -19,7 +19,6 @@ class ProjectsTest(unittest.TestCase): @staticmethod def test_parent_path_to_list(): - assert Server._parse_project_path_to_list(None) == [] assert Server._parse_project_path_to_list("") == [] assert Server._parse_project_path_to_list("parent") == ["parent"] assert Server._parse_project_path_to_list("parent/child") == ["parent", "child"] diff --git a/tests/commands/test_session.py b/tests/commands/test_session.py index 2b0811c7..98aaaa60 100644 --- a/tests/commands/test_session.py +++ b/tests/commands/test_session.py @@ -203,17 +203,22 @@ class PromptingTests(unittest.TestCase): def test_show_prompt_if_user_didnt_say(self): test_args = Namespace(**vars(args_to_mock)) mock_session = Session() - assert Session._allow_prompt(test_args) is True, test_args + mock_session._update_session_data(test_args) + assert mock_session._allow_prompt() is True, test_args def test_show_prompt_if_user_said_yes(self): test_args = Namespace(**vars(args_to_mock)) test_args.prompt = True - assert Session._allow_prompt(test_args) is True, test_args + mock_session = Session() + mock_session._update_session_data(test_args) + assert mock_session._allow_prompt() is True, test_args def test_dont_show_prompt_if_user_said_no(self): test_args = Namespace(**vars(args_to_mock)) test_args.no_prompt = True - assert Session._allow_prompt(test_args) is False, test_args + mock_session = Session() + mock_session._update_session_data(test_args) + assert mock_session._allow_prompt() is False, test_args """ diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index b22f7ff1..516ba3de 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -3,7 +3,7 @@ from tabcmd.commands.user.user_data import UserCommand, Userdata from tabcmd.execution.logger_config import log -from typing import List +from typing import List, Optional import io import tableauserverclient as TSC @@ -73,7 +73,8 @@ def test_get_user_detail_empty_line(self): def test_get_user_detail_standard(self): test_line = "username, pword, fname, license, admin, pub, email" - test_user: TSC.UserItem = UserCommand._parse_line(test_line) + test_user: Optional[TSC.UserItem] = UserCommand._parse_line(test_line) + assert test_user is not None assert test_user.name == "username", test_user.name assert test_user.fullname == "fname", test_user.fullname assert test_user.site_role == "Unlicensed", test_user.site_role @@ -81,7 +82,8 @@ def test_get_user_detail_standard(self): def test_get_user_details_only_username(self): test_line = "username" - test_user: TSC.UserItem = UserCommand._parse_line(test_line) + test_user: Optional[TSC.UserItem] = UserCommand._parse_line(test_line) + assert test_user is not None def test_populate_user_details_only_some(self): values = ["username", "", "", "creator", "admin"] diff --git a/tests/e2e/language_tests.py b/tests/e2e/language_tests.py index 6fced7a3..5a701416 100644 --- a/tests/e2e/language_tests.py +++ b/tests/e2e/language_tests.py @@ -95,6 +95,12 @@ def _get_view(self, wb_name_on_server, sheet_name): arguments = [command, server_file] _test_command(arguments) + def _get_workbook(self, wb_name_on_server): + server_file = "/workbooks/" + wb_name_on_server + command = "get" + arguments = [command, server_file] + _test_command(arguments) + def _get_datasource(self, server_file): command = "get" server_file = "/datasources/" + server_file diff --git a/tests/e2e/tests_integration.py b/tests/e2e/tests_integration.py index f63588b1..136866b3 100644 --- a/tests/e2e/tests_integration.py +++ b/tests/e2e/tests_integration.py @@ -14,6 +14,7 @@ fakeserver = "http://SRVR" logging.disable(logging.ERROR) +logger = log("tests_integration", "info") # these are integration tests because they don't just run a command, they call interior methods # pytest -v tests/e2e/integration_tests.py @@ -130,7 +131,6 @@ def test_read_password_file(self): # test_session.create_session(args) def test_get_project(self): - logger = log(__class__.__name__, "info") server = E2EServerTests.test_log_in() Server.get_project_by_name_and_parent_path(logger, server, "Default", None)