Skip to content
Merged

2.0.9 #207

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b518881
configure black (#133)
jacalata Jun 24, 2022
648d7b2
Jac/args (#134)
jacalata Jul 8, 2022
9f21a45
Fix exit_with_error handling (#137)
bcantoni Jul 8, 2022
d493cdd
TFS 1428581 publish project (#139)
jacalata Jul 10, 2022
e099732
Jac/groups (#140)
jacalata Jul 12, 2022
e62bc12
Update session.py (#141)
jacalata Jul 13, 2022
2ad6352
Fix export success log message params (#142)
bcantoni Jul 13, 2022
313e486
Jac/tfs 1428582 create edit site (#146)
jacalata Jul 15, 2022
c1f3941
Getting workbook works as expected, but appends extra file extensions
bhuvneshdev Jul 18, 2022
52a4793
Fix get view, get csv (#143)
jacalata Jul 18, 2022
869068d
merge conflicts resolved
bhuvneshdev Jul 18, 2022
1a9435c
Merge pull request #149 from tableau/bhuvnesh.singh/1427042
bhuvneshdev Jul 19, 2022
7696434
Change install destination (#150)
bcantoni Jul 19, 2022
d000dc4
Jac/publish to folder (#145)
jacalata Jul 26, 2022
fdcb18d
Jac/delete command (#144)
jacalata Aug 1, 2022
cc4ee24
Jac/e2e test workflow (#154)
jacalata Aug 3, 2022
7fb7b9c
Add command to list items of a given content type (#153)
jacalata Aug 5, 2022
6722d61
make export use filters, add tests (#164)
jacalata Sep 1, 2022
c8451db
Fix some e2e tests (#166)
jacalata Sep 8, 2022
ed3efea
Trace logging and bug fix (#165)
jacalata Sep 8, 2022
a12ee64
Merge branch 'main' into development
jacalata Sep 8, 2022
5eb3d7c
Update run-e2-tests.yml
jacalata Sep 8, 2022
9afddcb
Merge branch 'main' into development
jacalata Sep 8, 2022
9aea40f
Implement command line and url parameters for get/export (#175)
jacalata Sep 12, 2022
a92d7f4
remove setup.py from build steps
jacalata Sep 13, 2022
4eeca1d
Jac/update build scripts (#182)
jacalata Sep 29, 2022
a8d0e66
Jac/image export (#186)
jacalata Oct 11, 2022
cf75a1f
tfs 1427025: Confusing error message related to sign-in (#187)
jacalata Oct 11, 2022
8592753
add arguments to publish command (#181)
jacalata Oct 11, 2022
b96cc08
Improve confusing help message for signing in (#190)
jacalata Oct 18, 2022
6be0c25
extract encryption defaults to false (#193)
jacalata Nov 1, 2022
e88f3ab
call sites endpoint correctly (#194)
jacalata Nov 9, 2022
35ec9af
publish data source to Tableau Cloud for published live connection w…
jacalata Nov 9, 2022
d747da6
Merge branch 'main' into development
jacalata Nov 10, 2022
37919b4
open csv files allowing for BOM (#200)
jacalata Nov 21, 2022
a4f6f39
Jac/packaging (#198)
jacalata Nov 28, 2022
51d91b0
Jac/delete site (#202)
jacalata Nov 29, 2022
4565b7e
Add Datasources to the Get Command (#162)
andyoneal Dec 6, 2022
eafe30e
format command-specific help (#203)
jacalata Dec 7, 2022
35ff22a
Tfs 1482014 publishing (#205)
jacalata Dec 7, 2022
c587f0b
Tfs 1482014 publish - error handling (#206)
jacalata Dec 8, 2022
5ab8172
Merge branch 'main' into development
jacalata Dec 8, 2022
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 .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