{"id":34095868,"url":"https://github.com/felix-martel/pydanclick","last_synced_at":"2026-04-07T15:31:23.282Z","repository":{"id":223043409,"uuid":"755951604","full_name":"felix-martel/pydanclick","owner":"felix-martel","description":"Add click options from a Pydantic model","archived":false,"fork":false,"pushed_at":"2025-02-26T07:38:52.000Z","size":566,"stargazers_count":57,"open_issues_count":20,"forks_count":11,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-15T02:33:29.367Z","etag":null,"topics":["argparse","cli","click","pydantic","python"],"latest_commit_sha":null,"homepage":"https://felix-martel.github.io/pydanclick/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/felix-martel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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":"2024-02-11T15:08:50.000Z","updated_at":"2026-02-11T19:16:37.000Z","dependencies_parsed_at":"2024-03-14T07:37:06.631Z","dependency_job_id":"6d3cf69d-9230-42da-a59e-c081ba352cad","html_url":"https://github.com/felix-martel/pydanclick","commit_stats":null,"previous_names":["felix-martel/pydanclick"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/felix-martel/pydanclick","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felix-martel%2Fpydanclick","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felix-martel%2Fpydanclick/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felix-martel%2Fpydanclick/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felix-martel%2Fpydanclick/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/felix-martel","download_url":"https://codeload.github.com/felix-martel/pydanclick/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felix-martel%2Fpydanclick/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31518406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["argparse","cli","click","pydantic","python"],"created_at":"2025-12-14T15:21:12.470Z","updated_at":"2026-04-07T15:31:23.271Z","avatar_url":"https://github.com/felix-martel.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pydanclick\n\n\u003c!-- these tags are used by `pymarkdownx.snippets` to include some parts of the readme in the Mkdocs documentation --\u003e\n\u003c!-- --8\u003c-- [start:overview] --\u003e\n\n![Release](https://img.shields.io/github/v/release/felix-martel/pydanclick)\n![Build status](https://img.shields.io/github/actions/workflow/status/felix-martel/pydanclick/main.yml?branch=main)\n![codecov](https://codecov.io/gh/felix-martel/pydanclick/branch/main/graph/badge.svg)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pydanclick)\n![License](https://img.shields.io/github/license/felix-martel/pydanclick)\n\nUse Pydantic models as Click options.\n\n## Getting started\n\nInstall:\n\n```sh\npip install pydanclick\n```\n\nLet's assume you have a Pydantic model:\n\n```python\nclass TrainingConfig(BaseModel):\n    epochs: int\n    lr: Annotated[float, Field(gt=0)] = 1e-4\n    early_stopping: bool = False\n```\n\nAdd all its fields as options in your Click command:\n\n```python\nfrom pydanclick import from_pydantic\n\n@click.command()\n@from_pydantic(TrainingConfig)\ndef cli(training_config: TrainingConfig):\n    # Here, we receive an already validated Pydantic object.\n    click.echo(training_config.model_dump_json(indent=2))\n```\n\n```shell\n~ python my_app.py --help\nUsage: my_app.py [OPTIONS]\n\nOptions:\n  --early-stopping / --no-early-stopping\n  --lr FLOAT RANGE                [x\u003e0]\n  --epochs INTEGER                [required]\n  --help                          Show this message and exit.\n```\n\n\u003c!-- --8\u003c-- [end:overview] --\u003e\n\n- Take a tour of the features below\n- Find examples in the `examples/` folder\n- Read the **[📚 Documentation](https://felix-martel.github.io/pydanclick/)**\n\n\u003c!-- --8\u003c-- [start:features] --\u003e\n\n## Features\n\n### Use native Click types\n\nThe following types are converted to native Click types:\n\n| Pydantic type                            | Converted to         |\n| :--------------------------------------- | :------------------- |\n| `bool`                                   | `click.BOOL`         |\n| `str`                                    | `click.STRING`       |\n| `int`                                    | `click.INT`          |\n| `float`                                  | `click.FLOAT`        |\n| `Annotated[int, Field(lt=..., ge=...)`   | `click.IntRange()`   |\n| `Annotated[float, Field(lt=..., ge=...)` | `click.FloatRange()` |\n| `pathlib.Path`                           | `click.Path()`       |\n| `uuid.UUID`                              | `click.UUID`         |\n| `datetime.datetime`, `datetime.date`     | `click.DateTime()`   |\n| `Literal`                                | `click.Choice`       |\n\nComplex container types such as lists or dicts are also supported: they must be passed as JSON strings, and will be validated through Pydantic `TypeAdapter.validate_json` method:\n\n```shell\n--arg1 '[1, 2, 3]' --arg2 '{\"a\": bool, \"b\": false}'\n```\n\nIn any case, Pydantic validation will run during model instantiation.\n\n### Add multiple models\n\n`pydanclick.from_pydantic` can be called several times with different models.\n\nUse the `prefix` parameter to namespace the options from different models:\n\n```python\nclass Foo(BaseModel):\n    a: str = \"\"\n    b: str = \"\"\n\nclass Bar(BaseModel):\n    x: int = 0\n    y: int = 0\n\n@click.command()\n@from_pydantic(Foo, prefix=\"foo\")\n@from_pydantic(Bar, prefix=\"bar\")\ndef cli(foo: Foo, bar: Bar):\n    pass\n```\n\nwill give:\n\n```\n~ python cli.py\nUsage: cli.py [OPTIONS]\n\nOptions:\n  --foo-a TEXT\n  --foo-b TEXT\n  --bar-x INTEGER\n  --bar-y INTEGER\n  --help           Show this message and exit.\n```\n\n### Add regular options and arguments\n\n`pydanclick` can be used alongside regular options and arguments:\n\n```python\n@click.command()\n@click.argument(\"arg\")\n@click.option(\"--option\")\n@from_pydantic(Foo)\ndef cli(arg, option, foo: Foo):\n    pass\n```\n\nwill give:\n\n```\n~ python cli.py\nUsage: cli.py [OPTIONS] ARG\n\nOptions:\n  --option TEXT\n  --a TEXT\n  --b TEXT\n  --help         Show this message and exit.\n```\n\nSpecify a custom variable name for the instantiated model with the same syntax as a regular Click option:\n\n```python\n@click.command()\n@from_pydantic(\"some_name\", Foo)\ndef cli(some_name: Foo):\n    pass\n```\n\n### Document options\n\nOptions added with `pydanclick.from_pydantic` will appear in the command help page.\n\n**From docstrings**: if `griffe` is installed, model docstring will be parsed and the _Attributes_ section will be used to document options automatically (you can use `pip install pydanclick[griffe]` to install it). Use `docstring_tyle` to choose between `google`, `numpy` and `sphinx` coding style. Disable docstring parsing by passing `parse_docstring=False`.\n\n**From field description**: `pydanclick` supports the [`Field(description=...)`](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.Field) syntax from Pydantic. If specified, it will take precedence over the docstring description.\n\n**Explicitly**: you can always specify a custom help string for a given field by using `extra_options={\"my_field\": {\"help\": \"my help string\"}}` where `my_field` is the name of your field.\n\nHere are these three methods in action:\n\n```python\nclass Baz(BaseModel):\n    \"\"\"Some demo model.\n\n    Attributes:\n        a: this comes from the docstring (requires griffe)\n    \"\"\"\n\n    a: int = 0\n    b: Annotated[int, Field(description=\"this comes from the field description\")] = 0\n    c: int = 0\n\n\n@click.command()\n@from_pydantic(Baz, extra_options={\"c\": {\"help\": \"this comes from the `extra_options`\"}})\ndef cli(baz: Baz):\n    pass\n```\n\nwill give:\n\n```\n~ python cli.py --help\nUsage: cli.py [OPTIONS]\n\nOptions:\n  --a INTEGER  this comes from the docstring (requires griffe)\n  --b INTEGER  this comes from the field description\n  --c INTEGER  this comes from the `extra_options`\n  --help       Show this message and exit.\n```\n\n### Customize option names\n\nSpecify option names with `rename` and short option names with `shorten`:\n\n```python\n@click.command()\n@from_pydantic(Foo, rename={\"a\": \"--alpha\", \"b\": \"--beta\"}, shorten={\"a\": \"-A\", \"b\": \"-B\"})\ndef cli(foo: Foo):\n    pass\n```\n\nwill give:\n\n```\n~ python cli.py --help\nUsage: cli.py [OPTIONS]\n\nOptions:\n  -A, --alpha TEXT\n  -B, --beta TEXT\n  --help            Show this message and exit.\n```\n\nNote that `prefix` won't be prepended to option names passed with `rename` or `shorten`.\n\n### Pass extra parameters\n\nUse `extra_options` to pass extra parameters to `click.option` for a given field.\n\nFor example, in the following code, the user will be prompted for the value of `a`:\n\n```python\n@click.command()\n@from_pydantic(Foo, extra_options={\"a\": {\"prompt\": True}})\ndef cli(foo: Foo):\n    pass\n```\n\n### Add nested models\n\nNested Pydantic models are supported, with arbitrary nesting level.\nOption names will be built by joining all parent names and the field names itself with dashes.\n\n```python\nclass Left(BaseModel):\n    x: int\n\nclass Right(BaseModel):\n    x: int\n\nclass Root(BaseModel):\n    left: Left\n    right: Right\n    x: int\n\n@click.command()\n@from_pydantic(Root)\ndef cli(root: Root):\n    pass\n```\n\nwill give:\n\n```shell\n~ python cli.py --help\nUsage: cli.py [OPTIONS]\n\nOptions:\n  --left-x INTEGER   [required]\n  --right-x INTEGER  [required]\n  --x INTEGER        [required]\n  --help             Show this message and exit.\n```\n\nTo use `rename`, `shorten`, `exclude`, `extra_options` with a nested field, use its _dotted name_, e.g. `left.x` or `right.x`. Note that the alias of a field will apply to all its sub-fields:\n\n```python\n@click.command()\n@from_pydantic(Root, rename={\"right\": \"--the-other-left\"})\ndef cli(root: Root):\n    pass\n```\n\nwill give:\n\n```shell\n~ python cli.py --help\nUsage: cli.py [OPTIONS]\n\nOptions:\n  --left-x INTEGER            [required]\n  --the-other-left-x INTEGER  [required]\n  --x INTEGER                 [required]\n  --help                      Show this message and exit.\n```\n\n### Unpacking (experimental)\n\n_Unpacking_ provides a simpler API when working with list of submodels.\n\nConsider the following example:\n\n```python\nclass Author:\n    name: str\n    primary: bool = False\n\n\nclass Book:\n    title: str\n    authors: list[Author]\n\n@click.command()\n@from_pydantic(Book, unpack_list=True)\ndef cli(book: Book):\n    pass\n```\n\nBy default, this would create two command-line arguments `--title` and `--authors`. Since `authors` has a complex type, it should be passed as a JSON string (e.g. `--authors '[{\"authors\": {\"name\": \"Alice\", \"primary\": true}, {\"name\": \"Bob\"}]'). Using `unpacked_list`will instead \"unpack\" the nested field`name`into the main namespace: this new argument is called`--authors-name` and can be specified multiple time, for example:\n\n```shell\npython cli.py --authors-name Alice --authors-primary --authors-name Bob\n```\n\nwould create:\n\n```python\nBook(authors=[Author(name=\"Alice\", primary=True), Author(name=\"Bob\")])\n```\n\nNote that you must always specify objects with optional arguments _before_ objects without them. For example, the following command would make `Bob` the primary author, not `Alice`:\n\n```shell\npython cli.py --authors-name Bob --authors-name Alice --authors-primary\n```\n\n_(Why? Because under the hood, arguments are collected per field `{\"name\": [Bob, Alice], \"primary\": [True]}`, and relative placement between fields cannot be accessed.)_\n\nWhen in doubt, you can simply specify all arguments:\n\n```shell\npython cli.py --authors-name Bob --no-authors-primary --authors-name Alice --authors-primary\n```\n\nThis API is experimental and will not work in complex cases (deeply nested lists, lists of union, and much more).\nSee issue [#20](https://github.com/felix-martel/pydanclick/issues/20) for context and details.\n\n\u003c!-- --8\u003c-- [end:features] --\u003e\n\n## API Reference\n\n**Functions:**\n\n- [**from_pydantic**](#pydanclick.main.from_pydantic) – Decorator to add fields from a Pydantic model as options to a Click command.\n\n### pydanclick.from_pydantic\n\n```python\nfrom_pydantic(\n    __var_or_model,\n    model=None,\n    *,\n    exclude=(),\n    rename=None,\n    shorten=None,\n    prefix=None,\n    parse_docstring=True,\n    docstring_style=\"google\",\n    extra_options=None\n)\n```\n\nDecorator to add fields from a Pydantic model as options to a Click command.\n\n**Parameters:**\n\n- **\\_\\_var_or_model** (\u003ccode\u003e[Union](#typing.Union)\\[[str](#str), [Type](#typing.Type)\\[[BaseModel](#pydantic.BaseModel)\\]\\]\u003c/code\u003e) – name of the variable that will receive the Pydantic model in the decorated function\n- **model** (\u003ccode\u003e[Optional](#typing.Optional)\\[[Type](#typing.Type)\\[[BaseModel](#pydantic.BaseModel)\\]\\]\u003c/code\u003e) – Pydantic model\n- **exclude** (\u003ccode\u003e[Sequence](#typing.Sequence)\\[[str](#str)\\]\u003c/code\u003e) – field names that won't be added to the command\n- **rename** (\u003ccode\u003e[Optional](#typing.Optional)\\[[Dict](#typing.Dict)\\[[str](#str), [str](#str)\\]\\]\u003c/code\u003e) – a mapping from field names to command line option names (this will override any prefix). Option names\n  must start with two dashes\n- **shorten** (\u003ccode\u003e[Optional](#typing.Optional)\\[[Dict](#typing.Dict)\\[[str](#str), [str](#str)\\]\\]\u003c/code\u003e) – a mapping from field names to short command line option names. Option names must start with one dash\n- **prefix** (\u003ccode\u003e[Optional](#typing.Optional)\\[[str](#str)\\]\u003c/code\u003e) – a prefix to add to option names (without any dash)\n- **parse_docstring** (\u003ccode\u003e[bool](#bool)\u003c/code\u003e) – if True and `griffe` is installed, parse the docstring of the Pydantic model and pass argument\n  documentation to the Click `help` option\n- **docstring_style** (\u003ccode\u003e[Literal](#typing.Literal)\\['google', 'numpy', 'sphinx'\\]\u003c/code\u003e) – style of the docstring (`google`, `numpy` or `sphinx`). Ignored if `parse_docstring` is False\n- **extra_options** (\u003ccode\u003e[Optional](#typing.Optional)\\[[Dict](#typing.Dict)\\[[str](#str), [\\_ParameterKwargs](#pydanclick.types._ParameterKwargs)\\]\\]\u003c/code\u003e) – a mapping from field names to a dictionary of options passed to the `click.option()` function\n\n**Returns:**\n\n- \u003ccode\u003e[Callable](#typing.Callable)\\[\\[[Callable](#typing.Callable)\\[..., [T](#pydanclick.main.T)\\]\\], [Callable](#typing.Callable)\\[..., [T](#pydanclick.main.T)\\]\\]\u003c/code\u003e – a decorator that adds options to a function\n\n## Contributing\n\nInstall the environment and the pre-commit hooks with\n\n```bash\nmake install\n```\n\nRun tests with:\n\n```shell\npytest\n```\n\n\u003c!--  --8\u003c-- [start:limitations] --\u003e\n\n## Limitations\n\n`pydanclick` doesn't support (yet!):\n\n- Pydantic v1\n- converting fields to arguments, instead of options\n- fields annotated with union of Pydantic models can only be used with JSON inputs, instead of properly merging all sub-fields\n- custom argument validators\n\nOther missing features:\n\n- Reading model from file\n- Specifying all field-specific options directly in the Pydantic model (would allow easier reuse)\n- Most Click features should be supported out-of-the-box through the `extra_options` parameter. However, most of them aren't tested\n- Click and Pydantic both include validation logic. In particular, Click support custom `ParamType`, validation callbacks and `BadParameter` errors: it's not clear if we want to fully rely on Pydantic or on Click or on a mixture of both\n- populating Pydantic fields from existing options or arguments (combined with `exclude`, it will provide a complete escape hatch to bypass Pydantclick when needed)\n- attaching Pydanclick arguments directly to the model class, to avoid duplication when re-using a model in multiple commands\n\n\u003c!--  --8\u003c-- [end:limitations] --\u003e\n\n---\n\nRepository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelix-martel%2Fpydanclick","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffelix-martel%2Fpydanclick","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelix-martel%2Fpydanclick/lists"}