Skip to content

Commit

Permalink
2.0.9 (#207)
Browse files Browse the repository at this point in the history
* Add Datasources to the Get Command (#162)
* format command-specific help (#203)
* hide token-value like we do password
* Tfs 1482014 publishing (#205)
* make db-auth/oauth mutually exclusive
* Create a db credentials item for publishing with --db-username, etc arguments

Co-authored-by: Brian Cantoni <bcantoni@salesforce.com>
Co-authored-by: Bhuvnesh Singh <bhuvnesh.singh@salesforce.com>
Co-authored-by: andyoneal <andyoneal@me.com>
  • Loading branch information
4 people authored Dec 8, 2022
1 parent 842168a commit be955d2
Show file tree
Hide file tree
Showing 60 changed files with 754 additions and 524 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
python -m pip install --upgrade pip
python -m pip install --upgrade build
pip install .
pip install .[test]
pip install .[package]
doit version
python -m build
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ site-packages
# local
tabcmd-dev
workon
test.junit.xml
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ To run tabcmd from your local copy, from a console window in the same directory
> mypy tabcmd tests
- packaging is done with pyinstaller. You can only build an executable for the platform you build on.
- To package a release, we first bump the version with `doit version` and build as 2.x.0 before packaging
> pyinstaller tabcmd\tabcmd.py --clean --noconfirm
produces dist/tabcmd.exe
Expand Down Expand Up @@ -113,3 +114,4 @@ Copies of tabcmd that shipped with Tableau Server are referred to by the version
At some point in the future, tabcmd will no longer be included with Tableau Server.
*We have no intention of breaking Server install flows.*
If you have specific suggestions or concerns on what that will look like, feel free to open an issue here or a thread on the Community Forums.

Binary file added World Indicators.tdsx
Binary file not shown.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ test = [
"pytest-runner",
"requests-mock>=1.0,<2.0"]
localize = ["doit", "ftfy"]
package = ["pyinstaller>=5.1"]
package = ["pyinstaller>=5.1", "doit"]
[project.urls]
repository = "https://github.com/tableau/tabcmd"
[project.scripts]
Expand Down
32 changes: 19 additions & 13 deletions tabcmd.spec
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
# -*- mode: python ; coding: utf-8 -*-


block_cipher = None

localized_strings = [
('tabcmd/locales/en/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/en/LC_MESSAGES'),
('tabcmd/locales/de/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/de/LC_MESSAGES'),
('tabcmd/locales/es/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/es/LC_MESSAGES'),
('tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/fr/LC_MESSAGES'),
('tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/ga/LC_MESSAGES'),
('tabcmd/locales/it/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/it/LC_MESSAGES'),
('tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/ja/LC_MESSAGES'),
('tabcmd/locales/ko/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/ko/LC_MESSAGES'),
('tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/pt/LC_MESSAGES'),
('tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/sv/LC_MESSAGES'),
('tabcmd/locales/zh/LC_MESSAGES/tabcmd.mo', 'tabcmd/locales/zh/LC_MESSAGES'),
]

a = Analysis(
['tabcmd\\tabcmd.py'],
pathex=[],
binaries=[],
datas=[],
datas=localized_strings,
hiddenimports=['tableauserverclient', 'requests.packages.urllib3', 'pkg_resources'],
hookspath=[],
hooksconfig={},
Expand All @@ -24,27 +36,21 @@ pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
exclude_binaries=True,
name='tabcmd',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='tabcmd',
)
4 changes: 2 additions & 2 deletions tabcmd/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
try:
from tabcmd.tabcmd import main
except ImportError:
print("Tabcmd needs to be run as a module, it cannot be run as a script")
print("Try running python -m tabcmd")
print(sys.stderr, "Tabcmd needs to be run as a module, it cannot be run as a script")
print(sys.stderr, "Try running python -m tabcmd")
sys.exit(1)

if __name__ == "__main__":
Expand Down
14 changes: 9 additions & 5 deletions tabcmd/commands/auth/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,16 @@ def _update_session_data(self, args):
# user id and site id are never passed in as args
# last_login_using and tableau_server are internal data
# self.command = args.???
self.username = args.username or self.username
self.username = args.username or self.username or ""
self.username = self.username.lower()
self.server_url = args.server or self.server_url or "http://localhost"
self.server_url = self.server_url.lower()
if args.server is not None:
self.site_name = None
self.site_name = args.site_name or self.site_name or ""
self.site_name = self.site_name.lower()
if self.site_name == "default":
self.site_name = ""
self.server_url = args.server or self.server_url or "http://localhost"
self.logging_level = args.logging_level or self.logging_level
self.password_file = args.password_file
self.token_name = args.token_name or self.token_name
Expand Down Expand Up @@ -190,10 +195,9 @@ def _sign_in(self, tableau_auth):
if not self.username:
self.username = self.tableau_server.users.get_by_id(self.user_id).name
self.logger.info(_("common.output.succeeded"))
except TSC.ServerResponseError as e:
Errors.exit_with_error(self.logger, _("publish.errors.unexpected_server_response"), e)
except Exception as e:
Errors.exit_with_error(self.logger, e)
self.logger.debug("Signed into {0}{1} as {2}".format(self.server_url, self.site_name, self.username))

return self.tableau_server

def _get_saved_credentials(self):
Expand Down
41 changes: 29 additions & 12 deletions tabcmd/commands/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import inspect
import sys

from tableauserverclient import ServerResponseError

from tabcmd.execution.localize import _


Expand Down Expand Up @@ -30,6 +32,10 @@ def is_login_error(error):
if hasattr(error, "code"):
return error.code == Constants.login_error

@staticmethod
def is_server_response_error(error):
return isinstance(error, ServerResponseError)

# https://gist.github.com/FredLoney/5454553
@staticmethod
def log_stack(logger):
Expand All @@ -44,10 +50,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 @@ -57,19 +64,29 @@ def exit_with_error(logger, message=None, exception=None):
if message and not exception:
logger.error(message)
if exception:
if Errors.is_expired_session(exception):
logger.error(_("session.errors.session_expired"))
# TODO: add session as an argument to this method
# and add the full command line as a field in Session?
# "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(sys.stderr, "Error during log call from exception - {}".format(exc))
try:
logger.info("Exiting...")
except Exception:
print(sys.stderr, "Exiting...")
sys.exit(1)

@staticmethod
def check_common_error_codes_and_explain(logger, error):
logger.error(error)
def check_common_error_codes_and_explain(logger, exception):
if Errors.is_server_response_error(exception):
logger.error(_("publish.errors.unexpected_server_response").format(exception))
if Errors.is_expired_session(exception):
logger.error(_("session.errors.session_expired"))
# TODO: add session as an argument to this method
# and add the full command line as a field in Session?
# "session.session_expired_login"))
# session.renew_session
return
if exception.code.startswith(Constants.source_not_found):
logger.error(_("publish.errors.server_resource_not_found"), exception)
else:
logger.error(exception)
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ 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))
except Exception as e:
Errors.exit_with_error(logger, e)
if len(matching_views) < 1:
Errors.exit_with_error(logger, message=_("errors.xmlapi.not_found"))
return matching_views[0]
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(""))
except Exception as e:
Errors.exit_with_error(logger, e)
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 Exception as e:
Errors.exit_with_error(logger, e)
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
13 changes: 7 additions & 6 deletions tabcmd/commands/datasources_and_workbooks/delete_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ class DeleteCommand(DatasourcesAndWorkbooks):

@staticmethod
def define_args(delete_parser):
delete_parser.add_argument("name", help=_("content_type.workbook") + "/" + _("content_type.datasource"))
set_ds_xor_wb_options(delete_parser)
set_project_r_arg(delete_parser)
set_parent_project_arg(delete_parser)
group = delete_parser.add_argument_group(title=DeleteCommand.name)
group.add_argument("name", help=_("content_type.workbook") + "/" + _("content_type.datasource"))
set_ds_xor_wb_options(group)
set_project_r_arg(group)
set_parent_project_arg(group)

@staticmethod
def run_command(args):
Expand Down Expand Up @@ -72,5 +73,5 @@ def run_command(args):
else:
server.datasources.delete(item_to_delete.id)
logger.info(_("common.output.succeeded"))
except TSC.ServerResponseError as e:
Errors.exit_with_error(logger, "Error deleting from server", e)
except Exception as e:
Errors.exit_with_error(logger, e)
23 changes: 12 additions & 11 deletions tabcmd/commands/datasources_and_workbooks/export_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ class ExportCommand(DatasourcesAndWorkbooks):

@staticmethod
def define_args(export_parser):
export_parser.add_argument("url", help="url of the workbook or view to export")
export_parser_group = export_parser.add_mutually_exclusive_group(required=True)
group = export_parser.add_argument_group(title=ExportCommand.name)
group.add_argument("url", help="url of the workbook or view to export")
export_parser_group = group.add_mutually_exclusive_group(required=True)
export_parser_group.add_argument("--pdf", action="store_true", help=_("export.options.pdf"))
export_parser_group.add_argument("--fullpdf", action="store_true", help=_("export.options.fullpdf"))
export_parser_group.add_argument("--png", action="store_true", help=_("export.options.png"))
export_parser_group.add_argument("--csv", action="store_true", help=_("export.options.csv"))

export_parser.add_argument(
group.add_argument(
"--pagelayout",
choices=["landscape", "portrait"],
type=str.lower,
default=None,
help="page orientation (landscape or portrait) of the exported PDF",
)
export_parser.add_argument(
group.add_argument(

"--pagesize",
choices=[
pagesize.A3,
Expand All @@ -47,16 +50,15 @@ def define_args(export_parser):
pagesize.Tabloid,
pagesize.Unspecified,
],
type=str.lower,
default="letter",
help="Set the page size of the exported PDF",
)

export_parser.add_argument(
"--width", default=800, help="Set the width of the image in pixels. Default is 800 px"
)
export_parser.add_argument("--filename", "-f", help="filename to store the exported data")
export_parser.add_argument("--height", default=600, help=_("export.options.height"))
export_parser.add_argument(
group.add_argument("--width", default=800, help="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(
"--filter",
metavar="COLUMN:VALUE",
help="View filter to apply to the view",
Expand Down Expand Up @@ -113,7 +115,6 @@ def run_command(args):
else:
ExportCommand.save_to_file(logger, output, save_name)


except Exception as e:
Errors.exit_with_error(logger, "Error saving to file", e)

Expand Down
Loading

0 comments on commit be955d2

Please sign in to comment.