{"id":16512954,"url":"https://github.com/maximlt/guide_script_to_command_line","last_synced_at":"2026-04-11T22:44:24.473Z","repository":{"id":160860397,"uuid":"203236643","full_name":"maximlt/guide_script_to_command_line","owner":"maximlt","description":"Mini-Guide: Turn a simple Python script into command line tool made available in a conda environment","archived":false,"fork":false,"pushed_at":"2019-08-21T18:14:11.000Z","size":46,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-02T09:17:28.393Z","etag":null,"topics":["command-line-tool","conda","packaging","pth","python-script","windows"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maximlt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-19T19:27:04.000Z","updated_at":"2019-08-21T18:15:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"fda62056-ed7b-4559-94e8-60224fb95934","html_url":"https://github.com/maximlt/guide_script_to_command_line","commit_stats":{"total_commits":11,"total_committers":1,"mean_commits":11.0,"dds":0.0,"last_synced_commit":"0af177a7467638585112adb75618d102ff20f6bc"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/maximlt/guide_script_to_command_line","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximlt%2Fguide_script_to_command_line","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximlt%2Fguide_script_to_command_line/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximlt%2Fguide_script_to_command_line/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximlt%2Fguide_script_to_command_line/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maximlt","download_url":"https://codeload.github.com/maximlt/guide_script_to_command_line/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maximlt%2Fguide_script_to_command_line/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31698152,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T21:17:31.016Z","status":"ssl_error","status_checked_at":"2026-04-11T21:17:24.556Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line-tool","conda","packaging","pth","python-script","windows"],"created_at":"2024-10-11T16:06:45.171Z","updated_at":"2026-04-11T22:44:24.442Z","avatar_url":"https://github.com/maximlt.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mini-Guide: Turn a simple Python script into command line tool made available in a conda environment\n\n## Intro\n\nThis repo contains examples and explanations about **how to turn a simple Python script into a command line script/tool**. While this may seem like a trivial task for seasoned developers, this isn't actually easy for beginners or occasional users of Python. The benefits of learning how to make that conversion can be huge though, making availabe (i.e. from the command line prompt) any custom script, ready to fire!\n\nWe use an examplary script that parses one XML input file whose path is hard-coded and prints some output. It is demonstrated that building a command line script from that initial script can be achieved with minimal effort. But it is also demonstrated that with some additional but limited work, it is possible to get a quite advanced and robust tool.\n\n## General setup\n\nWhile the Zen of Python stipulates that `There should be one-- and preferably only one --obvious way to do it`, it is difficult for beginners/non-developers to come up with an **obvious** way to create that command line script. It is thus interesting to constrain the problem space to come up with a reduced set of solutions. The solutions suggested in this repo work for the following general setup:\n- Windows 10\n- conda 4.7.11 (installed from an Anaconda distribution and updated afterwards)\n- python 3.7.3\n- pip 19.2.2\n\nIt is likely that the solutions would work for a different setup (e.g. Windows 7, different versions of conda/pip/python), however, they haven't been tested so you're on your own unfortunately.\n\n\n## For a standalone script: two solutions with minimal effort\n\n[Here](minimal_effort/README.md) you'll find two simple solutions for creating a command line script from a standalone script.\n\nThe first one requires to create a *setup.py* file and to run `pip install -e .` , the second one requires to manually add a *path configuration file* (.pth) to *Lib\\site-packages*.\n\nNote that both solutions work well with a script that relies on other/helper scripts located in the same directory.\n\n[Solution with minimal effort](minimal_effort/README.md#from-a-standalone-script-to-a-command-line-tool-with-minimal-effort)\n* [Context](minimal_effort/README.md#context)\n* [Read one command line argument](minimal_effort/README.md#read-one-command-line-argument)\n* [Solution 1: *pip install -e .*](minimal_effort/README.md#solution-1-pip-install--e-)\n* [Solution 2: add a *path configuration file*](minimal_effort/README.md#solution-2-add-a-path-configuration-file)\n* [Pros and cons](minimal_effort/README.md#pros-and-cons)\n\n## A slightly more advanced case with a script supported by another local script\n\n[Here](more_advanced/README.md) you'll find a solution for creating a command line script from a script that makes use of another local script (`import somehelperscript`). This solution is slightly more advanced compared to the previous two solutions because we improve the code, its documentation and the way it is distributed. While these small changes are limited compared to what experienced developers could do (see [here](https://vincent.bernat.ch/en/blog/2019-sustainable-python-script) and [here](https://www.madmode.com/2019/python-eng.html)), they make our script more understandable, robust and reusable.\n\n[More Advanced Case](more_advanced/README.md#table-of-content)\n* [Improving the minimal command line tool](more_advanced/README.md#improving-the-minimal-command-line-tool)\n  * [Context](more_advanced/README.md#context)\n  * [Problem](more_advanced/README.md#problem)\n  * [Suggested solution](more_advanced/README.md#suggested-solution)\n    * [What we've done](more_advanced/README.md#what-weve-done)\n    * [New directory structure](more_advanced/README.md#new-directory-structure)\n    * [Modified *parsenote.py* and *xmlhelper.py*](more_advanced/README.md#modified-parsenotepy-and-xmlhelperpy)\n    * [New *\\__init__.py* file](more_advanced/README.md#new-_init_py-file)\n    * [New *setup.py* file](more_advanced/README.md#new-setuppy-file)\n\n[Alternative ways to distribute the scripts](more_advanced/README.md#alternative-ways-to-distribute-the-scripts) are also introduced:\n  * [Use *\\__main__.py*](more_advanced/README.md#use-_main_py)\n  * [Use of the `scripts` keyword in *setup.py*](more_advanced/README.md#use-of-the-scripts-keyword-in-setuppy)\n    * [Solution 1: simple batch file executing Python](more_advanced/README.md#solution-1-simple-batch-file-executing-python)\n    * [Solution 2: `python -x` hack in a batch file](more_advanced/README.md#solution-2-python--x-hack-in-a-batch-file)\n    * [Solution 3: executable .py files](more_advanced/README.md#solution-3-executable-py-files)\n  * [Use of `py_modules` keyword in *setup.py*](more_advanced/README.md#use-of-py_modules-keyword-in-setuppy)\n  * [Going even further](more_advanced/README.md#going-even-further)\n\n## Summary\n\n### The original script :japanese_ogre:...\n\n```python\n\"\"\"Tool to parse an xml note and print it in a reable format.\n\nUsage:\n- Set the path of the input file in INPUTFILE\n- Run `python parsenote.py` from the directory of parsenote.py\n\"\"\"\nfrom lxml import etree\n\nINPUTFILE = r\"..\\inputdata\\inputfile.xml\"\n\ntree = etree.parse(INPUTFILE)\nroot = tree.getroot()\nparsed_xml = {child.tag: child.text for child in root.getchildren()}\nprint(\n    f\"Note from {parsed_xml['author']} ({parsed_xml['date']})\"\n    f\"  --\u003e  {parsed_xml['content']}\"\n)\n```\n\nWe usually execute it with the command `python parsenote.py`\nThe output we get with the [example input file](inputdata/inputfile.xml) is:\n```\nNote from Bob (18-08-2019)  --\u003e  Call Bill\n```\n\n## ...turned into a command line script :wrench: ...\n\n```python\nr\"\"\"Tool to parse an xml note and print it in a reable format.\n\nUsage:\n- Run `python -m parsenote path\\to\\inputfile\n\"\"\"\nimport sys\nfrom lxml import etree\n\ninputfile = sys.argv[1]\ntree = etree.parse(inputfile)\nroot = tree.getroot()\nparsed_xml = {child.tag: child.text for child in root.getchildren()}\nprint(\n    f\"Note from {parsed_xml['author']} ({parsed_xml['date']})\"\n    f\"  --\u003e  {parsed_xml['content']}\"\n)\n\n```\n\n## ...made distributable with a *setup.py* file saved in the same directory :two_men_holding_hands: ...\n\n```python\n# setup.py\nfrom setuptools import setup\n\nsetup(\n    name=\"parsenote-editable\",\n    install_requires=[\"lxml\"],\n)\n```\n\n## ...and a quick way to install it :motorcycle: ...\n\n- Open the Anaconda command prompt and activate the targeted environment with `conda activate envname` (not required if that environment is in the PATH, as it can be for *base* if adding *conda* to PATH was selected during the Anaconda install).\n- `cd path\\to\\parsenote_folder`\n- Execute `pip install -e .` to install the script in editable/develop mode. In this way, changes to *parsenote.py* will be directely reflected so there is no need to `pip install` it again.\n\n## ...so that it can be used super easily :clap: !\n\n- Open the Anaconda command prompt and activate the environment where *parsenote* is installed (not required if that environment is in the PATH, as it can be for *base* if adding *conda* to PATH was selected during the Anaconda install)\n- Execute `python -m parsenote someinputfile`\n\n## But we can go just a little further to improve it :100: ...\n\n### ...by separating the code into two scripts :family: ...\n\n```python\n# parsenote_folder\\parsenote\\xmlhelper.py\n\"\"\"Helper script for parsenote.\n\nAuthor: myname\nChangelog:\n- 0.0.1: xx/xx/xxxx: initial script\n- 0.0.2: xx/xx/xxxx: improved doc\n\"\"\"\nfrom lxml import etree\n\n\ndef parse_xml(xml_file):\n    \"\"\"Helper function to parse a XML file.\n\n    XML file content:\n    \u003cnote\u003e\n    \u003cauthor\u003eBob\u003c/author\u003e\n    \u003cdate\u003e18-08-2019\u003c/date\u003e\n    \u003ccontent\u003eCall Bill\u003c/content\u003e\n    \u003c/note\u003e\n\n    \u003e\u003e\u003e parse_xml(file)\n    {'author': 'Bob', 'date': '18-08-2019', 'content': 'Call Bill'}\n    \"\"\"\n    tree = etree.parse(xml_file)\n    root = tree.getroot()\n    return {child.tag: child.text for child in root.getchildren()}\n```\n### ...refactoring and documenting the main code :book: ...\n\n```python\n# parsenote_folder\\parsenote\\parsenote.py\n\"\"\"Tool to parse an xml note and print it in a reable format.\n\nCommand line usage:\n    - Script executed directly\n        python parsenote.py [-h/--help] xml_file\n    - Script folder in sys.path\n        python -m parsenote [-h/--help] xml_file\n    - Package parsenote installed\n        parsenote [-h/--help] xml_file\n\nOptions:\n    -h/--help: Print this doctring and exit\nArgument:\n    xml_file: path to an xml note file\n\nIt can also be imported and reused by another script.\nLibrary usage:\nimport parsenote; processed_file = parsenote.print_formatted_note(note)\n\nAuthor: myname\nChangelog:\n- 0.0.1: xx/xx/xxxx: initial script\n- 0.0.2: xx/xx/xxxx: added command line ability\n\"\"\"\nimport sys\nfrom . import xmlhelper\n\n\ndef main(args=None):\n    \"\"\"Used when the script is run directly.\n\n    # args: optional list of command line args -\u003e useful for testing cli()\n\n    Return 1 if an error occured, otherwise 0 or None.\n    \"\"\"\n    # Get the command line argument.\n    if args is None:\n        # sys.argv[0] is discarded because it's the module path.\n        args = sys.argv[1:]\n\n    # This script requires only one argument.\n    if len(args) != 1:\n        print(\"Use -h/--help for command line help.\")\n        return 0\n    if args[0] in ['-h', '--help']:\n        # The help is the module docstring.\n        print(__doc__, end=\" \")\n        return 1\n    else:\n        input_xml = args[0]\n\n    # Main logic.\n    parsed_xml = xmlhelper.parse_xml(input_xml)\n    print_formatted_note(parsed_xml)\n\n\ndef print_formatted_note(note):\n    \"\"\"Print a note in a nicely formatted way.\n\n    \u003e\u003e\u003e note = dict(author='Bob', date='18-08-2019', content='Call Bill')\n    \u003e\u003e\u003e print_formatted_note(note)\n    Note from Bob (18-08-2019)  --\u003e  Call Bill\n    \"\"\"\n    print(\n        f\"Note from {note['author']} ({note['date']})\"\n        f\"  --\u003e  {note['content']}\"\n    )\n\n\n# True only when the script is executed directly, not when imported.\nif __name__ == \"__main__\":\n    sys.exit(main())\n```\n\n### ...turning it into a package :package: ...\n\n```python\n# parsenote_folder\\parsenote\\__init__.py\n\n# Make the modules discovarable when importing the package\n# with `import parsenote`\nfrom . import parsenote, xmlhelper\n# Add the modules parsenote and xmlhelper to the namespace\n# when doing `from parsenote import *`\n__all__ = [\"parsenote\", \"xmlhelper\"]\n```\n\n### ...distributing it properly :mailbox_with_mail: ...\n\n```python\n# parsenote_folder\\setup.py\nfrom setuptools import setup\n\nsetup(\n    name=\"parsenote\",  # package name\n    version=\"0.0.2\",  # keep it manually updated\n    description=\"Tool to parse an xml note and print it in a reable format.\",\n    python_requires=\"\u003e=3.7\",  # make sure the right version of python is used\n    install_requires=[\"lxml\u003e=4.4\"],  # make sure it's installed\n    packages=[\"parsenote\"],  # point to the package folder\n    entry_points={\n        \"console_scripts\": [\n            \"parsenote=parsenote.parsenote:main\"\n            # parsenote is now a command available in the environment\n            # where it's installed.\n            # it runs the main() function in parsenote.py\n        ]\n    },\n)\n```\n\n## ...and be proud of ourself :muscle: !\n\nNow our code is:\n- better documented\n- easier to reuse both as a command line tool (better interactive doc) and a library (it can be imported)\n- easier to distribute (we're not far from being able to upload it on PyPi!)\n\n## But! There's still a long way to go :running: ...\n\nWe could add:\n- some sort of input data validation\n- tests\n- and millions of other things to make it an advanced package\n\nIf this piece of code isn't something too serious (let's say we use it instead of firing Excel and doing some horrible things manually), **this is already a great job**!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaximlt%2Fguide_script_to_command_line","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaximlt%2Fguide_script_to_command_line","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaximlt%2Fguide_script_to_command_line/lists"}