dtyper


Namedtyper JSON
Version 2.5.1 PyPI version JSON
download
home_pagehttps://github.com/rec/dtyper
Summary🗝 Fix and improve `typer` 🗝
upload_time2024-01-25 10:42:54
maintainer
docs_urlNone
authorTom Ritchford
requires_python>=3.8
license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI
coveralls test coverage No coveralls.
            # 🗝 Fix and improve `typer` 🗝

### What is `dtyper`, in one sentence?

Using `import dtyper as typer` instead of `import typer` would make your
`typer.command`s directly callable.

(There's also a neat way to make dataclasses from typer commands, but that
would be two sentences.)

### Why `dtyper`?

[`typer`](https://typer.tiangolo.com/) is a famously clear and useful system
for writing Python CLIs but it has two issues that people seem to run into a
lot:

1. You can't call the `typer.command` functions it creates directly because
they have the wrong defaults.

2. As you add more arguments to your CLI, there is no easy way to break up the
code sitting in one file without passing around long, verbose parameter lists.

`dtyper` is a tiny, single-file library that adds to an existing installation
of `typer` to solve these two problems without changing existing code at all.

* `dtyper.command` executes `typer.command` then fixes the defaults.

* `dtyper.function` decorates an existing `typer.command` to have correct
  defaults.

* `dtyper.dataclass` automatically makes a `dataclass` from
   a `typer.command`.

### How to use `dtyper`?

Install as usual with `poetry add dtyper`, `pip install dtyper`, or your
favorite package manager.

`dtyper` is a drop-in replacement for `typer` - it copies all `typer`s
properties - so you can even write

    import dtyper as typer

to experiment with it before deciding.

`dtyper` has two new functions that `typer` doesn't, and overrides a `typer`
class:

* `@dtyper.function` is a decorator that takes a `typer` command and returns
  a callable function with the correct defaults.  It is unncessary if you use
  `dtyper.Typer` (below)

* `@dtyper.dataclass` is a decorator that takes an existing `typer` or `dtyper`
command and makes a `dataclass` from it.

* `dtyper.Typer`is a class identical to `typer.Typer`, except it fixes
  `Typer.command` functions so you can call them directly.

None of the `typer` functionality is changed to the slightest degree - adding
`dtyper` will not affect how your command line program runs at all.

## Example 1: using `dtyper` instead of `typer`

    from dtyper import Argument, Option, Typer

    app = Typer()

    @app.command(help='test')
    def get_keys(
        bucket: str = Argument(
            'buck', help='The bucket to use'
        ),

        keys: bool = Option(
            False, help='The keys to download'
        ),
    ):
        print(bucket, keys)

You can call `get_keys()` from other code and get the right defaults.

Without regular `typer`, you sometimes get a `typer.Argument` or
`typer.Option` in place of an expected `str` or `bool`.

### Example 2: a simple `dtyper.dataclass`

Here's a simple CLI in one Python file with two `Argument`s and an `Option`:

    @command(help='test')
    def get_keys(
        bucket: str = Argument(
            ..., help='The bucket to use'
        ),

        keys: str = Argument(
            'keys', help='The keys to download'
        ),

        pid: Optional[int] = Option(
            None, '--pid', '-p', help='process id, or None for this process'
        ),
    ):
        get_keys = GetKeys(**locals())
        print(get_keys.run())


    @dtyper.dataclass(get_keys)
    class GetKeys:
        site = 'https://www.some-websijt.nl'

        def run(self):
            return self.url, self.keys, self.pid

        def __post_init__(self):
            self.pid = self.pid or os.getpid()

        def url(self):
           return f'{self.site}/{self.url}/{self.pid}'


### Example: splitting a large `typer.command` into multiple files

Real world CLIs frequently have dozens if not hundreds of commands, with
hundreds if not thousands of options, arguments, settings or command line
flags.

The natural structure for this is the "big ball of mud", a popular anti-pattern
known to cause misery and suffering to maintainers.

`dtyper.dataclass` can split the user-facing definition of the API from its
implementation and then split that implementation over multiple files in a
natural and convenient way.

The example has three Python files.

`interface.py` contains the Typer CLI definitions for this command.

    @command(help='test')
    def big_calc(
        bucket: str = Argument(
            ..., help='The bucket to use'
        ),
        more: str = Argument(
            '', help='More information'
        ),
        enable_something: boolean = Option(
            False, help='Turn on one of many important parameters'
        ),
        # [dozens of parameters here]
    ):
        d = dict(locals())  # Capture all the command line arguments as a dict

        from .big_calc import BigCalc  # Lazy import to avoid a cycle

        bc = BigCalc(**d)
        bc.run()


`big_calc.py` contains the `dtyper.dataclass` implementation

    from .interface import big_calc
    from . import helper
    import dtyper


    @dtyper.dataclass(big_calc)
    class BigCalc:
        def run(self):
           # Each argument in `big_calc` becomes a dataclass field
           print(self.bucket, self.more)
           print(self)  # dataclass gives you a nice output of all fields

           if helper.huge_thing(self) and self._etc():
              self.stuff()
              helper.more_stuff(self)
              ...

        def _etc(self):
           ...
           # Dozens more methods here perhaps!

Some of the code is offloaded to helper files like `helper.py`:

    def huge_thing(big_calc):
        if has_hole(big_calc.bucket):
           fix_it(big_calc.bucket, big_calc.more)

    def more_stuff(big_calc):
        # even more code


### [API Documentation](https://rec.github.io/dtyper#dtyper--api-documentation)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/rec/dtyper",
    "name": "dtyper",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "",
    "author": "Tom Ritchford",
    "author_email": "tom@swirly.com",
    "download_url": "https://files.pythonhosted.org/packages/b3/3c/4202f1c087524dde9240fe95f7cbd1181c21949f2e8f5634dd3ac413200d/dtyper-2.5.1.tar.gz",
    "platform": null,
    "description": "# \ud83d\udddd Fix and improve `typer` \ud83d\udddd\n\n### What is `dtyper`, in one sentence?\n\nUsing `import dtyper as typer` instead of `import typer` would make your\n`typer.command`s directly callable.\n\n(There's also a neat way to make dataclasses from typer commands, but that\nwould be two sentences.)\n\n### Why `dtyper`?\n\n[`typer`](https://typer.tiangolo.com/) is a famously clear and useful system\nfor writing Python CLIs but it has two issues that people seem to run into a\nlot:\n\n1. You can't call the `typer.command` functions it creates directly because\nthey have the wrong defaults.\n\n2. As you add more arguments to your CLI, there is no easy way to break up the\ncode sitting in one file without passing around long, verbose parameter lists.\n\n`dtyper` is a tiny, single-file library that adds to an existing installation\nof `typer` to solve these two problems without changing existing code at all.\n\n* `dtyper.command` executes `typer.command` then fixes the defaults.\n\n* `dtyper.function` decorates an existing `typer.command` to have correct\n  defaults.\n\n* `dtyper.dataclass` automatically makes a `dataclass` from\n   a `typer.command`.\n\n### How to use `dtyper`?\n\nInstall as usual with `poetry add dtyper`, `pip install dtyper`, or your\nfavorite package manager.\n\n`dtyper` is a drop-in replacement for `typer` - it copies all `typer`s\nproperties - so you can even write\n\n    import dtyper as typer\n\nto experiment with it before deciding.\n\n`dtyper` has two new functions that `typer` doesn't, and overrides a `typer`\nclass:\n\n* `@dtyper.function` is a decorator that takes a `typer` command and returns\n  a callable function with the correct defaults.  It is unncessary if you use\n  `dtyper.Typer` (below)\n\n* `@dtyper.dataclass` is a decorator that takes an existing `typer` or `dtyper`\ncommand and makes a `dataclass` from it.\n\n* `dtyper.Typer`is a class identical to `typer.Typer`, except it fixes\n  `Typer.command` functions so you can call them directly.\n\nNone of the `typer` functionality is changed to the slightest degree - adding\n`dtyper` will not affect how your command line program runs at all.\n\n## Example 1: using `dtyper` instead of `typer`\n\n    from dtyper import Argument, Option, Typer\n\n    app = Typer()\n\n    @app.command(help='test')\n    def get_keys(\n        bucket: str = Argument(\n            'buck', help='The bucket to use'\n        ),\n\n        keys: bool = Option(\n            False, help='The keys to download'\n        ),\n    ):\n        print(bucket, keys)\n\nYou can call `get_keys()` from other code and get the right defaults.\n\nWithout regular `typer`, you sometimes get a `typer.Argument` or\n`typer.Option` in place of an expected `str` or `bool`.\n\n### Example 2: a simple `dtyper.dataclass`\n\nHere's a simple CLI in one Python file with two `Argument`s and an `Option`:\n\n    @command(help='test')\n    def get_keys(\n        bucket: str = Argument(\n            ..., help='The bucket to use'\n        ),\n\n        keys: str = Argument(\n            'keys', help='The keys to download'\n        ),\n\n        pid: Optional[int] = Option(\n            None, '--pid', '-p', help='process id, or None for this process'\n        ),\n    ):\n        get_keys = GetKeys(**locals())\n        print(get_keys.run())\n\n\n    @dtyper.dataclass(get_keys)\n    class GetKeys:\n        site = 'https://www.some-websijt.nl'\n\n        def run(self):\n            return self.url, self.keys, self.pid\n\n        def __post_init__(self):\n            self.pid = self.pid or os.getpid()\n\n        def url(self):\n           return f'{self.site}/{self.url}/{self.pid}'\n\n\n### Example: splitting a large `typer.command` into multiple files\n\nReal world CLIs frequently have dozens if not hundreds of commands, with\nhundreds if not thousands of options, arguments, settings or command line\nflags.\n\nThe natural structure for this is the \"big ball of mud\", a popular anti-pattern\nknown to cause misery and suffering to maintainers.\n\n`dtyper.dataclass` can split the user-facing definition of the API from its\nimplementation and then split that implementation over multiple files in a\nnatural and convenient way.\n\nThe example has three Python files.\n\n`interface.py` contains the Typer CLI definitions for this command.\n\n    @command(help='test')\n    def big_calc(\n        bucket: str = Argument(\n            ..., help='The bucket to use'\n        ),\n        more: str = Argument(\n            '', help='More information'\n        ),\n        enable_something: boolean = Option(\n            False, help='Turn on one of many important parameters'\n        ),\n        # [dozens of parameters here]\n    ):\n        d = dict(locals())  # Capture all the command line arguments as a dict\n\n        from .big_calc import BigCalc  # Lazy import to avoid a cycle\n\n        bc = BigCalc(**d)\n        bc.run()\n\n\n`big_calc.py` contains the `dtyper.dataclass` implementation\n\n    from .interface import big_calc\n    from . import helper\n    import dtyper\n\n\n    @dtyper.dataclass(big_calc)\n    class BigCalc:\n        def run(self):\n           # Each argument in `big_calc` becomes a dataclass field\n           print(self.bucket, self.more)\n           print(self)  # dataclass gives you a nice output of all fields\n\n           if helper.huge_thing(self) and self._etc():\n              self.stuff()\n              helper.more_stuff(self)\n              ...\n\n        def _etc(self):\n           ...\n           # Dozens more methods here perhaps!\n\nSome of the code is offloaded to helper files like `helper.py`:\n\n    def huge_thing(big_calc):\n        if has_hole(big_calc.bucket):\n           fix_it(big_calc.bucket, big_calc.more)\n\n    def more_stuff(big_calc):\n        # even more code\n\n\n### [API Documentation](https://rec.github.io/dtyper#dtyper--api-documentation)\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "\ud83d\udddd Fix and improve `typer` \ud83d\udddd",
    "version": "2.5.1",
    "project_urls": {
        "Documentation": "https://rec.github.io/dtyper",
        "Homepage": "https://github.com/rec/dtyper",
        "Repository": "https://github.com/rec/dtyper"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7b291887e294502992a04852c7b6d860c1e586917cf1eb3c14aec1bad6c4eea9",
                "md5": "8cbf55d1e5beb22e29728dafbd71bb23",
                "sha256": "50d2257dae1dfca1380f8a1c1495e88e096b9fa55ca829555a9b4e7c78ce8873"
            },
            "downloads": -1,
            "filename": "dtyper-2.5.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "8cbf55d1e5beb22e29728dafbd71bb23",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 8518,
            "upload_time": "2024-01-25T10:42:53",
            "upload_time_iso_8601": "2024-01-25T10:42:53.410289Z",
            "url": "https://files.pythonhosted.org/packages/7b/29/1887e294502992a04852c7b6d860c1e586917cf1eb3c14aec1bad6c4eea9/dtyper-2.5.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b33c4202f1c087524dde9240fe95f7cbd1181c21949f2e8f5634dd3ac413200d",
                "md5": "8c792bc48cccd4e64532ce18870389fa",
                "sha256": "7644462564c99e7eca8a1635c4b5841c118c16443d72237fc0e2c992a111fba0"
            },
            "downloads": -1,
            "filename": "dtyper-2.5.1.tar.gz",
            "has_sig": false,
            "md5_digest": "8c792bc48cccd4e64532ce18870389fa",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 6227,
            "upload_time": "2024-01-25T10:42:54",
            "upload_time_iso_8601": "2024-01-25T10:42:54.989014Z",
            "url": "https://files.pythonhosted.org/packages/b3/3c/4202f1c087524dde9240fe95f7cbd1181c21949f2e8f5634dd3ac413200d/dtyper-2.5.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-25 10:42:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "rec",
    "github_project": "dtyper",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "dtyper"
}
        
Elapsed time: 0.56988s