Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ec730ae
Fix for not retrieving all items when response has multiple pages of …
renoyjohnm Dec 13, 2024
0075f17
fix for error when item is not in first page
jacalata Dec 21, 2024
ce4048e
fix query filter for projects
jacalata Dec 21, 2024
3be7100
Merge pull request #320 from tableau/jac/improve-not-found
jacalata Dec 27, 2024
ae7382c
Update package.yml
jacalata Jan 2, 2025
2cb76ad
Update package.yml
jacalata Jan 2, 2025
ae53393
Update package.yml
jacalata Jan 3, 2025
0e1d997
Update package.yml
jacalata Jan 3, 2025
7a43038
Update package.yml
jacalata Jan 3, 2025
67477af
Update package.yml
jacalata Jan 3, 2025
292cd23
Update package.yml
jacalata Jan 3, 2025
25b3f75
Update package.yml
jacalata Jan 3, 2025
a169306
Update package.yml
jacalata Jan 3, 2025
0a543f9
Update package.yml
jacalata Jan 3, 2025
1675a8c
Update package.yml
jacalata Jan 3, 2025
71b75b2
Update package.yml
jacalata Jan 3, 2025
6349fb7
Roll back changes
jacalata Jan 3, 2025
28c950e
change mac bundles name
jacalata Jan 3, 2025
cfcdbb5
Merge pull request #2 from jacalata/jacalata-patch-1
jacalata Jan 3, 2025
4bdda10
Update package.yml
jacalata Jan 3, 2025
46c9ef7
remove mac bundling step
jacalata Jan 3, 2025
cbc2b05
Create mac_bundle_step.sh
jacalata Jan 3, 2025
89187de
call mac script
jacalata Jan 3, 2025
e09317b
use newlines to separate commands
jacalata Jan 3, 2025
168de7d
troubleshooting
jacalata Jan 3, 2025
47c8fd7
move to right folder
jacalata Jan 3, 2025
0b19642
troubleshooting script invocation
jacalata Jan 3, 2025
43eca6b
Update package.yml
jacalata Jan 3, 2025
84e5773
Update package.yml
jacalata Jan 3, 2025
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
68 changes: 31 additions & 37 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Release-Executable
name: Package-and-release

# Pyinstaller requires that executables for each OS are built on that OS
# This action is intended to build on each of the supported OS's: mac, windows, linux.
# and then upload all three files to a new release
# (We run two macos versions to build for arm and x64)
# and then upload all four files to a new release

# reference material:
# https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions
Expand All @@ -15,7 +16,6 @@ on:
workflow_dispatch:

jobs:

buildexe:
name: Build executables and upload them to the existing release
runs-on: ${{ matrix.os }}
Expand All @@ -29,22 +29,34 @@ jobs:
pyinstaller tabcmd-windows.spec --clean --noconfirm --distpath ./dist/windows
OUT_FILE_NAME: tabcmd.exe
ASSET_MIME: application/vnd.microsoft.portable-executable
CMD_INVOKE: >
./dist/windows/tabcmd.exe
- os: macos-13
TARGET: macos
CMD_BUILD: >
TARGET: macos-x86
CMD_BUILD: |
pyinstaller tabcmd-mac.spec --clean --noconfirm --distpath ./dist/macos/
ls
pwd
./mac_bundle_step.sh
BUNDLE_NAME: tabcmd.app
OUT_FILE_NAME: tabcmd.app
APP_BINARY_FILE_NAME: tabcmd
ASSET_MIME: application/zip
CMD_INVOKE: >
./dist/macos/tabcmd.app/Contents/MacOS/tabcmd
- os: macos-latest
TARGET: macos
CMD_BUILD: >
TARGET: macos-arm
CMD_BUILD: |
pyinstaller tabcmd-mac.spec --clean --noconfirm --distpath ./dist/macos/
BUNDLE_NAME: tabcmd_arm64.app
ls
pwd
./mac_bundle_step.sh
BUNDLE_NAME: tabcmd.app
OUT_FILE_NAME: tabcmd.app
APP_BINARY_FILE_NAME: tabcmd
ASSET_MIME: application/zip
CMD_INVOKE: >
./dist/macos/tabcmd.app/Contents/MacOS/tabcmd
- os: ubuntu-latest
TARGET: ubuntu
# https://stackoverflow.com/questions/31259856
Expand All @@ -53,6 +65,8 @@ jobs:
pyinstaller --clean -y --distpath ./dist/ubuntu tabcmd-linux.spec &&
chown -R --reference=. ./dist/ubuntu
OUT_FILE_NAME: tabcmd
CMD_INVOKE: >
./dist/ubuntu/tabcmd

steps:
- uses: actions/checkout@v4
Expand All @@ -71,44 +85,24 @@ jobs:
doit version
python -m build

- name: Package with pyinstaller for ${{matrix.TARGET}}
- name: Build executable with pyinstaller for ${{matrix.TARGET}}
run: ${{matrix.CMD_BUILD}}

- name: Validate package for ${{matrix.TARGET}}
if: matrix.TARGET != 'macos'
run: ./dist/${{ matrix.TARGET }}/${{matrix.OUT_FILE_NAME}}

- name: Validate package for Mac
if: matrix.TARGET == 'macos'
run: ./dist/${{ matrix.TARGET }}/${{ matrix.OUT_FILE_NAME }}/Contents/MacOS/${{ matrix.APP_BINARY_FILE_NAME }}

- name: Tar app bundle for Mac
if: matrix.TARGET == 'macos'
run: |
rm -f dist/${{ matrix.TARGET }}/${{ matrix.APP_BINARY_FILE_NAME }}
cd dist/${{ matrix.TARGET }}
tar -cvf ${{ matrix.BUNDLE_NAME }}.tar ${{ matrix.OUT_FILE_NAME }}

- name: Upload artifact
if: matrix.TARGET != 'macos'

- name: Validate executable for ${{matrix.TARGET}}
run: ${{matrix.CMD_INVOKE}}

- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.OUT_FILE_NAME }}
path: ./dist/${{ matrix.TARGET }}/${{ matrix.OUT_FILE_NAME }}

- name: Upload artifact for Mac
if: matrix.TARGET == 'macos'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.BUNDLE_NAME }}
path: ./dist/${{ matrix.TARGET }}/${{ matrix.BUNDLE_NAME }}.tar

- name: Upload binaries to release
- name: Copy binary to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./dist/${{ matrix.TARGET }}/${{ matrix.OUT_FILE_NAME }}/
file: ./dist/${{ matrix.TARGET }}/${{ matrix.OUT_FILE_NAME }}
asset_name: ${{ matrix.TARGET }}-${{ matrix.OUT_FILE_NAME }}
tag: ${{ github.ref_name }}
overwrite: true
promote: true

18 changes: 18 additions & 0 deletions mac_bundle_step.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

# Pyinstaller bundles cannot be run by double-clicking them
# https://github.com/pyinstaller/pyinstaller/issues/5154#issuecomment-2508011279
APP_NAME="tabcmd"

cd dist/$APP_NAME.app/Contents/MacOS
mv $APP_NAME ${APP_NAME}_cli

cat << EOF > $APP_NAME
#!/bin/bash
# This is the launcher for OSX, this way the app will be opened
# when you double click it from the apps folder
open -n /Applications/${APP_NAME}.app/Contents/MacOS/${APP_NAME}_cli
EOF

chmod +x $APP_NAME

75 changes: 49 additions & 26 deletions tabcmd/commands/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,59 @@ def get_items_by_name(logger, item_endpoint, item_name: str, container: Optional
container_name: str = "[{0}] {1}".format(container.__class__.__name__, 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))
all_items, pagination_item = item_endpoint.get(req_option)
if all_items is None or all_items == []:
raise TSC.ServerResponseError(
code="404",
summary=_("errors.xmlapi.not_found"),
detail=_("errors.xmlapi.not_found") + ": " + item_log_name,

result = []
total_available_items = None
page_number = 1
total_retrieved_items = 0

while True:
req_option = TSC.RequestOptions(pagenumber=page_number)
req_option.filter.add(
TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, item_name)
)
if len(all_items) == 1:
logger.debug("Exactly one result found")
result = all_items
if len(all_items) > 1:

# todo - this doesn't filter if the project is in the top level.
# todo: there is no guarantee that these fields are the same for different content types.
# probably better if we move that type specific logic out to a wrapper
if container:
# the name of the filter field is different if you are finding a project or any other item
if type(item_endpoint).__name__.find("Projects") < 0:
parentField = TSC.RequestOptions.Field.ProjectName
parentValue = container.name
else:
parentField = TSC.RequestOptions.Field.ParentProjectId
parentValue = container.id
logger.debug("filtering for parent with {}".format(parentField))

req_option.filter.add(TSC.Filter(parentField, TSC.RequestOptions.Operator.Equals, parentValue))

all_items, pagination_item = item_endpoint.get(req_option)

if pagination_item.total_available == 0:
raise TSC.ServerResponseError(
code="404",
summary=_("errors.xmlapi.not_found"),
detail=_("errors.xmlapi.not_found") + ": " + item_log_name,
)

total_retrieved_items += len(all_items)

logger.debug(
"{}+ items of this name were found: {}".format(
len(all_items), all_items[0].name + ", " + all_items[1].name + ", ..."
"{} items of name: {} were found for query page number: {}, page size: {} & total available: {}".format(
len(all_items),
item_name,
pagination_item.page_number,
pagination_item.page_size,
pagination_item.total_available,
)
)

if container:
container_id = container.id
logger.debug("Filtering to items in project {}".format(container.id))
result = list(filter(lambda item: item.project_id == container_id, all_items))
else:
result = all_items
result.extend(all_items)
if total_retrieved_items >= pagination_item.total_available:
break

page_number = pagination_item.page_number + 1

return result

Expand Down Expand Up @@ -146,12 +174,7 @@ def _parse_project_path_to_list(project_path: str):
def _get_project_by_name_and_parent(logger, server, project_name: str, parent: Optional[TSC.ProjectItem]):
# logger.debug("get by name and parent: {0}, {1}".format(project_name, parent))
# get by name to narrow down the list
projects = Server.get_items_by_name(logger, server.projects, project_name)
if parent is not None:
parent_id = parent.id
for project in projects:
if project.parent_id == parent_id:
return project
projects = Server.get_items_by_name(logger, server.projects, project_name, parent)
return projects[0]

@staticmethod
Expand Down
8 changes: 8 additions & 0 deletions tabcmd/execution/parent_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ def parent_parser_with_global_options():
version=strings[6] + "v" + version + "\n \n",
help=strings[7],
)

parser.add_argument(
"--query-page-size",
type=int,
default=None,
metavar="<PAGE_SIZE>",
help="Specify the page size for query results.",
)
return parser


Expand Down
3 changes: 3 additions & 0 deletions tabcmd/execution/tabcmd_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os
import sys

from .localize import set_client_locale
Expand Down Expand Up @@ -37,6 +38,8 @@ def run(parser, user_input=None):
logger.debug(namespace)
if namespace.language:
set_client_locale(namespace.language, logger)
if namespace.query_page_size:
os.environ["TSC_PAGE_SIZE"] = str(namespace.query_page_size)
try:
func = namespace.func
# if a subcommand was identified, call the function assigned to it
Expand Down
6 changes: 5 additions & 1 deletion tests/commands/test_projects_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
fake_item = mock.MagicMock()
fake_item.name = "fake-name"
fake_item.id = "fake-id"
getter = mock.MagicMock("get", return_value=([fake_item], 1))
fake_item_pagination = mock.MagicMock()
fake_item_pagination.page_number = 1
fake_item_pagination.total_available = 1
fake_item_pagination.page_size = 100
getter = mock.MagicMock("get", return_value=([fake_item], fake_item_pagination))


class ProjectsTest(unittest.TestCase):
Expand Down
7 changes: 6 additions & 1 deletion tests/commands/test_publish_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
fake_item.pdf = b"/pdf-representation-of-view"
fake_item.extract_encryption_mode = "Disabled"

fake_item_pagination = MagicMock()
fake_item_pagination.page_number = 1
fake_item_pagination.total_available = 1
fake_item_pagination.page_size = 100

fake_job = MagicMock()
fake_job.id = "fake-job-id"

creator = MagicMock()
getter = MagicMock()
getter.get = MagicMock("get", return_value=([fake_item], 1))
getter.get = MagicMock("get", return_value=([fake_item], fake_item_pagination))
getter.publish = MagicMock("publish", return_value=fake_item)


Expand Down
9 changes: 7 additions & 2 deletions tests/commands/test_run_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@
fake_item = MagicMock()
fake_item.name = "fake-name"
fake_item.id = "fake-id"
fake_item.project_id = "fake-id"
fake_item.pdf = b"/pdf-representation-of-view"
fake_item.extract_encryption_mode = "Disabled"

fake_item_pagination = MagicMock()
fake_item_pagination.page_number = 1
fake_item_pagination.total_available = 1
fake_item_pagination.page_size = 100

fake_job = MagicMock()
fake_job.id = "fake-job-id"

creator = MagicMock()
getter = MagicMock()
getter.get = MagicMock("get", return_value=([fake_item], 1))
getter.get = MagicMock("get", return_value=([fake_item], fake_item_pagination))
getter.publish = MagicMock("publish", return_value=fake_item)
getter.create_extract = MagicMock("create_extract", return_value=fake_job)
getter.decrypt_extract = MagicMock("decrypt_extract", return_value=fake_job)
Expand Down Expand Up @@ -214,7 +220,6 @@ def test_refresh_extract(self, mock_session, mock_server):
mock_args.removecalculations = None
mock_args.incremental = None
mock_args.synchronous = None
print(mock_args)

refresh_extracts_command.RefreshExtracts.run_command(mock_args)
mock_session.assert_called()
Expand Down
Loading
Loading