{ "info": { "author": "M. Kocher", "author_email": "michael.kocher@me.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# Pydantic Commandline Tool Interface\n\nTurn Pydantic defined Data Models into CLI Tools!\n\n\n## Features\n\n1. Schema driven interfaces built on top of [Pydantic](https://github.com/samuelcolvin/pydantic)\n2. Validation is performed in a single location as defined by Pydantic's validation model\n3. CLI parsing is only structurally validating that the args or optional arguments are provided\n4. Clear interface between the CLI and your application code\n5. Easy to test (due to reasons defined above)\n\n\n## Quick Start\n\n\nTo create a commandline tool that takes an input file and max number of records to process as positional arguments:\n\n```bash\nmy-tool /path/to/file.txt 1234\n```\n\nThis requires two components.\n\n- Create Pydantic Data Model of type `T` \n- write a function that takes an instance of `T` and returns the exit code (e.g., 0 for success, non-zero for failure).\n- pass the `T` into to the `to_runner` function, or the `run_and_exit`\n\nExplicit example show below. \n\n```python\nimport sys\n\nfrom pydantic import BaseModel\nfrom pydantic_cli import run_and_exit, to_runner\n\nclass MinOptions(BaseModel):\n input_file: str\n max_records: int\n\n\ndef example_runner(opts: MinOptions) -> int:\n print(f\"Mock example running with options {opts}\")\n return 0\n\nif __name__ == '__main__':\n # to_runner will return a function that takes the args list to run and \n # will return an integer exit code\n sys.exit(to_runner(MinOptions, example_runner, version='0.1.0')(sys.argv[1:]))\n\n```\n\nOr to implicitly use `sys.argv[1:]`, call can leverage `run_and_exit` (`to_runner` is also useful for testing).\n\n```python\nif __name__ == '__main__':\n run_and_exit(MinOptions, example_runner, description=\"My Tool Description\", version='0.1.0')\n\n```\n\nIf the data model has default values, the commandline argument with be optional and the CLI arg will be prefixed with `--'.\n\nFor example:\n\n```python\nfrom pydantic import BaseModel\nfrom pydantic_cli import run_and_exit\n\nclass MinOptions(BaseModel):\n input_file: str\n max_records: int = 10\n\n\ndef example_runner(opts: MinOptions) -> int:\n print(f\"Mock example running with options {opts}\")\n return 0\n\n\nif __name__ == '__main__':\n run_and_exit(MinOptions, example_runner, description=\"My Tool Description\", version='0.1.0')\n\n```\n\nWill create a tool with `my-tool /path/to/input.txt --max_records 1234`\n\n```bash\nmy-tool /path/to/input.txt --max_records 1234\n```\n\nwith `--max_records` being optional to the commandline interface.\n\n\n**WARNING**: Boolean values must be communicated explicitly (e.g., `--run_training True`)\n\n\nThe `--help` is quite minimal (due to the lack of metadata), however, verbosely named arguments can often be good enough to communicate the intent of the commandline interface.\n\n\nFor customization of the CLI args, such as max number of records is `-m 1234` in the above example, there are two approaches.\n\n- The first is the \"quick\" method that is a minor change to the `Config` of the Pydantic Data model. \n- The second \"schema\" method is to define the metadata in the [`Schema` model in Pydantic](https://pydantic-docs.helpmanual.io/#schema-creation) \n\n\n### Quick Model for Customization\n\nWe're going to change the usage from `my-tool /path/to/file.txt 1234` to `my-tool /path/to/file.txt -m 1234` .\n\nThis only requires adding `CLI_EXTRA_OPTIONS` to the Pydantic `Config`.\n\n```python\nfrom pydantic import BaseModel\n\nclass MinOptions(BaseModel):\n\n class Config:\n CLI_EXTRA_OPTIONS = {'max_records': ('-m', )}\n\n input_file: str\n max_records: int = 10\n\n```\n\nYou can also override the \"long\" argument. However, **note this is starting to add a new layer of indirection** on top of the schema. (e.g., 'max_records' to '--max-records') that may or may not be useful.\n\n\n```python\nfrom pydantic import BaseModel\n\nclass MinOptions(BaseModel):\n\n class Config:\n CLI_EXTRA_OPTIONS = {'max_records': ('-m', '--max-records')}\n\n input_file: str\n max_records: int = 10\n\n```\n\n\n### Schema Approach\n\n\n```python\nfrom pydantic import BaseModel, Schema\n\n\nclass Options(BaseModel):\n\n class Config:\n validate_all = True\n validate_assignment = True\n\n input_file: str = Schema(\n ..., # this implicitly means required=True\n title=\"Input File\",\n description=\"Path to the input file\",\n required=True,\n extras={\"cli\": ('-f', '--input-file')}\n )\n\n max_records: int = Schema(\n 123,\n title=\"Max Records\",\n description=\"Max number of records to process\",\n gt=0,\n extras={'cli': ('-m', '--max-records')}\n )\n\n```\n\n\n## Hooks into the CLI Execution\n\n- exception handler\n- epilogue handler\n\nBoth of these cases can be customized to by passing in a function to the running/execution method. \n\n\nThe exception handler should handle any logging or writing to stderr as well as mapping the specific exception to non-zero integer exit code. \n\nFor example: \n\n```python\nimport sys\n\nfrom pydantic_cli import run_and_exit\n\n\ndef custom_exception_handler(ex) -> int:\n exception_map = dict(ValueError=3, IOError=7)\n sys.stderr.write(str(ex))\n exit_code = exception_map.get(ex.__class__, 1)\n return exit_code\n\n\nif __name__ == '__main__':\n run_and_exit(MinOptions, example_runner, exception_handler=custom_exception_handler)\n```\n\nSimilarly, the post execution hook can be called. This function is `Callable[[int, float], None]` that is the `exit code` and `program runtime` in sec as input.\n\n\n```python\nimport sys\n\nfrom pydantic_cli import run_and_exit\n\n\ndef custom_epilogue_handler(exit_code: int, run_time_sec:float):\n m = \"Success\" if exit_code else \"Failed\"\n msg = f\"Completed running ({m}) in {run_time_sec:.2f} sec\"\n print(msg)\n\n\nif __name__ == '__main__':\n run_and_exit(MinOptions, example_runner, epilogue_handler=custom_epilogue_handler)\n\n```\n\n## SubParsers\n\nDefining a subparser to your commandline tool is enabled by creating a container `SubParser` dict and calling `run_sp_and_exit`\n\n\n```python\nimport typing as T\nfrom pydantic import BaseModel\nfrom pydantic.schema import UrlStr\n\n\nfrom pydantic_cli.examples import ConfigDefaults\nfrom pydantic_cli import run_sp_and_exit, SubParser\n\n\nclass AlphaOptions(BaseModel):\n\n class Config(ConfigDefaults):\n CLI_EXTRA_OPTIONS = {'max_records': ('-m', '--max-records')}\n\n input_file: str\n max_records: int = 10\n\n\nclass BetaOptions(BaseModel):\n\n class Config(ConfigDefaults):\n CLI_EXTRA_OPTIONS = {'url': ('-u', '--url'),\n 'num_retries': ('-n', '--num-retries')}\n\n url: UrlStr\n num_retries: int = 3\n\n\ndef printer_runner(opts: T.Any):\n print(f\"Mock example running with {opts}\")\n return 0\n\n\ndef to_runner(sx):\n def example_runner(opts) -> int:\n print(f\"Mock {sx} example running with {opts}\")\n return 0\n return example_runner\n\n\ndef to_subparser_example():\n\n return {\n 'alpha': SubParser(AlphaOptions, to_runner(\"Alpha\"), \"Alpha SP Description\"),\n 'beta': SubParser(BetaOptions, to_runner(\"Beta\"), \"Beta SP Description\")}\n\n\nif __name__ == \"__main__\":\n run_sp_and_exit(to_subparser_example(), description=__doc__, version='0.1.0')\n\n```\n\n# Limitations\n\n- Currently **only support flat \"simple\" types** (e.g., floats, ints, strings, boolean). There's no current support for `List[T]` or nested dicts.\n- Leverages [argparse](https://docs.python.org/3/library/argparse.html#module-argparse) underneath the hood and argparse is a bit thorny of an API to build on top of.\n\n\n### To Improve\n\n- Better type descriptions in help\n- Better communication of required \"options\" in help\n- Add load from JSON file\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/mpkocher/pydantic-cli", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "pydantic-cli", "package_url": "https://pypi.org/project/pydantic-cli/", "platform": "", "project_url": "https://pypi.org/project/pydantic-cli/", "project_urls": { "Homepage": "http://github.com/mpkocher/pydantic-cli" }, "release_url": "https://pypi.org/project/pydantic-cli/0.4.0/", "requires_dist": null, "requires_python": "", "summary": "Turn Pydantic defined Data Models into CLI Tools", "version": "0.4.0" }, "last_serial": 5580822, "releases": { "0.2.0": [ { "comment_text": "", "digests": { "md5": "b8bbf17b53f66eb0a334f2092962a619", "sha256": "fccc1f78e2699dfbddf750256a9b35daf5203e41cbaf60268cac8341d94bc911" }, "downloads": -1, "filename": "pydantic_cli-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "b8bbf17b53f66eb0a334f2092962a619", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 6173, "upload_time": "2019-07-24T05:15:01", "url": "https://files.pythonhosted.org/packages/ae/f8/f7e3a1d726e50d18ef509bd22fac21b072a84d3a350fb2c5d7027c5c22ed/pydantic_cli-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4bbb1cdd5a6e1f462e5bad0f214e6290", "sha256": "1ac6ed28cbd384b42550ecd31e197f29f81d0a0fe36c2896fdfb9a60f5c35f00" }, "downloads": -1, "filename": "pydantic_cli-0.2.0.tar.gz", "has_sig": false, "md5_digest": "4bbb1cdd5a6e1f462e5bad0f214e6290", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5410, "upload_time": "2019-07-24T05:15:03", "url": "https://files.pythonhosted.org/packages/23/88/ee7d95ccae033c264ea1f49b643019825a9a9a7540664aaa0e2a99ea5d92/pydantic_cli-0.2.0.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "85934490fb1d367c0cfbeb9920cba538", "sha256": "ff4edbf70f525077dab25cec1fe26fe0f08c76ade826d7a87fe9972d5e10e4db" }, "downloads": -1, "filename": "pydantic_cli-0.3.0-py3-none-any.whl", "has_sig": false, "md5_digest": "85934490fb1d367c0cfbeb9920cba538", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 6157, "upload_time": "2019-07-24T05:18:07", "url": "https://files.pythonhosted.org/packages/d0/26/9b4490d60c5991e98c9d1e67e797d55e10ff9fdaa75a8a4582ed3a1d61be/pydantic_cli-0.3.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1942bb7de1e87284ceae496d5e881f49", "sha256": "c6bb59bfe67189fde4e60863deea34c9a040a987f646b502dde58473feca38df" }, "downloads": -1, "filename": "pydantic_cli-0.3.0.tar.gz", "has_sig": false, "md5_digest": "1942bb7de1e87284ceae496d5e881f49", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5395, "upload_time": "2019-07-24T05:18:09", "url": "https://files.pythonhosted.org/packages/b3/10/d17c49c07bca8303b6f02036b2b70f3547c79a4adfb252c5295c189f22f1/pydantic_cli-0.3.0.tar.gz" } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "25bd869826500783f43d3b45391118af", "sha256": "4d3858d98452e86dc805814a358680aee79d37709e65ae260d2dd217c45d9049" }, "downloads": -1, "filename": "pydantic_cli-0.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "25bd869826500783f43d3b45391118af", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 7250, "upload_time": "2019-07-25T02:13:04", "url": "https://files.pythonhosted.org/packages/7e/28/4666e5300aa961503af13dd0b9e6f0ac763bdfb1505c828440ff3e1559ca/pydantic_cli-0.4.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f96e21a0ada3cb54a8c6b54e2a8f4059", "sha256": "3af38fa489adc32b3b8e7f84873543b5c53a3069e47c7a53d48e32b76d2b0107" }, "downloads": -1, "filename": "pydantic_cli-0.4.0.tar.gz", "has_sig": false, "md5_digest": "f96e21a0ada3cb54a8c6b54e2a8f4059", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6647, "upload_time": "2019-07-25T02:13:06", "url": "https://files.pythonhosted.org/packages/23/ad/3935cab35b92a29b099e30b2cb450b08085968b711b978b49cb181684f1b/pydantic_cli-0.4.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "25bd869826500783f43d3b45391118af", "sha256": "4d3858d98452e86dc805814a358680aee79d37709e65ae260d2dd217c45d9049" }, "downloads": -1, "filename": "pydantic_cli-0.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "25bd869826500783f43d3b45391118af", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 7250, "upload_time": "2019-07-25T02:13:04", "url": "https://files.pythonhosted.org/packages/7e/28/4666e5300aa961503af13dd0b9e6f0ac763bdfb1505c828440ff3e1559ca/pydantic_cli-0.4.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f96e21a0ada3cb54a8c6b54e2a8f4059", "sha256": "3af38fa489adc32b3b8e7f84873543b5c53a3069e47c7a53d48e32b76d2b0107" }, "downloads": -1, "filename": "pydantic_cli-0.4.0.tar.gz", "has_sig": false, "md5_digest": "f96e21a0ada3cb54a8c6b54e2a8f4059", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 6647, "upload_time": "2019-07-25T02:13:06", "url": "https://files.pythonhosted.org/packages/23/ad/3935cab35b92a29b099e30b2cb450b08085968b711b978b49cb181684f1b/pydantic_cli-0.4.0.tar.gz" } ] }