Skip to content
8 changes: 5 additions & 3 deletions tabcmd/commands/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ def log_stack(logger):
start = 0
n_lines = 5
logger.trace(HEADER_FMT % (file, func))
for frame in stack[start + 1 : n_lines]:

for frame in stack[start + 2 : n_lines]:
file, line, func = frame[1:4]
logger.trace(STACK_FMT % (file, line, func))
except BaseException as e:
except Exception as e:
logger.info("Error printing stack trace:", e)

@staticmethod
Expand All @@ -64,10 +65,11 @@ def exit_with_error(logger, message=None, exception=None):
# "session.session_expired_login"))
# session.renew_session()
if message:
logger.debug(message)
logger.debug("Error message: " + message)
Errors.check_common_error_codes_and_explain(logger, exception)
except Exception as exc:
print("Error during log call from exception - {} {}".format(exc.__class__, message))
print("Exiting...")
sys.exit(1)

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def get_view_by_content_url(logger, server, view_content_url) -> TSC.ViewItem:
try:
req_option = TSC.RequestOptions()
req_option.filter.add(TSC.Filter("contentUrl", TSC.RequestOptions.Operator.Equals, view_content_url))
logger.trace(req_option.get_query_params())
matching_views, paging = server.views.get(req_option)
except TSC.ServerResponseError as e:
Errors.exit_with_error(logger, _("publish.errors.unexpected_server_response").format(e))
Expand All @@ -36,13 +37,28 @@ def get_wb_by_content_url(logger, server, workbook_content_url) -> TSC.WorkbookI
try:
req_option = TSC.RequestOptions()
req_option.filter.add(TSC.Filter("contentUrl", TSC.RequestOptions.Operator.Equals, workbook_content_url))
logger.trace(req_option.get_query_params())
matching_workbooks, paging = server.workbooks.get(req_option)
except TSC.ServerResponseError as e:
Errors.exit_with_error(logger, _("publish.errors.unexpected_server_response").format(""))
if len(matching_workbooks) < 1:
Errors.exit_with_error(logger, message=_("dataalerts.failure.error.workbookNotFound"))
return matching_workbooks[0]

@staticmethod
def get_ds_by_content_url(logger, server, datasource_content_url) -> TSC.DatasourceItem:
logger.debug(_("export.status").format(datasource_content_url))
try:
req_option = TSC.RequestOptions()
req_option.filter.add(TSC.Filter("contentUrl", TSC.RequestOptions.Operator.Equals, datasource_content_url))
logger.trace(req_option.get_query_params())
matching_datasources, paging = server.datasources.get(req_option)
except TSC.ServerResponseError as e:
Errors.exit_with_error(logger, _("publish.errors.unexpected_server_response").format(""))
if len(matching_datasources) < 1:
Errors.exit_with_error(logger, message=_("dataalerts.failure.error.datasourceNotFound"))
return matching_datasources[0]

@staticmethod
def apply_values_from_url_params(request_options: TSC.PDFRequestOptions, url, logger) -> None:
# should be able to replace this with request_options._append_view_filters(params)
Expand All @@ -63,7 +79,7 @@ def apply_values_from_url_params(request_options: TSC.PDFRequestOptions, url, lo
else: # it must be a filter
DatasourcesAndWorkbooks.apply_filter_value(request_options, value, logger)

except BaseException as e:
except Exception as e:
logger.warn("Error building filter params", e)
# ExportCommand.log_stack(logger) # type: ignore

Expand All @@ -86,7 +102,7 @@ def apply_option_value(request_options: TSC.PDFRequestOptions, value: str, logge
logger.debug("Set max age to {} from {}".format(request_options.max_age, value))
elif ":size" == setting[0]:
height, width = setting[1].split(",")
logger.warn("Height/weight parameters not yet implemented ({})".format(value))
logger.warn("Height/width parameters not yet implemented ({})".format(value))
else:
logger.debug("Parameter[s] not recognized: {}".format(value))

Expand Down Expand Up @@ -122,16 +138,3 @@ def save_to_file(logger, output, filename):
with open(filename, "wb") as f:
f.write(output)
logger.info(_("export.success").format("", filename))

@staticmethod
def get_ds_by_content_url(logger, server, datasource_content_url) -> TSC.DatasourceItem:
logger.debug(_("export.status").format(datasource_content_url))
try:
req_option = TSC.RequestOptions()
req_option.filter.add(TSC.Filter("contentUrl", TSC.RequestOptions.Operator.Equals, datasource_content_url))
matching_datasources, paging = server.datasources.get(req_option)
except TSC.ServerResponseError as e:
Errors.exit_with_error(logger, _("publish.errors.unexpected_server_response").format(""))
if len(matching_datasources) < 1:
Errors.exit_with_error(logger, message=_("dataalerts.failure.error.datasourceNotFound"))
return matching_datasources[0]
34 changes: 22 additions & 12 deletions tabcmd/commands/datasources_and_workbooks/get_url_command.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import inspect

import tableauserverclient as TSC
from tableauserverclient import ServerResponseError

Expand Down Expand Up @@ -60,7 +62,7 @@ def run_command(args):
GetUrl.get_content_as_file(file_type, content_type, logger, args, server, url)
except ServerResponseError as e:
Errors.exit_with_error(logger, e)
except BaseException as be:
except Exception as be:
Errors.exit_with_error(logger, be, "Error during file download")

## this first set of methods is all parsing the url and file input from the user
Expand All @@ -77,12 +79,14 @@ def evaluate_content_type(logger, url):
Errors.exit_with_error(logger, message=_("get.errors.invalid_content_type").format(url))

@staticmethod
def explain_expected_url(logger, url):
def explain_expected_url(logger, url: str, command: str):
view_example = "/views/<workbookname>/<viewname>[.ext]"
wb_example = "/workbooks/<workbookname>[.ext]"
ds_example = "/datasources/<datasourcename[.ext]"
message = _("export.errors.requires_resource_param").format(
__class__.__name__, url, view_example, wb_example, ds_example
# todo when strings are updated # message:str = _("export.errors.requires_resource_param").format(
message = (
"The ''{0}'' command requires a resource path in a specific format."
"Given: {1}. Accepted values: {2}, {3}, {4}".format(command, url, view_example, wb_example, ds_example)
)
Errors.exit_with_error(logger, message)

Expand All @@ -94,10 +98,10 @@ def get_file_type_from_filename(logger, file_name, url):

if not type_of_file:
Errors.exit_with_error(logger, _("tabcmd.get.extension.not_found").format(file_name))
else:
logger.debug("filetype: {}".format(type_of_file))
if type_of_file in ["pdf", "csv", "png", "twb", "twbx", "tdsx"]:
return type_of_file

logger.debug("filetype: {}".format(type_of_file))
if type_of_file in ["pdf", "csv", "png", "twb", "twbx", "tdsx"]:
return type_of_file

Errors.exit_with_error(logger, _("tabcmd.get.extension.not_found").format(file_name))

Expand All @@ -123,10 +127,11 @@ def get_name_without_possible_extension(filename):
return filename

@staticmethod
def get_resource_name(url, logger): # workbooks/wb-name" -> "wb-name", datasource/ds-name -> ds-name
def get_resource_name(url: str, logger): # workbooks/wb-name" -> "wb-name", datasource/ds-name -> ds-name
url = url.lstrip("/") # strip opening / if present
name_parts = url.split("/")
if len(name_parts) != 2:
GetUrl.explain_expected_url(logger, url)
GetUrl.explain_expected_url(logger, url, "GetURl")
resource_name_with_params = name_parts[::-1][0] # last part
resource_name_with_ext = GetUrl.strip_query_params(resource_name_with_params)
resource_name = GetUrl.get_name_without_possible_extension(resource_name_with_ext)
Expand All @@ -136,7 +141,7 @@ def get_resource_name(url, logger): # workbooks/wb-name" -> "wb-name", datasour
def get_view_url(url, logger): # "views/wb-name/view-name" -> wb-name/sheets/view-name
name_parts = url.split("/") # ['views', 'wb-name', 'view-name']
if len(name_parts) != 3:
GetUrl.explain_expected_url(logger, url)
GetUrl.explain_expected_url(logger, url, "GetURl")
workbook_name = name_parts[1]
view_name = name_parts[::-1][0]
view_name = GetUrl.strip_query_params(view_name)
Expand Down Expand Up @@ -171,6 +176,7 @@ def get_content_as_file(file_type, content_type, logger, args, server, url):

@staticmethod
def generate_pdf(logger, server, args, view_url):
logger.trace("Entered method " + inspect.stack()[0].function)
try:
view_item: TSC.ViewItem = GetUrl.get_view_by_content_url(logger, server, view_url)
logger.debug(_("content_type.view") + ": {}".format(view_item.name))
Expand All @@ -184,6 +190,7 @@ def generate_pdf(logger, server, args, view_url):

@staticmethod
def generate_png(logger, server, args, view_url):
logger.trace("Entered method " + inspect.stack()[0].function)
try:
view_item: TSC.ViewItem = GetUrl.get_view_by_content_url(logger, server, view_url)
logger.debug(_("content_type.view") + ": {}".format(view_item.name))
Expand All @@ -197,6 +204,7 @@ def generate_png(logger, server, args, view_url):

@staticmethod
def generate_csv(logger, server, args, view_url):
logger.trace("Entered method " + inspect.stack()[0].function)
try:
view_item: TSC.ViewItem = GetUrl.get_view_by_content_url(logger, server, view_url)
logger.debug(_("content_type.view") + ": {}".format(view_item.name))
Expand All @@ -212,6 +220,7 @@ def generate_csv(logger, server, args, view_url):

@staticmethod
def generate_twb(logger, server, args, file_extension, url):
logger.trace("Entered method " + inspect.stack()[0].function)
workbook_name = GetUrl.get_resource_name(url, logger)
try:
target_workbook = GetUrl.get_wb_by_content_url(logger, server, workbook_name)
Expand All @@ -225,7 +234,8 @@ def generate_twb(logger, server, args, file_extension, url):

@staticmethod
def generate_tds(logger, server, args, file_extension):
datasource_name = GetUrl.get_resource_name(logger, args.url)
logger.trace("Entered method " + inspect.stack()[0].function)
datasource_name = GetUrl.get_resource_name(args.url, logger)
try:
target_datasource = GetUrl.get_ds_by_content_url(logger, server, datasource_name)
logger.debug(_("content_type.datasource") + ": {}".format(datasource_name))
Expand Down
24 changes: 21 additions & 3 deletions tabcmd/commands/datasources_and_workbooks/publish_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,31 @@ def run_command(args):
args.project_name = "default"
args.parent_project_path = ""

publish_mode = PublishCommand.get_publish_mode(args)
publish_mode = PublishCommand.get_publish_mode(args) # --overwrite, --replace
logger.info("Publishing as " + publish_mode)

if args.db_username:
creds = TSC.models.ConnectionCredentials(args.db_username, args.db_password, embed=args.save_db_password)
elif args.oauth_username:
creds = 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")
creds = None

source = PublishCommand.get_filename_extension_if_tableau_type(logger, args.filename)
logger.info(_("publish.status").format(args.filename))
if source in ["twbx", "twb"]:
new_workbook = TSC.WorkbookItem(project_id, name=args.name, show_tabs=args.tabbed)
try:
new_workbook = server.workbooks.publish(new_workbook, args.filename, publish_mode)
new_workbook = server.workbooks.publish(
new_workbook,
args.filename,
publish_mode,
connection_credentials=creds,
as_job=False,
skip_connection_check=False,
)

except IOError as ioe:
Errors.exit_with_error(logger, ioe)
logger.info(_("publish.success") + "\n{}".format(new_workbook.webpage_url))
Expand All @@ -68,7 +84,9 @@ def run_command(args):
new_datasource = TSC.DatasourceItem(project_id, name=args.name)
new_datasource.use_remote_query_agent = args.use_tableau_bridge
try:
new_datasource = server.datasources.publish(new_datasource, args.filename, publish_mode)
new_datasource = server.datasources.publish(
new_datasource, args.filename, publish_mode, connection_credentials=creds
)
except IOError as ioe:
Errors.exit_with_error(logger, exc)
logger.info(_("publish.success") + "\n{}".format(new_datasource.webpage_url))
Expand Down
6 changes: 3 additions & 3 deletions tabcmd/commands/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ def find_user(logger, server, username):
@staticmethod
def get_items_by_name(logger, item_endpoint, item_name: str, container: Optional[TSC.ProjectItem] = None) -> List:
# TODO: typing should reflect that this returns TSC.TableauItem and item_endpoint is of type TSC.QuerysetEndpoint[same]
item_type = type(item_endpoint).__name__
item_log_name: str = "[" + item_type + "] " + item_name
item_log_name: str = "[{0}] {1}".format(type(item_endpoint).__name__, item_name)
if container:
item_log_name = str(container) + "/" + item_log_name
container_name: str = "({0}) {1}".format(container.__class__, container.name)
item_log_name = "{0}/{1}".format(container_name, item_log_name)
logger.debug(_("export.status").format(item_log_name))
req_option = TSC.RequestOptions()
req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, item_name))
Expand Down
2 changes: 1 addition & 1 deletion tabcmd/commands/site/delete_site_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def run_command(args):
server.sites.delete(target_site_id)
except TSC.ServerResponseError as e:
Errors.exit_with_error(logger, strings[1], e)
except BaseException as e:
except Exception as e:
Errors.exit_with_error(logger, strings[4], e)
logger.info(strings[0].format(args.site_name_to_delete))

Expand Down
13 changes: 8 additions & 5 deletions tabcmd/execution/global_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,15 @@ def set_publish_args(parser):
parser.add_argument("-n", "--name", help="Name to publish the new datasource or workbook by.")

set_overwrite_option(parser)
parser.add_argument(

creds = parser.add_mutually_exclusive_group()
creds.add_argument("--oauth-username", help="The email address of a preconfigured OAuth connection")
creds.add_argument(
"--db-username",
help="Use this option to publish a database user name with the workbook, data source, or data extract.",
)
parser.add_argument("--save-oauth", action="store_true", help="Save embedded OAuth credentials in the datasource")

parser.add_argument(
"--db-password",
help="publish a database password with the workbook, data source, or extract",
Expand All @@ -304,6 +309,7 @@ def set_publish_args(parser):
action="store_true",
help="Stores the provided database password on the server.",
)

parser.add_argument(
"--tabbed",
action="store_true",
Expand All @@ -320,14 +326,11 @@ def set_publish_args(parser):
action="store_true",
help="Encrypt extracts in the workbook, datasource, or extract being published to the server",
)
parser.add_argument("--oauth-username", help="The email address of a preconfigured OAuth connection")
parser.add_argument("--save-oauth", action="store_true", help="Save embedded OAuth credentials in the datasource")

thumbnails = parser.add_mutually_exclusive_group()
thumbnails.add_argument("--thumbnail-username", help="Not yet implemented")
thumbnails.add_argument("--thumbnail-group", help="Not yet implemented") # not implemented in the REST API

parser.add_argument("--use-tableau-bridge", help="Refresh datasource through Tableau Bridge")
parser.add_argument("--use-tableau-bridge", action="store_true", help="Refresh datasource through Tableau Bridge")


def set_overwrite_option(parser):
Expand Down
2 changes: 2 additions & 0 deletions tests/commands/test_run_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def test_publish(self, mock_session, mock_server):
mock_args.parent_project_path = "projects"
mock_args.name = ""
mock_args.tabbed = True
mock_args.db_username = None
mock_args.oauth_username = None
mock_server.projects = getter
publish_command.PublishCommand.run_command(mock_args)
mock_session.assert_called()
Expand Down
Loading