diff --git a/.github/workflows/check-coverage.yml b/.github/workflows/check-coverage.yml index 7de40783..0bfaa03a 100644 --- a/.github/workflows/check-coverage.yml +++ b/.github/workflows/check-coverage.yml @@ -28,8 +28,9 @@ jobs: run: | python --version python -m pip install --upgrade pip - pip install -e .[build] - pip install -e .[test] + python -m pip install --upgrade build + pip install . + pip install .[test] doit version python -m build diff --git a/.github/workflows/generate-metadata.yml b/.github/workflows/generate-metadata.yml index a1d6e88b..7f4abb2c 100644 --- a/.github/workflows/generate-metadata.yml +++ b/.github/workflows/generate-metadata.yml @@ -20,12 +20,15 @@ jobs: - name: Install App and Extras run: | + python --version python -m pip install --upgrade pip - python -m pip install -e .[build] + python -m pip install --upgrade build + pip install . + pip install .[test] doit version python -m build - - name: Generate dependencies list + - name: Run license check run: python bin/license-checker.py - name: Type-check @@ -35,7 +38,10 @@ jobs: run: black . --check - name: Generate pyinstaller specs - run: echo Using locally generated specs - # pyi-makespec tabcmd\tabcmd.py -n tabcmd-windows -i res\tabcmd.ico -F -c --version-file versionfile.txt --onefile --collect-data tabcmd.locales && - # pyi-makespec tabcmd\tabcmd.py -n tabcmd-mac -i res\tabcmd.icns -F -c --version-file versionfile.txt --onefile --collect-data tabcmd.locales --osx-bundle-identifier com.tableau.tabcmd - # 3. Unix + run: | + echo "Using manually generated specs" + + # todo: add hidden dependencies to these command lines + # pyi-makespec tabcmd\tabcmd.py -n tabcmd-windows -i res\tabcmd.ico -F -c --version-file versionfile.txt --onefile --collect-data tabcmd.locales + # pyi-makespec tabcmd\tabcmd.py -n tabcmd-mac -i res\tabcmd.icns -F -c --version-file versionfile.txt --onefile --collect-data tabcmd.locales --osx-bundle-identifier com.tableau.tabcmd + # 3. Unix diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 476e06d5..2b46a3e6 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -26,13 +26,12 @@ jobs: TARGET: windows CMD_BUILD: > pyinstaller tabcmd-windows.spec --clean --noconfirm --distpath ./dist/windows - OUT_FILE_NAME: tabcmd.exe + OUT_FILE_NAME: tabcmd-windows.exe ASSET_MIME: application/vnd.microsoft.portable-executable - os: macos-latest TARGET: macos CMD_BUILD: > - pyinstaller tabcmd-mac.spec --clean --noconfirm --distpath ./dist/macos && - cwd && ls && ls dist + 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 ASSET_MIME: application/zip @@ -54,8 +53,11 @@ jobs: - name: Install dependencies and build run: | - pip install -e .[build] - pip install -e .[package] + python --version + python -m pip install --upgrade pip + python -m pip install --upgrade build + pip install . + pip install .[test] doit version python -m build @@ -66,7 +68,7 @@ jobs: uses: WebFreak001/upload-asset@v1.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions - OS: windows # just a variable we use in the name pattern + OS: windows # a variable we use in the name pattern? with: file: ./dist/${{ matrix.TARGET }}/tabcmd/${{ matrix.OUT_FILE_NAME}} mime: ${{ matrix.ASSET_MIME}} # required by GitHub API diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index b9ea0521..42c234e3 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -23,7 +23,11 @@ jobs: python-version: 3.8 - name: Build dist files run: | - pip install -e .[build] + python --version + python -m pip install --upgrade pip + python -m pip install --upgrade build + pip install . + pip install .[package] doit version python -m build - name: Publish distribution 📦 to Test PyPI diff --git a/.github/workflows/run-e2-tests.yml b/.github/workflows/run-e2-tests.yml index 0bf4c3ea..ec1262af 100644 --- a/.github/workflows/run-e2-tests.yml +++ b/.github/workflows/run-e2-tests.yml @@ -34,8 +34,9 @@ jobs: run: | python --version python -m pip install --upgrade pip - pip install -e .[build] - pip install -e .[test] + python -m pip install --upgrade build + pip install . + pip install .[test] doit version python -m build diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9c981c75..bad6a73b 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,8 +31,9 @@ jobs: run: | python --version python -m pip install --upgrade pip - pip install -e .[build] - pip install -e .[test] + python -m pip install --upgrade build + pip install . + pip install .[test] doit version python -m build diff --git a/pyproject.toml b/pyproject.toml index 485decd9..77c00308 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,73 @@ [build-system] requires = ["build", "setuptools>=62", "wheel", "setuptools_scm>=6.2"] +build-backend = "setuptools.build_meta" [tool.setuptools_scm] -"local_scheme"= "no-local-version" # require pypi supported versions always +local_scheme = "no-local-version" # require pypi supported versions always +[tool.setuptools] +packages = ["tabcmd"] +[tool.setuptools.package-data] +tabcmd = ["tabcmd.locales/**/*.mo"] [tool.black] line-length = 120 +target-version = ['py37', 'py38', 'py39', 'py310'] extend-exclude = '^/bin/*' +[tool.mypy] +disable_error_code = [ + 'misc', + 'import' +] +files = ["tabcmd", "tests"] +show_error_codes = true +ignore_missing_imports = true +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "--junitxml=./test.junit.xml" + +[project] +name="tabcmd" +dynamic = ["version"] +description="A command line client for working with Tableau Server." +authors = [{name="Tableau", email="github@tableau.com"}] +license = {file = "LICENSE"} +readme = "README.md" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10" +] +dependencies = [ + 'argparse', + "appdirs", + "doit", + "ftfy", + "polling2", + "pyinstaller_versionfile", + "requests>=2.11,<3.0", + "setuptools_scm", + "types-appdirs", + "types-mock", + "types-requests", + "types-setuptools", + "tableauserverclient>=0.19", + "urllib3>=1.24.3,<2.0", +] +[project.optional-dependencies] +test = [ + "black", + "mock", + "mypy", + "pytest>=7.0", + "pytest-cov", + "pytest-order", + "pytest-runner", + "requests-mock>=1.0,<2.0"] +localize = ["doit", "ftfy"] +package = ["pyinstaller>=5.1"] +[project.urls] +repository = "https://github.com/tableau/tabcmd" +[project.scripts] +tabcmd = "tabcmd.tabcmd:main" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d4230e64..00000000 --- a/setup.cfg +++ /dev/null @@ -1,10 +0,0 @@ -[pycodestyle] -max_line_length = 120 -[mypy] -exclude = tests/e2e -ignore_missing_imports = True -[tool:pytest] -# useful settings for debugging -# log_cli = True -# log_cli_level = DEBUG - diff --git a/setup.py b/setup.py index 8d834b82..914e6b5d 100644 --- a/setup.py +++ b/setup.py @@ -1,63 +1,4 @@ -from setuptools import setup, find_packages +# legacy stub setup.py, do not add configuration here +from setuptools import setup -setup( - name="tabcmd", - author="Tableau", - author_email="github@tableau.com", - description="A command line client for working with Tableau Server.", - long_description="A command line client for working with Tableau Server.", - license="MIT", - url="https://github.com/tableau/tabcmd", - python_requires=">=3.7", - packages=find_packages(), - package_data={"tabcmd": ["tabcmd.locales/**/*.mo"]}, - include_package_data=True, - entry_points={"console_scripts": ["tabcmd = tabcmd.tabcmd:main"]}, - setup_requires=[ - # copy of pyproject.toml for back compat - "build", - "setuptools>=62", - "setuptools_scm>=6.2", - "wheel", - ], - install_requires=[ - "polling2", - "requests>=2.11,<3.0", - "tableauserverclient>=0.19", - "urllib3>=1.24.3,<2.0", - ], - extras_require={ - "localize": [ - "doit", - "ftfy", - ], - "build": [ - "appdirs", - "black", - "doit", - "ftfy", - "mypy", - "pyinstaller_versionfile", - "setuptools>=62", - "setuptools_scm", - "types-appdirs", - "types-mock", - "types-requests", - "types-setuptools", - ], - "package": [ - "pyinstaller>=5.1", - "pyinstaller-versionfile", - ], - "test": [ - "mock", - "pytest", - "pytest-cov", - "pytest-order", - "pytest-runner", - "requests-mock>=1.0,<2.0", - ], - }, - test_suite="tests", - zip_safe=False, -) +setup() diff --git a/tabcmd-linux.spec b/tabcmd-linux.spec index d71fd83e..c5e367fc 100644 --- a/tabcmd-linux.spec +++ b/tabcmd-linux.spec @@ -12,7 +12,7 @@ a = Analysis( pathex=[], binaries=[], datas=datas, - hiddenimports=[], + hiddenimports=['tableauserverclient', 'requests.packages.urllib3', 'pkg_resources'], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/tabcmd-mac.spec b/tabcmd-mac.spec index fcce4bfe..fee41fa8 100644 --- a/tabcmd-mac.spec +++ b/tabcmd-mac.spec @@ -13,7 +13,7 @@ a = Analysis( pathex=[], binaries=[], datas=datas, - hiddenimports=[], + hiddenimports=['tableauserverclient', 'requests.packages.urllib3', 'pkg_resources'], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/tabcmd-windows.spec b/tabcmd-windows.spec index d71fd83e..03f1dfd1 100644 --- a/tabcmd-windows.spec +++ b/tabcmd-windows.spec @@ -12,7 +12,7 @@ a = Analysis( pathex=[], binaries=[], datas=datas, - hiddenimports=[], + hiddenimports=['tableauserverclient', 'requests', 'pkg_resources'], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/tabcmd.spec b/tabcmd.spec index 650b541a..a1e0421c 100644 --- a/tabcmd.spec +++ b/tabcmd.spec @@ -9,7 +9,7 @@ a = Analysis( pathex=[], binaries=[], datas=[], - hiddenimports=[], + hiddenimports=['tableauserverclient', 'requests.packages.urllib3', 'pkg_resources'], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/tabcmd/execution/localize.py b/tabcmd/execution/localize.py index a53e43f1..ec70ee30 100644 --- a/tabcmd/execution/localize.py +++ b/tabcmd/execution/localize.py @@ -3,6 +3,7 @@ import logging import os import sys +from os import listdir from typing import Any from typing import Callable @@ -20,7 +21,7 @@ def _(string_key: str) -> str: def _identity_func(x: Any) -> Any: - return x + return "++" + x + "++" # The client should present text in the OS language, or english if not present. @@ -41,7 +42,7 @@ def set_client_locale(lang: str = None, logger=None) -> Callable: for lang in locale_options: try: if lang: - translate = _load_language(lang, domain) + translate = _load_language(lang, domain, logger) break except Exception as e: print("Failed to load language '", lang, "':", e) @@ -49,23 +50,32 @@ def set_client_locale(lang: str = None, logger=None) -> Callable: return translate or _identity_func -# Handling file locations in unbundled (e.g dev) layout and when bundled by pyinstaller +"""Get absolute path to resource, works for unbundled (e.g dev) and when bundled by PyInstaller""" # https://stackoverflow.com/questions/7674790/bundling-data-files-with-pyinstaller-onefile/13790741#13790741 -def resource_path(relative_path): - """Get absolute path to resource, works for dev and for PyInstaller""" - base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) - return os.path.join(base_path, relative_path) - - -def _load_language(current_locale, domain): - locale_path = os.path.join("..", "locales") - - # fallback=True means if loading the translated files fails, strings will be returned - # we use the identity function above instead - locale_dir = resource_path(locale_path) - language: gettext.NullTranslations = gettext.translation( - domain, locale_dir, languages=[current_locale], fallback=False - ) +def define_locale_dir(logger): + try: + base_path = getattr(sys, "_MEIPASS") + except AttributeError: # sys._MEIPASS will only exist in bundled pyinstaller exe, + # in unbundled src code we take the location of the current file + # and go 2 dirs up so that the relative path /tabcmd/locales is still correct + base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..") + relative_path = os.path.join(".", "tabcmd", "locales") + locale_dir = os.path.join(base_path, relative_path) + logger.debug("Checking for language resources at " + locale_dir) + """ to debug pyinstaller file bundling, try something like this example debug line + try: + logger.debug(listdir(sys._MEIPASS)) + except AttributeError as e: + logger.debug(e) + """ + print(locale_dir) + print(listdir(locale_dir)) + return locale_dir + + +def _load_language(current_locale, domain, logger): + locale_dir = define_locale_dir(logger) + language: gettext.NullTranslations = gettext.translation(domain, locale_dir, languages=[current_locale]) language.install() # I believe this is the expensive call _ = language.gettext return _ diff --git a/tabcmd/execution/logger_config.py b/tabcmd/execution/logger_config.py index 96f7f067..8a42e2cb 100644 --- a/tabcmd/execution/logger_config.py +++ b/tabcmd/execution/logger_config.py @@ -6,7 +6,7 @@ FORMATS = { logging.ERROR: "%(asctime)s %(levelname)-5s:(%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", logging.WARN: "%(asctime)s %(levelname)-5s: (%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", - logging.INFO: "%(message)-30s", + logging.INFO: "%(filename)-10s: %(message)-30s", logging.DEBUG: "%(asctime)s %(levelname)-5s: (%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", } diff --git a/tests/e2e/setup_e2e.py b/tests/e2e/setup_e2e.py index ddbe1573..4edecf7c 100644 --- a/tests/e2e/setup_e2e.py +++ b/tests/e2e/setup_e2e.py @@ -1,9 +1,8 @@ import subprocess import os -import pytest try: - from tests.e2e import credentials + from tests.e2e import credentials # type: ignore except ImportError: credentials = None # type: ignore diff --git a/tests/e2e/tests_integration.py b/tests/e2e/tests_integration.py index d41070a1..e18c38b6 100644 --- a/tests/e2e/tests_integration.py +++ b/tests/e2e/tests_integration.py @@ -8,7 +8,7 @@ try: - from tests.e2e import credentials + from tests.e2e import credentials # type: ignore except ImportError: credentials = None # type: ignore