Skip to content

Commit

Permalink
Development (#216)
Browse files Browse the repository at this point in the history
fix handling of -f argument for exporting
add --auth and --role args
add --timeout arg
  • Loading branch information
jacalata authored Dec 16, 2022
1 parent 91d89f1 commit f7ce761
Show file tree
Hide file tree
Showing 20 changed files with 278 additions and 126 deletions.
26 changes: 11 additions & 15 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,22 @@ jobs:
TARGET: windows
CMD_BUILD: >
pyinstaller tabcmd-windows.spec --clean --noconfirm --distpath ./dist/windows
OUT_FILE_NAME: tabcmd-windows.exe
OUT_FILE_NAME: tabcmd.exe
ASSET_MIME: application/vnd.microsoft.portable-executable
- os: macos-latest
TARGET: macos
CMD_BUILD: >
pyinstaller tabcmd-mac.spec --clean --noconfirm --distpath ./dist/macos && ls && ls dist
# zip -r9 mac tabcmd-mac*
OUT_FILE_NAME: tabcmd-mac.app # tabcmd.zip
pyinstaller tabcmd-mac.spec --clean --noconfirm --distpath ./dist/macos
OUT_FILE_NAME: tabcmd.app
ASSET_MIME: application/zip
- os: ubuntu-latest
TARGET: ubuntu
# https://stackoverflow.com/questions/31259856
# /how-to-create-an-executable-file-for-linux-machine-using-pyinstaller
CMD_BUILD: >
pyinstaller --clean -y --distpath ./dist/linux tabcmd-linux.spec &&
chown -R --reference=. ./dist/linux
OUT_FILE_NAME: tab-for-linux
pyinstaller --clean -y --distpath ./dist/ubuntu tabcmd-linux.spec &&
chown -R --reference=. ./dist/ubuntu
OUT_FILE_NAME: tabcmd

steps:
- uses: actions/checkout@v3
Expand All @@ -64,12 +63,9 @@ jobs:
- name: Package with pyinstaller for ${{matrix.TARGET}}
run: ${{matrix.CMD_BUILD}}

- name: Upload assets to release
uses: WebFreak001/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
OS: windows # a variable we use in the name pattern?
- name: Validate package for ${{matrix.TARGET}}
run: ./dist/${{ matrix.TARGET }}/${{matrix.OUT_FILE_NAME}}

- uses: actions/upload-artifact@v3
with:
file: ./dist/${{ matrix.TARGET }}/${{ matrix.OUT_FILE_NAME}}
mime: ${{ matrix.ASSET_MIME}} # required by GitHub API
name: ${{ matrix.OUT_FILE_NAME}} # name pattern to upload the file as
path: ./dist/${{ matrix.TARGET }}/${{ matrix.OUT_FILE_NAME }}/
6 changes: 3 additions & 3 deletions tabcmd-linux.spec
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ print(datas)
block_cipher = None

a = Analysis(
['tabcmd\\tabcmd.py'],
['tabcmd.py'],
pathex=[],
binaries=[],
datas=datas,
hiddenimports=['tableauserverclient', 'requests.packages.urllib3', 'pkg_resources'],
hiddenimports=['tableauserverclient', 'requests', 'urllib3', 'pkg_resources'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
Expand All @@ -31,7 +31,7 @@ exe = EXE(
a.zipfiles,
a.datas,
[],
name='tabcmd-windows',
name='tabcmd',
debug=False,
bootloader_ignore_signals=False,
strip=False,
Expand Down
15 changes: 8 additions & 7 deletions tabcmd-mac.spec
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ a = Analysis(
pathex=[],
binaries=[],
datas=datas,
hiddenimports=['tableauserverclient', 'requests.packages.urllib3', 'pkg_resources'],
hiddenimports=['tableauserverclient', 'requests', 'urllib3', 'pkg_resources'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
Expand All @@ -32,19 +32,20 @@ exe = EXE(
a.zipfiles,
a.datas,
[],
name='tabcmd-mac',
name='tabcmd.app',
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,
version='program_metadata.txt',
)

app = BUNDLE(
exe,
name = 'tabcmd-app',
icon='res/tabcmd.icns',
bundle_identifier = None,
)
2 changes: 1 addition & 1 deletion tabcmd-windows.spec
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ exe = EXE(
a.zipfiles,
a.datas,
[],
name='tabcmd-windows',
name='tabcmd',
debug=False,
bootloader_ignore_signals=False,
strip=False,
Expand Down
3 changes: 2 additions & 1 deletion tabcmd/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

try:
from tabcmd.tabcmd import main
except ImportError:
except ImportError as e:
print(sys.stderr, e)
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)
Expand Down
77 changes: 64 additions & 13 deletions tabcmd/commands/auth/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import requests
import tableauserverclient as TSC
import urllib3
from urllib3.exceptions import InsecureRequestWarning

from tabcmd.commands.constants import Errors
Expand Down Expand Up @@ -74,7 +75,26 @@ def _update_session_data(self, args):
self.no_certcheck = args.no_certcheck or self.no_certcheck
self.no_proxy = args.no_proxy or self.no_proxy
self.proxy = args.proxy or self.proxy
self.timeout = args.timeout or self.timeout
self.timeout = self.timeout_as_integer(self.logger, args.timeout, self.timeout)

@staticmethod
def timeout_as_integer(logger, option_1, option_2):
result = None
if option_1:
try:
result = int(option_1)
except Exception as anyE:
result = 0
if option_2 and (not result or result <= 0):
try:
result = int(option_2)
except Exception as anyE:
result = 0
if not option_1 and not option_2:
logger.debug(_("setsetting.status").format("timeout", "None"))
elif not result or result <= 0:
logger.warning(_("sessionoptions.errors.bad_timeout").format("--timeout", result))
return result or 0

@staticmethod
def _read_password_from_file(filename):
Expand Down Expand Up @@ -108,7 +128,7 @@ def _create_new_credential(self, password, credential_type):
credentials = self._create_new_token_credential()
return credentials
else:
Errors.exit_with_error(self.logger, "Couldn't find credentials")
Errors.exit_with_error(self.logger, _("session.errors.missing_arguments").format(""))

def _create_new_token_credential(self):
if self.token_value:
Expand All @@ -127,31 +147,60 @@ def _create_new_token_credential(self):
else:
Errors.exit_with_error(self.logger, _("session.errors.missing_arguments").format("token name"))

def _set_connection_options(self):
def _set_connection_options(self) -> TSC.Server:
self.logger.debug("Setting up request options")
# args still to be handled here:
# proxy, --no-proxy,
# cert
# timeout
http_options = {}
if self.no_certcheck:
http_options = {"verify": False}
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
http_options["verify"] = False
urllib3.disable_warnings(category=InsecureRequestWarning)
if self.proxy:
# do we catch this error? "sessionoptions.errors.bad_proxy_format"
self.logger.debug("Setting proxy: ", self.proxy)
if self.timeout:
http_options["timeout"] = self.timeout
try:
tableau_server = TSC.Server(self.server_url, use_server_version=True, http_options=http_options)
self.logger.debug(http_options)
tableau_server = TSC.Server(self.server_url, http_options=http_options)

except Exception as e:
self.logger.debug(
"Connection args: server {}, site {}, proxy {}, cert {}".format(
self.server_url, self.site_name, self.proxy, self.certificate
)
)
Errors.exit_with_error(self.logger, "Failed to connect to server", e)

self.logger.debug("Finished setting up connection")
return tableau_server

def _create_new_connection(self):
def _verify_server_connection_unauthed(self):
try:
self.tableau_server.use_server_version()
except requests.exceptions.ReadTimeout as timeout_error:
Errors.exit_with_error(
self.logger,
message="Timed out after {} seconds attempting to connect to server".format(self.timeout),
exception=timeout_error,
)
except requests.exceptions.RequestException as requests_error:
Errors.exit_with_error(
self.logger, message="Error attempting to connect to the server", exception=requests_error
)
except Exception as e:
Errors.exit_with_error(self.logger, exception=e)

def _create_new_connection(self) -> TSC.Server:
self.logger.info(_("session.new_session"))
self.tableau_server = self._set_connection_options()
self._print_server_info()
self.logger.info(_("session.connecting"))
try:
self.tableau_server.use_server_version() # this will attempt to contact the server
self.tableau_server = self._set_connection_options()
except Exception as e:
Errors.exit_with_error(self.logger, "Failed to connect to server", e)
return self.tableau_server

def _read_existing_state(self):
if self._check_json():
Expand All @@ -168,7 +217,7 @@ def _print_server_info(self):

def _validate_existing_signin(self):
self.logger.info(_("session.continuing_session"))
self.tableau_server = self._set_connection_options()
# 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():
response = self.tableau_server.users.get_by_id(self.user_id)
Expand All @@ -181,7 +230,8 @@ def _validate_existing_signin(self):
self.logger.info(_("errors.internal_error.request.message"), e)
return None

def _sign_in(self, tableau_auth):
# server connection created, not yet logged in
def _sign_in(self, tableau_auth) -> TSC.Server:
self.logger.debug(_("session.login") + self.server_url)
self.logger.debug(_("listsites.output").format("", self.username or self.token_name, self.site_name))
try:
Expand Down Expand Up @@ -245,7 +295,8 @@ def create_session(self, args):

if credentials and not signed_in_object:
# logging in, not using an existing session
self._create_new_connection()
self.tableau_server = self._create_new_connection()
self._verify_server_connection_unauthed()
signed_in_object = self._sign_in(credentials)

if not signed_in_object:
Expand Down
4 changes: 2 additions & 2 deletions tabcmd/commands/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def is_expired_session(error):
@staticmethod
def is_resource_conflict(error):
if hasattr(error, "code"):
return error.code.startswith(Constants.resource_conflict_general)
return error.code == Constants.source_already_exists

@staticmethod
def is_login_error(error):
Expand Down Expand Up @@ -86,7 +86,7 @@ def check_common_error_codes_and_explain(logger, exception):
# "session.session_expired_login"))
# session.renew_session
return
if exception.code.startswith(Constants.source_not_found):
if exception.code == 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 @@ -32,7 +32,6 @@ def define_args(export_parser):
help="page orientation (landscape or portrait) of the exported PDF",
)
group.add_argument(

"--pagesize",
choices=[
pagesize.A3,
Expand Down
40 changes: 29 additions & 11 deletions tabcmd/commands/datasources_and_workbooks/get_url_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def run_command(args):
Errors.exit_with_error(logger, _("export.errors.white_space_workbook_view"))

url = args.url.lstrip("/") # strip opening / if present
content_type = GetUrl.evaluate_content_type(logger, url)
file_type = GetUrl.get_file_type_from_filename(logger, url, args.filename)

GetUrl.get_content_as_file(file_type, content_type, logger, args, server, url)

## this first set of methods is all parsing the url and file input from the user

Expand All @@ -60,7 +64,6 @@ def evaluate_content_type(logger, url):
return content_type
Errors.exit_with_error(logger, message=_("get.errors.invalid_content_type").format(url))


@staticmethod
def explain_expected_url(logger, url: str, command: str):
view_example = "/views/<workbookname>/<viewname>[.ext]"
Expand All @@ -74,13 +77,19 @@ def explain_expected_url(logger, url: str, command: str):
Errors.exit_with_error(logger, message)

@staticmethod
def get_file_type_from_filename(logger, file_name, url):
def get_file_type_from_filename(logger, url, file_name):
logger.debug("Choosing between {}, {}".format(file_name, url))
file_name = file_name or url
logger.debug(_("get.options.file") + ": {}".format(file_name))
type_of_file = GetUrl.get_file_extension(file_name)

if not type_of_file:
Errors.exit_with_error(logger, _("tabcmd.get.extension.not_found").format(file_name))
if not type_of_file and file_name is not None:
# check the url
backup = GetUrl.get_file_extension(url)
if backup is not None:
type_of_file = backup
else:
Errors.exit_with_error(logger, _("tabcmd.get.extension.not_found").format(file_name))

logger.debug("filetype: {}".format(type_of_file))
if type_of_file in ["pdf", "csv", "png", "twb", "twbx", "tdsx"]:
Expand Down Expand Up @@ -134,14 +143,17 @@ def get_view_url(url, logger): # "views/wb-name/view-name" -> wb-name/sheets/vi
@staticmethod
def filename_from_args(file_argument, item_name, filetype):
if file_argument is None:
file_argument = "{}.{}".format(item_name, filetype)
file_argument = item_name
if not file_argument.endswith(filetype):
file_argument = "{}.{}".format(file_argument, filetype)
return file_argument

## methods below here have done all the parsing and just have to do the download and saving
## these should be able to be shared with export

@staticmethod
def get_content_as_file(file_type, content_type, logger, args, server, url):
logger.debug("fetching {} as {}".format(content_type, file_type))
if content_type == "workbook":
return GetUrl.generate_twb(logger, server, args, file_type, url)
elif content_type == "datasource":
Expand Down Expand Up @@ -207,9 +219,12 @@ def generate_twb(logger, server, args, file_extension, url):
target_workbook = GetUrl.get_wb_by_content_url(logger, server, workbook_name)
logger.debug(_("content_type.workbook") + ": {}".format(workbook_name))
file_name_with_path = GetUrl.filename_from_args(args.filename, workbook_name, file_extension)
logger.debug("Saving as {}".format(file_name_with_path))
server.workbooks.download(target_workbook.id, filepath=None, no_extract=False)
logger.info(_("export.success").format(target_workbook.name, file_name_with_path))
# the download method will add an extension. How do I tell which one?
file_name_with_path = GetUrl.get_name_without_possible_extension(file_name_with_path)
file_name_with_ext = "{}.{}".format(file_name_with_path, file_extension)
logger.debug("Saving as {}".format(file_name_with_ext))
server.workbooks.download(target_workbook.id, filepath=file_name_with_path, no_extract=False)
logger.info(_("export.success").format(target_workbook.name, file_name_with_ext))
except Exception as e:
Errors.exit_with_error(logger, e)

Expand All @@ -221,8 +236,11 @@ def generate_tds(logger, server, args, file_extension):
target_datasource = GetUrl.get_ds_by_content_url(logger, server, datasource_name)
logger.debug(_("content_type.datasource") + ": {}".format(datasource_name))
file_name_with_path = GetUrl.filename_from_args(args.filename, datasource_name, file_extension)
logger.debug("Saving as {}".format(file_name_with_path))
server.datasources.download(target_datasource.id, filepath=None, no_extract=False)
logger.info(_("export.success").format(target_datasource.name, file_name_with_path))
# the download method will add an extension
file_name_with_path = GetUrl.get_name_without_possible_extension(file_name_with_path)
file_name_with_ext = "{}.{}".format(file_name_with_path, file_extension)
logger.debug("Saving as {}".format(file_name_with_ext))
server.datasources.download(target_datasource.id, filepath=file_name_with_path, no_extract=False)
logger.info(_("export.success").format(target_datasource.name, file_name_with_ext))
except Exception as e:
Errors.exit_with_error(logger, e)
Loading

0 comments on commit f7ce761

Please sign in to comment.