Note
These instructions are for people who want to work with the python code behind tabcmd. If you are interested in tabcmd but not the code, see here.
####To work with tabcmd, you need to have Python 3.8+ installed. To propose changes, you must have a Github account.
Fork the tabcmd repo and create a branch off the development branch, not the default branch (named main) (See Github Docs)
To install the current release:
pip install tabcmdOr install the current work-in-progress version from Git
Only do this if you know you want the development version, no guarantee that we won't break APIs during development
pip install git+https://github.com/tableau/tabcmd.git@developmentTip
If you want to switch back to the non-development version, you need to run the following command before installing the stable version:
pip uninstall tabcmd- Cross-platform
- Build on our existing Python Tableau Server Client
The core design principles for this app are
- it must provide the functionality of the instance of tabcmd, with drop-in replacement CLI options
- it should be able to call tsc for all server actions
- architecture is as simple as possible
- tabcmd.py exists only as a module entry point that calls TabCmdController.
- the 'parsers' module contains only argument and option definitions, no logic.
- the 'commands' module contains the logic required to translate the tabcmd CLI interface into calls to tsc. This is completely dissociated from the parsers, and could theoretically be called from a completely different interface.
- The 'execution' module is the core logic. TabcmdController gets an argparse parser, then attaches all the defined parsers to it and associates one command with each parser.
- choose the single word that will be used as your command. Let's call this one
dream - add parsers/dream_parser.py, and use methods from parent_parser to define the arguments
- add commands/dreams/dream_command.py. It must have a method run_command.py(args) and the args object must contain all information needed from the user.
- in map_of_parsers.py, add an entry for your new parser, like "dreams": DreamParser.dream_parser
- in map_of_commands.py, add an entry for your new command, like "dream": ("dream", DreamCommand, "Think about picnics"),"
- add tests!
Code contributions and improvements by the community are welcomed!
See the LICENSE file for current open-source licensing and use information.
Before we can accept pull requests from contributors, we require a signed Contributor License Agreement (CLA).
To work on the tabcmd code, use these scripts. (note that running mypy and black with no errors is required before code will be merged into the repo)
- build and run
pip install build python setup.py build python -m tabcmd.py [command_name] [--flags]
- run tests
pip install .[test] pytest
- run tests against a live server
python -m tabcmd login {your server info here} pytest -q tests\e2e\online_tests.py -r pfE
- autoformat your code with black (https://pypi.org/project/black/)
black .
- check types
mypy tabcmd tests
- do test coverage calculation (https://coverage.readthedocs.io/en/6.3.2)
bin/coverage.sh
We have three levels of testing.
-
Testing for all the little helper methods. These should be straightforward unit tests.
-
"e2e" offline testing: Every command needs to have test coverage calling "MyCommand.run_command()" that will run through the basic happy path of the command. Since they are offline, they need to use the mocked server object set up in mock_data. These tests are in files named test_e2e_x_command.py, and they are run with the unit tests against every checkin.
-
real, live, e2e tests that you can run against a known server when given credentials. These tests should not have any mocking code. You can launch these tests manually (see above)
Strings should be added/edited in /tabcmd/locales/en/{name}.properties by id and referred to in code as
string = _("string.id")
- regenerate updated strings for packaging as exe
python -m doit properties po mo
Versioning is done with setuptools_scm and based on git tags. The version number will be x.y.dev0.dirty except for commits with a new version tag. This is pulled from the git state, and to get a clean version like "v2.1.0", you must be on a commit with the tag "v2.1.0" (Creating a Github release also creates a tag on the selected branch.)
The version reflected in the executable (tabcmd -v) is stored in a metadata file created by a .doit script:
python -m doit version
Packaging for release is done in a github action and should not need to be done locally.
- build an executable package with pyinstaller.
Note
You can only build an executable for the platform you are running pyinstaller on. The spec for each platform is stored in tabcmd-{platform}.spec and the exact build commands for each platform can be checked in our packaging script.
e.g for Windows
pyinstaller tabcmd-windows.spec --clean --distpath .\dist\windows
produces dist\windows\tabcmd.exe To run the newly created executable, from a console window in the same directory as the file tabcmd.py:
dist\windows\tabcmd.exe --help dist\windows\tabcmd.exe publish --country FR --language FR cookie.twbx
To investigate what's packaged in the executable, use https://pyinstxtractor-web.netlify.app/
-
Create a new Github project release manually: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases
- include a useful list of changes since the last release
- include a clear list of remaining non-server-admin functional gaps
-
This will trigger our github packaging action to run on 3 different OSs.
- write an updated metadata file with the correct version number.
- build the python wheel
- run pyinstaller to create executables
- save the executable as an artifact on that job.
-
Find the artifacts created by this job and manually copy them to the new release.
- manually download. They will all be returned as zips
- unzip the windows.exe and mac.app.tar files and upload those
- do not unzip the linux app, github doesn't like it. upload as tabcmd.zip (Pay attention to what the file type is, github also sends it as a zip if you download with curl etc. TODO: automate workflow with a github action)
-
To trigger publishing to pypi run the manual workflow on main with 'pypi'.
-
When the packages are available on pypi, you can run the 'Pypi smoke test action'. This action will also be run every 24 hours to validate doing pip install. (TODO: automate the after-release trigger)