Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tabcmd/commands/auth/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def _sign_in(self, tableau_auth) -> TSC.Server:
self.username = self.tableau_server.users.get_by_id(self.user_id).name
self.logger.info(_("common.output.succeeded"))
except Exception as e:
Errors.exit_with_error(self.logger, e)
Errors.exit_with_error(self.logger, exception=e)
self.logger.debug("Signed into {0}{1} as {2}".format(self.server_url, self.site_name, self.username))
return self.tableau_server

Expand Down
23 changes: 15 additions & 8 deletions tabcmd/commands/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import inspect
import sys
from typing import Optional

import tableauserverclient

from tabcmd.execution.localize import _

Expand Down Expand Up @@ -44,7 +47,7 @@ def log_stack(logger):
stack = inspect.stack()
here = stack[0]
file, line, func = here[1:4]
start = 0
start = 1
n_lines = 5
logger.debug(HEADER_FMT % (file, func))
for frame in stack[start + 1 : n_lines]:
Expand All @@ -54,15 +57,17 @@ def log_stack(logger):
logger.info("Error printing stack trace:", e)

@staticmethod
def exit_with_error(logger, message=None, exception=None):
def exit_with_error(logger, message: Optional[str] = None, exception: Optional[Exception] = None):
try:
Errors.log_stack(logger)
if message and not exception:
logger.error(message)
if exception:
Errors.log_stack(logger)
elif exception:
if message:
logger.debug("Error message: " + message)
Errors.check_common_error_codes_and_explain(logger, exception)
logger.info("Error message: " + message)
Errors.check_common_error_codes_and_explain(logger, exception)
else:
logger.info("No exception or message provided")
except Exception as exc:
print(sys.stderr, "Error during log call from exception - {} {}".format(exc.__class__, message))
try:
Expand All @@ -72,7 +77,7 @@ def exit_with_error(logger, message=None, exception=None):
sys.exit(1)

@staticmethod
def check_common_error_codes_and_explain(logger, exception):
def check_common_error_codes_and_explain(logger, exception: Exception):
# most errors contain as much info in the message as we can get from the code
# identify any that we can add useful detail for and include them here
if Errors.is_expired_session(exception):
Expand All @@ -83,5 +88,7 @@ def check_common_error_codes_and_explain(logger, exception):
# "session.session_expired_login"))
# session.renew_session()
return
else:
if exception.__class__ == tableauserverclient.ServerResponseError:
logger.error(exception)
else:
logger.exception(exception)
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def get_view_by_content_url(logger, server, view_content_url) -> TSC.ViewItem:
logger.debug(req_option.get_query_params())
matching_views, paging = server.views.get(req_option)
except Exception as e:
Errors.exit_with_error(logger, e)
Errors.exit_with_error(logger, exception=e)
if len(matching_views) < 1:
Errors.exit_with_error(logger, message=_("errors.xmlapi.not_found"))
return matching_views[0]
Expand All @@ -42,7 +42,7 @@ def get_wb_by_content_url(logger, server, workbook_content_url) -> TSC.WorkbookI
logger.debug(req_option.get_query_params())
matching_workbooks, paging = server.workbooks.get(req_option)
except Exception as e:
Errors.exit_with_error(logger, e)
Errors.exit_with_error(logger, exception=e)
if len(matching_workbooks) < 1:
Errors.exit_with_error(logger, message=_("dataalerts.failure.error.workbookNotFound"))
return matching_workbooks[0]
Expand All @@ -56,7 +56,7 @@ def get_ds_by_content_url(logger, server, datasource_content_url) -> TSC.Datasou
logger.debug(req_option.get_query_params())
matching_datasources, paging = server.datasources.get(req_option)
except Exception as e:
Errors.exit_with_error(logger, e)
Errors.exit_with_error(logger, exception=e)
if len(matching_datasources) < 1:
Errors.exit_with_error(logger, message=_("dataalerts.failure.error.datasourceNotFound"))
return matching_datasources[0]
Expand Down
4 changes: 3 additions & 1 deletion tabcmd/commands/datasources_and_workbooks/export_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def define_args(export_parser):
help="Set the page size of the exported PDF",
)

group.add_argument("--width", default=800, help="Set the width of the image in pixels. Default is 800 px")
group.add_argument(
"--width", default=800, help="[Not Yet Implemented] Set the width of the image in pixels. Default is 800 px"
)
group.add_argument("--filename", "-f", help="filename to store the exported data")
group.add_argument("--height", default=600, help=_("export.options.height"))
group.add_argument(
Expand Down
47 changes: 39 additions & 8 deletions tabcmd/commands/datasources_and_workbooks/publish_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def define_args(publish_parser):
)
set_publish_args(group)
set_project_r_arg(group)
set_overwrite_option(group)
set_append_replace_option(group)
set_parent_project_arg(group)

@staticmethod
Expand All @@ -52,8 +54,7 @@ def run_command(args):
args.project_name = "default"
args.parent_project_path = ""

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

if args.db_username:
creds = TSC.models.ConnectionCredentials(args.db_username, args.db_password, embed=args.save_db_password)
Expand All @@ -66,18 +67,27 @@ def run_command(args):
source = PublishCommand.get_filename_extension_if_tableau_type(logger, args.filename)
logger.info(_("publish.status").format(args.filename))
if source in ["twbx", "twb"]:
if args.thumbnail_group:
raise AttributeError("Generating thumbnails for a group is not yet implemented.")
if args.thumbnail_username and args.thumbnail_group:
raise AttributeError("Cannot specify both a user and group for thumbnails.")

new_workbook = TSC.WorkbookItem(project_id, name=args.name, show_tabs=args.tabbed)
try:
print(creds)
new_workbook = server.workbooks.publish(
new_workbook,
args.filename,
publish_mode,
# args.thumbnail_username, not yet implemented in tsc
# args.thumbnail_group,
connection_credentials=creds,
as_job=False,
skip_connection_check=False,
)
except Exception as e:
Errors.exit_with_error(logger, e)
Errors.exit_with_error(logger, exception=e)

logger.info(_("publish.success") + "\n{}".format(new_workbook.webpage_url))

elif source in ["tds", "tdsx", "hyper"]:
Expand All @@ -88,13 +98,34 @@ def run_command(args):
new_datasource, args.filename, publish_mode, connection_credentials=creds
)
except Exception as exc:
Errors.exit_with_error(logger, exc)
Errors.exit_with_error(logger, exception=exc)
logger.info(_("publish.success") + "\n{}".format(new_datasource.webpage_url))

# todo write tests for this method
@staticmethod
def get_publish_mode(args):
def get_publish_mode(args, logger):
# default: fail if it already exists on the server
default_mode = TSC.Server.PublishMode.CreateNew
publish_mode = default_mode

if args.replace:
raise AttributeError("Replacing an extract is not yet implemented")

if args.append:
if publish_mode != default_mode:
publish_mode = None
else:
# only relevant for datasources, but tsc will throw an error for us if necessary
publish_mode = TSC.Server.PublishMode.Append

if args.overwrite:
publish_mode = TSC.Server.PublishMode.Overwrite
else:
publish_mode = TSC.Server.PublishMode.CreateNew
if publish_mode != default_mode:
publish_mode = None
else:
# Overwrites the workbook, data source, or data extract if it already exists on the server.
publish_mode = TSC.Server.PublishMode.Overwrite

if not publish_mode:
Errors.exit_with_error(logger, "Invalid combination of publishing options (Append, Overwrite, Replace)")
logger.debug("Publish mode selected: " + publish_mode)
return publish_mode
2 changes: 1 addition & 1 deletion tabcmd/commands/user/user_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def act_on_users(
try:
group = UserCommand.find_group(logger, server, args.name)
except Exception as e:
Errors.exit_with_error(logger, e)
Errors.exit_with_error(logger, exception=e)

n_users_handled: int = 0
number_of_errors: int = 0
Expand Down
64 changes: 46 additions & 18 deletions tabcmd/execution/global_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def set_encryption_option(parser):
"--encrypt",
dest="encrypt",
action="store_true", # set to true IF user passes in option --encrypt
help="Encrypt the newly created extract.",
help="Encrypt the newly created extract. [N/a on Tableau Cloud: extract encryption is controlled by Site Admin]",
)
return parser

Expand Down Expand Up @@ -230,7 +230,8 @@ def set_common_site_args(parser):
"--extract-encryption-mode",
choices=encryption_modes,
type=case_insensitive_string_type(encryption_modes),
help="The extract encryption mode for the site can be enforced, enabled or disabled. ",
help="The extract encryption mode for the site can be enforced, enabled or disabled. "
"[N/a on Tableau Cloud: encryption mode is always enforced] ",
)

parser.add_argument(
Expand Down Expand Up @@ -275,8 +276,6 @@ def set_filename_arg(parser, description=_("get.options.file")):
def set_publish_args(parser):
parser.add_argument("-n", "--name", help="Name to publish the new datasource or workbook by.")

set_overwrite_option(parser)

creds = parser.add_mutually_exclusive_group()
creds.add_argument("--oauth-username", help="The email address of a preconfigured OAuth connection")
creds.add_argument(
Expand All @@ -302,34 +301,63 @@ def set_publish_args(parser):
navigate through the workbook",
)
parser.add_argument(
"--replace", action="store_true", help="Use the extract file to replace the existing data source."
"--disable-uploader",
action="store_true",
help="[DEPRECATED - has no effect] Disable the incremental file uploader.",
)
parser.add_argument("--disable-uploader", action="store_true", help="Disable the incremental file uploader.")
parser.add_argument("--restart", help="Restart the file upload.")
parser.add_argument("--restart", help="[DEPRECATED - has no effect] Restart the file upload.")
parser.add_argument(
"--encrypt-extracts",
action="store_true",
help="Encrypt extracts in the workbook, datasource, or extract being published to the server",
help="Encrypt extracts in the workbook, datasource, or extract being published to the server. "
"[N/a on Tableau Cloud: extract encryption is controlled by Site Admin]",
)

# These two only apply for a workbook, not a 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
thumbnails.add_argument(
"--thumbnail-username",
help="If the workbook contains user filters, the thumbnails will be generated based on what the "
"specified user can see. Cannot be specified when --thumbnail-group option is set.",
)
thumbnails.add_argument(
"--thumbnail-group",
help="[Not yet implemented] If the workbook contains user filters, the thumbnails will be generated based on what the "
"specified group can see. Cannot be specified when --thumbnail-username option is set.",
)

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


def set_overwrite_option(parser):
# these two are used to publish an extract to an existing data source
def set_append_replace_option(parser):
append_group = parser.add_mutually_exclusive_group()
append_group.add_argument(
"-o",
"--overwrite",
"--append",
action="store_true",
help="Overwrites the workbook, data source, or data extract if it already exists on the server.",
help="Set to true to append the data being published to an existing data source that has the same name. "
"The default behavior is to fail if the data source already exists. "
"If append is set to true but the data source doesn't already exist, the operation fails.",
)

# what's the difference between this and 'overwrite'?
# This is meant for when a) the local file is an extract b) the server item is an existing data source
append_group.add_argument(
"--append",
"--replace",
action="store_true",
help="Use the extract file being published to replace data in the existing data source. The default "
"behavior is to fail if the item already exists.",
)


# this is meant to be like replacing like
def set_overwrite_option(parser):
parser.add_argument(
"-o",
"--overwrite",
action="store_true",
help="Append the extract file to the existing data source.",
help="Overwrites the workbook, data source, or data extract if it already exists on the server. The default "
"behavior is to fail if the item already exists.",
)


Expand All @@ -351,12 +379,12 @@ def set_calculations_options(parser):
calc_group.add_argument(
"--addcalculations",
action="store_true",
help="DEPRECATED [has no effect] Add precalculated data operations in the extract data source.",
help="[Not implemented] Add precalculated data operations in the extract data source.",
)
calc_group.add_argument(
"--removecalculations",
action="store_true",
help="DEPRECATED [has no effect] Remove precalculated data in the extract data source.",
help="[Not implemented] Remove precalculated data in the extract data source.",
)
return calc_group

Expand Down
2 changes: 1 addition & 1 deletion tabcmd/execution/parent_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def parent_parser_with_global_options():
parser.add_argument(
"--continue-if-exists",
action="store_true", # default behavior matches old tabcmd
help=strings[9],
help=strings[9], # kind of equivalent to 'overwrite' in the publish command
)

parser.add_argument(
Expand Down
4 changes: 4 additions & 0 deletions tests/commands/test_run_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ def test_publish(self, mock_session, mock_server):
mock_args.tabbed = True
mock_args.db_username = None
mock_args.oauth_username = None
mock_args.append = False
mock_args.replace = False
mock_args.thumbnail_username = None
mock_args.thumbnail_group = None
mock_server.projects = getter
publish_command.PublishCommand.run_command(mock_args)
mock_session.assert_called()
Expand Down
Loading