pax_global_header00006660000000000000000000000064141131163420014506gustar00rootroot0000000000000052 comment=aab42c985d99b7e997f7e52e83863305b828e5a1 typer-0.4.0/000077500000000000000000000000001411311634200126525ustar00rootroot00000000000000typer-0.4.0/.coveragerc000066400000000000000000000001021411311634200147640ustar00rootroot00000000000000[run] source = typer tests docs_src parallel = True typer-0.4.0/.github/000077500000000000000000000000001411311634200142125ustar00rootroot00000000000000typer-0.4.0/.github/FUNDING.yml000066400000000000000000000000231411311634200160220ustar00rootroot00000000000000github: [tiangolo] typer-0.4.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001411311634200163755ustar00rootroot00000000000000typer-0.4.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002211411311634200203600ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Security Contact about: Please report security vulnerabilities to security@tiangolo.com typer-0.4.0/.github/ISSUE_TEMPLATE/feature-request.yml000066400000000000000000000141541411311634200222460ustar00rootroot00000000000000name: Feature Request description: Suggest an idea or ask for a feature that you would like to have in Typer labels: [enhancement] body: - type: markdown attributes: value: | Thanks for your interest in Typer! πŸš€ Please follow these instructions, fill every question, and do every step. πŸ™ I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time. I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues. All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others. If more Typer users came to help others like them just a little bit more, it would be much less effort for them (and you and me πŸ˜…). By asking questions in a structured way (following this) it will be much easier to help you. And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎 As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. πŸ€“ - type: checkboxes id: checks attributes: label: First Check description: Please confirm and check all the following options. options: - label: I added a very descriptive title to this issue. required: true - label: I used the GitHub search to find a similar issue and didn't find it. required: true - label: I searched the Typer documentation, with the integrated search. required: true - label: I already searched in Google "How to X in Typer" and didn't find any information. required: true - label: I already read and followed all the tutorial in the docs and didn't find an answer. required: true - label: I already checked if it is not related to Typer but to [Click](https://github.com/pallets/click). required: true - type: checkboxes id: help attributes: label: Commit to Help description: | After submitting this, I commit to one of: * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there. * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future. * Implement a Pull Request for a confirmed bug. options: - label: I commit to help with one of those options πŸ‘† required: true - type: textarea id: example attributes: label: Example Code description: | Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case. If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you. placeholder: | import typer def main(name: str): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) render: python validations: required: true - type: textarea id: description attributes: label: Description description: | What is your feature request? Write a short description telling me what you are trying to solve and what you are currently doing. placeholder: | * Create a Typer CLI that says "Hello". * Run it on the command line without a name. * I would like it to use Privacy-Aware Artificial Intelligence based on Block Chain and predict my name without me typing it. validations: required: true - type: textarea id: wanted-solution attributes: label: Wanted Solution description: | Tell me what's the solution you would like. placeholder: | I would like it to say my name without me having to type it. validations: required: true - type: textarea id: wanted-code attributes: label: Wanted Code description: Show me an example of how you would want the code to look like. placeholder: | import typer def main(name: str): typer.guess_name(f"Hello {name}") if __name__ == "__main__": typer.run(main) render: python validations: required: true - type: textarea id: alternatives attributes: label: Alternatives description: | Tell me about alternatives you've considered. placeholder: | To hardcode my name, so that if I don't type it it will be the default. - type: dropdown id: os attributes: label: Operating System description: What operating system are you on? multiple: true options: - Linux - Windows - macOS - Other validations: required: true - type: textarea id: os-details attributes: label: Operating System Details description: You can add more details about your operating system here, in particular if you chose "Other". - type: input id: typer-version attributes: label: Typer Version description: | What Typer version are you using? You can find the Typer version with: ```bash python -c "import typer; print(typer.__version__)" ``` validations: required: true - type: input id: python-version attributes: label: Python Version description: | What Python version are you using? You can find the Python version with: ```bash python --version ``` validations: required: true - type: textarea id: context attributes: label: Additional Context description: Add any additional context information or screenshots you think are useful. typer-0.4.0/.github/ISSUE_TEMPLATE/question.yml000066400000000000000000000123121411311634200207660ustar00rootroot00000000000000name: Question or Problem description: Ask a question or ask about a problem labels: [question] body: - type: markdown attributes: value: | Thanks for your interest in Typer! πŸš€ Please follow these instructions, fill every question, and do every step. πŸ™ I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time. I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues. All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others. If more Typer users came to help others like them just a little bit more, it would be much less effort for them (and you and me πŸ˜…). By asking questions in a structured way (following this) it will be much easier to help you. And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎 As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. πŸ€“ - type: checkboxes id: checks attributes: label: First Check description: Please confirm and check all the following options. options: - label: I added a very descriptive title to this issue. required: true - label: I used the GitHub search to find a similar issue and didn't find it. required: true - label: I searched the Typer documentation, with the integrated search. required: true - label: I already searched in Google "How to X in Typer" and didn't find any information. required: true - label: I already read and followed all the tutorial in the docs and didn't find an answer. required: true - label: I already checked if it is not related to Typer but to [Click](https://github.com/pallets/click). required: true - type: checkboxes id: help attributes: label: Commit to Help description: | After submitting this, I commit to one of: * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there. * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future. * Implement a Pull Request for a confirmed bug. options: - label: I commit to help with one of those options πŸ‘† required: true - type: textarea id: example attributes: label: Example Code description: | Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case. If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you. placeholder: | import typer def main(name: str): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) render: python validations: required: true - type: textarea id: description attributes: label: Description description: | What is the problem, question, or error? Write a short description telling me what you are doing, what you expect to happen, and what is currently happening. placeholder: | * Create a small Typer script. * Open a Terminal with Ninja-Turtle-Shell. * Trigger autocompletion hitting TAB. * I don't see any completion in the terminal using Ninja-Turtle-Shell. * I expected to see autocompletion there. validations: required: true - type: dropdown id: os attributes: label: Operating System description: What operating system are you on? multiple: true options: - Linux - Windows - macOS - Other validations: required: true - type: textarea id: os-details attributes: label: Operating System Details description: You can add more details about your operating system here, in particular if you chose "Other". - type: input id: typer-version attributes: label: Typer Version description: | What Typer version are you using? You can find the Typer version with: ```bash python -c "import typer; print(typer.__version__)" ``` validations: required: true - type: input id: python-version attributes: label: Python Version description: | What Python version are you using? You can find the Python version with: ```bash python --version ``` validations: required: true - type: textarea id: context attributes: label: Additional Context description: Add any additional context information or screenshots you think are useful. typer-0.4.0/.github/dependabot.yml000066400000000000000000000001521411311634200170400ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" typer-0.4.0/.github/workflows/000077500000000000000000000000001411311634200162475ustar00rootroot00000000000000typer-0.4.0/.github/workflows/deploy-docs.yml000066400000000000000000000015411411311634200212150ustar00rootroot00000000000000name: Build and Deploy to Netlify on: push: pull_request: types: [opened, synchronize] jobs: build-docs: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1 with: python-version: "3.7" - name: Install Flit run: python3.7 -m pip install flit - name: Install docs extras run: python3.7 -m flit install --extras doc - name: Build MkDocs run: python3.7 -m mkdocs build - name: Deploy to Netlify uses: nwtgck/actions-netlify@v1.0.3 with: publish-dir: './site' production-branch: master github-token: ${{ secrets.GITHUB_TOKEN }} env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} typer-0.4.0/.github/workflows/issue-manager.yml000066400000000000000000000011001411311634200215220ustar00rootroot00000000000000name: Issue Manager on: schedule: - cron: "0 0 * * *" issue_comment: types: - created - edited issues: types: - labeled jobs: issue-manager: runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.2.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "message": "Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues." } } typer-0.4.0/.github/workflows/latest-changes.yml000066400000000000000000000021571411311634200217010ustar00rootroot00000000000000name: Latest Changes on: pull_request_target: branches: - master types: - closed workflow_dispatch: inputs: number: description: PR number required: true debug_enabled: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false jobs: latest-changes: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: # To allow latest-changes to commit to the main branch token: ${{ secrets.ACTIONS_TOKEN }} # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} with: limit-access-to-actor: true - uses: docker://tiangolo/latest-changes:0.0.3 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/release-notes.md latest_changes_header: '## Latest Changes\n\n' debug_logs: true typer-0.4.0/.github/workflows/publish.yml000066400000000000000000000010671411311634200204440ustar00rootroot00000000000000name: Publish on: release: types: - created jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1 with: python-version: "3.6" - name: Install Flit run: pip install flit - name: Install Dependencies run: flit install --symlink - name: Publish env: FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }} FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }} run: bash scripts/publish.sh typer-0.4.0/.github/workflows/test.yml000066400000000000000000000016271411311634200177570ustar00rootroot00000000000000name: Test on: push: pull_request: types: [opened, synchronize] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9] click-7: [true, false] fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install Flit run: pip install flit - name: Install Dependencies run: flit install --deps=develop --symlink - name: Install Click 7 if: matrix.click-7 run: pip install "click<8.0.0" - name: Lint if: ${{ matrix.python-version != '3.6' && matrix.click-7 == false }} run: bash scripts/lint.sh - name: Test run: bash scripts/test.sh - name: Upload coverage uses: codecov/codecov-action@v1 typer-0.4.0/.gitignore000066400000000000000000000001651411311634200146440ustar00rootroot00000000000000.vscode *.pyc __pycache__ env3.7 env3.6 env dist .mypy_cache .idea site .coverage htmlcov .pytest_cache coverage.xml typer-0.4.0/CONTRIBUTING.md000066400000000000000000000001751411311634200151060ustar00rootroot00000000000000Please read the [Development - Contributing](https://typer.tiangolo.com/contributing/) guidelines in the documentation site. typer-0.4.0/LICENSE000066400000000000000000000020761411311634200136640ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019 SebastiΓ‘n RamΓ­rez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. typer-0.4.0/README.md000066400000000000000000000230161411311634200141330ustar00rootroot00000000000000

Typer

Typer, build great CLIs. Easy to code. Based on Python type hints.

Test Publish Coverage Package version

--- **Documentation**: https://typer.tiangolo.com **Source Code**: https://github.com/tiangolo/typer --- Typer is a library for building CLI applications that users will **love using** and developers will **love creating**. Based on Python 3.6+ type hints. The key features are: * **Intuitive to write**: Great editor support. Completion everywhere. Less time debugging. Designed to be easy to use and learn. Less time reading docs. * **Easy to use**: It's easy to use for the final users. Automatic help, and automatic completion for all shells. * **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. * **Start simple**: The simplest example adds only 2 lines of code to your app: **1 import, 1 function call**. * **Grow large**: Grow in complexity as much as you want, create arbitrarily complex trees of commands and groups of subcommands, with options and arguments. ## FastAPI of CLIs **Typer** is FastAPI's little sibling. And it's intended to be the FastAPI of CLIs. ## Requirements Python 3.6+ **Typer** stands on the shoulders of a giant. Its only internal dependency is Click. ## Installation
```console $ pip install typer ---> 100% Successfully installed typer ```
## Example ### The absolute minimum * Create a file `main.py` with: ```Python import typer def main(name: str): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) ``` ### Run it Run your application:
```console // Run your application $ python main.py // You get a nice error, you are missing NAME Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing argument 'NAME'. // You get a --help for free $ python main.py --help Usage: main.py [OPTIONS] NAME Arguments: NAME [required] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion // Now pass the NAME argument $ python main.py Camila Hello Camila // It works! πŸŽ‰ ```
**Note**: auto-completion works when you create a Python package and run it with `--install-completion` or when you use Typer CLI. ## Example upgrade This was the simplest example possible. Now let's see one a bit more complex. ### An example with two subcommands Modify the file `main.py`. Create a `typer.Typer()` app, and create two subcommands with their parameters. ```Python hl_lines="3 6 11 20" import typer app = typer.Typer() @app.command() def hello(name: str): typer.echo(f"Hello {name}") @app.command() def goodbye(name: str, formal: bool = False): if formal: typer.echo(f"Goodbye Ms. {name}. Have a good day.") else: typer.echo(f"Bye {name}!") if __name__ == "__main__": app() ``` And that will: * Explicitly create a `typer.Typer` app. * The previous `typer.run` actually creates one implicitly for you. * Add two subcommands with `@app.command()`. * Execute the `app()` itself, as if it was a function (instead of `typer.run`). ### Run the upgraded example
```console // Check the --help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: goodbye hello // You have 2 subcommands (the 2 functions): goodbye and hello // Now get the --help for hello $ python main.py hello --help Usage: main.py hello [OPTIONS] NAME Arguments: NAME [required] Options: --help Show this message and exit. // And now get the --help for goodbye $ python main.py goodbye --help Usage: main.py goodbye [OPTIONS] NAME Arguments: NAME [required] Options: --formal / --no-formal [default: False] --help Show this message and exit. // Automatic --formal and --no-formal for the bool option πŸŽ‰ // And if you use it with the hello command $ python main.py hello Camila Hello Camila // And with the goodbye command $ python main.py goodbye Camila Bye Camila! // And with --formal $ python main.py goodbye --formal Camila Goodbye Ms. Camila. Have a good day. ```
### Recap In summary, you declare **once** the types of parameters (*CLI arguments* and *CLI options*) as function parameters. You do that with standard modern Python types. You don't have to learn a new syntax, the methods or classes of a specific library, etc. Just standard **Python 3.6+**. For example, for an `int`: ```Python total: int ``` or for a `bool` flag: ```Python force: bool ``` And similarly for **files**, **paths**, **enums** (choices), etc. And there are tools to create **groups of subcommands**, add metadata, extra **validation**, etc. **You get**: great editor support, including **completion** and **type checks** everywhere. **Your users get**: automatic **`--help`**, **auto-completion** in their terminal (Bash, Zsh, Fish, PowerShell) when they install your package or when using Typer CLI. For a more complete example including more features, see the Tutorial - User Guide. ## Optional Dependencies Typer uses Click internally. That's the only dependency. But you can also install extras: * colorama: and Click will automatically use it to make sure your terminal's colors always work correctly, even in Windows. * Then you can use any tool you want to output your terminal's colors in all the systems, including the integrated `typer.style()` and `typer.secho()` (provided by Click). * Or any other tool, e.g. wasabi, blessings. * shellingham: and Typer will automatically detect the current shell when installing completion. * With `shellingham` you can just use `--install-completion`. * Without `shellingham`, you have to pass the name of the shell to install completion for, e.g. `--install-completion bash`. You can install `typer` with `colorama` and `shellingham` with `pip install typer[all]`. ## Other tools and plug-ins Click has many plug-ins available that you can use. And there are many tools that help with command line applications that you can use as well, even if they are not related to Typer or Click. For example: * click-spinner: to show the user that you are loading data. A Click plug-in. * There are several other Click plug-ins at click-contrib that you can explore. * tabulate: to automatically display tabular data nicely. Independent of Click or Typer. * tqdm: a fast, extensible progress bar, alternative to Typer's own `typer.progressbar()`. * etc... you can re-use many of the great available tools for building CLIs. ## License This project is licensed under the terms of the MIT license. typer-0.4.0/SECURITY.md000066400000000000000000000022101411311634200144360ustar00rootroot00000000000000# Security Policy Security is very important for Typer and its community. πŸ”’ Learn more about it below. πŸ‘‡ ## Versions The latest versions of Typer are supported. You are encouraged to [write tests](https://typer.tiangolo.com/tutorial/testing/) for your application and update your Typer version frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**. ## Reporting a Vulnerability If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: security@tiangolo.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. I (the author, [@tiangolo](https://twitter.com/tiangolo)) will review it thoroughly and get back to you. ## Public Discussions Please restrain from publicly discussing a potential security vulnerability. πŸ™Š It's better to discuss privately and try to find a solution first, to limit the potential impact as much as possible. --- Thanks for your help! The Typer community and I thank you for that. πŸ™‡ typer-0.4.0/docs/000077500000000000000000000000001411311634200136025ustar00rootroot00000000000000typer-0.4.0/docs/alternatives.md000066400000000000000000000114061411311634200166270ustar00rootroot00000000000000What inspired **Typer**, how it compares to other alternatives and what it learned from them. ## Intro **Typer** wouldn't exist if not for the previous work of others. There have been many tools created before that have helped inspire its creation. ## Previous tools ### `argparse` `argparse` is the Python standard library's module to write CLIs. It provides a better alternative than reading the *CLI Parameters* as a `list` of `str` and parsing everything by hand. !!! check "Inspired **Typer** to" Provide a better development experience than just reading *CLI Parameters* by hand. ### Hug Hug is a library to create APIs and CLIs, it uses parameters in functions to declare the required data. It inspired a lot of the ideas in **FastAPI** and **Typer**. !!! check "Inspired **Typer** to" Use function parameters to declare *CLI arguments* and *CLI options* as it simplifies a lot the development experience. ### Plac Plac is another library to create CLIs using parameters in functions, similar to Hug. !!! check "Inspired **Typer** to" Provide a simple way to use a function as a command line app, without having to create a complete app, with `typer.run(some_function)`. ### Pydantic Pydantic is a library to handle data validation using standard modern Python type annotations. It powers **FastAPI** underneath. It is not used by **Typer**, but it inspired a lot of the design (through **FastAPI**). !!! check "Inspired **Typer** to" Use standard Python type annotations to declare types instead of library-specific types or classes and use them for data validation and documentation. ### Click Click is one of the most widely used libraries to create CLIs in Python. It's a very powerful tool and there are many CLIs built with it. It is what powers **Typer** underneath. It also uses functions with parameters for *CLI arguments* and *CLI options*, but the declaration of the specific *CLI arguments*, *CLI options*, types, etc, is done in decorators on top of the function. This requires some code repetition (e.g. a *CLI Option* name `--verbose` and a variable name `verbose`) and synchronization between two places related to the same information (the decorator and the parameter function). It uses decorators on top of functions to modify the actual value of those functions, converting them to instances of a specific class. This is a clever trick, but code editors can't provide great support for autocompletion that way. It was built with some great ideas and design using the features available in the language at the time (Python 2.x). !!! check "**Typer** uses it for" Everything. πŸš€ **Typer** mainly adds a layer on top of Click, making the code simpler and easier to use, with autocompletion everywhere, etc, but providing all the powerful features of Click underneath. As someone pointed out: "Nice to see it is built on Click but adds the type stuff. Me gusta!" ### `click-completion` `click-completion` is a plug-in for Click. It was created to extend completion support for shells when Click only had support for Bash completion. Previous versions of **Typer** had deep integrations with `click-completion` and used it as an optional dependency. But now all the completion logic is implemented internally in **Typer** itself, the internal logic was heavily inspired and using some parts of `click-completion`. And now **Typer** improved it to have new features, tests, some bug fixes (for issues in plain `click-completion` and Click), and better support for shells, including modern versions of PowerShell (e.g. the default versions that come with Windows 10). !!! check "Inspired **Typer** to" Provide auto completion for all the shells. ### FastAPI I created **FastAPI** to provide an easy way to build APIs with autocompletion for everything in the code (and some other features). **Typer** is the "FastAPI of CLIs". It uses the same design and usage of FastAPI as much as possible. So, if you have used FastAPI, you know how to use Typer. typer-0.4.0/docs/contributing.md000066400000000000000000000135341411311634200166410ustar00rootroot00000000000000First, you might want to see the basic ways to [help Typer and get help](help-typer.md){.internal-link target=_blank}. ## Developing If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment. ### Virtual environment with `venv` You can create a virtual environment in a directory using Python's `venv` module:
```console $ python -m venv env ```
That will create a directory `./env/` with the Python binaries and then you will be able to install packages for that isolated environment. ### Activate the environment Activate the new environment with: === "Linux, macOS"
```console $ source ./env/bin/activate ```
=== "Windows PowerShell"
```console $ .\env\Scripts\Activate.ps1 ```
=== "Windows Bash" Or if you use Bash for Windows (e.g. Git Bash):
```console $ source ./env/Scripts/activate ```
To check it worked, use: === "Linux, macOS, Windows Bash"
```console $ which pip some/directory/typer/env/bin/pip ```
=== "Windows PowerShell"
```console $ Get-Command pip some/directory/typer/env/bin/pip ```
If it shows the `pip` binary at `env/bin/pip` then it worked. πŸŽ‰ !!! tip Every time you install a new package with `pip` under that environment, activate the environment again. This makes sure that if you use a terminal program installed by that package (like `flit`), you use the one from your local environment and not any other that could be installed globally. ### Flit **Typer** uses Flit to build, package and publish the project. After activating the environment as described above, install `flit`:
```console $ pip install flit ---> 100% ```
Now re-activate the environment to make sure you are using the `flit` you just installed (and not a global one). And now use `flit` to install the development dependencies: === "Linux, macOS"
```console $ flit install --deps develop --symlink ---> 100% ```
=== "Windows" If you are on Windows, use `--pth-file` instead of `--symlink`:
```console $ flit install --deps develop --pth-file ---> 100% ```
It will install all the dependencies and your local Typer in your local environment. #### Using your local Typer If you create a Python file that imports and uses Typer, and run it with the Python from your local environment, it will use your local Typer source code. And if you update that local Typer source code, as it is installed with `--symlink` (or `--pth-file` on Windows), when you run that Python file again, it will use the fresh version of Typer you just edited. That way, you don't have to "install" your local version to be able to test every change. ### Format There is a script that you can run that will format and clean all your code:
```console $ bash scripts/format.sh ```
It will also auto-sort all your imports. For it to sort them correctly, you need to have Typer installed locally in your environment, with the command in the section above using `--symlink` (or `--pth-file` on Windows). ### Format imports There is another script that formats all the imports and makes sure you don't have unused imports:
```console $ bash scripts/format-imports.sh ```
As it runs one command after the other and modifies and reverts many files, it takes a bit longer to run, so it might be easier to use `scripts/format.sh` frequently and `scripts/format-imports.sh` only before committing. ## Docs The documentation uses MkDocs. All the documentation is in Markdown format in the directory `./docs`. Many of the tutorials have blocks of code. In most of the cases, these blocks of code are actual complete applications that can be run as is. In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs_src/` directory. And those Python files are included/injected in the documentation when generating the site. ### Docs for tests Most of the tests actually run against the example source files in the documentation. This helps making sure that: * The documentation is up to date. * The documentation examples can be run as is. * Most of the features are covered by the documentation, ensured by test coverage. During local development, there is a script that builds the site and checks for any changes, live-reloading:
```console $ bash scripts/docs-live.sh [INFO] - Building documentation... [INFO] - Cleaning site directory [INFO] - Documentation built in 2.74 seconds [INFO] - Serving on http://127.0.0.1:8008 ```
It will serve the documentation on `http://127.0.0.1:8008`. That way, you can edit the documentation/source files and see the changes live. ## Tests There is a script that you can run locally to test all the code and generate coverage reports in HTML:
```console $ bash scripts/test-cov-html.sh ```
This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing. typer-0.4.0/docs/css/000077500000000000000000000000001411311634200143725ustar00rootroot00000000000000typer-0.4.0/docs/css/custom.css000066400000000000000000000007031411311634200164160ustar00rootroot00000000000000.termynal-comment { color: #4a968f; font-style: italic; display: block; } .termy [data-termynal] { white-space: pre-wrap; } a.external-link::after { /* \00A0 is a non-breaking space to make the mark be on the same line as the link */ content: "\00A0[β†ͺ]"; } a.internal-link::after { /* \00A0 is a non-breaking space to make the mark be on the same line as the link */ content: "\00A0β†ͺ"; } typer-0.4.0/docs/css/termynal.css000066400000000000000000000041651411311634200167450ustar00rootroot00000000000000/** * termynal.js * * @author Ines Montani * @version 0.0.1 * @license MIT */ :root { --color-bg: #252a33; --color-text: #eee; --color-text-subtle: #a2a2a2; } [data-termynal] { width: 750px; max-width: 100%; background: var(--color-bg); color: var(--color-text); font-size: 18px; /* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */ font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; border-radius: 4px; padding: 75px 45px 35px; position: relative; -webkit-box-sizing: border-box; box-sizing: border-box; } [data-termynal]:before { content: ''; position: absolute; top: 15px; left: 15px; display: inline-block; width: 15px; height: 15px; border-radius: 50%; /* A little hack to display the window buttons in one pseudo element. */ background: #d9515d; -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; } [data-termynal]:after { content: 'bash'; position: absolute; color: var(--color-text-subtle); top: 5px; left: 0; width: 100%; text-align: center; } a[data-terminal-control] { text-align: right; display: block; color: #aebbff; } [data-ty] { display: block; line-height: 2; } [data-ty]:before { /* Set up defaults and ensure empty lines are displayed. */ content: ''; display: inline-block; vertical-align: middle; } [data-ty="input"]:before, [data-ty-prompt]:before { margin-right: 0.75em; color: var(--color-text-subtle); } [data-ty="input"]:before { content: '$'; } [data-ty][data-ty-prompt]:before { content: attr(data-ty-prompt); } [data-ty-cursor]:after { content: attr(data-ty-cursor); font-family: monospace; margin-left: 0.5em; -webkit-animation: blink 1s infinite; animation: blink 1s infinite; } /* Cursor animation */ @-webkit-keyframes blink { 50% { opacity: 0; } } @keyframes blink { 50% { opacity: 0; } } typer-0.4.0/docs/features.md000066400000000000000000000105261411311634200157460ustar00rootroot00000000000000## Design based on **FastAPI** **Typer** is FastAPI's little sibling. It follows the same design and ideas. If you know **FastAPI**, you already know **Typer**... more or less. ## Just Modern Python It's all based on standard **Python 3.6 type** declarations. No new syntax to learn. Just standard modern Python. If you need a 2 minute refresher of how to use Python types (even if you don't use FastAPI or Typer), check the FastAPI tutorial section: Python types intro. You will also see a 20 seconds refresher on the section [Tutorial - User Guide: First Steps](tutorial/first-steps.md){.internal-link target=_blank}. ## Editor support **Typer** was designed to be easy and intuitive to use, to ensure the best development experience. With autocompletion everywhere. You will rarely need to come back to the docs. Here's how your editor might help you: * in Visual Studio Code: ![editor support](img/vscode-completion.png) * in PyCharm: ![editor support](img/pycharm-completion.png) You will get completion for everything. That's something no other CLI library provides right now. No more guessing what type was that variable, if it could be `None`, etc. ### Short It has sensible **defaults** for everything, with optional configurations everywhere. All the parameters can be fine-tuned to do what you need, customize the help, callbacks per parameter, make them required or not, etc. But by default, it all **"just works"**. ## User friendly CLI apps The resulting CLI apps created with **Typer** have the nice features of many "pro" command line programs you probably already love. * Automatic help options for the main CLI program and all its subcommands. * Automatic command and subcommand structure handling (you will see more about subcommands in the Tutorial - User Guide). * Automatic completion for the CLI app in all operating systems, in all the shells (Bash, Zsh, Fish, PowerShell), so that the final user of your app can just hit TAB and get the available options or subcommands. * !!! note "* Auto completion" Auto completion works when you create a package (installable with `pip`). Or when using [Typer CLI](typer-cli.md){.internal-link target=_blank}. If you also add `shellingham` as a dependency, **Typer** will use it to auto-detect the current shell when installing completion. **Typer** will automatically create 2 *CLI options*: * `--install-completion`: Install completion for the current shell. * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. If you didn't add `shellingham` those *CLI options* take a value with the name of the shell to install completion for, e.g.: * `--install-completion bash`. * `--show-completion powershell`. Then you can tell the user to install completion after installing your CLI program and the rest will just work. !!! tip **Typer**'s completion is implemented internally, it uses ideas and components from Click and ideas from `click-completion`, but it doesn't use `click-completion` and re-implements some of the relevant parts of Click. Then it extends those ideas with features and bug fixes. For example, **Typer** programs also support modern versions of PowerShell (e.g. in Windows 10) among all the other shells. ## The power of Click Click is one of the most popular tools for building CLIs in Python. **Typer** is based on it, so you get all its benefits, plug-ins, robustness, etc. But you can write simpler code with the benefits of modern Python. ## Tested * 100% test coverage. * 100% type annotated code base. * Used in production applications. typer-0.4.0/docs/help-typer.md000066400000000000000000000114241411311634200162170ustar00rootroot00000000000000Are you liking **Typer**? Would you like to help Typer, other users, and the author? Or would you like to get help with **Typer**? There are very simple ways to help (several involve just one or two clicks). And there are several ways to get help too. ## Star **Typer** in GitHub You can "star" Typer in GitHub (clicking the star button at the top right): https://github.com/tiangolo/typer. By adding a star, other users will be able to find it more easily and see that it has been already useful for others. ## Watch the GitHub repository for releases You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/typer. There you can select "Releases only". Doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **Typer** with bug fixes and new features. ## Connect with the author You can connect with me (SebastiΓ‘n RamΓ­rez / `tiangolo`), the author. You can: * Follow me on **GitHub**. * See other Open Source projects I have created that could help you. * Follow me to see when I create a new Open Source project. * Follow me on **Twitter**. * Tell me how you use Typer (I love to hear that). * Ask questions. * Connect with me on **Linkedin**. * Talk to me. * Endorse me or recommend me :) * Read what I write (or follow me): * Read other ideas, articles and tools I have created. * Follow me to see when I publish something new. * On **Dev.to**. * On **Medium**. ## Tweet about **Typer** Tweet about **Typer** and let me and others know why you like it. ## Let me know how are you using **Typer** I love to hear about how **Typer** is being used, what have you liked in it, in which project/company you are using it, etc. You can let me know: * On **Twitter**. * On **Linkedin**. * On **Dev.to**. * On **Medium**. ## Help others with issues in GitHub You can see existing issues and try and help others. ## Watch the GitHub repository You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/typer. If you select "Watching" instead of "Releases only", you will receive notifications when someone creates a new issue. Then you can try and help them solving those issues. ## Create issues You can create a new issue in the GitHub repository, for example to: * Report a bug/issue. * Suggest a new feature. * Ask a question. ## Create a Pull Request You can create a Pull Request, for example: * To fix a typo you found on the documentation. * To propose new documentation sections. * To fix an existing issue/bug. * To add a new feature. ## Sponsor the author You can also financially support the author (me) through GitHub sponsors. There you could buy me a coffee β˜•οΈ to say thanks. πŸ˜„ ## Sponsor the tools that power Typer As you have seen in the documentation, Typer is built on top of Click. You can also sponsor: * Pallets Project (Click maintainers)via the PSF or via Tidelift --- Thanks! πŸš€ typer-0.4.0/docs/img/000077500000000000000000000000001411311634200143565ustar00rootroot00000000000000typer-0.4.0/docs/img/favicon.png000077500000000000000000000006671411311634200165250ustar00rootroot00000000000000‰PNG  IHDR$$ᘘsBIT|dˆ pHYs%%IR$πtEXtSoftwarewww.inkscape.org›ξ<4IDATX…νΧMJ1ΖρŸ"/QŠ7PΠMέTGβ%\κRoQτΆ"΄[½”ήBtΡΖ†‘RΫΔ™.ζw13™7ςζɍ₯i§τ|†CμUΤ'ή0\φ±―š’_†)j„ qΞ’4ΗάΣ<ͺP18Α ΅’FcάW΄΅`·’ެh•r]α C,\6²˜Θ£5s΄1@“\@©VοΰΕ κ=(ηjγYbω’»aŽψΏδς 7•Υ‘PΎ­³ύ2—­«rΉ'2”,Εe·ΠΤlΧή†Ό%›Κ`ϋe.[qœΘ΄0†*p7βχζ Cž³s?CŽmν U sθ#zΧΕuEύΗ jΜΰRύΧ ‹2νc0’|•ξαH΅WιW³sT£FYτ GΧ­Α&ΊyIENDB`‚typer-0.4.0/docs/img/github-social-preview.png000066400000000000000000001363711411311634200213100ustar00rootroot00000000000000‰PNG  IHDR€ΓσΥ‘sBIT|dˆ pHYsΔΔ•+tEXtSoftwarewww.inkscape.org›ξ< IDATxœμέw|TUώρs„$@ ½K― H `B ‘¬4‘¦Θ Φ…eΏώDύ*Ί»( ¬μwQΐ—""]"B¨ι5@€„B€$„P’™{μβ”Ιœ;w^ΟΏφακάOfn9η}O‘¦iζ Ά€©.@α!lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1@ΐΖ#lŒ°1‡κΰ›²³³Ε;wDVV–T] ί Š-j+VLθΊΊπ@ͺ“'OΚ;vȝ;wΚ€€$™’’"Μ9#οάΉ£Ί4>¬lΩ²fΥͺUE΅jΥΜ&Mš˜-Z΄0›4ib¨. άN𦙫ΊΨGnnˆ—ί|σΆbΕ ™ššΚ?^Αίί_DEE™½zυ2zφμi”-[VuIΰ€p‹ .ΘyσζΙ3fh.\ τΰΥ4MΡΡΡζΘ‘#ίώχS†x3@HRR’œ4i’ΆxρbΝ0 Υε€ΫΥ¬YΣ|λ­·ŒώύϋšΖήYΌ Ι΅kΧğώτ'}Μ9šΣιT]Ίϊυλ›Σ§OwEEE™ͺk€ό DΎ-\ΈP{ωε—υ΄΄4Υ₯€GI)ΕπαÍ)S¦ΈBCCU—…νφνΫβ…^ΠgϞΝ8>­B… ζβΕ‹]­Z΅b4 Λ#ΔCINN–½{χΦ<Θ ώ³kπ΄iΣ\#GŽdT–Fˆί”˜˜(;uκ€_Όx‘π~fόψρΖ_ώς—κ:ΰA˜Κ‰_΅gΟΩΆm[αάί_ϊWmΒ„ Ίκ:ΰAˆJNN–­[·v°ΩόΆ?όΠυκ«―2€εβΎ222DΛ–-IIIŒό€‡ išψκ«―\}ϊτ!`)€Έ―θ‹-bŠ8δCρβΕΕΎ}ϋœU«Vew`–Aΐƒ_˜={ΆFψω—••%† ’ƒX#ρ’F~W―^U] x­Y³fΉFŒA ΐε…Ÿ˜4i’Nψσ?σ?ϊυλΧU—BFβηϟ—ΥͺUsδεε©.ΌήδΙ“]'N΄Μ(ΐ‹/Κ!C†θͺλ€ϋ=ϋμ³ΖΣO?m™s ΦγP]¬γο»Fψξ1cΖ νυΧ_7όόόT—"„βζΝ›bύϊυμμnCΡΡΡͺK€Ε1B!ξάΉ#>ϋμ3Ξp“‹/Κ+Vp_ !„XΏ~½ΌrεŠκ2ΐVΎώϊkFάPŽB!V\ΙΉnΆfΝ–V ‘„‚u‘ dee‰½{χr  DNNŽ8uκT(ΰώ @)@ˆΓ‡KΓ0T—ΆtθΠ!@JB\ΈpΞ)’‹/ͺ.€#„ΘΚΚR]ΨVVV/Y(εP]ΤΛΞΞVvμΐΐ@1wξ\—²ψ„Ο>ϋL[·n’ Ž—,T#„pΉΤεo‡Cτλ׏ͺ7JU k¬P)ΐ€6FΨ `c€€6FΨ `c€€6FΨ `c€€6FؘCu πU¬XΡά΅k—SuγΤ©Srΐ€ΊΚž{ξ9cΤ¨Q†ΚV… LΥ5ΐΪπώώώ’Y³f^ωϋϋ«.A”-[ΦkΎ/ΰ·0°1@ΐΖ#lŒ°1@ΐΖs¨. KFFƏ»xρβBΣαKœN§HII‘'NœιιιςζΝ›"++KάΈqCήΌySάΈqC+VL8Q’D ΣΟΟO‹ΐΐ@QͺT)³rεΚ’Z΅jf`` κ? „€ε†!ΣΣΕεΛ—eZZšΈtι’LOOiii2==]\Ώ~]dffΚμμlqύϊu‘-rrrdVV–0 た«iš(^ΌΈπχχfpp°(V¬˜([Ά¬Y\9QΎ|yσ±Η5kΦ4kΤ¨aϊϋϋ{π―FA€§§‹Ν›7k ςΨ±cςΔ‰βτιΣ2//―ΐŸ!*WlV\Ω¬Y³¦hΩ²₯Ρ²eK3<<ά •C₯άά\‘––&―]»&233EFF†ΜΘΘ"33SfddˆλΧ―‹›7o !ώsoΚΚΚ’wϋμμlαt:…B„„„‡Γ!Š/njš&BCCΌη”(QΒ aaa",,Μ •+W6ƒƒƒΥόαπ9€”Έ|ω²8vμ˜<}ϊ΄<}ϊ΄8}ϊ΄™žžΊ,·ΙΞΞί~ϋ­όφΫoυqγΖ‰ ˜4žyζ³bŊ„…δ»οΎ“sηΞΥV¬X‘εδδ¨.ηNŸ>-§OŸ.§OŸU«VΝ2dˆ1rδH£lΩ²ͺK³œœ±cΗΉuλVΉmΫ6ν‡~Χ―_W]–ΫέΊuK?~\?~\.[ΆμΗξp8D΅jΥΜ† š524h 6lΘK < €Σ4MtθΠΑX°`Q€ϋχο—YYY’xρβ*Λx$G•ͺΓΏR₯J‰ζΝ›{ΌΣg†8tθάΌy³άΊu«ά²e‹LMMυ©!Om€ΏρΖ"::Ϊ3fŒΡ³gOƒiΒηt:ΕβΕ‹΅Ώώυ―ځΌξΌJNN–o½υ–>yςd½OŸ>ΖψG£I“&„3(##ClΨ°AΫΌy³άΆm›|ΈΎdΙΒΏ8{φ¬|ν΅ΧτκΥ«ϋ}τΡGڝ;wT—δ\.—˜5k–φΨcω½τKΊΒΏ{™¦)V\©=ώψγŽaΓ†ι©©©ͺK²œ‹/Κ™3gj±±±zXX˜ίO<αxο½χ΄;wώ=Σ4ΕΎ}ϋ msx@WΆlYQΏ~}彃ψψx―μμ«5M111…>rrλΦ­ΪΝ;₯axέ Me.]Ί$^{ν5½N:Ž%K–Πχϊ ²I“&Ž‘#Gκ—.]R]N‘2 C|ρΕZ­Z΅όf̘‘ωϊ5uθΠ!ωή{οi?ώΈ£B… Ž1cΖθ+WΤ²³³U—f š¦‰ίύξwʟρP‡D—.]”wωΔ+ΞϋG•””$###υ£G|;vμνΫ·w\½zUu)πb*T0K•*₯Ί X„­2¬iΦ¬Yڈ#τΌΌ<Υ₯ξQ―^=³bŊΚ%oY0!!AiRJΦ³ Γ0ΔΛ/Ώ¬O˜0Α–Γ9>,£’’t^ηΟΎ}ϋdLLŒ#;;[u)πRMš4QώL‡uPβσΟ?ΧΊuλζΘΚΚR] ΰ:uRήYπ–PυϊΏϋέοΜ₯K«,nφΧΏώU{ηwlΥGKHHνΪ΅s€¦¦zΕum5»wο–½zυrδζζͺ.^¨qγΖͺK€…Ψκαΐ»|ύχ²uλ֎””„`V˜|μΨ1™––¦ΊŒί€:dϊ―=Mš4IŸ>}Ί-ϊi›7o–;wvddd¨.Ε«mΨ°Aώα°εθP.Fβ^Άx°π^‰‰‰²yσζŽ-[Ά€tκΤIω¦¦iŠψψxK·S/]Ί$TΏΐbϊ―}½όςΛϊ’E‹,} ό–]»vΙ=z8rrrT—b sηΞΥ¦L™βΥη<χβ@Ή+WˆN:9ΎόςKξI XΙ’%-±cΰƍ-ύb(!!Aι3+44T΄lΩRωο„Βa†>|Έz—ιGuμΨ1Ω΅kWΗυλΧU—b+'NΤyiއUͺT)a…u}atΆX;wΔΰΑƒυI“&1½³Β4`«―¨zϊoǎ ‡Γ‘²²[·n‰ž={κ—.]R]JΎ\½zUtοή]gχZχs:bΠ Aϊ΅kΧT—/ΠΈqcεΟrX  Λ0MSΌσΞ;Ϊ°aΓt:uΊvνͺ|jiRR’Όxρ’eC@Υ;³ώŸoHMM•O?ύ΄Γεr©.ε‘δεε‰>}ϊ8N:eΩkΧ۝;wNΎψβ‹Ό0Ηobϊ/~Ž€ε|ρΕZtt΄#==]u)ΰ“ZΆliFDD¨.Γ²£sssΕή½{•Υ&₯dύ?/½egΰ‰'κ›7oΆδuk' ,ΠV¬XαηΤωέο~GˆŸΰ¦ΐ’dΛ–-G₯ ¦išθΦ­›ς€Ιͺΰή½{ενΫ·•ΏQ£FfΩ²e•ž7yςd}ηΝ–ΌξZΎ|ΉφΡGΡΏτ1cΖhl°‚_CˆŸγ ΐ²’““e›6mV퀝ΕΖΖ*ο8lΨ°Α’χΥΣcbb”6π,§Σ)† ¦ίΉsGu)χuι%ράsΟι¦Ι©ι).\|πSq_!!!’F\ψ @–vνΪ5γ˜;w.χ+π Ξ;JkHII‘‰‰‰– Uob…5αyG•οΎϋ%Ÿ‘#G²ι‡~ψ‘vφμYΛέ#‘^Γ† MM£ϋ„ŸβŒ`yΉΉΉbψπαϊΈqγtΓ Οž$Ϊ·o―|τΐͺU«,ΧΉU9°D‰’uλΦΚ¨1eΚνΔ‰–Ί&ζϟ―­\Ή’~₯·nέωΛ_ψξρ l‚ϋq¨.κˆΠΠP%Η βΖ„‡6}ϊt---M|ρΕ.Υ£RΐτθΡΓX³fG«V­Ζo™·?gΜQΊ;q‡ ‡ƒ&Ό―Ί}ϋΆxε•WτU«V9UΧ"„7nάγǏχšΚαpˆJ•*™Υ«WΕ‹7CCC…ΏΏΏΘΛΛ·nέ7oή.\III[F4Ξ™3G›4i’Λ 7Α:XχCλbτθΡΖθΡ£-Σ°~ΝβΕ‹΅Σ§OΛ+V8K—.­Ί°΅=z˜cƌ*ΧυΪΎ}»Όvνš(Y²€²ξ₯zύΏ]»©ϋ‡CDDD˜εΚ•Ε‹!!!¦ΓαŊwξά7nά7nܐ/^'Ož”yyyͺKΞ·Υ«WΛ­[·Κ6mΪ(?ή}=55ΥR#ο(ΊtιbDEE™O<ρ„YΏ~}σaτ+WˆmΫΆiqqqr͚5ςΜ™3–ό;oέΊ%>ώψc}ςδΙ.Υ΅ΐ:q?€ΌΞΝ;eΣ¦M+Wtρp€ΒSΎ|y³I“&ζž={”u|N§ψφΫo΅AƒYβe₯ΚPJιΣ€Š%K–aaaf©R₯DXX˜(Y²€&ΒΓΓΝ₯K )ξ'ΚΛΛIIIrΧ]rυκΥς»οΎΣ_Ώ^Θ…{ΌσΞ;ϊwί}§t`JJŠ΄κΏ 64_xα£ώFρβΕι3J•*%zφμiτμΩS†!Φ¬Y#?ώψc}ύϊυ– gΜ©ύιOr+VLu)°Q―^=Ÿ}VΰΑx₯ .Θ'žxΒροΫΩ½{wpPHzτθaμΩ³Gυ4`9hΠ •%όHeΨ A³|ως>ϋΜ+Y²€9{φl·rςσσυκΥ3λΥ«g6LδεεΉ–.]ͺM™2EΫ½{·εBž{­[·NnίΎ]ΆjΥJΩω0~όxνφνΫͺ_UͺT1ί}χ]cΰΐ†;7@Π4Mtοήέμή½»sΫΆmrΒ„ ϊΆmΫ,sŽdffŠ™3gj&L°Δ‹¨UΏ~}ΣΟΟOu° KΎ±€‡‘-zυκε˜1cχ2($±±±Κ§ΈΈ8ΝιTΏδYNNŽ8xπ ²NΏ/ώσ???ΡΏcΧ]ΞυλΧ;λΦ­kιοϋwήQΜoήΌY.Y²Δ2ν/)₯=z΄qψπaη Aƒάώύ\dd€ΉuλVηάΉs]:Ί°0όσŸΤT.Χλ`†„€ΌšΛεcΗŽΥ“’’δΤ©S]Ίt ΨNγƍ͊+šηΝS|eddˆ­[·Κ¨¨(₯š;wJ•AdΧ]έγ!νΫ·7χνΫηόΰƒ΄χή{OΟΝΝU]/¬]»VξάΉS6oήάγΧΕ[o½e™Wpp°ψςΛ/]±±±½>†jDGG›½{χΦχξέ«|4`rr²άΎ}»d—pχ*Z΄¨ˆˆˆ0Λ”)#"""Μ₯K‹2eʈ’E‹š~~~"88XρŸΪ₯”BΧuαrΉDNNŽΘΝΝYYYΒεr‰ΜΜL™››+._Ύ,RSSeZZšHOO—ιιιn―™Bΐ¦OŸ%%%ΙΕ‹;YάGJ)ΊwοnΜ9SiwΥͺUZTT”EξUN-V¬˜ˆŒŒ€SηAEŠoΎω¦Ρ];󩧞rXqW؏>ϊH[Έp‘G―‹={φΘψψx偗B”+WΞ\½z΅«qγΖJJ•*™›6mr>υΤSŽΈΈ8είΙ‚ ΄Φ­[³H>”,YRΤͺUΛ¬]»ΆY«V-³|ως’bŊf™2eD… Μ   B=~vvΆ8~όΈϋ¬Ρ³gOKu²Ώψβ‹BοΧ}ωε—šΚe„"((HLŸ>έRίύΟ½ψβ‹F½zυ”]³©©©ςΨ±c„€~€ν}ώωηZ·nέ™™™ͺK―&’££•QΉΉΉ"..NIϋUecιΐΓύσŸt…††ͺ.γGΛ—//τσΣ!γo™8q’ε7zΣu]όνoS: pγƍ€~AωM<αϋοΏ—‘‘‘«νήή€oίΎΚƒ(λfffŠγǏ+y~‰ΆmΫZ:ππEβ7ή°ΜTΰ€€$Y˜mœ#GŽΘ]»v)mC…‡‡‹qγΖ)Ώ=Œ]»ώbγOR½1k"ΰ3eσζΝ›7o¦Q OŸ>Κ§―]»V3 Οf ΣΗΌ+**ΚτχχWrlόΊ_|ΡP΅+υύζΪo‹/Vήoœ0a‚+$$Duν₯—^RVnΪ΄I3M˜š,Bω<ιΚ•+’sηΎωσηs€|*Y²€hίΎ½^εεΛ—ΕŽ;<ϊ"'!!AΩ3£K—.^1βΙ)RDΌύφΫ–ω} 3\Άl™—§!!!βω矷Μwύ0ϊυλg¨Ϊ8--M$&&ςΒΐOΠΰsξάΉ#† ’Oš4Iην(δE¦{΄ «rΰ˜˜TΦ―_?£rεΚ–ψ6lΨ Ή\ξŸ•œ’’"<¨4Loϋφν²eΛ–Ž£Gς¦‚¦―\Ή#χμ„„eΟ†.]ΊxeθαkŠ+&bcc•_Bό'°vηη†!6oή¬΄Ο₯|Τρ£ͺ\Ή²Y±bE%Χ1#ό !’““e›6mͺίr€7θέ»·Q€H₯5,[ΆΜ#νXU;Η)RD΄oίή‘~ΫΰΑƒ-ρ[Ή;\7nœn–x™–€zp^^žXΉre‘Άe:$U­«rχPδŸΓα:tPώ›έΉsGΈsI“={φ( ‚‚‚D͚5½:¬R₯вϊ“’’όˆξcϊτιZίΎ}υ›7oͺ.,Ι"Σ€ ΅s»aΓeg6ρ>V™ͺΊ~··»wοV Υ¬YΣ”»3¬²eΛ*;φΕ‹•€υΐ,]ΊT‹ŽŽv€₯₯©.,§D‰Κ§ΗΕΕi…ω’FΥΊ°eΛ–54h`‰0 Ο*›ΆΈkσΓ0”O!­]»Ά%ΎΣ‚Wφ7€₯₯ywz ΐ­ΰWμάΉS6mΪΤ±oί>Pπ3ͺ§ίΌySΔΕΕJ{Φεr‰-[Ά(i+ΗΔΔxύ¨'_T©R%Sεh―»ΞŸ?ο–Ο9wξœT=’N:^*;φ₯K—”€υ8TVwαΒωΔO8ώύο;»wοξυ Qp—ή½{£Fsss•ΥπΝ7ίΘή½{»ύsχνΫWθ›Œ<ˆUF’!š6mj¬Z΅Jι ‹ .Έ%=>~όΈ;>¦@ΣΣεβΕ‹½zЊΚiΈ©©©ΌIπ#@ˆ€€$ιΞ΅B€ί’˜˜¨Ί„|ΛΞΞ½{χvL›6Ν5zτhε‹|€”(QBtξάYiΰ±zυj-//ΟεηηηΦΟU΅ώŸΓα:uβ9γ₯š5kfZ΅Ji ξ œŽ?Ό0}ϊtmϊτιͺΛπZŒp/@ˆΥ«WΛW_}UW]`uN§SŒ3F?zτ¨œ:uͺKΧΉl`πΰΑJkΧ‰M›6Ɏ;ΊuΤ\||Ό’P322,Y²€ŠCΓ κΥ«§ΊqρβE·wgΜQ’`Xΐ½Όz85¨0}ϊtνΙ'Ÿt\Ώ~]u) \ll¬ͺ΄†oΎωΖ­mΪΌΌ<±uλV%η'Ÿ|’Ρ^¬FΚ§oηδ䈬¬¬NjjͺͺJW―^¦©ό”`€πβββd›6mgϞεΝ*Ÿ |3eΛ–IΓp_ »vν’7nάpΫηεG=θ­{±Η{ΜΈœ?ΎΐEΈk$!Τ1 CάΉsGu,‚Ρ‘C‡dΛ–-»v풁 ΐ§ 2DixρβE™ΰΆ{±ͺυjΤ¨aΦ]›Π‹‰₯K«.C€§§ψ3Tn^χΉ}ϋΆκX @jjͺˆŽŽv,[ΆŒϋ)ŸiΦ¬YSipυΥW_Ήν>ΌaΓ%χtFΩCι₯•Ž·nέ*πg\»vœ6ΐ@wΡa€ΚΙΙ}ϊτΡ'MšΔ |ΦΣO?­tΰW_}₯Ήcπ;wĎ;”=zτ`ύ?(Uͺ”κΔνΫ· |³Φ±=άΉs‡ €‚άΒ4MρΞ;οh#FŒΠσςςT—7lΨ0₯kŸ₯¦¦ wLNHHξ=•_‘‘‘’M›6ΚGŽ‘ΰΒΓΓ•Ž=‡oήΌ)rssέT TRq?`M€ΰFŸώΉΦ΅kWGff¦κRΐ£*WlΆmΫViπ±xρβ·mγγγ•΄»tιb8‡†›………©.‘ΐ‘OvvΆ›*j¬ΰ.@p³υλΧΛΘΘHGrr2S.ψΥ›|ύυΧή 8>>^Υτ_ε£ΖΰͺK(pθ“››KΖ&άE… 11Q6oήά±yσfΠ|FίΎ}’E‹*;ώΕ‹εΆmΫωΎ{σζM‘bgw‡Γ!ΊvνΚϊ6Q€HΥ%x ΣνCΧY’ΐ@!ΉzυͺθάΉ³cώόωάkψ„bŊ‰ΨΨX囁<κ»eΛ©bΗΜΘΘH344ΤγΗEαπχχW>š³ η1λΫ‡Κ—2¬…N)’;wξˆ!C†θ“&MMSy 7OV΅ώ»Ϊ‹F΄§Σι¦J ZPPκX ²»;0@gvΧ₯K³\ΉrΚήxdπ† Xf…πΜίίΏ@=ΣFν#((ˆϋ ! xŒΛεŒ`w𦉁*½Ω-\Έ0ίmάΜΜL±oί>€5kΦ4kΦ¬ΙΓΑFςςς”―[ΠΠΟΟΟM•@5¦Έ‹<`μΨ±ΖβΕ‹]ͺK€B§zπΒ… ΅όŽΈώώϋο5#·ύg?VΨ@£ ;[a3 NJ)h{Έ‹ Q‘"EĜ9s\Σ¦Msi·\Ύ‘aΓ†fσζΝ•[Χ]K—.ΝΧM7..NΙ¨-Υ›¦ΐύ¬t`‘"E¦m @H©|@*‹ 7 …€dΙ’bνΪ΅ΞaΓ†ΡΉΰsžώy₯χΎΩ³g[>,S¦ŒˆŒŒ$h±™λΧ―«.‘ΐ#‹+ζ¦J RAƒ`φB… Z΅jζΦ­[ΡΡΡtμψ€Ε‹Wvόψψx™œœόP‘ή‘#GδΉsη<φιΣΗ`³ϋΙΜΜT]‚(Pϋ£hΡ’¬hό†ξEnΦΊuksǎΞ:uκώπYAAAβ駟V6 Π0 1gΜ‡jλͺšώΫ·o_FˆΫPFF†ς9—αααώ •>άƒ©άξEnΤΏcύϊυNw4Όΐۍ9Riΐ5wξ\ιrΉ~σί[»v­ΗΫΔeΚ”mΫΆ₯snCΧ]S]‚(S¦L?#44”σΣΛ•,YRu ,„ά@J)ή|σMcαΒ…‚»vΡΈqc₯›œ?^[·ξWGcέΊuKlέΊ•ιΏp› .((₯ΎξΚ•+ηŽr /€άΛ‘ΊπvώώώβσΟ?w 4ˆ©\π3#GŽ4vξά©,ιϊμ³Ο΄˜˜˜Œ—·nέςdIB!žzκ)ž6tλΦ-qεΚ₯5„††Ίeν·rεΚ™B₯aζœ9s\={φδZyD¬ΰ^€P₯J•K—.u2 ξoΐ€Ζk―½¦gee)9ώΚ•+΅ττtΧƒFΒΔΕΕy|FLι₯E»vνxnΨΠ™3g€iͺύiΓΓΓέR€F†!BCCU—Άΐ`xDuλΦ5wξάIψΏBυf ΉΉΉbήΌylσͺΨ€wοήL΅©””Υ%ˆˆˆ·|NΥͺU•·o²³³U—ΆA cǎfBB‚Σ c°:Υ›όγΠξ7*+%%E?~άγ »ΪΧαΓ‡•ο\‘B·΄MjΦ¬©Όsώόyεί'Ψ δӈ#Œ5kΦ8‹/Ίπ ͺ79qβ„όώϋο$¬]»ΦγαBι₯ΕO<‘ύτS­S§N?i礼ώΫ«W/¦ΪΨ‘C‡”·ojΥͺε–Ο©X±’$rrrάςyβπαΓʎ vCΡ°aC³aΓ†Ό‰†Ηœ?^z[$ζϟοκΥ«α<‚§Ÿ~ΪxύυΧυΜΜL%Η_Ήr₯vζΜ£rεΚ¦B8N±qγFΟ†ιΧ―Ο›ΚΙΙ±ΔΐΪ΅k»₯]―išhΤ¨‘™ μo:{φ¬όσΫΎ[·n•žή™8<<œέmlΫΆm2//Oi RJ·έΧ€Iεη«·½4«"€_Ρ Aσ‡~p5kΦLyΌέ¨Q£”ΎHωμ³Ο΄»M\\œΗΫΑ½{χ6&ΰΨΥζΝ›•χ­*T¨`-ZΤmŸg…π›oΎQώ½€p3€ˆ‰‰1·mΫζ¬X±’ςΖ/ΨAΓ† M•`€¦¦Š₯K—jB¨YένMΕ¦2?W»vm·~^dd€ς6ΠζΝ›εΑƒ•·ΰνΰ>Ǝk¬^½Ϊ’Ί°•_|Qi6sζL---MμίΏί£B™2eDTT”ς0…γμΩ³rοή½ΚCͺΗά­ηX͚5ΝrεΚ)=oMΣό1ύV( n€p‡Γ!¦OŸξš6mšKΣΈE€»υκΥΛ¨P‘‚²@aΣ¦MrκΤ©ΊizΆ„ώύϋ3ύΧΖ–.]*=}NέO«V­ά°«΅{Χόωσ5+μ° ήŒή-όWHHˆXΆl™Sυθ°3‡Γ!F­τ>;eʏ·Μ³ΕΖώυ―)οWI)E«V­άΦuξάYy˜——'^|ρEχ`'ΚT`UͺT1·oίξ|ςΙ'iY@!{ώω獀€eΗwΉ\=^έΊuΝ¦M›ς|±©;vΘ}ϋφ)V£F 3,,ΜνŸΫ½{wCΧu·n~mήΌY~ϊι§τ_ΰqΰσZ΄haξΨ±ΓY―^=:gΰααα’_Ώ~>3"ξ™gžρ™ΏΥύύο·DŸͺ0F !D©R₯ ν³σλΥW_Υ·mΫ¦ϋ¬5ώΨΊpα‚ŒŒŒt|ωε—τiΰ!p³ΰ3ͺW―nnΫΆΝm™7Ψΰˊ)"FŽi©PΑbccbŊ©.nfš¦=z΄₯Φ‘λΩ³g‘_G={φ4*Uͺd©6ΤΝ›7Ε3Ο<£9RΟΘΘP]ŽGdgg«.€—"ΰZ·nmnίΎέY»vmK5\ΐ׍5Κe•΅Ε܍ιΏφτζ›oκσζΝ³L?ͺ|ωςfΣ¦M ½}£λΊ3fŒ%ΟιY³fiuκΤρ›7ožfšφjꙦ)vνΪ%ηώG―_ΏΎ£wοήΥ5πN–yp@a6l˜ο W] ΰgΚ–-k™Fέ)""BΔΔΔΨ+‰PμΝ;233Si ϋΏ«½ϋξ»–κCυκΥΛ”3ƒGŽiΩQ­iiibȐ!z“&MΛ—/χκ 077WΔΕΕΙΡ£Gλ*Tp4oήά1yςdνΘ‘#211QuyΌ”₯^ΰNRJρζ›osζΜq)RDu9€xε•Wl0ΐp8¨γNW\΅jΥςϋΏϋ?-77Χ£ΗΎ}ϋΆ:t¨ώζ›oκ=πC>|ΈΗŸΠΠPρΒ /XϊzέΏΏμΥ«—ή€IΗgŸ}¦έΈqCuIΏΙιtŠ;vΘχί_λάΉ³#44Τ/&&ΖρόC»xρβOέΤΤT™••₯ͺT^Œ€-ˆyσζΉή~ϋm—κZΏE‹f›6mΌwΈΞ}0ύ·p\Ύ|YŒ5J―Z΅ͺγƒ>ψE8Rβγγeƒ ϊΧΏ,ΧwjΪ΄©G¦ήλΥW_u{ςdώύςωηŸΧΛ—/ο7rδH=..Nz:8~Γ0Δή½{ε‡~¨uοήέζΧͺU+ǟόg}έΊuςζΝ›Ώϊί'&&ZfύIήΓr1(¨R₯J‰οΎϋΞ9hΠ :_ΰ%^{ν5Ϋά³kΧm>ώψγΆ 4­ζβΕ‹rβΔ‰z₯J•:tpLŸ>];qβ„[C‘7ΚΨΨX½C‡Ž“'OZ2pyώωη=~έ”*UJΌϊκ«^s½^Ώ~]̚5K‹‰‰qDDDψυοί_τΣO΅H—Λ3ο‰ΟŸ?/ΏωζνΟώ³ή±cGGXX˜_Σ¦M―ΏώΊΎzυjyύϊυ|} €GΑΌΆR―^=sεΚ•ͺU«ρ/kΤ¬YSKJJςϊŽ-£<Ηεr‰ 6Θ 6θBQΉre³uλΦf‹-Μ† šuλΦ5K—.ύPŸ•““#φξέ+W―^­­X±B=zΤηbpp°xϊι§•œkγǏw͚5K¦¦¦Zϊ;ϊΉ¬¬,±xρbmρβΕB!BBBD£FΜZ΅j™΅jΥ2kΦ¬)Κ”)c–+WN„‡‡›Ώω™·nέW\‘iiiβςεΛ"==]ž:uJž8qBœ8qBžωΔ΅mΫ6ηc=ΖΉtΊuλšK—.u8pΐωΜ3Ο‡CuI– Ϊ΅kgάΊuKu)Ό ΌΖΤ©S•Ά_ǎλσ£΅T ―ΌςŠQΌxqΥ₯<MΣΔsΟ=g$%%ε7ŽpλWΤ―_ߜ7ož+%%ΕωΦ[oΉ*WLPϊ_!!!’oίΎΖΒ… ]ιιιy+Wt-ZTuYΌ ΌBRR’\½z΅²φkΉrεΜώύϋ*`~τΡGσηΟη͘1ΓU·n]― …Ϊ΅kgnίΎέωΩgŸΉ¬°‘·¨P‘‚9i$#99ΩωΩ»σψ˜ώΰίsg²‘DKˆϋΨ’vŠT,‰ΦZ[-₯ͺEO₯ZͺΪςԍRڊΪw’DΠΨc'– bΙFˆ%‹03χžί:σ»w2[’‰aςyΏ^χΥ€&χžΉχάsΟωžεξΨ±CΧ·o_‡/ΰAAA|Β„ ξέ»uΪ5kֈύϋχ—<==4x Ϋ ^σηΟ$Ιqρ·±cΗJ...;><ειιIcǎ•ΖŽ+ΕΖΖ²?ώψCX½z΅pώ}G'MA­VSxxΈ4aΒ©uλΦ/L°ςy$uι…wιEΌs玸jΥ*!**ŠΕΔΔYYYŽNžέΥ]›·oߞΏςΚ+ΌS§NΌR₯JΘ?PhΐsΕΟϏϜ9Σ‘ozEcύωsγΖ ΆtιR‡ώ+Q’=£ž3-Z΄ΰ-Z΄ηϟ/ξή½›mΨ°AΨ²e‹žžξ°4Υ­[—ιt:166–νή½›EGG ±±±]#΄ <==©iΣ¦ΌyσζΌE‹ΌM›6’ŸŸŸ£“e7žžžδθgzγƍq€πœρυυ₯I“&!Π “'O?~μ°γ2D*[Ά¬ΓŽ–ΉΊΊR·nέx·nέΔ_ύU0‰1F~~~ΌzυκT·n]C'88˜»»»α7[•,Y’ΪΆmΛΫΆm«Β&&&²sηΞ±kΧQZZKMM₯ΤΤT–ššJ·nέbφš>^ͺT)*S¦ χσσ£jΥͺρjΥͺQ`` ―V­―U«αe&ΰHΐsmΒ„ *GύΧ΅kW^―^=4ά_P*•Š^zι%ώK/)!ηœΣΣ)==έΉs‡΄Z-=|ψι½D‰άέݝʖ-KΎΎΎT\9ϊ^PΥ«WηΥ«W7{ηζζRzz:Σιt”••Eςfgg3­Vkψlι₯9ΡΣ|εννMžžžTΆlY^¦Lrζ‘|πβCž[Ώύφ›πΟ?0λŸ,:}τ‘CΧ―‚’Α£ *P… δ!z‹!K£σ'ΐ)8l*€%Χ]c}τ‘C‡Τ΄lΩ’‡„„ /4ΰΉ#I1BεθΕϋ§M›†ΡπΒCž;ί½Γ§ώ6oޜwι£ΰ…‡ |θθ€Π7ί|ƒΡΰ4‡KOO§N:©ΊξQϋφνyhh(F€Σ@θργΗ;φΕ‹Y»vνΤqqqώ1ΖhΦ¬YύN@ °°0uΧ]Υ6lžεϊ{Λ—/ZΆl©Ύ|ω²ΓƒDD={φ”Z΄hΡΰT(ζt:=z”νάΉ“υιΣGΰςΩgŸ©Š2(wζΜͺ2dˆ*33³¨“/...4cΖ ΙΡι°7ŠΉ³gΟ²μμlΓοiii4cΖ ‘N:κ5j¨ΗŒ£Ϊ΄i“••U¨γh΅ZΪΊu+{ν΅ΧTMš4QοΪ΅λΉυ§7fΜ©nέΊύNGνθ€c>|Ψl .11‘-X°€-X°€Τj΅ͺf͚ΌaΓ†ΌqγΖ<((ˆP©R₯ΘΫΫ›—.]ΪπwZ­–’““Ybb"ΕΕΕ±ύϋχ³˜˜!##γ™|§όςυυ₯/Ύψk€SB ˜³”ΣιtΟβγγٚ5kL~ΖΫΫ›rrrH_¬XΪ—_~)Κ˜Ξ@€bξΘ‘#v›ŠϋΌ¬η—AAA|τθΡXϋœΦ(ΖΣΣικΥ«ΟΥZ|Ο’ ΄`ΑQ₯R9:)E@€bμΠ‘CΕΊ>8jΤ(©M›6xρ8΅b]α(ξl]ΟU¨PfΜωb-VPcφ\οE3oή<ΌψŠŠ)­VKǏ/–ΐΧ^{M0`^όΕ€ΕΤιΣ§Ω£GŒgΞΧΧ—-Z„©ΏPl PLΧυ~ωε±B… ŽNΐ3ƒ @1UΧ1b„Τ―_?Lύ€b@€bκΠ‘CΕ*Ψ€IώΣO?aκ/;C·oί¦7n›`… hΛ–-’‡‡‡£“πΜ!P ψΰiΜ9’£Σ`O™™™tσζM–””DiiiμΝ;τΰΑφΰΑΊ>‰’H’(Rff&#"ςππΰξξξδξξNδεεEώώώΌB… δοοΟ+WL\ΠP”…=z¨ΆmΫ†Z8@!xzz₯K—t•*UΒ(&p8z@aξάΉ’›››£“πB›4i’ˆΰψ‹ΦP:uψ‡~ˆrž˜ yθt:jΫΆ­:66–9:-/777:tθiΣ¦ύΟ Œ„<Τj5EDDˆ₯K—vtR^(³gΟό€η €`R­Z΅ψφνΫu%J”ptR^ο½χžτώϋοcκ/~όx©W―^’‹‹‹£“P`‚]eggΣφνΫ…-[Ά°Ώώ[Έw“`΅ZMνΪ΅γ=zτ^ύu^½zuŒn§€ I’θόωσ,66–ΕΖΖ²+W°λΧ―SJJ ΣιtŽNcΎΎΎT­Z5Θƒƒƒy‹-ψΛ/ΏΜK–,ιθ€Ψ€πΜi΅ZΚΞΞ¦¬¬,–››KYYYŽN89WWW*Y²$ωψψpwwwB Šœή ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€N @'† €Cΐ‰!ΰΔpb81œ€NLνθ8Rtt4»zυ*#"ςςς’AƒIφΨοωσηف˜ώχAƒI^^^φΨ5s:Ž–,YbθΔ ζ͚5γŽLΌψξή½KλΧ―7䫐^³fMδ«bfΥͺUΒΓ‡‰ˆ(00‡††"ΐ3qαΒΆ~΄ŸŠγœk€’””Δrss©vνΪf+.-[ΆTΗΖΖ2"’φνΫσ˜˜=ŽύρΗ«Ύώ{ˆΘΥΥ•²²²΄φΨ5<ηrrr(##ƒέ½{—Κ—/OεΛ—ηΈφ`OηΝcAAA†Nά₯K—Šo½υ–]:° ψZ·nΠ―_?•ώχC‡ιZ΅j…ΰO1"Š"yzzΊ<~ό˜ˆˆ>ώψciφμΩ’ƒ“ΕΔ§Ÿ~ͺϊφΫo ν§μμl­‹‹K‘χ+IΕΗΗ3___^Ύ|ωBοΰE†€`Ρ‘#GΨΨ±cU–>γζζFήήήδοοΟ_~ωeή£G^©R%‡VΏωζaΚ”)*Ξ9}πΑœ9sςT^8ηtαΒC/ΣK/½d·4Ÿ9sΖ°ίZ΅j!δδ.\ΈΐώψγaϋφνμόωσLώonnnΤΎ}{>lΨ0iΰΐ’ ΨΆςBHHˆϊΑƒDDΤ¨Q#ΎtιBWΐΏώ{aŊ†μάΉSηλλ[Ψέ:Δwί}'¬Y³¦PΛXΌτKόΟ?DΓ^8ςg ‘}Ÿ_ΟΪτιΣ…Ν›7[Ό—½ΌΌΘΫΫ›Χ©S‡Z·nΝ»wο.ΉΉΉ=«$ς|Ε{‘σUQΉ}ϋ6uοήέjϋI₯RQ™2e¨|ωςΌeΛ–ΌkΧΌzυκΟύωΌrε ΣˆžΦ?˜(fβββ ePύϊυΉ=‚:ŽBCCΥ{χξeξξξ΄zυjρ΅Χ^C‡[‚Eǎc'OždΦ?IDDlΙ’%4nά88p 4ώ|±tιEš>S΄Z-}υΥW*ΟΦY~ψαaƌ’»»»βsΧ―_gYYY†ίνYΡ•?ΐPv^ιιιτΩgŸ©ώψγA§3=xτΙ“'΄kΧ.ΆkΧ.՜9s„7ŠσΔέ»wiϞ=†<ΤΈqc»δ‘θθhA?ϋψψΠ‹ό#"ŠŠŠςQ6™„{^Hς@ Τ Aƒ6/οάΉΣΦ{™mέΊ•ˆˆΚ•+§š:uͺ8vμX‰±B sζΜΓΟSος:{φl~κΕDD,""‚A ή½{KσζΝ“*Wόάή―ςϊ+‘ύκΆ8wξœαg{ε½έ»w³½{χ2"’ǏΣόωσ‘8C,:wξ\ΎkΦ:Ž"""„°Γ‡λ*T¨PI3‹1FςQVeΚ”!S#πŒ+9φ €₯₯ΡνΫ· Ώ#θ3gΞ]»ΖˆˆκΥ«Η?ψΰ»>μγββXxxΈκζΝ›yξ‘₯K“ΏuλΛΝΝ5ό“'O²–-[ͺŽ9b1xφμΩ"έ#―X½Θ’§λlvζΟΨ±cUZ­–ˆˆ:wξΜϋυλ‡ ΄ƒΘ€Υ«Wη%J”pdr Μx$Ύ­ξάΉCοΏΎκΤ©SlΙ’%Εk'ς|…ςΡ4γΊ£­$I’uλΦ ϋφνΆmΫ¦{ωε—ŸΙωMMMeΣ¦M3TŠί~ϋm©yσζf-―χ{xxPέΊu‘¬˜8q’κώύϋDDΤͺU+Žε &++‹δuj{ΥjeΈ£bŊΟ4O‹’Hcƌ1 L η@‚#!Ι+~~~τηŸζζτψρcΊtι‹‹‹c6l²³³‰ˆθΪ΅klθΠ‘κ;wΪe]=[©ΥjϊυΧ_ΕΟ>ϋL(Y²$͟?_45νR ±ηTS³μ±[Θ§9sζ¨ξή½KDD£GΆλƒ666–uιE­_$›θinΒ„ RxxΈT¦L"z ?pΰ›cΖ EE<&&ζΉ\ G^ Ϋσ-zzzR`` ϊg¬¨‚]œsκίΏΏ:55Υ°9sζˆ_|ρEžΰŸœ‹‹ -Y²D”/Zγ?š-εy³|ωςdiτƐΉqηLίεE!Ώ§πrΗ:}ϊ΄Σδ‚άΛoΌρ†$Ÿ>uωςe–––VΙ+VœιΕ2EE’$ΊxρbΎ§IϋψψΠϊυλuςδYZ;؞δχ˜΅εrssI߁O„ -P±ωΉ¬Z΅*/Uͺ”#“c7ςο₯V«©^½zΘ#ΰP‚YΖ=σΆ>Τ‚ƒƒŸKIIyξ€Z­–._Ύ\δoW―·υ­―`?Ζ@{Mυ\Ώ~½°oί>ΓΎ  ΩΊΆ ΏΏ?οή½»α³ΗŽSΨυŒGήΪk­>γJκ‹<ύΥΈ—ξ’'?ηώώώv1 ωηLyΎκΧ―_ z†|y(yΎrqqΑΪo&$&&2ύ27Dω}[ͺT)>|Έαœfggx=ΑόΓZΐςΒ… L€ΦΙΛ0OOOzήτόΌrΦΡ§ς΅jΥβxƒ=8¦ƒYνΥzςδ‰βwω”ΉνΫ·³˜˜θiΕƌ’΅·ω}ύυΧBVV#"jΪ΄)οίΏžΰΛ?ό θƒŽuκΤα¦ΦΊ|ω2“§Σ–οvεΚΆdΙ!&&†%''cŒͺV­J:uβΓ‡—ωγǏ‹,°xώόyφϋοΏ ϋφνc©©©€V«©jΥͺΤΉsgι­·ήβϊ·Κ-]ΊTΈtι#"jΫΆ­€Ÿž­Η9§Ι“'¦OυμΩSjΥͺί±cϋυΧ_…³gΟ2FC―Ός _Ύ|ΉΙαςœsϊϋοΏΩϊυλ…'N°ΜΜLrww§ΐΐ@κή½»4|ψp©dΙ’ωϊ~—/_f›6mb»vνRSSιΑƒδγγCυλΧη―½φ8p ΩQvGeλΧ―7DZ£££ ΧΐΝ͍Ύϋξ;Γb{υκΥγΓ‡Οχt1NG“'O6ΓΗΗ‡ς; C‡|γƍDτtQΰγǏ³ΕυΉyσ&ΛΜΜ4όnΗ€ΘΧσδϊu -9uκ[Ήr₯ΛRRRH’$ςυυ₯f͚ρΎ}ϋJ:urH­¨¦οΛ₯€€°M›6±Ώώ[Έqγέ»wΌ½½©zυκ& 6,ίyόόωσμ―Ώώ>Μ’““IE*]Ί45mΪ”χξέ[κΪ΅+7W_Ώ^8zτ¨α§N*Z;~ZZΝ›7ϐχΝ•‡¦lΪ΄IΠ―[IDŠ·RsΞiβΔ‰†ύvκΤ”ΆΦk IDATIκ₯‹ΩΌpυκUΆbΕ Άoί>!11‘8ηδεεE5β}ϊτα=zτŠ²£$11‘­X±‚ΕΔΔΧ]#I’¨L™2ϊγKέ»wΟw>>pΰ[»v­pτθQ¦_²B… Τ’E >tθP© Λ^δζζςεΛ…Ν›7 —.]’μμlV‘BήΈqc>pΰ@Γ9–jάάά¨N:Vuοή=ZΎ|Ή°kΧ.Οτy―I“&|ΰΐ’q9τ¬δgt’œF£Ιwΰ$==6mΪ$μΨ±ƒ%$$°ŒŒ ςττ€jΥͺQΧ]₯#FHωq’ΐ"""Ψ‘C‡„›7o’V«₯R₯JQ£Fψλ―ΏΞ_ύυ|ηνττtZΎ|Ή-\Ήr…DQ€²eΛRpp0lΨ0I2 ω 5kΦ΄k#υμΩ³lεΚ•ΒαΓ‡ΩΝ›7‰θιΛ²‚ƒƒω€l~–;vŒ­[·N zšχ§OŸ.=­?όυΧ_ž={ΨνΫ·I₯RQ:uψ€xώύνφ¦θŽφ2ξΠKOO7όόωηŸ^²T»vm>rδH«eudd$Ϋ·oŸ!ƒ 0@:xπ “―εxεΚΓΟIIIL^v:T’§I€Α°Ϋ₯K—ΨŠ+„ώω‡₯§§“J₯’ΊuλςπΎ}ϋζ»>uθΠ!ΆfΝαθΡ£LΏœAωςε©E‹|Ȑ!R³fΝl:―QQQlχξέΡΣ;θ;dO:ΕV¬X!tθPɞ:yη·~Ώ?¦υλΧ ›7of ,33“*V¬H:uβcǎ­ΝΙΙΙ‘ιΣ§I=€ΆmΫζIσO?ύ$θο›φνΫKaaa\’$ŠŒŒdkΧΝ;ΗΧ·o_Γώ%kϋΝΞΞΦ‚`ΨοΤ©SMξΧΗΗΗπ™±cNJ¦>³rεJEOž<©5wά'Ožh>ώψcΡΕΕ…ΛFΎ•(Q‚τΣOΊcǎiεϋοΏ7™Ζόl=Όχή{’ό»o^^^όχίΧιt:MΩ²e ήΌyyŽŸ˜˜¨HγΝ;΅C‡χΩ’E “ΧδθΡ£ΪƍKζBDάΟΟOϊηŸ̞Sγτ 8Πβχ#"^»vmιόωσ&χ9i€<ι7·ύη?1™'¬mΫ·oWœ7sω/?ϋψσΟ?σμcΛ–-ŠΟόϊλ―…ΞCœsM™2e ϋ ΅xΏ]Ώ~]ϋοhE‹η2$$DJKK+tΪς³I’€ρςς2€‘kΧVˎόlwξάь;Vtuu΅ψέ+V¬ΘmΝγ©©©ŠςΞάΦͺU+ιϊυλ&χ9sζLE™uόψq«ΗώπΓΗ\·nΝy©kΧV―Ώ~[²d‰Ιύ>xπ@3jΤ(Q­V[όϋf͚I—.]²ι\ζg{ψπ‘ζ½χή³Xvoם”ššjΣ>/\Έ }ε•W,žΖ2dˆ˜msZώϋomεΚ•-ξ7<<\|ψπ‘ζ₯—^2|aΓ†σΏ(ŠšΩ³gλΌ½½-žƒππp133σ™ήΛZ­VγξξnHCϞ=m.›»tιb8‚ π{χξYΜŸ|ς‰X’D ‹η L™2|Λ–-6ε̌ ΝπαΓEƘΕ}6jΤHΊpα‚MϋΤj΅š―ΏώZηιιiqŸύϊυm­-^ΌX'―™Κ«οΎϋ˜››«ΈoΌρFž“Ζ[jjͺ¦OŸ>VΟShh¨tλΦ-«ϋϋμ³Ο eZ“&M€άά\«υ³nέΊI?ΆKž>}Ί’>}ϊtΎΚ­_€gψϋ;vώ^~_Χ­[ΧκsνΙ“'Γί”+WŽgeeiΚ•+gSΉMDόΜ™3ŠττΡG†σ[³fMιΡ£GšώχΏΛπ^½z‰Ζ¦ο―νΨ±£ΥgΛΰΑƒΕ¬¬,«ϋ“·Szυκ%ή»wΟκ}9rδHQ’€η7ί|Σζϊζwί}§γœkξίΏ―‘§ιν·ίΆz]ΌxQ+―Ώϊ꫊<Ρ§OC:&L˜ ΖΔΔhλΤ©cφάͺΥj>cΖ ›λ999šώχΏλB‚ πqγΖ‰:ΠχΦ­[·4ς}oΪ΄I₯­Q£†ΩοT¦L~θΠ!‹χΰ‘C‡ujseΌ―――α3 .ԝ>}ZΫΌys³ΗvssγΛ–-3y>ΫΆmksύiυκΥ&χ±rεJέΏo+6»yxxπ9sζθ “Ÿ±Ο @lf·F °š5kΪΤΘΦιtšΰΰ`ΓίΉΈΈπ€€$“…mύϊυ Ÿ ³Ί#GŽ( ρ΅kΧζ)4“’’ŸY°`Ι‚υσΟ?7<8Υj5ΟΝΝ5yΜGi:wξlsAήΎ}{ΕgwξάY¨Fmff¦¦uλΦ6Ÿ1ΖǍ§¨˜DGGη9Ύ<ΐ$ 3Ή~ύϊε© όρΗ:kyύζξξΏ9bρϋόπC³•Ο£GjΛ—/oσ5©Q£†”žž^$e©Ν8€=aΒ»4T9ηšΛ—/k«W―ns^rssγG΅x}Ν;g5°#ί*W,™ΚγΫΆmS|οΏώϊΛb>Ύ{χFHhΤ¨‘”ŸJb~lκΔΗΗk«U«fσ>Κ–-ΛΝψ ²%$$hkΦ¬iσρ«V­*έΎ}Ϋβ>Χ­[§σππ°ωήhΡ’…dK fΡ’E:k ϊ­uλΦ’<Ψ1hΠ ³ω?''GcK _Ώ΅jΥJ²΅!oνΒ… Š<ύωηŸΫt/Ÿ?^«R© Χ‘C³υ‡δδdmPPΝη@₯Rρ¨¨(‹ω011Q[«V­|εmkAΐΜΜLM§NlήgHHˆ$Š’ΕστρΗΫ¨0»|ωε—…ξxΚοσ=00Πj‡λ―ΏnψN}ϊτΣmn+hǟρΦΏΓρ]\\ψ“'OςυχƝ•ρρρ†|1pΰ@ΓΏΉΊΊrkA•ί~ϋMQW˜5k–.--McλωvuuΝ“~y`½sηΞ’­ŒΙ“'[=Ώ7nΤY ΒΛ·f͚IΦ:Qδν”Ρ£G‹ςv…₯mΜ9ΞίςcZΫΆoίnΈΎUͺT1ό]ϋφν­Άy†Θ+T”!ς`_xxΈθζζfSš~ωε«ί=55U“Ÿrsψπα…ΎΏ’’’Œ;ΩEy9on+_Ύ<πΰΩύ.Z΄HqŸ$&&ζ)‹ο›3fXνˆ!zΪ~4’sˆbm3υlΘOΩmλύ‡ ›|C›ΙM«Υjδ[zζ%IŒ?^Qh7Ξδίi4ΌσΙ'ŸXέΏqeG^qo‘‘‘ŠΘώύϋMVΊε•Θ:uκ˜}2Dρ}\]]ω¨Q£ΔΘΘHm\\œvοή½ΪιΣ§λΜυΈft”$Išž={ζ9ώθΡ£ΕνΫ·kϜ9£ŽŽΦNžλααΑ»uλ&;V9r€ΈxρbE%aνΪ΅ŠFͺŸ6mšξτιΣΪ{χξiΟ?―|X₯ŠŠ.^ΌXρύ&L˜ κ-**J{χξέ|_ F£¦΄kΧΞ£ΞδΫ AƒDω9±Η¨œΏώ[q_,]ΊΤd0##CS©R%E₯―S§N† tηΝΣ^ΈpA»|ωr]νΪ΅Ÿ8pΰ3«|lήΌYq}ψγ»ŒΜΜΜΤδωξ‘‘‘ΪΤΤTV«Υ\ΌxQϋφΫo+ςc£FΜζ…μμlqͺM›6κΥ«uqqqΪψψxνͺU«tΖνnέΊεΩηΝ›7σ5uκΤ©ŠtnΪ΄)_ηI~Ο7Z.\¨“»q#ςƍZγžλ6mΪH«V­έΈqC›žž‰‰‰ΡφλΧO±ί:uκΨe€NjjjžkΩ‘Ci͚5Šγ—―–:’ΆlΩ’: ‚ΐ‡ &ξή½[›ššͺIIIΡ]»Vg<:ΪΪH¨¨(­q#§E‹₯KuǏמ8qB»lΩ2]Λ–-M6Ζ̍θΠιty:F‚ƒƒ₯•+Wκ’’’΄·oίΦDGGk;tθ ψΜW_}e—ϋΙ–mυκՊ{yΥͺUV}ϋφmΕH)AxLLŒΙηό“'OŸ%"ή²eKiύϊυΊ””­V«Υ\ΉrEϋα‡*F ˜}ni4MΣ¦Mϋl€‰‘;sζŒφςεΛΪ 6θZ΄h‘ψLσζΝΝΰ5Mžΰ_«V­€΅kΧκRRR΄·nέμΨ±CΫͺU+ΕgζΝkφ|ύόσΟyžσaaaκΥ«u§OŸΦ9rDϋΛ/Ώθ̍Ϊ°aC‘ςΑ‰'΄Ζ θππpqΛ–-Ϊ€€$Γw2ώή;v΄ΨQ!ο ΡΧG\]]ω›oΎ)nΪ΄IwκΤ)mTT”vĈŠkκκκΚ322 gε¦ϊυλη»P―^=Γί{yy)‚|Ζu³+W˜ λt:<]±bEž““£yψπ‘’μξέ»·"`ΉsηNΓΏνΫ·/ΟώεΟύω+Q’ηwΔΘΘHνιΣ§΅;wξΤ׍K”(Α-λ"##΅ςΞcΖσΝ7ΕθθhmJJŠ655U³~ύzρ½e)°dάNΡηOOO>fΜqϋφνΪΣ§Ok###΅ς:?ΡΣ QAG­νέ»Χp;lg͚₯x6ΚG1Κ;cόόόΈ₯cάΈqC+―KΟtΘΝΝ՘ ŽΥ¬YSš={ΆnίΎ}Ϊ3gΞhW¬X‘kΦ¬™βœzyyqK]>Τ4hΠ O$22R›––¦Ρ_+y^&"ΎqγΖB•sζΜQœK}ώ –/^¬;xπ φΨ±cڟ~ϊIηηη§ψήϊ‘–¦Άόη?†kοννΝM•/;wξTΤ―τy) @š9s¦nοή½Ϊ“'Oj#""ς|οΑƒ+ς¨N§SάƒΖνβ?όS‘GŒσ‘q[ΧΛΛ‹ςΙ'βώύϋ΅W―^ΥξίΏ_;qβDΕΘLΖ?pΰ€έgQ`sή @l&·σηΟ+ Γ)S¦XlΔΔΖΖjGΚuλΦMzτθ‘Ιϟ;wN±[ρ|π‘υππ0ωπώξ»οtςΡ\―|HyίΎ}M~· 6( a???Ιά΄»€€€<ΓΤ}}}yaΑ²eΛΗ―\Ή²dͺ§‰σ§£mόόόl:ώ Y€IžΖ/ZšB• -Y²€’’‘`2-Ζӫ͍T’WΘT*78κ7Q5ς^~•JeqΊΧƍΗ7ξ5-ΘvβΔ E~>}z‘5’6lhψΥͺU³K qφμيsbnΤΪ΄iΣtΆ|Οάά\|j„ &ƒΝE±7’Nœ8a—JρtYK=ͺΓ† mIΓΌyσi57ςR£ΡhBBBχ―©Ρ‘ς^eKAΧ¬¬,Ε”οΰΰΰ|ώ3ή¦Nͺ“_kK =N§yω嗍ΘoΏύΦμ•)S¦(ΞeaFfpώ΄γδΥW_Uί>nj£8Ύ©QΛΧ―_ΧΚ§Πz{{σέ»w›ΌζΉΉΉŠ`Ž &Gθ―“qƒ{ƌ&Ο•$Išwί}7Ο¨sΣ™ΎώϊkEή{ο½χDSA-N§υSΆlYnξΉmοΝ8H}ξά9³χςγǏ5Λ–-ΣΙG¦ ‚ΐώωg³ΧΦΈq>zτhΡά¨9ωΤG"σ£χ—/_ΨηπαΓMN'E1Ο΄sΟ"γϋG}d2FΣ];Γχ―T©’Ι@εΥ«W΅ς‘ͺnnn|εΚ•fΛrωυΧo—/_.pΉš™™©©Z΅ͺb&ˆΉ©r’(jŒΛSsΑGγe`τΟHsε―ρŒˆ5kΦͺlyςδ‰’γ:ΏΣ€W¬X‘Θ;C‡Uό½ρς‘‘‘f―ΑͺU«ϋ2WΖΙ;P­,οέ»—gτ`έΊu₯‹/šL‡qgΨΦ­[M~ξζΝ›Zy'΅§§§ΩQΆ?VΜΊaŒ™}Ύ·Sˆˆ7nάXΊzυjžΟK’€1ξLΆ6zί–νΗT\S£χυΫΔ‰Ηψπ‘ΩύΚσ.c,OZλ€DΔnj#ššΝ€Ρhςάγ–f PΫΤRBœ?]βCށмyσBΥWGŒ‘8?*•ŠΟš5ΛδσπμΩ³Zyμ•W^1{lωσΈeΛ–&?χύχίηι06l˜Ισyχξ]<i­­'ΝηζζΖΝu.qώ4¨-oΛU¨P›»φξέ«˜"n―e° @l&7γΚEΣ¦M₯Ύ}ϋŠΖ[ηΝ%γ©b‚ π1cΖX\d͚5Šύ;vΜκƒX^)hΪ΄©ΙB\ή+Y₯J“ŸΙΙΙQT"§M›–ηα&I’’w΅dΙ’<..Ξbχξέ«x [z YΫt:’]²dI«ΣγŒ–ζŽo<ΪΘά4iω&―8Wˆc+YΆnΏώϊ«βψ–*ζ…Ω4’G;<<ά.tyΛzžςιϋ 4°˜Χ―_―8'…1bλ&Ÿ&₯oD5Κ¦Ν40ωύfm„ηαΓ‡χΊΉ5πδO‹Sφvοήmu”¦|:Vpp°Ω4Κ5KqmΆmΫV¨όϊΖoΨΌ^«qΠΣΪ΄NQ5ςQs5jΤ(T#ΒψΩeνψΉΉΉš *>oj4ΊΌα¨R©Μτ[RR’V~=ΪdΎϊκ+EZ­κΤh4γ©§¦¦3%$$(Žί£G‹k^%$$(ςuɊrλΥ«—β^ξΩ³gž:F―^½Δ6mΪHΖ£Ι|}}M."ߌΛ3KχίεΛ—χŸΉ²ό^πρρ1»|η\sζΜΕ>g͚•gŸqqqŠ‘Q °˜_Ϟ=«Ψ§©QΖk”™ ώι·ττt<@S’D nmz±₯νΏύ―βψΏύφ›Εγηδδ(Τ!!!&Λγe`όόό€δδd³yΥψ\Y zΨ²>}ΊΐTΑAΘΤΉ~ύΊbζFxJ’€˜†κηη'™Λ‡ς“¦–u‘o111ŠγW­ZU²4cβθΡ£ŠΟϟ?ίdzε#­A°:Ε>55U#`9dΊΛϊ H–fLXΝ­½–ŸMށTͺT)“£Λτ›q=Φ\ςφνΫωTι=zδωώΖkIŽ3Ζβ΅MJJRŒ(47ΩxΆΘΔ‰-ξΧψœZΊ­mςŽC"λmωZΌfλ ς%mFeςϋΌυΦ[Š2λwή±ψ½ΗŽks0W>ς3((ΘbύΖx½BkΣ΅ελiV\ΉΘf&asΎ o“δob"":yς$;yς€ΥΧ¨Υ―_Ÿ―X±BΤΏ9̜sηΞ~Μ½IΙθo¬Ύ]Χθ3&χsώόy¦ΉΟνΪ΅‹ΙߜφΡGIΦήφΦΎ}{ξααAΉΉΉΣh‹ΘΘHαƍ†γO›6M¬_ΏΎΕύuοή]R©T*Q|ϊbZγ·Νi΅ZΕ[Š $½ϋξ»ίΰvΰΐΆsηNΓίL™2E¬U«–Ε΄τιΣGΪ½{·ŠˆθΘ‘#yςF£‘ˆˆΓt[΄haqN±6xτθ‘ΩΟ^ΈpAρ†XoooK»Ά‰qή/Θ>maόvκ   »μW~_š}ΣζυλΧ Ÿ3χV2½ΰΰ`.sθΏφ"gόΖeΛ–ΩόŠMsΧM§ΣΡ¬Y³ χ΅{­\ΉrŠίυχΌ±λΧ―~~ε•WΈ₯·6jΤHq>½ΌΌς|¦aΓ†όΐŒHωfGΉΗΣάΉs o˜kΡ’ +Τ΅‘ίS¦Κ½GΡΜ™3 ΗnΪ΄)Ÿ6mšΕ7e ‚@o½υ–4~όxΡΣ7Ÿ;wŽ΄όόκ«― 'ΉN:ό‹/Ύ°x|wwwκή½»τϋοΏ DDǏWœΧ#GްmΫΆφ9~όx«o,υχχη‘‘‘Φ­["’-[Ά .T€CEZ΄h‘aΏΥ«WηS¦L±X»ΈΈP‡ΈώΪ{yyQ΅jΥς€ε«―Ύτ刻»;ύϊλ―’₯· Φ¨Qƒ7nܘλΛΉ#GŽ0KouΆγ{yΣ¦M6έΛ#GŽ”Ύώ{ΡΪ{?ύτSI<¬Q£†Εϋοί °…{ڐζ-Zpwww³ϋ¬W―υΥW9ηOwmκΝλΣ¦Mt:y{{ΣO?ύd1ΏρΪ΅ksύsόΘ‘#¬M›6†΄ίΉs‡Φ]kψ’‘‘‘|ΐ€σUΉrε¨Q£F|ίΎ}LŸξ‚Ύ•;%%…-\ΈΠπΗ―½φšdνmΆ%J” HsηΞˆˆbbbΨΓ‡Ιψϊη—+Vˆ•+W6›O–“““o’—‰7[ύ›;wξΠ?ό š={Ά ΆΏώϋ†·/ΛΣλγγC< "RΤΥδΆoίΞδoŸ>>τΖoXݏžV«Uόn©α'Ο 4°υΙ+βήήήT±bE»μט‰F‘αΖ+Kϋ,UͺΟΘΘ`DDǎc’$‘Ή†`ΥͺUytt΄°ιΛ­VK—.]²ΪaŽΉΚ—Z­.T~4Wρσρρ1ό|βΔ ¦ΣιH­6ύΨ-[Ά,Y;Ÿςk—™™I·nέΚ“ψγA^VNŸ>έjΩe‰N§S-έSλΧ―nίΎmψύ‹/ΎT*•ΩΟλ΅jΥJq]Ž;V `ll,“7 ?ώψcΙάω–λΨ±#Ώyσ&πΰύ;Zΐ`Α‚† dΙ’τ駟Ϊt>[·nΝ·nέJDDiii”œœΜόύύ ϋήΊu«””dHλΔ‰mJ«q0ΦΈΑωΰΑZ³f!Νύϋχ—όόό¬ξ700Π”w>•άά\JLL,ΠqV­Z%σχή{Οβ=Ϋ§OŸ"½§γββθΡ£GdCΕΕΕ…vνΪeφžΎuλΙƒ#C† ‘,\τιςεΛD”χZ-Y²Dhϊχ­ΉVΙσ•©:‘­–.]Κ?~lψ}ϊτι6]ƒV­ZρΉsηΡΣ2ηδΙ“¬cǎŠtȟΤ];‹ιΤVυ<==mIŠYΖΟ爈φχί«άάάy@£ΡΠύϋχ)>>ž:uŠi4Ε~zχξ-͞=;ΟuaŒQƒ ψΑƒ™}ΦΙ;Yόύύω¨Q£Lžγ‹/*:»λΧ―oσχ+Uͺ5oήάΪωU€ΟT=~Α‚‚>ξααA“'OΆΉόάΈq#= β\Ώ~*#o§T―^Χ¨QΓbzοqkν[Υ7-ΏnέΊάΥΥ•τωΑT€χαΓ‡ŠϊϞ=%S—ςvLνΪ΅Ή«««Υ΄€999dd?~ό8“nj#Y *©Υjͺ\Ή2ΧηΥ›7oZM‡)Χ]cϊ "Ρ«―Ύj΅ ’ίߎ˜ΜγμΩ³VΫ{’$)ΚΏ:py{Ε[σRNNέΌyΣζ<ςο(aƒΨΨXfi€Μψργ₯ργΗΫόœΠCL’WjΥͺΕΏώϊk“Lnn.]Ίt‰­Y³†]½z•­Y³FΈwο‹ŠŠ™λ “ΆΦ*%Dyƒv¦‡W\QT<Ν5 ε Δ%Jq₯A«ΥRTT”αά«W/ΙTΟ½1{ožRƏ?¦””–••₯¨l>|XqΜUΠΒΒΒ A•ψψxͺž={Ά\ σi\y½tιϋχeDdΙhΈWΪΆmΛCCC u흖FG[·Ξp^Κ—/OΎ„ΒͺΊuλ*φYΠF„Όάtqq±94dΘiȐ!y>«Σιšξέ»KΎΎΎ6₯₯vνڊίoάΈAώώώ†ί·nέjH«»»;™:Ύ)ςrήΤ})Θ;’ƍgΣ~εχ•ΉΡoφtώόyE‡Υ°aΓ€ξέ»›Μ[tόψqΆzυj!''‡rrrh̘1*΅ZMζ‚ζdκƒΌrϋφνcϊ'"ϊμ³ΟΜiδυl"λΧU~~Mu.»vνšβwγ$IŠQ½]»v•*T¨`qŸzΖεηΝ›7σ\?γQ[Φφi<ΊΚ8½ω•‘‘Aς/kηΧΥΥ•jΧΝυι6u}ώωgααΓ‡DτttΌ©ΡDΚΌhkΐήΈs+33“ΙNλΧ―7\«%J΅‘»zφxvΨΖ3&ΏΏΝέΫΖujSη*11‘ΙGΫrly^*WœΩΞ…όαΫ΅kΗK•*Eϊ<0fΜΥ½{χhτθΡ’=ΦzB=RτΜ·hΡ‚[σε—_ΫoΏ­OΗ‹ŽŽfύυ—πζ›oζω;­Vk<’$_Σ‰ˆ6lhυ3ζŠςMέΊuσT"Ϟ=«ΙgmΊ—μψ†ŸcξE?}ϊ΄’ΑmKOΨΏΗΟWP­V[z}όψq–™™iψ}γƍBTT”ΥQΩΩيϘ« J’Dύ7[½z΅pΰΐvύϊu¦ο-6§R₯Jά\ςϊυλŠ`—½€ϊQqD…―4Z"οΡvqqΙ)ˆό§N*FFF2ύύΏ}ϋvΆ}ϋvuέΊuy—.]xXX˜dKοhQ1ώ.#FŒϊυλgΧήΟύϋχ³•+W {φμa Š …)––0˜0a‚ΈaΓΓ¨΄½{χ²f͚©kΦ¬ΙCCCyxxΈΤ±cG‹Σε‚‚‚8cŒτχΘ₯K—Ψ+―Όb8φͺU«y₯ΨΪτ[Ψڈ”$I¨ ΅iτΡΣN9sΣy¬‘/7Π€I^Ίtι‚μΖΰΤ©SL_'"2 2Εψ;O7;vμ˜!­-[ΆδΆtς$''3ύτ@"Σχ²<Κ£7ήxΓ¦‹––¦H5γ{ΉoίΎRxxΈΕσϋωηŸK!!!*}ψγU}ϋφ•¬]ηcǎ±ΏώϊKˆŽŽf—.]bΖ#ΓL17υέwί•V\)θσΪ±cΗXΫΆmΥU«Vε‘‘‘Ό{χξ<44T27*PN~­A °°0›UJJŠΙk₯ΣιθτιΣ†³΅ξbΛ¬[ά»wOq] sΏ˜ $Ψ² Œœq§haλΖηΙV₯J•’AƒIοΏΎd­Ξ%^)))Μx„ιŒ3 y€jΥͺ|ĈfŸς²[|²tlk –>O”χž9sζ »wοžαw{–ŸΖν[¦cŸ?ήπ³ V—ϊ°aŠοoΛώ‚‚‚δ@Εί?zτˆζϟoΈΎ}ϋφ•Œ§ =ε-/lν 5žΝαξξψ;yy$Š"Ϋ#B.θ³ΓΈύdν;qΝ6ζΞ½ΌΜ¨X±bžε[Œ?CdΫω4žϊmΛ爬—A^^^4oήϊθ#ΥηŸjΧοΪ΅«Τ£G^” x@ς0±FžΥ‚ΖΕΕ….\(FEE ·nέ""’E‹™ ^ΉrE1ŠΗ–‡¦όA[Ίti2΅ξ‹Όrf)Έe­ηLή0#"«ΣLL₯ΡίίίlΚšΨΨXΕρ›5k–ο€ŸŸŸΙυ†δŸ©Y³&·6ΌίψαυοZωͺ{xx©@ΐ‘C‡ΨΨ±cUς‹-,υ ΙΧΪψχ³v™B+oŒΨ2΄ δ[§uXc<ίύμλλK±±±Ί~ύϊ©ε•ΑψψxΟζϟ/”/_žήzλ-iΒ„ Χ* E1BRοβΕ‹lάΈqͺ={φδ+?˜]ΒΐΛΛ‹<¨Ν›7OX²d‰°xρb±ώG46lΨP4όΝ›7 ς2 °kιΙο©ΐΐ@³#Υ’’’Ώ/ΈnΙέ»wεŒ-kΦγœ+Κv{ŒΤMNNVό^΅jU›χ™‘‘‘ψ]ώ’’’£Π ςŒ!2݁UF›±ό|Ο‚’?‹τm[ώK—.άέݝτΛ}wΨι;vŒ…‡‡«ε ΐ—.]š:vμ(’ŸŸ/WΉ»»“V«₯ιΣ§ ϊ€zέΊu-Nτππ +Vˆ}τ‘τύχί [·nδλWeeeΡ―Ώώ*,[ΆL˜7ožhnέ\{_+γ)Ž©;”)S†*UͺT λŸœœ¬8Ύ₯ΰ§1χ‹βo 2EPώ½jΥͺe1fqΠ₯OŸ>Ηlχ΅·6l¨ε-~ϋν·†υτjΤ¨Α‡ fρψωYŸ.?…zςσkκž)ͺς“¨`νyzmYzΘω³ΡΗΗΗδΐcς}YYYtλΦ-ςσσ#­VKϋίk·ΪΤ"²}Δ|MI???Eύξέ»$―TAŸFm«Ÿ7žŽo*sΞmΥj|΄΄~=ΡΣrN~¬΄I ?Χ©SΗζŽ€ž={J!!!Β… …Ε‹ Ζ/};tθ;tθͺsηΞΒΚ•+uΟΊ3^|Bι™Χ“B=2Ήφ›ρ0|[ήlΛΥΆL±₯)oœϋψψ-£°4bZsa‚7ςό²eΛΪτΐΘΙΙ±ϊΆ7γΟΨ’Ζ¬¬,ΓΟ>>>”˜˜¨΅πq“Œ+έǎcςΰŸŸŸύψγb―^½$S/œHII±yΔ¨<ŸT©RΕκƒά€V« kΐ/θm‹;wξΠ†  _}ϋφά8ί€GΫςϋ’N:67‚BBBxHHˆ.;;›vνΪ%lίΎmήΌYΈsη= ¦χκΥK½{χn­#M Kώ]LMί/ˆδδdΦ―_?CπΟΛΛ‹f͚%Ž9R25ΥY’$zχέw Άu”iΫΆmyΫΆmΕG‰{φμa‘‘‘ΒζΝ›™>h•••Eƒ RyyyqKS₯‚‚‚hΫΆmD€8sζLCώκάΉ3·Χ5±΅i>>†Ρ`ςQ z–M*}πΟέݝΎϊκ+qμΨ±fΧӝ4i’αβΪzOσ+VˆOž<cbbXdd€°iΣ&¦ΠηζζθΡ£U%J” γΩœs’ 'Nœ(Mœ81ίΧJ>β¦ ωΚ–Q2Ά°g`\Ο3»ΩR/•Χ5 ΫX”£Οε|||Θίߟλ_€ο䉏gς:Δ”)S,Ύ4θΙ“'ŠάόΎΨΤΤScΦFAΩ+?”(Q‚ͺW―nvt‘-YzΘ£΅ΜmڟqΎg~~~<""ΒπR(΅ZMS§N5ά•w///›;»δƒ2š5k¦ΨVV–βϊόσΟβΐσΰ.H» ν'[’ΏXΔ–`~§’YΎφω Βσςς’ &H&L._ΎΜ"##Ydd€°wο^C[aΧ],<<\½~]a:8 ψAn<δ}eΔΦΏΥˆžVΤM­ηf< ίڐq[¦&δώ_{wE•θqόVw„%Θ6y  3ŽΐAΩDD၂ƒ0Θ8ϊž€ΰS}ˆ"ΛΩ4 " [X"Jˆ‘„-Θ&VΣU]χύΊOUuυ’EGΚοηœ:‡„JuuΥ­εώκΦ½7o ί $Αζ"²›Hγ@"Φ>2‚±NP\ u”΅`8` μ.4Φ€)’Χ|Œσ—,YR΅O-!„9r€?ό+_ΎΌHMMΥB5‹·>™Ž΄Ώβ8**JΤ¨QCϊZkGξŒΤΌyσ\―ΌςŠ?4ΚΜΜ θ„ͺΈ^Γ2ΚΛΛGŽ)R0'Ίwοwοή]̜9ΣϋΞ;οΈΖŒγΦu]¨ͺ*FŒαή½{χΟ>°5ΐ.ΚH•Fo½υ–ΛΧO‘ΫνkΧΥZ΄htΩΦ£ ϊšy©R₯D—.]d—.]ΌΣ§Oο½χžkĈnMΣ„λbΨ°aξΝ;ݞΖ}xμΨ1EΣ4±mΫ6ΕXa*ŽΎ„Θ?ώ­BS©άY_7‹‹…$Ε ΗΝpaf]ΧEjjͺ…Z΄ha ΈŒΧ!„(Y²daZ™Ϋώρœ]Ύ|ωb9g·άά\αkA+DΑΞKͺͺš^e³+wS§NuϋΘZΎ|ΉΦ΅kΧ ŸaνS« Ηt‰%Dϋφνeϋφν½S¦LσηΟw :ΤνΫΟ/Ώό²»W―^¦ΐΖΈŸ„">>ΎΘ}VZΛU$A”²ΨF.Κ™­[·ϊwδwή)­£›Λ~°n`Œ¬£o΅;’</¨»ξΊΛί’ΪχgΒ„ ._™©_ΏΎ΄λ^ΗΘΪΧeAFZ΅ͺΧ’θ₯KΒ8ΪΌέφ-lyRŠmΫΆωΧ§yσζϋΒυεmU˜‡Β1ΆξŠ4ά©Q£†,_ΎΌvθΠ!εΑ”&L0 ΆgΗ¨ ƒ΅‘ίb|`Ψ΄iSΣς­}.&$$όb׎μμlΕψΚwA[s&&&FΤ·ŸέΉΝ>Frώ‹dΐE!ςΟΗΖVήE λΧ―/λΧ―/_zι%ύΠ‘CJίΎ}έΎ{Ύέ»w+K—.΅νr ¦πwμp,γ“ω† Fόd^J)vμΨ៹N:Ά}˜;ŒMJJ »άH^M8pΰ€©³ώ`Oq"yΥΕΨχ„u0‹`Šσι°ρ’{ρβΕ€ sa?Ώ0―ΠTͺTΙ?Ο… D$§‡rξά9±iΣ&z άX—±S±bESα—xg!„IaŸΜ9rDyώωηM7Ε °½I1φqβΔ‰Λ=~όΈ2yςdr«U«fϋšL€αV$7‘Νš53ύ>%%%δ…0==]1†&Α ‰”΅Β²zυꐟΥW_)sζΜρ~υκΥmGΠ*Limڴэ}iEεε剑#GΊ―ƒϋX[Ί„»Θ-\ΈΠ΅|ωrg†Ίi;|ψ°ι璌ψgΥ³gOΣχ7ί|3’w[8  >ά?o₯J•ΔΰΑƒm KΥ·Ό %’ΧŠK—.-Ξ=«;vL9v옲eΛ–°l|-΄L™2u~]T?Η+Φ2αZbmΩ²E1vΤ]­Z΅€cΝεr‰kΧω·η¦M›Β3τ;***dΩ΅ή C«.]Ίθ‘‘:zτ¨ιηPύ,EEE‰vνΪωΛφϊυλMΑžττtSλΕδδdΩΎ}ϋB―Ώ±UÎ;”pηcΏKBaν{ρΎϋξ“ΖŸ~ϊiΨ}9kΦ,—±5Σ /Ό [ΛHΉrεLΫrΥͺU!—«ͺͺ2dˆιœμΪάΉsgSkRc‹λ`φνΫ§L™2ε»,l˜?wξ\Συ nέΊ²U«V¦Ώ½v횩…vΈ2πυΧ_+£Gφo[»ΡL…Θ―DϋŽiγH¬ΑΟ‘BΨ_»Œϋjοή½J$Ψvοή­»αΖλυΚ•+C./77WΌςΚ+¦e%¬R₯Šiτο””Εϊͺ³ΥΪ΅k•ωση›?°'Nœ0ψi @ίΏ‹:πΉsηLέΫόά ±½K—.™ή 3fŒm_ΙVΖsw­Z΅Bφύ{ψπaSKο‚nί`ΗLΣ¦M₯ρΥΜHΟ³gΟφ?Π"ΏSλ=Q„‰€λ‘pŒu…’~ύϊ­ρΫΈq£Λχ°*&&&lλ?λQΖξ‘‚yε•WάΖ}Α%κΤ©“½>όsW$V―^ν2Άΰ/ λŽαϊο=yςdGŽ€οψΒ„‘@γ1θv»C6Ί(Q’„ϊrμΨ1eοή½!·ύΝ›7M]Ψ΅„B’Rz˜˜|ΣΜ™35!„τM[ΆlQCΝουz=Λ–-ΣͺT©"χτΣO{ƒύΝώηz}σΉ\.ωΥW_Ω~FFF†ZΏ~}έΈά:θvσvθΠΑ?ίψGΫyNž<©—υή{οivσ©ͺκ©Z΅ͺyUͺT‘§N²]ΗνΫ·«·F«σO=zτϊέ#™TUυT¨PΑΏΌjΥͺιηΝ³wΛ–-jbb’ισ;uκdϋύϋ¨_Ώ~―c―^½όϋKQΉdΙΫν&₯τόπΓj«V­t!„¬U«–ž™™iΪniii¦}œœ¬λΊn[ώηώG‹ŠŠ2}·Αƒ]ο΅kך–μtzόρΗ½Ζε7.θvRzRSSUλq±rεJΫΏ9ώΌΗ8ί³Ο>[€2δ›:uκδ/ΗεΚ•“vΫZJιΉU•B%wοήtΫ}σΝ7j\\œ]»tιb»Ϋ·oW§M›¦νίΏΏXφC›6mόλ/‹c™/^τΈ\.w©Q£†~γΖ Ϋy?ϊθ#Νψ½…²]»vΆΗZοή½MΗΜƍƒnƒμμl5!!ΑΏΜ-ZΨ.Σ8υιΣΗT}ŸσΝ7ίk™ϋν·MΧƒΛ—/‡œύϊυ¦cπΉηž ZŽχοί―Φ¨QΓΏOcbbdzzz‘Φ?##CUΕωƒ ϊω;vμPΛ”)γŸχα‡ΆέξΓ‡7mλeΛ–=ξ-Z€•(QΒ?οwή©_Ώ~έv^λrηΝk»άK—.yΊvνκ΅ξλ+WΨ.χΗτ”.]Ϊ?o:uτό1θ6ϋμ³Ο΄[―nΚgžyΖ›——0ΟΦ­[ΥiΣ¦iί}χ]±”―ζΝ›ϋχ{ΥͺUeΈω/_Ύμ>|ΈΧx¬ !δΪ΅kΦ'//ΟSͺT)<*TΉΉΉΆΛύδ“O4γ΅V!οΉηΫrπΒ /˜φΑ₯Kƒ–ƒ“'OͺΥͺUσΗ Ψ.σΔ‰ͺ±ΌόξwΏΣ/]Ίt;,[ΆLσνΫΏόε/^UUζι₯‹=έn·LMM΅έgǏWο½χ^ΣύU΅jΥž{ΒM³gΟ63ή~ϋν ΫiΣ¦Mj||Όi_Ωέk}ώωη¦σΚΆmΫΒ–Cγ5­Q£FEϊ^7nŒθή±Έ¦ΌΌψ ώΣO?ΩΞλυz=3fΜΠ’’’€’(ς­·ή*τ1‘””δί7={φ » ¬χϊ›7oψώΗt 9vΉ?ό°³6lΆ\Xλ‘3gΞ ϊ½GŽι/ϋQQQ2ΨΆ”RzŽ=jΪφ-[ΆΤ=νΌΊ{^|ρEΣq΅~ύϊ€mpύϊuΟάΉs΅ΉsηjΑξC˜~» “i4hι€ςκ«―zǏ―M›6M›={Άzϋν·΅?ωΟήΪ΅k›n …²iΣ¦ϊΏώυ― Ÿ±xρbΣ¬zυκϊςεΛ΅Σ§O{Ύώ{uΓ† κ AƒΌ·:F7MΓ‡·=‰oΆƒ…Φ‹F°c)₯gάΈq¦u¬U«–ΎdΙνμΩ³ž³gΟzvξά©>χάs^k@%„£G.ςΝ‘ρΒ!„uλΦΥ—.]ͺ9sΖsϊτiΟζΝ›Υ§Ÿ~Ϊλv»#ΪFΦ€iβΔ‰―γ‰'Le·Ϋ-_zι%οΑƒU]Χ=ͺͺz233ΥΧ_έ[ΆlY|εΚ• ¨Πkšζ1Vϊ…Θ,?ϋμ3-;;[έΉs§:}μl>Z:IDATϊtΝx³fΩΆAoΣΣMϋ·}ϋφϊώύϋΥττt5++«Θלœ΅|ως¦υιΠ‘ƒΎjΥ*νΔ‰jnnηΘ‘#κςεΛ΅ξέ»TVǎt›oήΌ9`έǏ―dΪΉsgΐw4nλϋοΏ?θΝΝΆmΫL7eΛ–•3fΜЎ9’jšζΉqγ†'==]6l˜Χ.ΈέnΫΠfήΌyώ›Ξpb€“1L½γŽ; Ό}Ə―­Y³&`=xΰSYkΦ¬™ΎlΩ2νΐjZZš:wξ\-99ΩΆ<φιΣΗΆ<ξΫ·O5›₯J•’'NΤ:€ͺͺκΉyσ¦'33S}ν΅ΧΌΖ2₯(JΘσ’o?~Όf]—Η{¬XBcγ4ώ|Ση 2Δ›••₯¦₯₯©ΑŠ΄mΫΦ΄­zφμιυ=ΠuέsτθQuδΘ‘¦s…B~πΑΕR©6>°BΘ^½zyΑΥαΓ‡Υ#FxΑK||Ό}ϊx­ΗP¨πϋΘ‘#ͺ±‚-_{ν5oVV–zι%OvvΆ:yςdΝzΎ"ΏjLœ81ΰ»pαBΝw]Ύvνšg͚5κ#ψξΫ·oΠν›››λ©\Ή²ι<7zτhνΜ™3)σϟiiijΏ~ύLηΟ2eΚ=Χλ)εΛ—ϊ`ΣX&emĈEΎVY³ΖtœυξέΫϋέwί©_ύ΅κ+λΑ¦K—.yŒίU!cccεΙ“'Γ–«gžy&ΰα_||Όœ6mšvώόy”ωα\jjͺj|ΰΫ·6lωύϊυ3ύΝ=χά£ώωηκΝ›7ύϋsρβΕΪ}χέg*ŸϋΫί ΅M―\ΉbΪ‘ξ“}Σ€I“LΗ£έŒŒ Σώ <4|\·niΉ›6m Ί=g͚eZΟaΓ†y³³³Υέ»w«gϞ ˜ΏgϞ¦mί’E ύψ‡κ;ίύψ㏞””νV·ώω|πΑ€σΆ¦iγ>JNN.ςΉΙY “iΊώϋmoŠ#Ίuλζ υ[Κό‹q½zυ"ϊœJ•*Iγ…{ώόω'ράά\SΈ5i$Ϋ½΅Rj=oήΌιiΠ ADλΨ°aCέX™\±bE‘+²W―^υΤ¬Y3’Ο·VηΝ›πωΦ€ΙεD¨)%%E‹ŽŽψμΨΨXi Ί„Θ―ό»0.]Ί4 Ό6ύιO2}·P­ Ό^―©ε¦q UY,Θ΄nέ:S%0’)::ZNž<9d™˜1cFΔΫ$Ψd½ΑΉ|ω²ιΖ*Tk,)+’ΎΙ.δ"fςύχί·ύ^ΎV ΎiΤ¨QEΪώΦ»°“έΝε]»T»ΦvSƒ t_k)!„0`@Ποe­4†ΫžŠ’DΜ[+.—Kfdd{₯τδΙ“j°υύτΣOmΧυΜ™3¦›jίTͺT)i·έnwΘ§θ.\Έΰ©S§ŽνηΫI*T{χξ Ήν6nάh[FΚ–-+­9!„LHH»vν »?ήx㍈Žϋ˜˜Ω¨Q#w ΦκΦ7©ͺκιΡ£‡νρllgœš7o[ƒ)₯ΗZΡ+JK_™*Κ1\²dI9eΚ”λ™™©T„šjΦ¬©.<ςΘ#A·ν‚ 4»ύμBΘW^y%δΎΚΛΛ3΅V‹d_΅iΣFχUΘν&klͺP‘‚4«Γ† +–λdff¦j0ΖΕΕI»‡–±±±AΟ'RJOoC‹κ;ξΈ#lEφΪ΅k¦kίoΌQ€2;`ΐΣφ w[ΣSO=Δ„ ΊŒΣκΥ«ƒgΦsΖΦ‚uκΤ »}/^ΌhΊύοΉ}7oήljιξόY‘BΉ}ϋφ ηOc=%ΤƒMcy4.ίQΠι_ϊ—'Ψρ9gΜ°Λ·ή·:4’cΟΨj7!!!ΰšV¦LΫcL!§Nv½._Ύμ±Ά 6ž{ν~ί·o_Ϋ‡‘L»vν2ν›HBξώύϋϋΛk₯J•€έ<ό±ιΪϊν·ί”'kψξ­)[`{;KΚό·;μκGBωε—_¬Οωση=ρa»νƒ]cξΌσNέ¬΅Μ‡[W¦ίήD€π“RFΤi΄-ZΘ””οͺU«ΌΦ‘Ώ¬’££ΕΚ•+½ΑFNςiΦ¬™\Ήr₯ζρxόΏ+ΚθΆΖΎͺW―.C­gll¬Ψ°aƒΧΨ©Ό]»κ)))ήΌΌΌ°Ÿ_qqqbσζΝή€€€Λzβ‰'τiΣ¦™ϊσˆd΄΄ξέ»λ«V­¬#ίύτΣOΒΪΗOλΦ­eFF†l˜'Ÿ|RŸ:uͺ7TŸ4111bψπαϊ_|avψΒ… AΛ§Λε&L°νΫδΦθ"λΨ±£άΌy³Ι*BδχiΆgΟνΏώλΏBφλbν«0Œύ 8²eΈr9vμXο{ο½η΅φc7ςsνΪ΅εΚ•+½Αϊ’ΉΥ:ΐ―¨#ξeddK‡Τwί}wΐο’““εβΕ‹Ύ·‘Λε}ϋφΥΣ4γ.\ϊ7/½τ’Ύ`ΑoΉrεLΏ·ΫžΥͺU“‹/φŽ1"’0¬ίγρΗΧ‹cP«κΥ«ΛΏύνoΆλt+4±ϋ½ΨΉs§χώϋο7ύ7„ρ\.D~Ÿ‚›7oΦ†Zθ?¬DjjͺΧ:Í7LP ‘ίηQZZšvο½χ†άvmΫΆ•_~ω₯fνλςΚ•+#‚wκΤI¦§§k‘ŒΔ΅τεφ󭃍uτΝ‚ά;λω|μΨ±ή‚τ ά₯K—€ώ1…Θ(Κz?I_iΑζ"ό+­[·–6lΠ¬ƒΤ؝?Ϋ·o/χνΫ§΅hΡΒv™ΦzΚΏc@!ςΝxύυΧmνΚ•+‡ύ{c9.Y²€ΈυΦOHΦ‘ΰ[Άl©―_Ώήt_~υκΥ€c¬L™2bαΒ…ή_|1μuΆ\ΉrbγƍΆΗ£±ŸH!ςGy^°`wαΒ…ήHϊ₯΄SΤώ#ι/&&FΨΥεlξ‘Γ―±μ…XˆόAΙ‚ jV₯J•€ίU¬XQ€¦¦j?όpΐΊZ―1Š’ˆ'žxBίΎ}»f8°,νΉjΥͺAPΔoSπ8~s_Ώ.žxβ‰ˆ*b111">>^4lΨPΆhΡBڝ€BiάΈ±όξ»οΤ &ΈW―^­?~\qΉ\"!!A>πΐ²gϞ²[·nzVV–b<Ϊμέn·iδΘ`•Ή† ϊOΖα‚=!ς+^ϋφνΣfΜιZΆl™+;;[Ρu]TͺTIΆjΥJφλΧOoΫΆ­ΜΙΙρ―£’(Ε6ψDέΊueff¦φΏϋΏεΛ—»<¨¨ͺ*ͺT©"zθ!ΩΏ½E‹ςΝ7ίτ_y£££mΓ–Κ•+ϋ·Qtttΐh‘θάΉ³ΜΚΚ,XΰZΉr₯+;;[9{φ¬HLL‰‰‰²iΣ¦ς©§žΫ΄iv‹_|QoΫΆ­œ6mškΣ¦MΚ©S§”˜˜Q«V-Ω©S'9hΠ έ7jέΰΑƒuί M¨A„’oίΎϊ­J’λΫoΏUbbbDνΪ΅₯±cς’JNN–YYYΪ²eΛ\Ÿ|ς‰²wο^εάΉsŠω7…υλΧ—ΙΙΙςΙ'ŸΤ­!D0‘n"‘(J@™vΉ\¦γ’uλΦaΧε/ω‹ώψγλsζΜq­_ΏήuπΰAεόωσ"..NT¬XQ&''ˎ;Κ^½zι‘Fπž0a‚~γΖ qόψq₯GzϞ=‹πDGGitXλ?>?ώΈή¬Y39uκTΧΊuλ”γǏ+BδsνΪ΅“ΤψΗ?ϊΛ£―Cνpη½~ύϊι]»vΥ?ψΰΧΊuλ\PΟ?/J•*%d³fΝdǎeοή½υ’%KFό=Œ£»έn1f̘b Π¬&Nœθ­Z΅ͺόπΓ]999J™2eDύϊυeνΪ΅ƒ~χjΥͺΙ;vh+Wt-Z΄HΩ³g©^½Ίl€‰μΩ³§μΡ£G@@UͺU«&wνΪ₯­X±ΒυρΗ+_}υ•rξά9₯tιβŽ;ξ=τμΥ«—ή²eΛˆΟ ­Z΅’ΩΩΩΪG}δJIIq}ϋν·Jnn(Q’„¨[·|ΰδΣO?­Gόω(Š"f͚ενΪ΅«ώξ»οΊφμΩγ:ώΌ¨X±’hάΈ±|τΡGυκ%K–ΫΆm“ΎςVΏE!EGG‹ιΣ§{  ΟŸ?ίυόC9~όΈβρxDbb’¬U«–hΣ¦ώμ³ΟΚPΧƒwήyGWUUœ:uJyςΙ'υnέΊ©¬ΕΖΖF|,ΗΕΕ‰ΔΔDyο½χΚϋξ»O §}ϋφ2++K:uͺ{͚5JNNŽ’λΊ¨R₯ŠlΫΆ­μίΏΏήΌys)„ύϋχΧ}ϊGΆmΫV裏\kΦ¬qedd(ηϟ±±±">>^6iDΆoί^φνΫWt}K”(!ή}οΐυ Έ6oή¬?~\ρz½’rεΚ2))ItθΠAοίΏΏ^΅jΥ°Λ‹kΦ¬Ρ/^μš;wkώύΚΥ«WEŊE“&Mτ'žxBφιΣGwΉ\’]»vRΧu)DώƒάˆV85’ιιιΪβΕ‹]Λ–-s}σΝ7ΚωσηETT”¨Y³¦ΌώϋεSO=₯wμΨ1μύBΛ–-₯οZjjY)Šb*c‘^‡ƒiήΌΉτhΙ½cqΈ|ω²i$λ]»θsE«W―Φƌγώδ“O”sηΞ)ρρρ’Y³f¦k·”R<τΠCςΑ”BΡ]»°ŸeΪΎMš4 ϋ7-[Ά 8^ΌxQ”(QBΤ©SG>πΐ²_Ώ~ώc2k=₯{χξaΟ%eΛ–•ακ…ρΪk―ι*TοΎϋλΰΑƒJι₯E½zυd$eΔ8jλ Aƒ":=j ώχΏ½lήΌΉϋΜ%„}ϊτΡ-ZΆυΰΧοάΉs’nέΊΡΎQΤΧ]«GhΕνmύϊυJ§N’„ΘAωθΡ£j$­SRR\=zτπ§8+Wτ>ϊ裨”R€΅(€ί^ncιιιΚ?ωO•>άk?no»vνRV―^ν"Ώ%ΖθΡ£ΆΦ€_ΦΈqγάΎπ/99Yώ9‡λbδΘ‘ώoȐ!z$αŸBdffš~Ύηž{lΛα€p_™Ι“'»6mΪφ išxώωηέΎ>,’’’ğόgΒΐ‘TUƒφσ}ϊτΡΓ½Έ=€₯₯)οΏΎΏn6nά8Zw;ΘΜ™3]ϋφνS„Θο›οε—_Žxϋ΅‹5kΦδΪ P_‘?όΠ5bΔwϞ=£Άnέ4Όqγ†θέ»·Ϋχκ―BΌπΒ :7€3ιΊ.† βφuš'ƍGΰpβΔ ₯OŸ>n_ŸΗ:u’mΫΆεžΞ!ΆlΩ’όχ·Ώυί«―Ύκ­X±bΔo οΎϋξ°}g@0€ΐ―„ΧλΣ§OwI)EnnhΧ]Τΐέ;wξT^½*t]999ΚμΩ³]wί}wԊ+όΗοώπΙ“bΐ94MRJqύϊu±yσf₯C‡Q~ψ‘˜;v¬—ΐnOͺͺ !ςϋό›={Ά+99ΩνŒ¦tιβέwίεžξ6¦ͺͺΠ4Mδδδ(£Frwξά9Κχjw£Fδˆ#"~€———'>μOό|ƒ`@a0π+rόψq₯E‹ξ~ψ!βG{7–›6mB GΰφςωηŸ+έΊu‹ς½ξkΤ¦Mωε—_jŒκ·§'Ÿ|½bΕ —υ―(Š˜3gŽ—.]noΥ«W:}ϊ΄bέΏeΚ”›6mš6mqˆ—žžάsΟ=QΎŸηΝλ0`ε@‘ΠψIJJ’ZΏ~ύτpΝϋ£££Ε!Cτ΄΄4Β?ΐaN:PqBˆΦ­[ΛΥ«WώΐmΜξ―(Š˜>}:αίmΞλυŠsηΞμίΈΈ8ρΕ_(όΒόϊ―ωoύΓjψŠ ? €_R||ΌX°`χυΧ_Χ-ZδΪΊu«’““#_Ώ”.]ZΦ«WO΄lΩR0`€LJJβ&p +WEQ„―Q΅jUρΧΏώΥ;lΨ0=&&ζίΌv€’Έzυͺιηϋξ»OΎωζ›^ϊύ»ύ]ΌxQDGG MΣ„ωƒτ=ςΘ#ϊψργ 5p—¦i’M›6RˆόΈQ£F”…Ζ+ΐό ]»vM9rD)]Ί΄¨W―~€ƒœ>}Z9s挨U«–LHHψw―Š‘―ίξλΧ―‹zυκΙ₯K»W „€€£Ρ ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒF8 ΰ`€€ƒφΘ›ζΰ;IENDB`‚typer-0.4.0/docs/img/github-social-preview.svg000066400000000000000000000075401411311634200213160ustar00rootroot00000000000000 image/svg+xml Typer Build great CLIs. Easy to code. Based on Python type hints. typer-0.4.0/docs/img/icon-black.svg000066400000000000000000000004331411311634200171010ustar00rootroot00000000000000typer-0.4.0/docs/img/icon-white.svg000066400000000000000000000033171411311634200171510ustar00rootroot00000000000000 image/svg+xml typer-0.4.0/docs/img/logo-margin/000077500000000000000000000000001411311634200165715ustar00rootroot00000000000000typer-0.4.0/docs/img/logo-margin/logo-margin-vector.svg000066400000000000000000000151601411311634200230300ustar00rootroot00000000000000 image/svg+xml typer-0.4.0/docs/img/logo-margin/logo-margin.svg000066400000000000000000000057021411311634200215310ustar00rootroot00000000000000 image/svg+xml Typer typer-0.4.0/docs/img/pycharm-completion.png000066400000000000000000000331441411311634200207030ustar00rootroot00000000000000‰PNG  IHDRu_fΔrsBITΫαOΰtEXtSoftwareShutterc‚Π IDATxΪνέwxηa ρof{Αξ’χ^H‚½SΥ(в(KV±eYξ²μΔqΫqΞ%Ύ$N΅}IžΛYŽν$ξ-ξ–cΙͺVe{ˆB’χŽΆ—™οώXA@Ιχχ$ς`±˜ωv°Λy1³³£¬ίΈY,Ά²’’ššš±/έn·ΈΎŒŒŒ€ƒΚ*ΐΡ—J²;ξYŸAΠ—σB†{ΟΤtψ$ p 1&˞Qœ›κ°™΅†Ϊ€\c‹y;[ω \‹})bώώΞ‘~OqώB.»¬€hlΊ²²21QSS#„’TΆ}{™Ϋdž;ή§ !ŒωΫή²ΜυΛX4jLrΩfπΞSΕQΌν]λ² s»ΰFpn/Ÿ-³Ό(Νb4DρJwd ₯!qΖυU7ωρq5}ΥσLͺρά‰έϊΰι׎OΩ‡Α@Κζ»W:•@Wυ‘&Ÿbτμ‘*ϋ†-o)7ΛπPλΡcmαιΖξ:Ϋ”Ώαφ=zΌ―κε#Ӝ?šm‡4ΞυF€€rm_\M]³kƒ~ψ₯κα%}ԛ돀—­} €₯κ‡ >xκ…ω-Π—Σα݊Χ.Ž€Ύ} ϊ /°xŒB‘8³Κ RΜͺ"dΤίίΩΡΤX3˜s_ΚΠ`k}oT“ŠΙSP‘—ξ;»@—ΐuΩ—B‹E_* ¬\I_ ‘Ψ2JŠΣμ&%ΨΫdη%¬/e¨―±¦O΅$η₯»‡CQV ζΰβσΗυˆΧv€ΈM¬Μ½/Mv‡Υ „ŠΩεvˆX”ΣΗ07F!„b°§E±ΐ`[ǨΊΐœϋR†ZλXΈr\Ώτ%θKΠ—} ϊτ%0‘!;'wΡ‘’μιοοŸ"€Υμ6¦u덱ιζPTbϊόΣΓεǐv:4/ανψπ_nΉo°σ΅~yΩϋ*ξνόΙ­‘γUΓ.|dΞΎε£οΠ{vξάjm~£Ω+…BΝήρg½fψ•3ƒΊBΨ7?πι?»{Χ»–OmΉn―ψνότ_”w½Φθ•ΦښχfSψπΩΑ8―C'ΖλβA¨{ΚΕ«{#Οζož2²οω&GœΑ]My·ήν8ρυζΘΈΫ,«·οr{ςSΖ]kSŽ4Ύϊ”₯οό-ΑΓO}ω°aΝύΕφ%ˆω·ωΎ5K•RΧ‚ύ§ž{ξw{ϋ¦Μa5}Χηίνώυ“Ώͺ½ΜUFεHΝgn~ˎ”³/qΑ(#㏏«ŽάkΦ”$±Η ˜=‘y©ŒΧι><4ƒΎ΄TnXη―9Ω7ώŠέγƒή‹K»OξoρΚkπy«ύρΗρ³ϊoίό½―β]ά–«ΜΗLγ­‡œ7―ΛαM\WŒγκ2+ΣIeΑ–]VR46]YY™˜¨©©B8R O¬5TZDk»>v`ά`SXmΌ-U1Ετƒ΅ρuΚΈ’μΩfΊΣ)ά&ερjTΚ7ŽΖžςN²,ΗΦΥ/άcj°9²Ϊ»_2dϋεΈ>|βΗόZ›H»εγοΊ5Ο–€Τ|ηsOŸ™ξˆ°κY}ΫCoίP’juœ~φ{ΟλΡΤ’ώ‘”Ί“†‚ŠŒTK«ωΛ7ΪβΒ’Ηγ»·•§9qoγ‰g~ψJ͐qß|b§£ί’γκΪWgΨΈ>oτΐwευΦΨ$σLtpκ­πΣτ\]μrΑ­λz,Ψ}πXνΫΛΚ6ˆμK–. ξ{»oφΨέυ‰?]ρΊΎϊ½ΪˆB±—έΨ£·:ƒΝΟσ—o΄{όZ{K«σΆε™―ttEχ,†–t_ͺŽŒ,ΣPηhr’{!Ÿ¨Ι‰•©ͺo[gP›bo•…ΛLŸ1ŠSBEΩ½ΑΈΦ»#ΊίaψθVγξ‘Ψούςχ’ΟΪ ŸΏEyφεψρι³*Ζ`ώ₯γS_ξϊξ‘wε¬ωΪ¦€jόCO~΅ξH―ζYSρ”ΥοκίdσK'οyΩραΟ―ΛΉh–ΦΑGωl0§θΗQxϋΎκηΓBΕ™•czγΒ^½sό偔]ﳂύ_ωΞXIΌρžά—½γγŸςLΏFΤμ­οyΌΌύ»ίϊAu(νΆ‡>τ­g€JrΆ₯ϊ[ίψY4뭏ρ=‡£&bP΅―~λ-{εcο{τ½_ώζ‘BΗϋWξόμ;­?ύβ·2?ώθš‚½νΑIζΩ§ !„ΑαIΟT¬3ύ£BuV,/sŽœξΧ•I–^έφ»ο~ι™Δρρ―W…P’‹s~π―,zηί{OΕα¨ Ÿ Μ‘Ύ‘”Ό£θΎΠ’³Xš}©Ϊ3²­ήΆž˜3y ŒΙΰRWυŸ·Λ¨ ΝzC±*„P“ΤνIςιCϊ€.„O{eΐpOΊς¬_Ξψ`³Œ †ϊ‚ΡΞΡΘh_ΌΫqεšT?²·3ρνΎκž}αŠTE¦˜₯Œ8κ‘BτŽΦΖΣς=Šθ‘B(›UFζλ%sΣκŒϊ}ί:5’gοΡΖ{·W$BψšNΥ‡€}Ν=ښd—*ϊ‚­‡^Mό”―ξΝΖπ»“•3BΖ†ϊGC£^ίPh 4bςZ³œjfε$σμ”BΘΎgΏυΩggς<)ψKŸ~›j²|gžyϊ΅6]ΚK—.‚Sύ6‚  ΖtΡTՑݟμRE8Ργ2‰¨›I±σ?:γ!€%ά—Š-#Ϋ1ΪΡ]ψO+;,~Qc™„=&FυDΤΙ‘ΔΉΧΕePΩa~0Ρ Fαυ Eˆ™Ώ™QΣ₯RΣυΈ.€.UB-ΩRςηw₯U$©B(.—φ³ιή{ͺ…’‰₯ΙΈ η³5VΝ£Ϊ|¬5Ιν4/ίυ‰ΏΏ=ρ₯Yϊϊ¬‰‹K0MΣTUB˜Φ?ΈλΦ5™N£FkRθˆA!€.…ΊιΊR*Γ”σœ…xσοΏτ»κp,ˆΔØ|ιS€y8IόT<O ώά―Ϊl±θ‘pLςJΰϊκK“Γa±XŠ—§ ‘šTΥV^(Z‡δCc&=>.£"hvE)ƒbW'FΓϊO^‹ΧΞӹƊjZξ_?κΪχΥ£Ÿh‰i†δ/όγ²Ω”•ώήΠΦ¬UtΟm`Ί.• Ε%}£ώΡC/όΛOZΖηͺZ4ΙψSn½χm…ίώΟΫΊaΩ=Ÿ{2ΕÜ|ž³ ΜШߝΡeb±3aHΙHξξβŠΈž¨Bˆθ@cMMM]mMέ™.Ώξο>»q©(ŠΒb±Œέ’˜VEσιΥqucͺP„HΝVΛT!„Π|Ϊ›υώRΥ­ EΩiκJΗΒl°Ηͽ1M(i«³nΣ[O΅ΆκV{ygŽο”C½#ieyΞs…©χ9ν[{ΛεNƒGjωζ’Τ)φ*›­fm°Ώ?¨ ΥΉ|{ΉkΚL3O%υΆG>ϋ·χ¬0Ν~ΥM΅t ,ΩI3Ωn((*ς7Φ_τ)PsXνσ/₯”αp8//―©©IJ©(J^^^0”R )Ÿ:‘=±ΪόwΛ€Ο/Ί΄s?πάΡΈu•ροv+V!ΌϊοͺtO¦ήέυT~ζ [‰y;}5ΓR!ŒΙŸώΛwΫ§ΫlψΠΆ›cϊ±ίύόα©η©9vς‘;Χdz΅wΚΓΌ†e»>ρ•6£%Ι₯ΎλJγρΆίε7Η}B½ϋ΅—O~τΎΟ?yΌζχ_ωϊΙ@ׁώΨόΰcωΫt³τvγΧG'_½oΌ|τ‰έŸψ«ν£ΑPOk·wκ΅‘O=Oƒέ•’.-³Žγ©—.ύU/Yž'ώϊ-Z€φωϋnMdκ'_Ρ–rίώŸu^<ςΉ ,Κϊ›qρi©Ίuuuεδδ!ΊΊΊΙ•θήώŽΗ‹Ž|ύ'MžQ³Xkkήχ‰ό7ω™ϊ+€λΙ"΄uΰέnΟΝΝ΅Ωlέέέc·{<ŽΗΏΝγq\ΣnεδΟοO†lטϊƒλΪ~ϋ―ΟΤ‡¦Ώ?/Q9‹ύρp8’‘‘ήΦ֏ΗΗβς‘‡6½όr͞=k‡‡ƒ{φ¬eϊ†φzƒΌJΈΆ,ςρρ„²’’ρ'’?ψΰ¦W^©υz‰Πόυ―0}ΓNσΰΪλΛ 6,ϊ JKK―κ"Š ¬ό²Ηki ³ΐΥ ² @_βŠόρŸ|ρΛ_ώ2λΠ—XκŒ‹>›Ν&„°ΫνΑΰœ)lJ^ύž›6”Z•α†gνT―&„JFεƒd?τOG:4!„°Ϋϊΐ½ι‡mπ駟ΩZ:—ΖV2*ψˆνπ—Žvhs}μοήΆΎΤͺ σΌΧm_Z­ΦΜΜ̞žžŒŒŒήήήPθ*ΤΆiΕς ަ§ώ¦nd\’I_χ‰ηLcW  Ÿ8ψΣjρ{X9 ”σ˜§r΄λΔs¦a}Žσ4-_ΆήΡτΤλF΄~μ‹<υΐΥμK5ηΆ<ΌΚŽI!τ‘£OύϊθΰBμ΅³Z­YYYνννΑ`0ηηη_νΔT¬n›lσOΨn:<K™Ίp­^Ρ8«Ϋ*|ώΈBJ9φ_ΰF¦(\‰R_ !τξ}ύ«Ύ… EQrss[ZZ‡ΕƒΑ`{{{QQQβrδσΏ<5{ωž”ylV‹qΣ;KΦΛ‘ζ?|ύtŸp­~ό–Υ9f»ώά?™φΈ³β\±jΗ½%Ω)j΄«ύΠώνΧΥόΥΏΫΩV£f”Έ]ζΡ“?Ϊ_Υ₯ sΪΊGΧ-/J²5Kσ‘_U·z εοΏwmԜe<ά‘.Nχyώ?kϊbŠsωΚ›χg₯¨ΡŽΓΏ8ήΠ?ΕEΔΥ€UΌeUΆΙtι–;Κ«ϋΩw±Ρͺo=SQωΰΩ/“ΓΛv=šέχΣ—^¬ΈnΪvΟ#ύί¨BΈ“Mυ/ώΟΔSvνΊogNύΪ£ͺ>[ύά/ϊΌKαƒ·ίzŸχη?κ–Š­>ττώՏΌΥτΪΏ½δωΰŽβœΪΎ`ΕΞG²ϊ~ώ‡ΟD\ΫΆέσŽςώoΦy'-L}΄κΫΟT§W>πaΫ…ύ£–μυχzΊΎχϋίΆΕΝ)iɊ.§ΨAͺuΧ=ύ•ϊ€·?˜[σŸuiβ>ϊΧ<υιu’2`ΞΤ±~Ϊςφ~ψCο}ψŽ©FVΛ%y™²ΠΣXw€6 ιραƒM]©YynE!½M Q!toΫPάγ΄+B„ϊλφχxƒΊΠ‚mΗΊcin§"₯σ £Γ Χ7ϊΌF›CIY›οj¬;ZΠ΄ψπΑΖΞδΜ\—"€˜ς„”RΚ ·θΊ4:3œ“ˆφχθΣύ¬RJ]J!ΟΟ†]˜ΐe*“— ̍Q!τ£ΏόΞΎ°°fmΨsž-ƒ?ήί§/Θβ+++―Ύ΄%YŒekόμΚĎ “ [€”BDγq!„BΣ₯*!„)©tχšΥ+]UƒΩiP€BjšRΧ4]—Ί¦ ©«Σl(Yyί§W$aΓCf)§y“¦”Ί”rle€λπO“6ά²ρΎ=6½§ύθ―N6 θ3Ψ\J)t]Όόέ8ƒ=”μΛ€Ήφ₯ˆϊ}B!Β=Uu½Ϋ*Ν’oa8ώ²γK,7u©ͺc›ςE‚Ǐβ7cέ-₯jžβB&rΝΉuγMΉέΟ=Ή―?(ΥuοxΫω]‡ηwŽνˆ ކ'Nώκ·}3ΞΌD`Ž-Qϊλk_――&GΡΫvlΏ=£ιέϊ΄ΫIύόώOΚΈδ…0MDJ)IL˜e_Zέ©Ζΐ ?.ŒΙ₯ώƒQ6<£AWQšu[HBΘαSmΑ'V¬=1z²9,μΞ¬r»―ͺΧ/€”ΚωΝΟΉcΨ³!ξυ†t©ZσΦgΩDΗXJ)υs©K©W΅ήΏluαHUkD؝Yev_uŸOB(I›·ή΅=tθίOvΔ/ήƍ-Faρδ•ˆΑ†‘išŒιšœφ υs{?…`%n`ΣGδ4χ!1`v}©8Jn½s–YHχΆόύ‘}‘ΏΘ;,Υ΅½3ίl4ٝκΞΟeΕ΅COΎΩBΘ‘ύUΨόΨ?nΦΞϋιχšΓ=υ/ύΪΈύΑ»ή—b’α@uΝήͺΔ6G$X+‰i©:uζ‘΅χiy(κςi‰cγ©λΊ.₯Φ{ζ•ίΆή·σ]ΙFΤΦ¨:·SlVg²4ž?―ΊοΑ<³Ρh³«·~2#ύΓMΊ9cۚ›Ά™„hkΩTŸ6ύ I)υΔh)KάΈ:Ξΰ„ρ©Ž‰“˜0Σ?ζ7lΨ°θƒ(--mllΌͺ‹(*°ΞίφιάNψrόfKž?l>ξΎrŠω,‚ΗŸψœβ‹_ό"/άpδMˆ—~w&·&ΰϊγσ—‰}•βάyΪηvjŽX3qWζΜΨμ–Δo»}νΌL뺞8Dάx/αILψξ„ϋ_:V#Lϐ½θƒHIIΎΊΕφΈηS—&‰Ky~GεΨΛ)7I3ίHmίΎ2ˆ¬]WzΊΊuνΊ)§ƒŽM·μτŽW¬ZηJΙ[±jέΠ¨qΕΚJ%Ψ»lΥEχw»‹₯”―Ώώ:/ά°.»crό—Σ| 0ΙΏ±ŸCPŽR^œV{!Ο*ΰ:P__Obΐά₯bX γˆD"WwSqvήζ?ύα³Υ«…wΤΟ Έ¦y\Ξh4:MP—0 ήyEq9«οΰΕτ%f±b{πˆσΧοΑ|n‡¦g3Χx‹œ·Ω*„‰†[όήSήώP<Ξj–ΰK›Cα@_.\DΞaFI’ηΞ¬"³αΒ{^Σ¬φ4«}MrζzZš|ή)RΙ³νό+΅ϊcήΉe¨ΕXώINJUͺΩϋΏ‚ƒšB()Ϋ{Ψ΅χ;/΄jγŸ™φά·-ߌžψ͏_οΊΜΗ)–μ΅λ\νGκ΅Ω Η”Ήjsi²Ρdρ7ΌqΌ3rъ4Έ V)φ˜EΈύΔα³#RsΚ΅ξΞ㡃$8fOI*ίΊR­=X?"―ό_€ρ­Iz}Ήέ9}q–$yή’[:ιζΘl0Ό%·τΉΞΖ)SΗκ*|Ϊ\Ο¬+’"/ μ7h>ΌΟΤδεˆΞ²­›KœŠR…Ό½­ug»ύS>εGρ¦–Ζ½uCϊΏωHGΐlρώΛω―ΜIY Ζ;³Š¦ΩF)Bά™Ud5LUόxΗ+ρ9η•9E}ZpΒ69{¦¦έwΡ<«Σ)F†GgΆi6€δeΔzzη³ϊ“Ε"BπE³Τ½½ζμœ$Άρ7,½·ϊ΅πΪkG[#©ΛV8”%π’L…ύ—WΊα™αhmrϊψΓβ“G Α°69ύΰ@χ„Ώ εŸwVͺ5Ίο/wέξΨτN³Λ.4oΌρ›ώšΪΙΗ‘Xoύ¬5Ι‘˜MŽ{Ύ!δ`δΝΏ ι)xxcšΥΤ?υνsΗΗΥ΄-ίΏ>Ωb·ξ~όCwJΥΣΏΨί£S+wήuΣ² «ξλ:υκ‹{›ύc Rέ©)qoΫΈ~RΩΛ+‹3F‘E}uΗ›ΌšŠ5΅tyYžΗͺΖύ= ΅u=ΑΙλUq­_o7ZŒκΚνΫ5ιͺ:Φ8šψ”QЈ©,Υή8`ƒ£Ύδ„”zΜίΣ1PΌΑιPDΨ]XΉ,Ογ0+zx€³‘Ύ‘?,\Ε›Wgیf³Zysš.τα3‡j{5!„bJ)ή°27Ωjͺ>ΩζΣgzg_&Π—KN‘Σ36ύν―XΝζXC—½/Ÿ‘¦,£KLY]²-όΪΗŽ·Ίξ,=ϋdμόvuθΨoώλxΚΆΗv]XΞΐ‘_|η°{Γ£eϊΞs‰}Jκ¦{nKozκΏ~Φg,ΉύoΩ9ψύ© žb‡Γκ·χ^\ζ>ωΖαQΝhσ8„B(ŽΒΥ•ι#5O EmΉkΦ―(πk™΄₯Ώε؁V[ώ¦ΝI-ϋkϊ/J Dl.‡*O¦˜bNΞL6‡ϋCR(Jl¨αxέpP3₯Wl,σν―ξm>΄―%q||ίΨρqEΕ’š>rpoΤ]ΎeuazWu/oεϊrIμ=Ή²Γs™-γΏΌ«b•Α`xκδα‡Χm‰iΪ‹υU‰Ύœp·i$T{ΎΑΦυΔ―ΪζάUX’άsκxwX Ρtβ΄ο‘xHŸz7ηψ»MΛfΘήl΄š…Π„ -r•6©r΄΅y8kΝϊl«’:‹ΧUΊΊš;cΘΘΰΙγΉpHߐ”–ζ²¨ŠΊ”BΧ4!d ―'šVR豨B¨&GJfͺν\ Θ`0lw{L3(L%)Εδγ‰0φO—Α ‡Α˜Š%-+Ν|α9Ζ‡…=°xΨyeω5γ/99άΏ&93q ωγΫξHάψ‘›ΞM$n‰jΪΙαώKΆ£+νw~Μl6*V·Ψς€IΗ«ΎΰoΣΤΤ=φMWŠ ΥE}#6«={†ΌΫ»{ΉU5;μκžΗυ޽?}¦64Ω#8ςάλ»ξύΘ&«ξο~ώ…šΐΈοjC½₯™φφΦDω)fwώς•£ΠΓ#uuΓΊ"ΠRuΖ°¬bΫ­6ƒŒ‡Gϋ›κϋΞΝ;ΠΩΠ»ΊςζΫVκC΅NuMύΥ“™ν>ξ#/qώ‰θ:ΫSΉlΛ–’H,ζχŽϋŒώH[{Φς­·HmΈώ`M6o/v—ΐ )λ7n^τA”•ΥΤΤ\[)™ψrΒ§I)Χ­[ηυO:‡i>_]!…˜ξσΥ—μΘ’½v]Rϋ‘3ƒWρ­‘ζœ5λ<ΗjΈ~ŠΗε>¦°€kΪ](+ζŒύ— /°Tq||‘9Œ†iΙ•ngͺΕ,„ŒDkFό{†q•θKΜΞjO#ωΩΦqS™c³ζΨ¬;S~ήή]ευMω“JvΙ}Ÿ΄ύΒιΞΉ}6‹ΚΨτIDATL¦Ω΅ςΓkΦ-³(ƒν/όΓ™>M!TχƒOμNΓ―ώ³QB(φβ=±a…Υκι;ςροΧΝμC3[ΚΓ7ُΎήΡ<«q™“οΏ=»ΐdtŽvύϋށ‹―ϋ¨f•>ΈάιΡcΞΌ<(…0ήVšΧάπl/ ŽΩSμwάYh8^χ  τευ—ο-ʝτZrVƒϊή’άΆtN™˜rd κ·Fο\Λ΄Ίx£γwŸj7=trίqΫΐωK\›ΏφΥfSεm΅e›ξά² GGsΛl£7:ό?/ r‹>UrιSuΣrγρW«ίτΥ@ΌΆΑΏ½2%₯―ˆBΈΡv۝;\Š2vvφΎP5Τ7εSNI[Vώ.{χ׏ϋψs οΏ\ £α‘όμi.T¬ρH~ΆΓh˜βϋΑΡζύC9ζ•bρXΔ@00aƒm:έpzδ ’Ν˜΄>/^Ϋ™ΗκSΜ&§ˆ]|Ν‘ψ·Α’²ΖΝ…žoX²ξπ©/ύ¦κΙ7z}yχ•Yy*ΐRΖώΛ²#-ΩjΈLΝ[ ꎴδη{&ό ΰXω§Vε™lJο‹Ÿ;>znZqΛύ™›ˆϊκ~xμψΩΙχΦ¨ΉΕwIΎΫn±WΎύKΛεpη+Ψ/soΊσsΫ’NεΐO~™8>>ƒ;ονoέΈ«Πi νiχNΔΖΎ•μ*ЇΖέ9)ε-³–%„λmjy] &„jsέ²>g}ͺΩ ՜n{‘=2ωPλΆ›K6%&eΟξΚέ2vςPΓΓR!τ`λρΦLΛkή0{0oΤΒΊΤƒ#ΓΗ{²u[ "šUQxW±#Υ’ΔCSΥ―vGErΦ{·€ΈM&‡ZψΡL]θώ—^i«‹ !„==λэi–xKmΛ―B\ΜθΛλA₯Ϋ96ύΛ#/XL–˜-HΙξςφ+ŠΊ`Y~rVβn—τ₯8ύΥ7j²Kξϋ€ν–τυοpuνυί5i¦tO²2euιΝΟ~‘%iΧζϋ šωώσeΧyΰ₯t?ψΔξτιΗ­ΊχΌcΗ†Ξ½ω³N_Κ²OΌgϋžη~{ώΊΓe΅‡/,[-_‘“Υίόο―’FKž[θBΕΊeKAΩpΫwϊBŽΤΆlρ6πM6^~soΝAGϊ{o·x‘νμΕΡ;δ‹%{¬FŽρdΊ©η²tγHwTŠρ½ΆΏ­Υ§Ϋ³σή³1§χ…–ΣΓ=ίΎ7q|όcΗΗ!S‘;ϊγ?TSsίΏ%³Ό₯₯–ΐϊς:b1r{ΩZƒjxιτέ«ΆΗ5mΓ‰D_NΈΫ4€&gŽέήι φχ]΅ΝyzΡ-i=Oύ¨£?.DίΩ—šΧάWζψν€?±έΆ˜ z\_<*«έšb υ„"νƒB‘Έ<«’όϋχŽhBŒοΟά–azӝεnH‹λͺΙ`‚ΎΌ!)eλ+?ΉV˜ŒŠΏΏχ™³A]ˆΆΦs‰ωz‡cΉivELυ΄’ZcΛΠ &Εΐh‡ž•lS„ύΰ@_^_TEMuxΣ.«S‰Ef9Hαo;7ξ^υΆwXυΞ#ί―kμΥ―ΖPv·)λέ~ΰ!„³{ΞΘHLSͺQˆσ{Eυ³Υ­©•o½#Η₯‡‡Β…Εδ0$νΌ}Ε­‰…ί?‡wύ*&£ͺΗ4βςF%[ͺΟ<Σ₯ΗβρσοPΣς³w–Ή2,ŠŠΥ’›ξ-™zδܟAz\*oήϊςϊ0‰ζΨ¬Sn<Ηέm¦ΫΫΐι¦ΧO7 “­ψ±MΫχ€Ά|·^N—•R*κXJhΔίόύ―<=ΩΜΎpΈΨš¬ˆžσ@ωφυνͺ;7ο±u™%]m‘X 2όβ:ZυI—%fΈ­OI2 †9ͺyÊΗβώΘ…ηβH½g­q™_kΊκΌ{w«–Ξ_ 5#ώ±i]κ3ΉΫ΄¬όu.»IΊιR‹κσtΈOHΙ¨pœ«>­ΏeŸ/‘›3=‘,9%Ε«S.‘6δk59 .Ό-TΝΚrgYUEH]—BΧcRθ£ήΣaχŽr‡SB5€f$ŸŸΉ DFΞ<σ S΅¦hΝ½jβƒj£~MŠ33₯όΒ ε2Υ,IΦ$vRΐβaεΩ;0Ό#=%q ωΫ7νNά8a"¬ι{†/I«eχ Λb0Ϊ\ΚmŸͺiή#_9Υ¨™2wΨρA‹IΡ ϋ~04«£γ†¬χ|δ曬ͺέiUzpm\―{ιΩ―U…₯ZoύŸΎγ?ρξj]ί~ςεWή§ρ¦uΟΦ―|Ζi•±ώΆί<έra>ρΡγΩ;σ,‡Ο$ΚO±₯¦οήPΰ2ŠX(pςx{«.„<Τn^›χ‘{-f©y‡GφτžkΩсW;‹ο½{υ[uίs/6Ÿšϊ=ΖTOYdθ^ςη+rtπΥΆΒ»ξXvSX »CžώΎ£ωωά“‘Ε}/ΎάΖ©<°π”υ7/ϊ ΚJŠjjj–ϊφLΚK§γ»nέΊ;&?GgšΟWBH!¦ϋ|υ%ϋšΫυ{fωWΧοΑ»+/zβΔ !„’(ώ;~bΒ4 γγ §ΚλϋaKgX›dOcXΣ―ΕΈBΘΠΠ/_Ύͺq)„ˆW½YO\p­ΰψψB'f“?Έ#-ΉνL΅˜…ƒ‘h͈οΐp ~eύ€fg­Ωδœπχ‚ Ÿye0ΐae@_^ΏqνωžK>DύŠιέ='~Ηκ‹Žγγ /°Tq||ώmv ±€kœ“UsΖώKΠ— /@_³Γω=ΈΡ)“QΖbϊR‹jΆΫ Ρ@ˆKf_n·ϋή{οB<σΜ3###¬ΈZΫ3VΑZUͺͺ,ΘϚR+6¬-υ–Πψ/š»tέΚ|§²ΛΊόΛΡ]΄²,ΣΖυ¬―–’’"·Ϋνv»‹ŠŠXpυ°rJΞ·rEΎΣ H©k±p`€Ώ»³Λ‹6*ΞΒ΅+r­κ$ΦW€id’R–­Ο ֜nΞ~³ύY-8ΤΧ―‡ζuχΰτcP¬Ή+WNHFh=UΣΊτώΚr΄Ά/>Ι’m†Ύ0Ο ?λΙ/ΚΛtΫT-4ΫΡά鍌’β)ΫP€Ÿ=Ωδ³O2e’‡£\v0gγΖ›°bQM‘Δu!•x4zng±κ)\Y–n1©2βhmlL T1x +7YνFνοhnšΈ³uvΧ6ƒ_«#§’8;Ιj6(Z4ΰννhξ½€~'­bvηεgΊmFσuž­ι ˆKηΟλmjεεBˆ³gΟ°*€ΎΌ^¨&gZz²)ξ Ζ…°;]Ϋι*―fPeD±ηV”€[kOxeRή²\u‡9§8%ΦZ}l ¦ZL""…š<ρ–Λ1Ω&kuυ¨0%e——–δωN΅…‘ŽκΣA!€+φΌKέ΄\4Ό‹ζyρΟ–—¦G;κO GMΙ…e₯zMuWHΞl$mΖKΞdλα’ΑΈ/ΓLΧΎθm¨ξŠΖ{vYEiΟ{v(‘b±‘況ΈΙSPZ²LœlŸ°oyVΧrΩ_k[Πdwš|­Υ-#δL/*._..]i“V±εT”eD;ΟV…t£Q„ε€Ώ»‡ς§ŒΛ]»ξLL“˜p{‡U0Ž˜&Ύœφ0ͺβ)^·uΛζ›6―[™gmmhMD‘ŒG"±X,‰ GZ†ΝίΥ6ŒΕCC]½‹ΗmQ4M7Xμ6“’ΕBα˜.„Έτ–‰ Z»uσΖ­›7nέ΄"χά™"2F£‘ΐ`w_ΐ”δ4 !„”R—ΊλR(“.ϊβα],ύlj†=ΠΡ;ކ}½Ν~{zšcΚ5qΙH.y83ΜΈ1Μ\, Η΄xΤΧ3ΰW¬VΛΉΥ£Ό^8φυ΅tϋ¬©©Οφ™υγ½ά―υό}’ΡH`¨£‘3θ˜l₯]:ZΕ‘–aχw4χŒ„’ΡH0Ρ§^]˜·Wϊ€―wΐ4ΨΉ0δhGmγ`\Χb±ψδE€˜Ν&ΥU²~sρΉm˜ϋϊhG]KnAAε¦’@_{[Ϋ@H»τ– j―m8·WNO<‰(‹KΥ`Pf°θ™n€Ν“‰œ?>‹Dt³Ω¬q™μ;7yΙΓ‘W0˜ΛΥ’’_˜Ÿκ΄…¦+jΤ?I€F’šΙdšzό³}Ό3Y·ΡpXKΜδr£UΜ³ΔηηwwΫgΙΞK /—τξ )g΄MEΒ‘ι>ΥPΖ’qΝΫvόlΕχ τ·ΥφwΪΣ —•”Ζƒ§ΫƒΪ%·\4=\ “.LEUΥi­x¦θΕ?Ζ v‹Iˆ¨B˜¬5œΝΉρNΗLsρfϊ\OΙ/KΣ[kOφ…tcFω†ΜIξcΆZ 1EγΏ²Η;“uk4›ΥX(&/ϊeM:Z‹Ζ 6‹QˆΨež6ΈlbΞαΕΞΪ€βψψR!ƒύW^Q†Λf6™Μv§Ν$„0;\v³A±P(* FΓd·LΨ Œ¦ “oe$5»S\V³Εξ°¨“.zʁNψفΎ #―(Σe1[“2‹sœΑΎYΌύο’‡#g2˜‹Η`P“+6o¬HΉάζάΑNυ’O³TŒf‹ΙdΆ{rJsμ γΏ²Η;εΓQ ·Ϋa1[ιE9I‘ΑAΏBκšn΄Ω-꣕ΑώpR^I–Ηf6›mN›IΞκwwΓs»έ=φΨc=ζv»Ypυ°rΙΠgš”Β܊U…&E…ϊjΪύŽτβ’4»Q‘ρπho[—_=o™Jž’5›‹ΞηH¬ΏφψπdΥγοnλO**[Ÿ-’£uu=—.Ϊ;ΥY3—όlηΩF΅(oΩΪƒςφ6œιΝεΓ1yςVdٌ2κhο H!„τχuϋΛσK²Gk;'­ ΄Χ7Š’άςΥωFυ7W5{'™?'O!ρωꉉ“'O²Bΰ*QΦoάΌθƒ(+)ͺ©©YβkjΒqπ±/c_]»Φ;κ牡 O υejγΙ–ύZρωΟlεDο5σλCz\Ξ±p–§ϋΐe±rΖI0ν[-gώFLΜ―&ξοj Žκ¬ \Ζ>_}ϊ|$.€ΎΔυI υ„©KoΓΡόβΧ+Ξ©–ΰυτ%ζy €5Π—˜Ÿ[#€Χ5€χ_Ξn{3ώ$ž©ΞιρΈœ¬+ΰ:.NΊθΛMΟS§NΞ©¦,΅dœtšˆϊrΡ²rότ€7^Ί‘"7Ε ΚΛ†ζTυ  /―VPNσέΔ¦hότ₯AΙζ XjΉ9σ δυ τεΒηT;/'έ ± XĚœζ»ΌΥθΛΕ ΚΛ&ζ4)ΙF X‚ΡyΩΈδ• τε"DηψγγSmΨy ,…š$%€Ύ\Š59ՍμΌιθœτv^Ή@_.tbNZ“œ9\A9·βΠ—W71§©L6Qΐ5žΌr€Ύ\ΜΔ—;8ΰZΙJβθΛ%”˜3Ω,QŸΐ)HβθΛ%·έš[)²έΧ*ϊ‹Y™(K /Ae”%€Ύ\ΤνΡ ’@_‚-ΐ쨬Π— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θK€Ύ} ϊ /@_€ΎθKΠ— /@_τ%θKΠ—} ϊτ%θKVθKΠ— /ϊτ%θK€Ύ} ϊτ%@_€Ύ} Π— /@_τ%θKΠ— /ϊτ%θK€Ύ} ϊτ%@_€Ύ} Π— /@_τ%θKΠ— /ϊτ%θK€Ύ} ϊτ%@_€Ύ} Π— /@_τ%θKΠ— /ϊτ%θK€Ύ} ϊτ%@_€Ύ} Π— /@_τ%θKΠ— /ϊ‹ξW yiTIENDB`‚typer-0.4.0/docs/img/vscode-completion.png000066400000000000000000000543411411311634200205250ustar00rootroot00000000000000‰PNG  IHDR} ‘Χ™6sBITΫαOΰtEXtSoftwareShutterc‚Π IDATxΪμέw\Sχχ?π“„!A+l0PA+8@Α­₯Z±Z΄8°Ulm‘ ΏΟW; Ÿ ΅b«ΤΤVъ΅R΅   ΚPˆHΒH2 χfόώ"’ ¬γ<π‘ά›ϋΎο{n.Ύr'ΕΞΞzEΧ#*]‡€RΣΨ¬&•€B!„ΠCΡισ€κΤX)„B!τ8¨X„B!„Ή!„B='t°!„Bnœύ{S{i»”T‘r…άPίπολιoΎωf[[›žžN‰D.\xΎ‹ΰεε%“Ι:ίβώN„B‘§Φ¨λ%υL]¦\!η7 xυ·ΰƍT*•Ιd644TVV>χE ’­ άί‰B!4πLjΔ5ΝςfkcλK·rί|σMg΅ •Je³Ω••• …βυΧ_OLL|ΰnшˆˆΌΌ<ν‡8πάΤ„fhhψμφžn»6~ယ‚œ ~½B!τi‘·΄Ά΅κ3τJEU#μΨ±R©”ΗγI$±XΜ`0δryee%ΗΠ\©Vι5I›ψM©B&–‰+**Œ΅©©yv‹`ii©P(:ίRϊΌηSΝδΥχάθigO— σΆ{gQδ貈OΦφw φΜ¨„…‚° Έ2άB!τό7n\kkkηΫ?ΞΞ°3u¬#ΗΔΤ@WRxτ—,Ρ .MγΩέgq"„B=EaAρ<―°€pNV*αβΝe3d‚Μ„¨ΈΜ;)ήά+0$ΘΧΝΦšψ…iρ±‰y“ƒΞ8ί’˜ζΌ…ΉΑOέΆ&ΎΏθ!„Πfη»’”Š©:ŒκσΏ-YβΫ"khh166¨©!˜#'‰x₯:ΊzCΝ,ΉŒB₯θšŠΆΚsΦΥΥu=Ήσϊυλ555uuu˜;¨­ςΉJ½10˜Ή“¬ωαύέ?ΠmΧ~?ƒ~2oΫ‰’•sΝ”μŠόΓiύœ“ Ξ!ΖKv†ίώώAV›ιDίΘχ|λo=Q@· ΥυvζŽOψ-¦ŽΌϊ!Ρq ±3D2̜NΚOܚY*g؍6•Τώ˜΄μΗ‡:Ξ.J^ο—ό γμdajΆxΫλήζp}'Ϊ 3bΛθΐpqcG…-Ι™{GΔ„Fyλ“tאmαξ‚„ΨΠ,>Ψϊo †„ΥΉg™Αρ ςI *”1Ήξ\nω!„Π“'ή¨H™B‘˜J₯¨Tκϊϊ™L]•‘ΚΆ²o©γƒFC΄Ι˜l“f‘@Ej΅~ϊι§#FTUU :T"‘hbξ|:5ό’Ϋ"ΓΫm„ΌκJH^CxZHͺ―Νj€†¬όσ ζOυ6:ρ[3}τΘqϊ5‡Ύ/.m¨Ήš˜ζν3ρΐΉ»M6žϊώjQ΄Udρ:@ύ%Œφυ±9–,pφυαπSΣxwΖς3’2E ΚLJ ˆρυε&'ς˜>ώΎΜŒmQΗςH%Η§ωΖΌ>ρNpAJLbžde™"άπB‘AsC=ΓXΗX€y©Ή±™FγθλλSu•ζŠξ¦Yii©΅΅΅€Ήέΐΐ UΦΖ6ΎΥΪ°ΰŸλEΊ Q ΪKζΞ§©"HB „ HRtšvŸ(Ρ iΈσ]¨i€ιVFth6΅2’·k[:F4”7Λζ²- ‘τΞ’₯‘’aϋ[–šΕχΣFJηΧ'²y)‚Ξ -δ ο$P8ΆζtΰΩΈΨ2Œ=Ά{½KBΰNΒ$Δ₯₯˜6B‘§GUU•φκJ₯rȐ!2™L₯R1™LF£T*ο~€nψΧαEΈ'ύΓhtΊύ›žP‘ƒΪ?AjožŸ―sR–Ÿ»4ιž=”ŒΞtξτƒ¨H^ŸάcΆ$·p„Bθί₯¬½{}…ΈΔ=|¬₯σ3Οe,Ηu}χB=―ˆaΕΉsΣ(#{3h¨i&κ%€!ΫςΏ S'#f³€‘₯οΦHP=0φ`€Ngά7\”ωw)g’_€Ÿ;£05³Λ׎aΛεvΌδ:r@Θ‘‚R>p\έΨΈA#„Bθ™ςb='ΣΜ9ΰ[;S#Χw&{6œΛl² ΈHn5εH+K·1Ύ¦υΉΧKϋΡXKΜ†sbΠοΗο3wŠ„R–‹;›N§ί3…83­ωϊBFQjΦ=—q|ΧzqΝmάƒΌΩii<e$§Ιά‚#‚½mΜmœέ½ύ7†ω;γW!„BOΉ?ΞΞ`™θ‚‘ΎΠτLLT 4J‰ž‰εœ·£ί½s½ό¦Υϋ 2;μ“«½_&ŠΛς&}²M— Οο>uB{ϋ9‡¨ΜΥ+Η~Έέ›.“Tεώύυ~QŽSΛ³ryψΏX±ˆΡη}”΄ΘΌδȰΐˆ$?Ɲϋ(uŒ‘e₯I=œσRσξiƒ(LΛβ‰Šη0€₯i1Ϋ’΅'~’EqaQ5ΑΫόŒD“PP–uΟηD!„ΠΣnΐŸWDsœθ;¬KœUή:•xΊRυ―/ιΈυAkιgϋ‘ζscβύωA±EwΊGχ K gΔωoΛΔ“5B!τLμη©*NXe~t&ΫvbΠ<03#&B!„ž[:X‚™kȞθΧYΒά€¨V!„BΟ―?ΎB!„ΐ}ΗΩ©X„B!τ`ξD!„B˜;B!„ζN„B!„^˜ά©7nιœνϋήΫλ{ί―·₯?“‹ΐ ή“3Χόώα{R΅vϊΫέ=μPΚV_:ώ@!„žŒgϋ>Jφn }υŠΆ'*WI<υ7ΏdόJX( Š+λΗ‡y‰kόΑ&pOœϋƒ?@σ²²Εβι™Ό†₯ΈQώοΔ?‡χΎJξLψ.·_1=pOάBێ Hω₯Y)ρqρϊ^υήΖI K†=B!τœδN#‡ Ζ8pŒ τi*‰¨2βΉ’ΖA{VέΜΐšKJ$ςρ~λdQrtΡ΅Υ’›ΣΏΨχTfΕ„'”“γμΌfύV†((:️B½hΉ“aβh₯ͺ)Ί˜Ϋ,Qι;zLυžΝ žhπΔινχγϋ7]·½u²‹'>ΨΙ'S"–-¬?—$s^θcnFWΥgΨ])=Χw^ π±²2‚ζJώ©ύgO”4Οπ …ϊ5r3+Σζ²£oΞ΄‚ς‹_o+κeφ6ώ;γόDi₯ζξξ&!.Jމ9V&:wfhπo4‡λ“SΧ@ΣίaKbοT=1$&Θ—Λ&„y ΡQύΩuŠB‘§"wι)Oɝ3±]δκΘ‘U ψγΩΙΜΤe™@χπύv=-qΥ©œϋΒ‚Ύ‡ΗŒ¬ _ΐo ΉΎ$€εΜ7>œI;ΏϋθΧ·i. ^ άδΫ²)υ| €!Τ|U9?Β{ΎCζ—Ϋψαc§:'•χž°9ξ܌°…eΰ"\-`sˆΌδΨ„RΎ˜aλΌ1<\š,QςzΏδ³3l}ύΚβ’BYΎ!a!σ²Φ' z_ϊHΏL`ϊF%ήί/7ί‰)1αΑΆ_HXh„X°&±ΧΗ 5όutΩ_`9ηνm»—^™φušσ‡xΊztkΗζenG c?Ϊ(!2ιλšλΚωnς¬Ϊt±‚μ­ΒΪοšγω―iαΫ›I3+WC%ΘΛΎ|·μ!³w«A’ΐ`0θ$“Ν€νI. ¦›hΘΦQPtIfG/τ‹ξι8;Γm_J\Tˆ˜3/$tM OfdˆG!„Ι ^WΔ ι@›Dzς‹₯Γh.Oό±²V"[―δ6’`4Υ›#ΟΌ˜”ΥΨP#:Ώ;§¬¦MλΘ^7ω·ω%υΚΪb~UyMU³ž©­―YHσR’Κd²²δδBΦΔΧέι@ζ%Ζ&§ε• D"^^rRž”λκλV€YI{2ΛΌΌ€”"°uα>Ξ₯.ΒΜ=ρΩ<‘ /1>Mhλγλό¨ Ι« jͺ jIEqcUqM]ίPΏc\sξΥ³5*’θpQ•‘Σ4'€^+ά‘ r~Θll!Uς~NΙcο§³ηxqˆ< ‚Ώββeρ"AYZb*εβΞνGΉβ³Λe™I©₯ΐuεβŸ„BhπΪΰ5ΝξαΜ¨Ι)ύ ‹₯”έ©½'£˜š)kΣ:vŒ’ευ:3¬ H€’$H% χ™ό±πΞl™P W.2ΐvχ τs΅ε°Ϊ‘…t€Έ―ΆΪ3€”I₯ΐf<Ζ’ό;ϋJω₯BΒΟ†C‡²G:v¬„ IREMΏ£,Κ†ΫwI.i ¦V (ο©ΒωRYU,£ΨΖ―G§Ύ]Ϊ¦Τ¨Έ41λ»&ΨίΛΕΦΈ£~MόΎ IόŽμ/#€Α`πH;B!τlεN†εΔ“Œ*9Z"ύW–‹$UHβ!–θ]^kΫwcX;3*4E Ϊ|.S¨ον0ΡΔΤΚdΚ{žPsξβγ€b†[@ΘLgη™Α ]€Yη‘|žlάάΩ@7χ^θroΞ!DB)ΛΕǝM§Σϋy §Η“r&ϊy٘³ΩΜ~MΓρ^θncnγμΛ©ΘH+Œj2=<&š˜ZYM_ζf__~¦ό1*LJκuμN…ό­ΠqΩ«1?κφσ>J=*6%,$*ɏ‰E…)i…\ογEi )ξ‘AqΙλχήG !„BO ΕΞΞn›cYΰizΟ šΜύΗKڞ―ͺΩψοŒσΝ νγEΟ)σΐoηXKϊ2½ 7„BυfάΈq­­­ox§τκ‘=W±Θ!„B¨;*–!„B=|œ!„B!­nΗΩq'B!„z0w"„B!̝!„Bs'B!„B˜;B!„ζN„B!τβΰϋΖƒΡπW¦Ž±51ΠΧ…€‘_tρ\‘ˆΐ2#„BaξθUr~ΡΕόf ϊV“&ωω*¦ήΐ'*"„BaξXΝ—:Ÿ“)’8ΈΫšΐ !„Bθ7xηwX6Ž&Κƚf¬2B!„„6Ζ,Xδi a~κ©)V!„BΡ ΊΝφ&~EΕ _’oηκlάp³²U……F!„zΡXZZ*ŠΞ·ƒrœ676ΦV^M?W©λμ9’…UG!„zα ώύ;i ],3B!„Π o€Οο4q}Ε•VSΩΠ,'hVžŽϊββΚF,3B!„ζ΁mN!Qθ»{LuΧΣΧ…ΌΉ¦θΤΕK"¬2B!„ΰά)­ΜJ­ΜΒ²"„B‘nπωμ!„Bs'B!„Βά‰B!„ζN„B!„Ή!„BaξD!„BsgL‚\³Λ’Ο™§X/tY{xBδI―ΘΟLhƒΤ!φ»ρ_Ζ―2{Έ‰θΔη{kvNΤtLΆψ—ΒΘ3•‘g~{™ήe3`ρΙΛ~£»< Κ1rν™ΚΘ3•‘gR'Ϋβwϋnπž”˜Ήζ§™ώIMϊ{>R !„zt^°εε˜ΝX’_·³ΰΗ‹J TͺAš‹²κ΅«%ν5γτζΩλ,J·ΑcV―°(ί²c]F;‘P]Ζ‚Šσ&Εέ![Ώ›ΆlΓ‚πyΖWέ{krΈC»œ&~QZb\bžΈοΐ?3*a‘ ,(¬Ÿ3’œ>ΐΪ°£9ΰ¨ωOBόk€B=£Ή“5bφΫήVͺ²?όSϋ-/[νΉ …l0η"Ή°7ιΒΓMBΜWTόiRά}Έ™‰14œΚnΧί7EFΞgΟσw³βPXlš Ψ6ήΑ #"d!‘Ι‚Ÿ‹€yΈ’%`>™΄›ΐΏ!„Π3˜;\_ueH$ ?h§Ή˜/Ψdγd«Σ^RŸsO‘ϊΨΝZbβ`«M²C·Ž₯ΘUΰξϊΉ fύκ5 ro~ώicΟ»<Χ~Όk‚€˜a5n>.™ςξγ¦άθuG.ΘlΗlXχκ„‘ζ,VUΩwxΧΩzΐ`Aά[7 ό—θཝaQgFΤΆuΔΉ_ΑyΑs!:³+ρ«³wχίιΊΘ¦sIY]όΣ§Ο9?FΫαη#7@φ‘ο\'θo/>υ΅@}ξΖ)©ύΙKfΦο~>k©1(λς―ξΪrϊB€γηkw­ό‘ΨtΑtc]ι՟ŽDDW“ΜΙ‘³–O·±3c’Ή<γBμ–ΌŠVΟΈtw"œΖ©JΊPμ5yŽ£ς–}‘‡[€9vτΊHŸ)#ŠζςΣιΡ[σ»τΞ6gƒL,–‘ύŒβbO ΝvKZ3ѝ,ΣΉ3CƒηΉr9Ζ,hβf&ΔΕg‹ΐάgb#ΈΕ¦ϊξ «£ΖtŽ[ΰΦ?7ˆs“£Ά%—έν-γ"#lΎlμn£μnxΨ#„B¨Wƒr~§Ρ˜W\εy+oΣpV€ƒŸΏoUΑΑΏi£}˜ωYw"wεϊ‘£7φ,/ψqO«Ρ—Ή>4€ΌŠ˜7²·~\ΫL΄žx7{λΩ[{ Z FλρˆΔ“„Λβm±λ~>Οπ˜=eΛ€Q~nWXΜ»οΖDi›΄z„v Ι‘ΝΣ¦}ώCω’ k‚3ηψή·ηDl< Σ6ϊMξr²¦™‹ΒLΚ(ξz¨—<ύΗ‡­ΣήΙ­STƎί:Νaλ΄wk[%2Νaλμ-εD?Λ₯kςξΎ€C―Η.ίυξόƒGZ]"β';ή9§‘1e|ρ‰εn_Ό»Eΰ΄bΖΫΪπCŒu›OE\η·cupzΥΘџ;vtX—Eœ>φS³Σ OγŸφ…%΅OXαΒΗΡΫφΝ°+N˜ΏcupzΥعџΫwYFΆ_DbbBθDϊΓ―nBF@`sˆΌδ؈ΐΐΠΨ4†wxΈΏ €(y½ŸŸ_ΐΞBB˜ΊΡΟΟΟΟΟ―3t0l}ύXiQ‘‘©b—€y6][π­Ζбœξs}Œ#„Bθ‰δNšΙ„ΧFΚs.V΄ ^―iœQ¬ΦŒυΥEuκ­ …κΞ„5f‘Iϋί€J… a?= \^7zΤλ‡ˆͺλη+*.—·IΛˊωeU`akPrξ«½ΉJκ…Βϊ‚γ'ΞΤ±GŽaχέZρΉ.K”%§V1¬GΨέεh«"¦ ©\tŸΙ l*XαBv‹°ψφ‘­ΚmFΟΩ™»νKj$ΔΗ *,FŒΤV«ρψ–ΣGŽWWT΄π³―$`utFUEΓε aI† IΡpωtcqvΤΜΔBF¬˜<²"cΫ–λΕ-όμλ±ί—³|Fyό+v˜6ήΑώ. ai©ΘΌΔΨδ΄Ό2HΔΛKNΚ“r]]ϊ ₯YI{2ΛΌΌ€”"°uαvD!Τ©•# „Bƒk ³ΣΜ'Όβ$Ι9ZΡFƒΧk#ŽΈ΅‘cw–ͺŽ―Pjc}ˆ΅-Υt„[δό.Ÿ ³Z)w€φ_”$A(A`Ϊ-Ψ8wΑ+ VG€©bφ]I’I±ƒRͺ$@‡ΑΈ;J—‘‚2Xε²ka<Τ"μrdΨέaνuw.·'Z[ξ\œ€$ΐΪ±&f¬[α2‘ΥΡΟϊ†;V­C•(  %θκ0@ΧΞΡ”1vΖ―•3ΊΜΉάX £qρ±PΏcΣm—5I©k΄]δgν‰JΠ^+Δvχ τs΅εάιW!ΠΗ%G„XΐοΨW,“JΝΈw,E]Fχ‰ΊΓ!„ΊΟΤ©S)wv€ΞΓƒΉ―σNφPή ]Η¨Λ·_ώεoΝΰΜ–::“7v*‹έ˜x¦BB‚ΩͺψΠ)Χl½ŒΪΞRΌr‚썯.ι™^―m‹t*>Έόp΅°˜+–yΏοu"=}pApΕ@ Y‘›&&±X ΎsΫwcX;3*4ξl:ήοωŒυh‡½ŒϋΒε€t!„BψόNBΪ|χ€¦D*B(„ƒξ²–[«lrΩ΄Ϊ…­εΉmw QdTμ»KW`€TΡΜkΝΛSπάΫOΕώ6"bξ#Σ i{Υω¬“εζc€>9ΰΘΆ±)Τ)μΜbβZΔμΔ }ξ0“ι']lŽ|½]·pH_ΗΒu­7€―œsηŠμΨ2ΈΊ5:τ§Ž ΙΣ鱇mόzέ]¨ϊiWΰΦFPχ.ί'ϋ|ΖςkΒ†B“ <#§Χω§OE'-ΨΈoΣE{“ όΘαJ§Ω}-CE^ΔrεΊΘΙΏΝbA{]…ΰjub€K/JΩγΌηP0!—e€f Ί^Dζ%'f„F$ω1RΟθνo{@Ζ·ϊυψΗ!„d;;»AjΪhΜ‚EOΩ}γŸ^ΊάΦ£Ÿ+’Φš%‰±OŽΝλυ©σu—―š΅@!„ΪΈqγZ[[;ί iNνu%y…•R,y¨ΔŒβV•™ŠQΤ@Αj<)š‘#TΕœcΝB‘gii©Pά=Δ:ˆϋ;B!„Π‹¬ΫώN*V!„B=˜;B!„Π“ σ,vzΙ’9ιιιƒ=—–ΝsΏϊM­ώ­Y‡_ ωd n„!„žirΉ‹π<ηΞ^H1knΓR©ΓP€UχZρc†WΪpE#„Baξ|TmmχΕI=Ε§αBK΅φΑΠφ‰Skƍ†ύ?γ³=εrυsΏϊΫΫΫρ7"B!„žŒηκόNΉ‘³“iλGo+Ρ,ΣΥkΒ‘ΤΤΤΤΤ­Ύ]^ΓτJω9Δθζ} IDATυξφܘT­„ƒŸ~œY±[ηsꋝχκ/Ώ‰MŒKŒxέ΄ΛΗ€ϋaΣ”.wηρό 61>.1>ξ»χέπα?!„Π λy:ΘγωΰΗγX“yΠ½ψˆΝšϋpyqΑ‘Yb‚u}ς!ΜΛΚwΉΝ»ψX¨ί1`όJXψl•N^{­°τΦΓμzΤsσ_τRΛο_G]l"I²λ”-eΕW…]†(sΎέ˜:žοo9np!„ζΞ£7bή2ο.ϋΝ„™ϋSJžΜι•:*SύFι«Luρq™l[6ώΞˆeέǐEΙΡEΟΕΧ@rεhΒ•‡šΒΠΔZ‹Κͺ[ξ[Ή ΉΏΗ !„Bƒž;@))ϋηTQ³φBςΔιQκΤ΄˜>hT‹NΝ#„NΊ{X6νΣΦΧ'₯€ά˜Ή‘i$έ{λ±p€¦Τ°%qύ ŸLWΠΰΉΆΖ BXšΆ'&>[ΐ ήγZ”ΐγΞ›ΘeƒΈ09j[rΩρω _gŽ1ƒh⋏I.’Π½·&± ₯ζnqZRž<(Mˆ ?Ζ`rg‡,œΘε0!/+)&.MΠϋ³α§‡o]Μ¨ϊk[δQa—QΟ€wόΗ;˜κƒΌAX‘ώΫφτJ›_FΎj v‘q³€lσΆΏμ~ΊΥΧ μ··ŸoΑΝ !„Bƒ›;A)onll|ςΛΒ8–£λ?KqίpΚ­Vα#΄GζE/τ‹א„m6‡‚Βκr@ΜŒτΛ¦oTR`?γϊGEΜ•₯ΖEDσdμ‰A‘αDθϊDžΆγŽ^ξ©αΑ±–wΔΞАyYλ“ 6[œ•ΗJάy!‘aβ5‘ib6EEfD­ΰξ Λ ή6ΟΧωX|˜ΟŒˆ b€Εm‹)3έόCCΆ­Ηυ–<›NG…œΞ¬ˆpΟ{GzΏ³j<œώ!κlθ[8xšά!8ςIπ°x}k„gΡΆΟ’λξNRuθ³ΐCΰπαΈe!„Bθ δNƒ‘³]i4BR[žwρRE³κI-LΡ1ӟ\jW8ήsi‘’vθWΗθΔΏ[fΊ»\NibPbΆ ΗβR|ό|Ή‰ρΪΰ)ΜNϊK@ˆ³ώ."Β]Έt Ό»3½(!₯Θ/ĝKOΛ––ρΔEB‚U”Η+cρΔΚΈσΊ’‚γ3EiqI“‚}έφ呏ΠeCh)Ήr]؍Β*άVB!τtεNUsYΞ9‰°ΉM₯oε:a‚οl]ΕΑO*υ)ι;všΈ~Q?ΎσDOΉώŽφEεΏ]fΧƘεšœzw˜TΘ¦@ˆ…ϋRI’ €Ωqρ½ΉWpH€·‹­1K;€Θbή™– @@g0Ή\ΓeMbκš.sΞe?b—«.Ÿ―πy;ό ‡[‚ͺ²+g/ήlPβφ‚B‘§'w΅%Wk΅/ΣhFξnΆOnwc«ώT+0LΎF5Υκ¬?ŸŽJΒΤ° Έ²Fή?ΘΖ?"άW–ž'’έ+"9όΣu›fDDg“ΡaΑ?QŸ”ΈΌ<ΒuΤΘqo}ψκψί"·Ÿoΐ-!„BjPοί)mhTθθ0žθQ$-΄Ζk¬„Ώ ’―ιHZ¨Š'Ÿ0€NΏg©…<Αvv3ο#t«-‘—’”'’p\Μϋ,£ŒΗ³\άΈ=ί"“Ξ677gχšmΒά’Ώ‹όΉΈn.zURIι:ΈΙ!„B˜;‹m€«l“<αs+ Ί€ <ωšΚΚxBc7__9›ΝΦ'σ’SωΆώ}]mΜmΈήsC"‚έ{ €€@ dp=\˜@ηΞ φuμ{Ζe)‡ ™~‘‘sέΉζζ\W―™Α!ήΜ»γΩ~‰‰ρ!ϋ<νΌ,φagb`hβ0eΌ~‹°φ±nM ¬β λυκ0cΓ‘ϊ˜?B‘Οo4bκ]a₯°Yϊ&Ξξ8ςŠό'œ;Λςυ ήΌ ΧMή=(W9‡$Δϊuά₯Τ-:Υ wt^ρΞKŠ;Δ ‰Kd@ΣίaKb‹€—!  ˆˆ eA“ΟΛKφz8œ—“ΐ K82™¨4%-Χe^Ÿύύ΅-‚ƒ†Ε¬aTΘ/-LMλuωιc‚v¬uο8Φ6"q&Yπν‡?\Q‚\g7{iψ’‘ϊ oΰξϋξXEο3Χσ ί±Τ₯γΝ;;βίh=ϋυφέθԐρ[ςπ yŸl] δ•ψpΫC!„^4;;»lŽεψŠ―§ƒ‰(εβΪςK™—*₯~=ϋ’%sΣΣπ|φΥάόό?Ÿέά†ϋoΝϊ?­€ nč!„z^7΅΅΅σνοο”Vό“REF!„BέQ±/2Ϊνγ˜ΐbμή‘''|Έ‰ Αβ#„B/šgψϊ==½AmŸ 4Ούκw_½γϊώ'9L_sm_(“γψΔ†LYψθλλγFˆBθ™&—Λ±ΟξD―6'etπχ4σϊδBž\Θ€'3ΰ5¬?B!τBΰλŠκ?ggg,B!τ )++{¨ΟξuE=” ‹€ΠsΖǜ€ΚΚΫX „ž3φΩ^W„B!„ž̝θ9‘6sRΉLW; ξ\μ'¨\¦k† Ε‚#„B ³£§ΡHSΗnσ8L“Ώoe,9©PυύΨ)5w’zμZφ~hͺ|ΜΉk Μ•―|¨1s†΅όœNΪם£Tγό5œα”ί+)ν­ r™ze]ΗoΈ{?‰B!̝θ©foΌdΤμaFΦ …GyΝ‚ ·ω±ΎTp4·ξϊλƒΚmΖzExƒ",£4τφ JS%΅πΈ†m­±‡λ!„Βά‰ž Ί4ΖΌαΣfr§€ςΞοΜM"ΥJˆ½΄ίέbδͺ1 |Δ‰)ν-=~•sφCΞώ鉆e΄+ΙΤΫ—Ί’½ηΙςTΡ ͺθ†κ₯WT˜;B=k ƈ.…]ŽνVRRJΔ`Μs'zZ„O\i’gώΟ7u²†Γσꊯ§U|ΰ±ψ“I«7ŸyΐlΥΈ…ͺ ΪΧ΄μύ΄«G:G©‡MRΉϋk ­AERΔU΄Μο©βͺ^ϊ œ΄Jν6»γυΜ-Πεθ9ωζVΝθŽΝζχ¨’}.‘-ΗdΣ’Ω#emŠ3—―νψ-΅]AΰŠF!τ” bȐ!'zeeek‡xyyi4κA €Χ‘§Η(3'&]υ˜Γ ­»7`0FΞnμ`ohωΐ )’›Τ’TJ}y·αš‘JίΝ¦)υΪ κΝL°LϋΨj‹©E© ₯ς2΅(•ΒΟοU™M-JΉΈΏ?"ι:ί}²Ί±yΙ§ίn؞0’k³qρ,\Λ ?Ώ™;w~ϋιτ£G>|ψ€tΓΗΗ'>~ΟοΏωβ‹Ο»_ΊtIddDΧ!‘‘‘Gώ~τθολΧ―ΨΉ˜˜˜μΫ—8tθέΛi4Ϊwίνzωε—•β?ΦxΧiΚ).bΔΟ_ψω+CowόV[rr.Q©΄©S§ΐΤ©S¨TjNΞ₯Α›ζNτY{κ³λυεŸN^σ‘Ηs}c•6ΛΙϋί0 h6€Eχψ%δλ\ΨCά—;-F£4έ¦]9€saύP₯ζZΫΓ­‹:φP«€VrJηΒZYzΗϊk©:φP$υύ\–Ωή€R•˜RY[_r«ϊΫίRgOΕ?¬θ…Φ™½:Έsη·³fυλ—˜§§η/Ώ$ TΤjυ™3g[ZZΏ)ΆfMποΏ]ΊtΩΦ­ΫΊŽͺ¨ΰάσ)&&fώό·222aFιιgΊήy[₯R>|$0pω³ώ•(ΩV²δc²©·ŽΧωση)κ»οΎC‘P/\Έ0¨σΒγμθ)’PΏίHO坟ε8υsŸυ€ZY+mψοΉοψ’ΊGh"*iƒΖΪ•\~€",£ήΜ –ž~bΛς’­…ƒ₯Ω•€―ξΩoahΠΨ"Α^X₯₯₯qq» Ί‡‡ΗͺU+ΫΪΪΜ9σ$; R©vξά9 MιιιΘd²n£.^Ό8P644œ2eςƍ‘έ†_ΈpaΥͺ•ΞΞΞϋπ„ϊψ―“BΑά‰^,νJΕ‘²΄’ϊ›«G/ΨvaΟ£o<„œ~x£ΪΕWm;Nc1Be= τ iW’‘˚½PPΆώ«q…"tχ·₯‚¨€[·*έάάΌΌ<Ϝ9C₯Rί½Q£\MMMΔbqzϊ™C‡i4Ÿ >ΤNxτθοΪ©BCο&°ιΣ§/Z΄PGGηψργ‡ιsξΪF ,,όƍ»§hsΉά•+Wζ T*+*xί|σMο;DνμμvμψFϋϊ»οv@IIΙώ³-ZτΞ;‹ ??ΏΫNОθκκ.Ÿ4iF+..Ω»w―H$κ;i€ϊϊzmΡξ­€’  ΠΫΫϋqrη‡Ζl…Fs΄U"T©jΒ!vfσ^cX™·•ߍO&뛨T³…3¦Ž£κλ΅•ίKL!ͺ…Ÿw΄±X:OΧήJ%•‰ΣsR(Έ1ω$|σζTw‚ΰΘ‘#**|7·ςςrν9y,§­-ΘΠp₯­αΕΆΆ#Ιyy[?ζ²_Ÿ\ˆ5Z,›gυή’ΫΫΎγ7¦ωΈΧξ9DˆšΜΞ΄ ]^ρΡΧ VStΫΠ ι՚ψCΊ6–ΑώdcsλΉ\άžžžΤj΅6kž?ήΛΛkΒ„ —. Φ)žx~'zZh4*₯Η/$…BΥh°[Qε€UΚI«4Φ£@ν0^9i•+4lri‚ς•”γ«Ζ/JkΝ#φΝΘΊc.,3P»ΝQNZ₯9££o  Vk¬^VϊnRNZ₯ΆžΏ £ΧΌμhλ`iζ7y\δκ·q-#4ΝΛΛsτθΡΧ]₯RΉ{χžkΧ‰D’‚‚‚ση/Œ3¦ο½¨ΤψψψΫ·oŸ8qB,;::>r8σΒΒB‘HT]]šϊWSSΣ+…΅΅υ”)“cccoή,―©©Ω³'ήΖΖΖΡ‘Ϋωssσ¦¦_ΞΨΨΨhnnώ8sΟnk_S'œ'¨.%ˆOLLNΨΪ™υ» 9γ²τJ1! “ώΠwΖ°2φk^M'/H―–5’ΊŸŽΠMΩ,·α`0~…A―Ϋ—BԈ$— [2σŒ_Ÿˆ[ΑS‚Α`΄··gggίύVdg+ν cζˆϋ;ΡΣβHYΪΧΣ6ΈφGΎ°΄Ϋ({Cλ΅γ^\}` νΌσh,Fh,F€’„μDŠΌ™RW’ΆC QΛ3iُxƒO Σ΄λ\ΤNS€ΒΏB+>Y#-#Nε±Hν8(J{+*ςύ/γ7,~σϋπΥT*εvmύ™ψϋ½θάά\N¦R©j΅ϊμٳǎύ‘ξη7sζΜ™ζζζΪν^½ΪgSMMM …BϋZ"‘°X¬GξΥ‰©|πΟ7/^Ό T?±‚ΨΫΫS(”½{χvhnn~γΖMνk:ήΣmH’μ8`Q(8Ψk_Η‹›γ››;GU+•ί‹›w7‰ƒΩμ5l#c-ͺ±±χΦAΗ.^…@€Ρh’‘nΚVάj•*[dtsS`pL Q“† ;&αΧN‹ΫΒS‚ ˆn7ο€ϋ‡`ξDΟ‘ƒ%' E7‚άζΝtœΊZΗF:TΪΫ.Σ}Ό~+9™v+λ±S£fμžϋΐ)²Fϊ©/‘'τ“Ÿuί³R]ΠΣ\:φί”₯w^ωή‰/lά»Χ,BnάΈρέwίΡΠΠ T*΅==',]Ίtϋφ˜’’"…B±rε [[»>›κœόΞ/ΠG?cpώύgϞ=z΄——§ΏΫ[ΆlιŒ}O€B‘xηw{+‘H ©™LfΧ‹ά{'S«ύοδιζ{Οζ4‘Rί4`Ν10`Σhš[’%ν£ζΎχ¬ΐ³9ζNτ4*i䅝ζ0!b{·[kΨC†~=mseKυ¦τ―[x8BΟΌφvEUUχg78;»”””δζv°±±ν:–$I‡ώ―ΚΔΔD£Ρτˆ9ŸΟησωώωgllΜΨ±γ)wΆ··3™ΜCnίΎ­««λδδT^^ώΐIxΌ[NN>…ΐήήΎ’‚ΧίdH‘ά"Ιnέuu—NΦΣ»ͺPό §ΛδdZcΨXh_θژS(BΨj5ΩΨ¬kk!½rhCY:†L²ΎˆΊFΊΉ1…AΧξςΤ΅΅$„χ<Dέ ΠιΈuΌ πόNτtΡ€&½2gczτν–š[ΝΥ …)ί\ώC'BΟ±šš;;{ν}Ρ§L™ώψc==½ΞIςσσx<ύε—Gζη_yœώΌΖbςIΏΊfumέΙ~‡N0ςφ`uaΨp8sδ₯<’ΆΔιΩΖoLfqaX™[¬X@6΅H o€$χš†TZ,ŸΗ°27˜ΰfθνή|φžkVΪx|Γ‰cθfΖ4C nΟ;άί‰ώM>ζ=ύ‘#υ)‚z0π1Η:!τ<;sζ —;,66F₯R•––₯₯₯YZZu͝Ώώϊۚ5ΑFFF••·»ήGι‘,]Ίδ­·ήΎŽŽŽ‚;·:R«ΥlΆΡ¦M‘†††ΜΙΙy΄Yt»X^{Ϋ¦χlΚΘΘ9rδgŸmΣΣΣ‹ŠŠΦΞ(.nΧ’%KΦ―a±XυυυωωWΙ.;& Εbρ„ γϟΏηnήφφφ™Sω―ρ *qΪE3™ΊVfς·kvw,rΣΙσ:†–kΡτtεε|Aμ~P©@£ ψ1‰œ%sΈ_lPJΫΔ'Ο‰ΉΤυΘ{ύ‘“Vks·™ͺ£S©–·γFρ£ΨΩΩaB ggηΚΚΫX‡ω}ξγγηηΦuΰϊυ!ΝΝΝόŒυAO’ƒƒύΓή2vάΈq]ODΖύ!„ΠΣ+##ƒΝf:΄σ?o*•Z[[{βD*=s0w"„BO΅”””oΥjužΟ„ζN„Bθ°rεŠΏx1«€€λƒζN„Bh`όψγOX„ήG !„BaξD!„B˜;B!„Βά‰B!„0w"„B!̝!„BaξD!„BΟ*Ό'B‘AχΡG›±=[Φ­ Αά‰Bθcoo?~όx¬B/8̝!„]HΘz,BΟοD!„B˜;B!„ζN„B!„0w"„B!̝!„Bs'B!„B˜;B!„ζN„B!„0w"„B!̝!„Bs'B!„B˜;B!„ζN„B!„0w"„B!̝!„Bs'B!4ˆfϞ=z΄Ϋ#Onkk»lΩΑθΨΰ΅άzzzΛ—/311yvΧμŠAξξξΫζΠ‘CCCCMLŒϋ?‰υ΄i―φ4ΦΕΕ%$dn†˜;B½ή|s–΅΅u??¬««;~όxƒΡ9δ₯—œ^{ν΅ιΙ¨Q£8σ>[ž6mZ\άN­C·ΉΜ›7Ιdφψί9•Ίsη·Σ§ΏώΤΩ)S¦°ΩμG›Φήήώ‹/>υΧ_’“Q©Τ7¬Y¬-ΛΤ©SθtF›Z·nέπαΓ{kgg7f̘§yΡ–β—_’ll¬ŸΉ \B‘AΆxρβΧ^›Άrεͺž>πύχߝ;wξ—_~}ΨƒσŸO‚‚V1ΰ}ŽŒŒψφΫo…BQο‹ΕUU|₯Rω―Μ½“Z­Ύ}ϋvSSΣsωύΩΈqCMMνgŸ}F’J΅Z]SSΫήήφνŒ7ΞήήώσΟΏ€~FGGέjsrr’’’ΆŸώΉŽ½_̝!„^ ·oί666:thkk+X[[oΪ΄iηΝ·nέ===‡sλVε³ΈhωωωωωωOCOΎώzϋsωεΡΧΧ···φ۝<O;δΰΑƒΦΤΜ™3333₯Rι uυ‡φ2™ϊ —·=ϊ;47· ^)0w"„BέUVVΐ°a…0q’Χ°a“&MζN …’} ³gΟY²d \Ύ|yχξ=mmm ££σΞ;‹^yεΛ—/τS‚v8$$ό`ύϊ΄7οΪgnnήΠΠπγ?εζζj?ζθθΈrε ‡ΊΊΊ~Φ†ΕW^yεΝ7gq8Γ?όΣ΅Ϋ›6mΪ΄i­θ©εiΣ^]Ύ|ωςεΪX3oή\ccㆆ†/ΏŒͺͺͺκljĈΛΝΝΝY,VuuυςςhG}όρΗnnT*΅¬¬lχξ=B‘πs€>ΪΜαpδrω‰'’“w+ςž=»;–šϊ—©©ιϋοΏ7bΔ΅Z™™C׏͛7wξάΉϊϊϊΥΥ5ϋφν+((θ:ΦΙΙiΩ²₯666,λΜ™3»wοι©ςϊϊϊ«V­τςς€ΖΖƌŒŒΓ‡ ><::*(hEss3lΨπ!ƒΑψκ«uλκyωςε¦ …bΛ–O«««΅f±X°}ϋΧ@’δΒ…‹>ώψ£ΆΆφ;wvkφ«ψnβΡΡqssνάυΨ{ΰε—_^Ήr…T*=pΰΐΩ³€έ{ο­qrrjhh8pΰ@VVvΧIΚΛΛ΅/ζΝΫΪΪͺύΆί5«©©ι©J†††«W―rss“Λε'OžLI9Φ΅ύϋK‘ݚ–.]jjjZQQρύχ»΅ίΊϋΧ#ζN„B/ŠššΉ\Ξε:j'φττΌxρ’§§gRRpΉΓ$I]]φΓ7oήLOO722 \ΎtιmlZΎ|™»»{|όj΅jυκΥ{χξΥ~ώΣO#$ I’Ϊ·J₯ς·ί~kmmυφφ ϋx͚χšššLLŒ·mΫzκΤιŸώΩΛΛ+<gΞοΦΎ}ϋ ΈΈΔΛΛkόψ 'Ož€ϊϊϊΞύRέ466vέgωΐ–;Ηj4šΛ—sE"ΡΝ›εχ·F’δεΛ— /ΑβΕοj³3Έ0ŒM›B{š;δδδTUUΊΊŽ?ή£§άihhTSS£­ΥύnάΈ©}qλVε«―ΎbkkΣ­·ύΤξ`{`εGv>|ψ‡nΰσω0a‚§₯₯EΏ=-rEEωυλΧ8Immm·jtΣΛ*Φ266V(2™¬?uΠΡΡa2™ΕΕΕeeeg͚ΥΪΪΊ}{ŒZ­ΞΛ»bjjΊpα’ώδΞn_³žr§‡‡‡΅΅υ§ŸF΄··_»vΝΕΕeςδΙχηΞ₯X°ΰ­ΜΜΜ_~ωςσ―ξΪ7gΞμύϋt[˜;B½XJKK§M›“'OΊz΅ ±±©  `Κ”)ΏύvpψπαŒFFF`oo§££ρiη(:N§Σ{Ÿ£R©lhh`³`Ψ°a..Ξ‡μLff¦Ό,][ξTRRR\\²}ϋΧύχ‰'Ίξi»_~~ΎφŠlxϋν―Ός ›ΝnkkΣΥΥ₯Ρh*•ͺχΤΦΦYYYυ4φψρ?6mΪΔαX?ώGNΞ₯Bͺ]eIDATnc½Ό<ίzk₯₯…\.}}ύ^fΤSεššΔΪΠωa‘ϋΤη*ΦΧΧΧ.rκ T*?ΎvνΪΡ£G?ώgEE888”””tξs½v­h€‰Έ 6LWWwώ}Ϊ·T*U;ί^888€§ŸΡΎV«ΥΕΕ%Oσί̝!„ž„όό«ώώώ¦¦¦S§NύσΟpαΒΕE‹?ώ§΅΅υύgΧIΪϋ‘€Ύό2J$uMšήgJ¦£ύπ•+ωڝaZ}^\’ΡhϊΣrΧώDFFzzz.Xπ–vaiiiO“«T*ν‰Σ§ΏώΦ[oiOΛsuu]ΉrE?η~τN99—Φ­ ™5kVhhh^ή•―Ύϊͺs”υζΝ›““ηδδθιι}ρΕ网§Κ’§V«Έ\ΠυΞVχλi‘SŸ«X.—λιιυΏϋχΘΞΞ7oώώχΥO?%όωηŸ ₯χυO=UI©TΆΆΆnΩςi—ϊ“}Ά6 ]Βά‰BθΉRVV&•JgΞ|ΓΪΪ:''rrrή{oΝ‚oI₯ςςήφλΤΦΦͺT*33³nWŠΘdr`2™]³?@ πςς …ύΏα‘L&ΣΧg>Τ2ͺΥꬬ¬μμμΟ?lƌι½δNg@\.·¬¬μάΉspηͺ‘Gž{Wυυυ‰‰‰ωωωύογp8Χ*988¨Υκώ_ήSεkjͺMLLLLL»מπjiiΩ5§v’P(½,ς#Σ6Ϋη*‹ΕC† Ρξυμ© P:ίήΈqσ«―Ύ ˜;wΟώYUu{€ΙT*U»Λσε—Gυ~θΏ'=UI @η5U}βσω£F:sζ P©Τ‘#Gά\s'B‘ŽZ­.((˜3gNVV–φ‚θΆΆΆK—.Ϟ=;;;»χ}6‰δοΏ^Άl©nUΥν‘C ›š―_/ …‰Δίν³gΟZXXœ:uΊ§ώόσČ3ΒΒΒH’΄ΆΆ>~όxοζρx3fL‰DlΆQO'€vemmνμ<ΌͺŠΟdκ³Ωμ›7ovϋ€ΎΎώ»οΎSZZ6|ψπiΣ¦νΨρ­67x{{O™2ΉΊΊΖΞΞξ‘ηήΥ΄iΣδrΉ«λ(₯RΩyF#πω·―\Ι§R)}6ΥSε³²²κ7m =xπ “Ι=ΪM‘Δbρ7‚‚<(•ΚLMΝZ[;ξ"Τάάμααž››ΫΣ"?‚ζζfFγν흒’η*ζρx$IΎτK=ΥA,n64:~όψΌΌΌΩ³ί,--Σ†uνύΏNœH9sζ† ΣΣ]\\^yΕ':ϊ«GθvOUΚΝΝ­ͺͺ ;|ψHs³ΨΒΒ277·[²οζπα#ααaB‘°΄΄τ΅Χ^344ψΰκ«ϋ~]n)ͺ;ΰλΫ·oώΧ%&&–——?ςΘΟΣΣ‡ zσΝ·ΤΤΤ„Ό©Gχά3ζκ«ϋφνΫ7!!‘AƒŸ~ϊi^^^Ÿ>}RRRΚΛΛ'OžόΩgkŽvκΤ)³gΟ~睹νΫ·ΛΞΞ>ϋμ³7n\QQρόσ/,[Άμˆ•/Ύψ;wέuΧψρχoΨ°αˆωZ­mΫΆ·ίž}φΩgΗΕΕϘ1cωςΏ%Qllμ°aC»vνzΰΐΉsη͚5+ςπϊL#%%ε7~B˜?ώ”)SGŽΩ¨ΡY=φσψψψ±cά²eΛ&MšμΫ·oΜ9³f½yIγƍ‡ ωaϞ=C~ψαλ―Y«χή{ο½χήB<ψŽ;v΄nέzψπaιιιεεε3fΜXΌψG¬Ÿέ΅k—ζΝ›οίΏόψϋ‹‹‹/Έΰ‚ώπŽ΄΄΄mۢ͘ρr^^^dΝξέ»ίrΛ--[&οΪ΅kΛ–-“'O.))0α)S¦†’““υ«gG}ψή !tθΠαξ»G&$$TTlŸ?ή›oΞ!€§§ίvΫ­)))qqq .ŒŒπ{΅ρΰΑ·GΞηζŽKOΏΰΊλ6lψ‘Ι5pΰM—^ziTTΤ₯K_xα7UUU 4ΘΙrα…ΖΔΔlΪ΄iμΨάƒ.YςΙUW]³~=έ ΐ7KλΦ­‡Ν™6mΪΊuλ’““ΛΛΛwξܝ••΅zυκBVVV~~~α‚ χξέ;uκsqq±C† iΫΆν+―ΌZVVΦ·oίϋξ»oȐœHηνœsΞIIIyόρ_ΦΥΥ}χ»—δζŽ6lψαΧΎ333FŒ1iδ 6=ŸΓ‡jΩ2ωΌσΞ{β‰'λκj»wο1~όψŸόδΑ•+W†&L˜Π°aτΣO?ίδΦ[o‰‰‰yι₯—κ9mΫJyδ‘ž={I£F:tθ0mΪ΄M›6gff 4¨ΈxΛ’E‹B&<χΜ3ΟξΩ³»λΫ΅kΒ‘έ9}ϊτΌΌ?…vοήέ΄iӟύμ‘ΌΌΌG}4+«Ν}χέχπΓŠζˆ‹.Ί0?υ”)SγββΚΚΚ’’'N|pώόyωε—{φμ9n\ξwήYQ±½sηΞγΖεΎυΦοσςV$%%9²iΣ„’’ϊξmΫΆ=ό ;wξlΣ&+;;ϋΟ^·f͚֭ΟKOOβ‰'«ͺͺ82gΝzύψCδ΅ιι|α°ΩΩ·uιεΉη~]WW›““sσΝ7O›6­wοή={φ|φΩgwμΨΩ’EσΘΥωυλΧ7hΠ ===ςξBwπ ΄iΣƒ.]Ί¬΄΄tύϊ‚ΘΒ‚‚ ;wŠ”AFFΖάΉσ"Λ·oίiΠ”””+Έbώόω!„²²²§Ÿ~κΌσΞΫΈρ˜OBͺ^Ύ|yaΥͺU=zτθΤ©Σ‚ "?JNNΎσΞ͜9σγ?>Φ|Ž*r•vωςρρρ?ψΑ •+Wvμψ­ŒŒτ#ξΪΊuk‘ΆΆφG?ώϊλ―οΫ·―>Σ¨©©>βΌΰαV­Κ/**ϊτΣOΫ·oί­[ΧE‹uμψ­ΜΜΜQ£FoΪ΄)„Π½{–-Ώΰ[ω‡†½ζškvνΪ©ήεΛW4oήόϋίΏιˆξ !lΨPp(Θ½φΪΒΒΏΌψβ‹!„Ο>[Σ³gΟnέΊΟ›7οΖΏ·hΡ’ιΣ§‡š4irB‡»ΌΌ<’ςkΧ½δ’K²²²Φ¬Ysψ^=ڎ•ΗΩ9!„ΈΈΈ«ΊκΑ'Z΅*„Π€IΣΫn»uΪ΄i Mwοή½|ωŠšššCΩΏή½{}τt'ί8kΦ¬ωμ³5?ώ‹wί}wΜ9ΫCΛ–-ϋΞw.š1γεΤΤΤF}jͺ΄΄μPρD:&..>›«©©)++kΦ,αΠ’Q£ξ^ΏΎ rΑχXσ9–ΌΌΌaΓ††***"ΡBΘΟϏŠŠJIIY·n]=§Q[Άl=χάs#›ΫΎ½2υ”––Άf͚C§„σσW]tΡ…ΗΙωηŸί¦MΦk―Νό[DE΅hΡ<2ΤϋοπΟξΤΤΤ[o½%##£.&&¦qγΖ'JMmυΐχZ½pαϋ½z]1iΣoΏύφ{ο-8ta}ίΎ}±±±>zΊ€oœššš &τθΡγ{ί»!rΦjνΪ΅‹}qβO‡Ν3ζžΜ›7ζΜ™υάτϊυ³f½~ν΅ύ6lΈxρβϊtgMMΝΔ‰?ΝΙΙΙΝΝ­¬¬¬­­=ϊ»όΣ§Ώ8rδΘqγr+++7mΪ\PP0~όψαΓ‡ίψςςŠ_ώς‰O>ω*--7n\NNΘ1χμίΏΏ¨¨hΜ9uuuοΎϋ^llάΥW_»aΓ†ΒΑƒu‘ψώΝo¦_}λ―Ώ~Χ]«W―ή»wΟε±Η~>xπν'>xπΰΑΚΚΚ’’’ϊσΚ+―šσνow|ύ¦M{~χξέύϋ__QQρΚ+―†RRRnΈαϊΈΈΈ;vΌωζ›‘ο-uνΪuνΪ΅ύλ_Γ‰Ÿη_κŒΦ­[Ϋ œBYYYŸ^ί3‘?όΠ‡ώoδK막Λ/Ώ,;;;;ϋφΣj―Ž;Άͺjί€I“ΏόM§¦¦>ωδC‡+++;ύί~111Ο=7uΚ”)Gί»”“”––zθ¦υΤΉsηΘIθη;ψ $''7lΨπβ‹/NLLŒ|Χ‡£υλΧoγƍϋχοoΫΆMχξέώαEσS¨}ϋv‰‰‰%%₯ρρράXXXψ΅ˆΞΒM7έTQ±½>g”ωςιNΎš’ΊςΚ+ zθaxχ…bbbzφμ9`ΐΡΡΡ[·n}ζ™gΎΜ?&'·ΌρΖ“’wοޝŸŸK3Ύ.ϋ­ΈΈψγ?>ΦσψjΉΞΐ)vBΧف―‹“ΏΞξ>J|t'Ίέ Ί€Σ‹ϋ(pκ₯₯₯Ϊ €ξΰ_λDο΄|CΈΞ€ξ@w€ξ@w ;@w ;@w ;Н ;Н ;НθNНθNt'θNt'θNt'Ίt'Ίt'Ίέ Ίέ Ίέ €ξέ €ξ@w€ξ@w€ξ@w ;@w ;@w ;Н ;НθNНθNНθNt'θNt'θNt'Ίt'Ίt'Ίέ Ίέ €ξέ €ξέ €ξ@w€ξ@w€ξ@w ;@w ;@w ;Н ;НθNНθNНθNt'θNt'θNt'Ίt'Ίέ Ίέ Ίέ €ξέ €ξέ €ξ@w€ξ@w€ξ@w ;@w ;Н ;Н ;НθNНθNНθNt'θNt'θNt'Ίt'Ίέ Ίέ Ίέ €ξέ €ξέ €ξ@w€ξ@w ;@w ;@w ;Н ;Н ;НθNНθNНθNt'θNt'Ίt'Ίt'Ίέ Ίέ Ίέ €ξέ €ξ@w€ξ@w€ξ@w ;@w ;@w ;Н ;Н ;НθNНθNt'θNt'θNt'Ίt'Ίt'Ίέ Ίέ Ίέ €ξέ €ξ@w€ξ@w€ξ@w ;@w ;@w ;Н ;8}ύώYΥ"8₯ ‡IENDB`‚typer-0.4.0/docs/index.md000066400000000000000000000230161411311634200152350ustar00rootroot00000000000000

Typer

Typer, build great CLIs. Easy to code. Based on Python type hints.

Test Publish Coverage Package version

--- **Documentation**: https://typer.tiangolo.com **Source Code**: https://github.com/tiangolo/typer --- Typer is a library for building CLI applications that users will **love using** and developers will **love creating**. Based on Python 3.6+ type hints. The key features are: * **Intuitive to write**: Great editor support. Completion everywhere. Less time debugging. Designed to be easy to use and learn. Less time reading docs. * **Easy to use**: It's easy to use for the final users. Automatic help, and automatic completion for all shells. * **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. * **Start simple**: The simplest example adds only 2 lines of code to your app: **1 import, 1 function call**. * **Grow large**: Grow in complexity as much as you want, create arbitrarily complex trees of commands and groups of subcommands, with options and arguments. ## FastAPI of CLIs **Typer** is FastAPI's little sibling. And it's intended to be the FastAPI of CLIs. ## Requirements Python 3.6+ **Typer** stands on the shoulders of a giant. Its only internal dependency is Click. ## Installation
```console $ pip install typer ---> 100% Successfully installed typer ```
## Example ### The absolute minimum * Create a file `main.py` with: ```Python import typer def main(name: str): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) ``` ### Run it Run your application:
```console // Run your application $ python main.py // You get a nice error, you are missing NAME Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing argument 'NAME'. // You get a --help for free $ python main.py --help Usage: main.py [OPTIONS] NAME Arguments: NAME [required] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion // Now pass the NAME argument $ python main.py Camila Hello Camila // It works! πŸŽ‰ ```
**Note**: auto-completion works when you create a Python package and run it with `--install-completion` or when you use Typer CLI. ## Example upgrade This was the simplest example possible. Now let's see one a bit more complex. ### An example with two subcommands Modify the file `main.py`. Create a `typer.Typer()` app, and create two subcommands with their parameters. ```Python hl_lines="3 6 11 20" import typer app = typer.Typer() @app.command() def hello(name: str): typer.echo(f"Hello {name}") @app.command() def goodbye(name: str, formal: bool = False): if formal: typer.echo(f"Goodbye Ms. {name}. Have a good day.") else: typer.echo(f"Bye {name}!") if __name__ == "__main__": app() ``` And that will: * Explicitly create a `typer.Typer` app. * The previous `typer.run` actually creates one implicitly for you. * Add two subcommands with `@app.command()`. * Execute the `app()` itself, as if it was a function (instead of `typer.run`). ### Run the upgraded example
```console // Check the --help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: goodbye hello // You have 2 subcommands (the 2 functions): goodbye and hello // Now get the --help for hello $ python main.py hello --help Usage: main.py hello [OPTIONS] NAME Arguments: NAME [required] Options: --help Show this message and exit. // And now get the --help for goodbye $ python main.py goodbye --help Usage: main.py goodbye [OPTIONS] NAME Arguments: NAME [required] Options: --formal / --no-formal [default: False] --help Show this message and exit. // Automatic --formal and --no-formal for the bool option πŸŽ‰ // And if you use it with the hello command $ python main.py hello Camila Hello Camila // And with the goodbye command $ python main.py goodbye Camila Bye Camila! // And with --formal $ python main.py goodbye --formal Camila Goodbye Ms. Camila. Have a good day. ```
### Recap In summary, you declare **once** the types of parameters (*CLI arguments* and *CLI options*) as function parameters. You do that with standard modern Python types. You don't have to learn a new syntax, the methods or classes of a specific library, etc. Just standard **Python 3.6+**. For example, for an `int`: ```Python total: int ``` or for a `bool` flag: ```Python force: bool ``` And similarly for **files**, **paths**, **enums** (choices), etc. And there are tools to create **groups of subcommands**, add metadata, extra **validation**, etc. **You get**: great editor support, including **completion** and **type checks** everywhere. **Your users get**: automatic **`--help`**, **auto-completion** in their terminal (Bash, Zsh, Fish, PowerShell) when they install your package or when using Typer CLI. For a more complete example including more features, see the Tutorial - User Guide. ## Optional Dependencies Typer uses Click internally. That's the only dependency. But you can also install extras: * colorama: and Click will automatically use it to make sure your terminal's colors always work correctly, even in Windows. * Then you can use any tool you want to output your terminal's colors in all the systems, including the integrated `typer.style()` and `typer.secho()` (provided by Click). * Or any other tool, e.g. wasabi, blessings. * shellingham: and Typer will automatically detect the current shell when installing completion. * With `shellingham` you can just use `--install-completion`. * Without `shellingham`, you have to pass the name of the shell to install completion for, e.g. `--install-completion bash`. You can install `typer` with `colorama` and `shellingham` with `pip install typer[all]`. ## Other tools and plug-ins Click has many plug-ins available that you can use. And there are many tools that help with command line applications that you can use as well, even if they are not related to Typer or Click. For example: * click-spinner: to show the user that you are loading data. A Click plug-in. * There are several other Click plug-ins at click-contrib that you can explore. * tabulate: to automatically display tabular data nicely. Independent of Click or Typer. * tqdm: a fast, extensible progress bar, alternative to Typer's own `typer.progressbar()`. * etc... you can re-use many of the great available tools for building CLIs. ## License This project is licensed under the terms of the MIT license. typer-0.4.0/docs/js/000077500000000000000000000000001411311634200142165ustar00rootroot00000000000000typer-0.4.0/docs/js/custom.js000066400000000000000000000074711411311634200160770ustar00rootroot00000000000000document.querySelectorAll(".use-termynal").forEach(node => { node.style.display = "block"; new Termynal(node, { lineDelay: 500 }); }); const progressLiteralStart = "---> 100%"; const promptLiteralStart = "$ "; const customPromptLiteralStart = "# "; const termynalActivateClass = "termy"; let termynals = []; function createTermynals() { document .querySelectorAll(`.${termynalActivateClass} .highlight`) .forEach(node => { const text = node.textContent; const lines = text.split("\n"); const useLines = []; let buffer = []; function saveBuffer() { if (buffer.length) { let isBlankSpace = true; buffer.forEach(line => { if (line) { isBlankSpace = false; } }); dataValue = {}; if (isBlankSpace) { dataValue["delay"] = 0; } if (buffer[buffer.length - 1] === "") { // A last single
won't have effect // so put an additional one buffer.push(""); } const bufferValue = buffer.join("
"); dataValue["value"] = bufferValue; useLines.push(dataValue); buffer = []; } } for (let line of lines) { if (line === progressLiteralStart) { saveBuffer(); useLines.push({ type: "progress" }); } else if (line.startsWith(promptLiteralStart)) { saveBuffer(); const value = line.replace(promptLiteralStart, "").trimEnd(); useLines.push({ type: "input", value: value }); } else if (line.startsWith("// ")) { saveBuffer(); const value = "πŸ’¬ " + line.replace("// ", "").trimEnd(); useLines.push({ value: value, class: "termynal-comment", delay: 0 }); } else if (line.startsWith(customPromptLiteralStart)) { saveBuffer(); const promptStart = line.indexOf(promptLiteralStart); if (promptStart === -1) { console.error("Custom prompt found but no end delimiter", line) } const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "") let value = line.slice(promptStart + promptLiteralStart.length); useLines.push({ type: "input", value: value, prompt: prompt }); } else { buffer.push(line); } } saveBuffer(); const div = document.createElement("div"); node.replaceWith(div); const termynal = new Termynal(div, { lineData: useLines, noInit: true, lineDelay: 500 }); termynals.push(termynal); }); } function loadVisibleTermynals() { termynals = termynals.filter(termynal => { if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { termynal.init(); return false; } return true; }); } window.addEventListener("scroll", loadVisibleTermynals); createTermynals(); loadVisibleTermynals(); typer-0.4.0/docs/js/termynal.js000066400000000000000000000225161411311634200164150ustar00rootroot00000000000000/** * termynal.js * A lightweight, modern and extensible animated terminal window, using * async/await. * * @author Ines Montani * @version 0.0.1 * @license MIT */ 'use strict'; /** Generate a terminal widget. */ class Termynal { /** * Construct the widget's settings. * @param {(string|Node)=} container - Query selector or container element. * @param {Object=} options - Custom settings. * @param {string} options.prefix - Prefix to use for data attributes. * @param {number} options.startDelay - Delay before animation, in ms. * @param {number} options.typeDelay - Delay between each typed character, in ms. * @param {number} options.lineDelay - Delay between each line, in ms. * @param {number} options.progressLength - Number of characters displayed as progress bar. * @param {string} options.progressChar – Character to use for progress bar, defaults to β–ˆ. * @param {number} options.progressPercent - Max percent of progress. * @param {string} options.cursor – Character to use for cursor, defaults to β–‹. * @param {Object[]} lineData - Dynamically loaded line data objects. * @param {boolean} options.noInit - Don't initialise the animation. */ constructor(container = '#termynal', options = {}) { this.container = (typeof container === 'string') ? document.querySelector(container) : container; this.pfx = `data-${options.prefix || 'ty'}`; this.originalStartDelay = this.startDelay = options.startDelay || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600; this.originalTypeDelay = this.typeDelay = options.typeDelay || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90; this.originalLineDelay = this.lineDelay = options.lineDelay || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500; this.progressLength = options.progressLength || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40; this.progressChar = options.progressChar || this.container.getAttribute(`${this.pfx}-progressChar`) || 'β–ˆ'; this.progressPercent = options.progressPercent || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100; this.cursor = options.cursor || this.container.getAttribute(`${this.pfx}-cursor`) || 'β–‹'; this.lineData = this.lineDataToElements(options.lineData || []); this.loadLines() if (!options.noInit) this.init() } loadLines() { // Load all the lines and create the container so that the size is fixed // Otherwise it would be changing and the user viewport would be constantly // moving as she/he scrolls const finish = this.generateFinish() finish.style.visibility = 'hidden' this.container.appendChild(finish) // Appends dynamically loaded lines to existing line elements. this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData); for (let line of this.lines) { line.style.visibility = 'hidden' this.container.appendChild(line) } const restart = this.generateRestart() restart.style.visibility = 'hidden' this.container.appendChild(restart) this.container.setAttribute('data-termynal', ''); } /** * Initialise the widget, get lines, clear container and start animation. */ init() { /** * Calculates width and height of Termynal container. * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. */ const containerStyle = getComputedStyle(this.container); this.container.style.width = containerStyle.width !== '0px' ? containerStyle.width : undefined; this.container.style.minHeight = containerStyle.height !== '0px' ? containerStyle.height : undefined; this.container.setAttribute('data-termynal', ''); this.container.innerHTML = ''; for (let line of this.lines) { line.style.visibility = 'visible' } this.start(); } /** * Start the animation and rener the lines depending on their data attributes. */ async start() { this.addFinish() await this._wait(this.startDelay); for (let line of this.lines) { const type = line.getAttribute(this.pfx); const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay; if (type == 'input') { line.setAttribute(`${this.pfx}-cursor`, this.cursor); await this.type(line); await this._wait(delay); } else if (type == 'progress') { await this.progress(line); await this._wait(delay); } else { this.container.appendChild(line); await this._wait(delay); } line.removeAttribute(`${this.pfx}-cursor`); } this.addRestart() this.finishElement.style.visibility = 'hidden' this.lineDelay = this.originalLineDelay this.typeDelay = this.originalTypeDelay this.startDelay = this.originalStartDelay } generateRestart() { const restart = document.createElement('a') restart.onclick = (e) => { e.preventDefault() this.container.innerHTML = '' this.init() } restart.href = '#' restart.setAttribute('data-terminal-control', '') restart.innerHTML = "restart ↻" return restart } generateFinish() { const finish = document.createElement('a') finish.onclick = (e) => { e.preventDefault() this.lineDelay = 0 this.typeDelay = 0 this.startDelay = 0 } finish.href = '#' finish.setAttribute('data-terminal-control', '') finish.innerHTML = "fast β†’" this.finishElement = finish return finish } addRestart() { const restart = this.generateRestart() this.container.appendChild(restart) } addFinish() { const finish = this.generateFinish() this.container.appendChild(finish) } /** * Animate a typed line. * @param {Node} line - The line element to render. */ async type(line) { const chars = [...line.textContent]; line.textContent = ''; this.container.appendChild(line); for (let char of chars) { const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; await this._wait(delay); line.textContent += char; } } /** * Animate a progress bar. * @param {Node} line - The line element to render. */ async progress(line) { const progressLength = line.getAttribute(`${this.pfx}-progressLength`) || this.progressLength; const progressChar = line.getAttribute(`${this.pfx}-progressChar`) || this.progressChar; const chars = progressChar.repeat(progressLength); const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`) || this.progressPercent; line.textContent = ''; this.container.appendChild(line); for (let i = 1; i < chars.length + 1; i++) { await this._wait(this.typeDelay); const percent = Math.round(i / chars.length * 100); line.textContent = `${chars.slice(0, i)} ${percent}%`; if (percent>progressPercent) { break; } } } /** * Helper function for animation delays, called with `await`. * @param {number} time - Timeout, in ms. */ _wait(time) { return new Promise(resolve => setTimeout(resolve, time)); } /** * Converts line data objects into line elements. * * @param {Object[]} lineData - Dynamically loaded lines. * @param {Object} line - Line data object. * @returns {Element[]} - Array of line elements. */ lineDataToElements(lineData) { return lineData.map(line => { let div = document.createElement('div'); div.innerHTML = `${line.value || ''}`; return div.firstElementChild; }); } /** * Helper function for generating attributes string. * * @param {Object} line - Line data object. * @returns {string} - String of attributes. */ _attributes(line) { let attrs = ''; for (let prop in line) { // Custom add class if (prop === 'class') { attrs += ` class=${line[prop]} ` continue } if (prop === 'type') { attrs += `${this.pfx}="${line[prop]}" ` } else if (prop !== 'value') { attrs += `${this.pfx}-${prop}="${line[prop]}" ` } } return attrs; } } /** * HTML API: If current script has container(s) specified, initialise Termynal. */ if (document.currentScript.hasAttribute('data-termynal-container')) { const containers = document.currentScript.getAttribute('data-termynal-container'); containers.split('|') .forEach(container => new Termynal(container)) } typer-0.4.0/docs/release-notes.md000066400000000000000000000351261411311634200167010ustar00rootroot00000000000000## Latest Changes ## 0.4.0 ### Features * ✨ Add support for Click 8 while keeping compatibility with Click 7. PR [#317](https://github.com/tiangolo/typer/pull/317) by [@tiangolo](https://github.com/tiangolo). ### Internal * πŸ“ Add Security policy. PR [#324](https://github.com/tiangolo/typer/pull/324) by [@tiangolo](https://github.com/tiangolo). * πŸ”§ Add updated issue templates. PR [#323](https://github.com/tiangolo/typer/pull/323) by [@tiangolo](https://github.com/tiangolo). * πŸ‘· Enable tests for Python 3.9. PR [#322](https://github.com/tiangolo/typer/pull/322) by [@tiangolo](https://github.com/tiangolo). * πŸ‘· Add GitHub Action Latest Changes. PR [#321](https://github.com/tiangolo/typer/pull/321) by [@tiangolo](https://github.com/tiangolo). * πŸ‘· Update docs CI name. PR [#320](https://github.com/tiangolo/typer/pull/320) by [@tiangolo](https://github.com/tiangolo). * πŸ”§ Add sponsors docs and badge. PR [#319](https://github.com/tiangolo/typer/pull/319) by [@tiangolo](https://github.com/tiangolo). ## 0.3.2 ### Features * Add support for `mypy --strict`. Original PR [#147](https://github.com/tiangolo/typer/pull/147) by [@victorphoenix3](https://github.com/victorphoenix3). ### Docs * Update docs with new `--help` showing default values. PR [#135](https://github.com/tiangolo/typer/pull/135) by [@victorphoenix3](https://github.com/victorphoenix3). * Add `Optional` to docs for *CLI Arguments and Options* with a default of `None`. PR [#131](https://github.com/tiangolo/typer/pull/131) by [@rkbeatss](https://github.com/rkbeatss). * Add valid date formats to docs. PR [#122](https://github.com/tiangolo/typer/pull/122) by [@IamCathal](https://github.com/IamCathal). ### Internal * Report coverage in XML to support GitHub Actions. PR [#146](https://github.com/tiangolo/typer/pull/146). * Update badges and remove Travis, now that GitHub Actions is the main CI. PR [#145](https://github.com/tiangolo/typer/pull/145). ## 0.3.1 * Add GitHub Actions, move from Travis. PR [#144](https://github.com/tiangolo/typer/pull/144). * Pin dependencies. PR [#138](https://github.com/tiangolo/typer/pull/138). * Add Dependabot. PR [#136](https://github.com/tiangolo/typer/pull/136). * Upgrade Isort to version 5.x.x. PR [#137](https://github.com/tiangolo/typer/pull/137). ## 0.3.0 * Add support for `help` parameter in *CLI arguments*: * As `help` in *CLI arguments* is not supported by Click, there are two new internal classes (Click sub-classes) to support it: * `typer.core.TyperArgument` * `typer.core.TyperCommand` * This includes a new auto-generated help text section `Arguments` for *CLI arguments*, showing defaults, required arguments, etc. * It's also possible to disable it and keep the previous behavior, not showing automatic help for *CLI arguments* (Click's default) using the `hidden` parameter. * Now `show_default` is `True` by default. * And now `show_envvar` is `True` by default. * So, default values and env vars are shown in the help text by default, without having to manually enable them, for both *CLI arguments* and *CLI options*. * New docs: * [CLI Arguments Intro](https://typer.tiangolo.com/tutorial/arguments/). * [Optional CLI Arguments](https://typer.tiangolo.com/tutorial/arguments/optional/). * [CLI Arguments with Default](https://typer.tiangolo.com/tutorial/arguments/default/). * [CLI Arguments with Help](https://typer.tiangolo.com/tutorial/arguments/help/). * [CLI Arguments with Environment Variables](https://typer.tiangolo.com/tutorial/arguments/envvar/). * [CLI Arguments: Other uses](https://typer.tiangolo.com/tutorial/arguments/other-uses/). * [CLI arguments with tuples](https://typer.tiangolo.com/tutorial/multiple-values/arguments-with-multiple-values/#cli-arguments-with-tuples). * Lot's of tests for all the new examples in the new docs, keeping coverage at 100%. * PR [#123](https://github.com/tiangolo/typer/pull/123). * Add docs for calling packages with `python -m some_package` using `__main__.py`: [Building a Package: Support `python -m`](https://typer.tiangolo.com/tutorial/package/#support-python-m-optional). PR [#121](https://github.com/tiangolo/typer/pull/121). * Add support for `*args` and `**kwargs` when calling the Typer app, just like in Click. PR [#120](https://github.com/tiangolo/typer/pull/120) by [@teymour-aldridge](https://github.com/teymour-aldridge). * Fix typos in README and main docs [#103](https://github.com/tiangolo/typer/pull/103) by [@mrcartoonster](https://github.com/mrcartoonster). * Fix typo in docs. PR [#98](https://github.com/tiangolo/typer/pull/98) by [@mrcartoonster](https://github.com/mrcartoonster). * Fix typos and rewording in docs. PR [#97](https://github.com/tiangolo/typer/pull/97) by [@mrcartoonster](https://github.com/mrcartoonster). * Update GitHub Action issue-manager. PR [#114](https://github.com/tiangolo/typer/pull/114). ## 0.2.1 * Add support for forward references (types declared inside of strings). PR [#93](https://github.com/tiangolo/typer/pull/93). ## 0.2.0 * Add support for completion for commands/programs not available on startup. * This allows installing a Typer program/script in a virtual environment and still have completion globally installed. * PR [#92](https://github.com/tiangolo/typer/pull/92). * Add note about `typer.echo()` and `print()` for colors in Windows. PR [#89](https://github.com/tiangolo/typer/pull/89). * Upgrade Mkdocs-Material version, update contributing guide style. PR [#90](https://github.com/tiangolo/typer/pull/90). ## 0.1.1 * Fix completion evaluation for Bash and Zsh when the program is not installed/found. PR [#83](https://github.com/tiangolo/typer/pull/83). * Fix completion script for Fish. PR [#82](https://github.com/tiangolo/typer/pull/82). * Fix shell installation for Bash to `~/.bashrc` and update Windows development docs. PR [#81](https://github.com/tiangolo/typer/pull/81). * Update coverage badge. PR [#78](https://github.com/tiangolo/typer/pull/78). ## 0.1.0 * Fix coverage instructions. PR [#72](https://github.com/tiangolo/typer/pull/72). * Add docs for [Building a Package](https://typer.tiangolo.com/tutorial/package/). PR [#71](https://github.com/tiangolo/typer/pull/71). * Add docs for [Using Click (with Typer)](https://typer.tiangolo.com/tutorial/using-click/). PR [#70](https://github.com/tiangolo/typer/pull/70). * Add support for type-based callbacks and autocompletion functions, extra tests and docs: * Extra tests, raising coverage to 100%. * New docs: [Printing and Colors: "Standard Output" and "Standard Error"](https://typer.tiangolo.com/tutorial/printing/#standard-output-and-standard-error). * New docs: [Password CLI Option and Confirmation Prompt](https://typer.tiangolo.com/tutorial/options/password/). * Support for callbacks based on type annotations. New docs: [CLI Option Callback and Context](https://typer.tiangolo.com/tutorial/options/callback-and-context/). * New docs: [Version CLI Option, is_eager](https://typer.tiangolo.com/tutorial/options/version/). * Support for autocompletion functions based on type annotations. New docs: [CLI Option autocompletion](https://typer.tiangolo.com/tutorial/options/autocompletion/). * New docs: [Commands: Using the Context](https://typer.tiangolo.com/tutorial/commands/context/). * New docs: [Testing](https://typer.tiangolo.com/tutorial/testing/). * PR [#68](https://github.com/tiangolo/typer/pull/68). * Fix Zsh completion install script. PR [#69](https://github.com/tiangolo/typer/pull/69). * Fix typo in progressbar example. PR [#63](https://github.com/tiangolo/typer/pull/63) by [@ValentinCalomme](https://github.com/ValentinCalomme). ## 0.0.11 * Re-implement completion system: * Remove optional dependency `click-completion` (with its sub-dependencies, like Jinja). * Add optional dependency `shellingham` to auto detect shell to install (it was used by `click-completion`). * Completion now doesn't require a third party library. * If `shellingham` is not installed/added as a dependency, `--install-completion` and `--show-completion` take a value with the name of the shell. * Fix support for user provided completion in *CLI Parameters*. * Fix completion for files in Bash, Zsh, and Fish. * Add support for modern versions of PowerShell, 5, 6, and 7 (e.g. in Windows 10). * Add support for `pwsh` (PowerShell Core). * PowerShell support includes help strings for commands and *CLI Parameters*. * Several bug fixes. * Tests for the completion logic/code. * Tested in all the shells in Linux and Windows. * PR [#66](https://github.com/tiangolo/typer/pull/66). * Fix format in docs with highlighted lines. PR [#65](https://github.com/tiangolo/typer/pull/65). * Add docs about [Typer CLI - completion for small scripts](https://typer.tiangolo.com/typer-cli/). PR [#64](https://github.com/tiangolo/typer/pull/64). * Add docs about [Alternatives, Inspiration and Comparisons](https://typer.tiangolo.com/alternatives/). PR [#62](https://github.com/tiangolo/typer/pull/62). * Add [Development - Contributing Guide](https://typer.tiangolo.com/contributing/). PR [#61](https://github.com/tiangolo/typer/pull/61). ## 0.0.10 * Add support for Click version 7.1.1. PR [#60](https://github.com/tiangolo/typer/pull/60). ## 0.0.9 * Add support for PEP 561, to allow `mypy` to type check applications built with **Typer**. PR [#58](https://github.com/tiangolo/typer/pull/58). * Upgrade deploy docs to Netlify GitHub action. PR [#57](https://github.com/tiangolo/typer/pull/57). * Add support for Mermaid JS for visualizations. PR [#56](https://github.com/tiangolo/typer/pull/56). * Update CI to run docs deployment in GitHub actions. PR [#50](https://github.com/tiangolo/typer/pull/50). * Update format for internal links. PR [#38](https://github.com/tiangolo/typer/pull/38). * Tweak external links' format. PR [#36](https://github.com/tiangolo/typer/pull/36). ## 0.0.8 * Update docs and add latest changes to MkDocs/website. PR [#33](https://github.com/tiangolo/typer/pull/33). * Add extra tests for edge cases that don't belong in docs' examples. PR [#32](https://github.com/tiangolo/typer/pull/32). * Add docs for CLI Parameters with [Multiple Values](https://typer.tiangolo.com/tutorial/multiple-values/). Includes tests for all the examples and bug fixes. PR [#31](https://github.com/tiangolo/typer/pull/31). * Add docs for extra *CLI parameter* types: [CLI Parameter Types: Number](https://typer.tiangolo.com/tutorial/parameter-types/number/) and [CLI Parameter Types: Boolean CLI Options](https://typer.tiangolo.com/tutorial/parameter-types/bool/). PR [#30](https://github.com/tiangolo/typer/pull/30). * Extend docs for Commands, add [Commands: Typer Callback](https://typer.tiangolo.com/tutorial/commands/callback/) and [Commands: One or Multiple](https://typer.tiangolo.com/tutorial/commands/one-or-multiple/). This includes tests for all the examples and bug fixes. PR [#29](https://github.com/tiangolo/typer/pull/29). * Add docs for [SubCommands - Command Groups](https://typer.tiangolo.com/tutorial/subcommands/). This includes tests for all the examples and bug fixes. PR [#28](https://github.com/tiangolo/typer/pull/28). * Remove unneeded code for argument handling. PR [#26](https://github.com/tiangolo/typer/pull/26). * Add docs for [Launching Applications](https://typer.tiangolo.com/tutorial/launch/). PR [#25](https://github.com/tiangolo/typer/pull/25). * Add docs for getting the [CLI Application Directory](https://typer.tiangolo.com/tutorial/app-dir/). PR [#24](https://github.com/tiangolo/typer/pull/24). * Add docs for [Progress Bars](https://typer.tiangolo.com/tutorial/progressbar/). PR [#23](https://github.com/tiangolo/typer/pull/23). * Add docs for [Asking with Interactive Prompts](). PR [#22](https://github.com/tiangolo/typer/pull/22). * Update docs for path *CLI option*. PR [#21](https://github.com/tiangolo/typer/pull/21). * Add colors module and docs for [Printing and Colors](https://typer.tiangolo.com/tutorial/printing/) and for [Terminating](https://typer.tiangolo.com/tutorial/terminating/), including tests. PR [#20](https://github.com/tiangolo/typer/pull/20). * Refactor docs to make each individual page/section "bite-sized" / small. Add docs for [CLI option names](https://typer.tiangolo.com/tutorial/options/name/). Update `typer.Argument()` to remove invalid positional `param_decls`. PR [#19](https://github.com/tiangolo/typer/pull/19). ## 0.0.7 * Add docs for [*CLI parameter* types](https://typer.tiangolo.com/tutorial/parameter-types/). Includes tests and file classes refactor. PR [#17](https://github.com/tiangolo/typer/pull/17). * Add tests for completion. PR [#15](https://github.com/tiangolo/typer/pull/15) and [#16](https://github.com/tiangolo/typer/pull/16). ## 0.0.6 * Add docs for [Commands](https://typer.tiangolo.com/tutorial/commands/). Includes a bug fix for handling default values set in `typer.Typer()` parameters. PR [#14](https://github.com/tiangolo/typer/pull/14). * Add docs for [CLI Arguments](https://typer.tiangolo.com/tutorial/arguments/). PR [#13](https://github.com/tiangolo/typer/pull/13). * Add docs for [CLI Options](https://typer.tiangolo.com/tutorial/options/). PR [#12](https://github.com/tiangolo/typer/pull/12). ## 0.0.5 * Clean exports from Typer. Remove unneeded components from Click and add needed `Exit` exception. PR [#11](https://github.com/tiangolo/typer/pull/11). * Fix and document extracting help from a function's docstring [First Steps: Document your CLI app](https://typer.tiangolo.com/tutorial/first-steps/#document-your-cli-app). PR [#10](https://github.com/tiangolo/typer/pull/10). * Update references to `--install-completion` and `--show-completion` in docs. PR [#9](https://github.com/tiangolo/typer/pull/9). * Fix testing utilities, add tests for First Steps examples. PR [#8](https://github.com/tiangolo/typer/pull/8). * Add auto completion options by default when [click-completion](https://github.com/click-contrib/click-completion) is installed: `--install-completion` and `--show-completion`. PR [#7](https://github.com/tiangolo/typer/pull/7). * Update Termynal to have fixed sizes, add "fast" button, and use it in [First Steps](https://typer.tiangolo.com/tutorial/first-steps/). PR [#6](https://github.com/tiangolo/typer/pull/6). * Add custom automatic [Termynal](https://github.com/tiangolo/termynal) for docs. PR [#5](https://github.com/tiangolo/typer/pull/5). ## 0.0.4 * Update short descriptions and assets. * Docs rewording and fix typos. PR [#1](https://github.com/tiangolo/typer/pull/1) by [@mariacamilagl](https://github.com/mariacamilagl). ## 0.0.3 * Fix group creation without name. ## 0.0.2 * Add initial version of code, docs, etc. ## 0.0.1 * First commit. Publish to PyPI to reserve package name. typer-0.4.0/docs/tutorial/000077500000000000000000000000001411311634200154455ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/app-dir.md000066400000000000000000000026451411311634200173320ustar00rootroot00000000000000You can get the application directory where you can, for example, save configuration files with `typer.get_app_dir()`: ```Python hl_lines="9" {!../docs_src/app_dir/tutorial001.py!} ``` It will give you a directory for storing configurations appropriate for your CLI program for the current user in each operating system. Check it:
```console $ python main.py Config file doesn't exist yet ```
## About `Path` If you hadn't seen something like that: ```Python Path(app_dir) / "config.json" ``` A `Path` object can be used with `/` and it will convert it to the separator for the current system (`/` for Unix systems and `\` for Windows). If the first element is a `Path` object the next ones (after the `/`) can be `str`. And it will create a new `Path` object from that. If you want a quick guide on using `Path()` you can check this post on Real Python or this post by Trey Hunner. In the code above, we are also explicitly declaring `config_path` as having type `Path` to help the editor provide completion and type checks: ```Python config_path: Path = Path(app_dir) / "config.json" ``` Otherwise it could think it's a sub-type (a `PurePath`) and stop providing completion for some methods. typer-0.4.0/docs/tutorial/arguments/000077500000000000000000000000001411311634200174525ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/arguments/default.md000066400000000000000000000046051411311634200214250ustar00rootroot00000000000000We can also use the same `typer.Argument()` to set a default value. That way the *CLI argument* will be optional *and also* have a default value. ## An optional *CLI argument* with a default We can also use `typer.Argument()` to make a *CLI argument* have a default value other than `None`: ```Python hl_lines="4" {!../docs_src/arguments/default/tutorial001.py!} ``` !!! tip Because now the value will be a `str` passed by the user or the default value of `"Wade Wilson"` which is also a `str`, we know the value will never be `None`, so we don't have to (and shouldn't) use `Optional[str]`. Have in mind that the `Optional[something]` tells Python that a value "could be `None`". But the use of `Optional` doesn't affect Typer in any way, e.g. it doesn't tell Typer if a value is required or not. Check it:
```console // Check the help $ python main.py --help // Notice the [default: Wade Wilson] ✨ Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] [default: Wade Wilson] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // With no optional CLI argument $ python main.py Hello Wade Wilson // With one CLI argument $ python main.py Camila Hello Camila ```
## Dynamic default value And we can even make the default value be dynamically generated by passing a function as the first function argument: ```Python hl_lines="6 7 10" {!../docs_src/arguments/default/tutorial002.py!} ``` In this case, we created the function `get_name` that will just return a random `str` each time. And we pass it as the first function argument to `typer.Argument()`. Check it:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] [default: (dynamic)] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it several times, it will use a random default each time $ python main.py Hello Deadpool $ python main.py Hello Hiro $ python main.py Hello Rick // Now pass a value for the CLI argument $ python main.py Camila Hello Camila ```
typer-0.4.0/docs/tutorial/arguments/envvar.md000066400000000000000000000060431411311634200213000ustar00rootroot00000000000000You can also configure a *CLI argument* to read a value from an environment variable if it is not provided in the command line as a *CLI argument*. To do that, use the `envvar` parameter for `typer.Argument()`: ```Python hl_lines="4" {!../docs_src/arguments/envvar/tutorial001.py!} ``` In this case, the *CLI argument* `name` will have a default value of `"World"`, but will also read any value passed to the environment variable `AWESOME_NAME` if no value is provided in the command line:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] [env var: AWESOME_NAME;default: World] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Call it without a CLI argument $ python main.py Hello Mr. World // Now pass a value for the CLI argument $ python main.py Czernobog Hello Mr. Czernobog // And now use the environment variable $ AWESOME_NAME=Wednesday python main.py Hello Mr. Wednesday // CLI arguments take precedence over env vars $ AWESOME_NAME=Wednesday python main.py Czernobog Hello Mr. Czernobog ```
## Multiple environment variables You are not restricted to a single environment variable, you can declare a list of environment variables that could be used to get a value if it was not passed in the command line: ```Python hl_lines="4" {!../docs_src/arguments/envvar/tutorial002.py!} ``` Check it:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] [env var: AWESOME_NAME, GOD_NAME;default: World] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try the first env var $ AWESOME_NAME=Wednesday python main.py Hello Mr. Wednesday // Try the second env var $ GOD_NAME=Anubis python main.py Hello Mr. Anubis ```
## Hide an env var from the help text By default, environment variables used will be shown in the help text, but you can disable them with `show_envvar=False`: ```Python hl_lines="4" {!../docs_src/arguments/envvar/tutorial003.py!} ``` Check it:
```console //Check the help $ python main.py --help // It won't show the env var Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] [default: World] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // But it will still be able to use it $ AWESOME_NAME=Wednesday python main.py Hello Mr. Wednesday ```
!!! note "Technical Details" In Click applications the env vars are hidden by default. πŸ™ˆ In **Typer** these env vars are shown by default. πŸ‘€ typer-0.4.0/docs/tutorial/arguments/help.md000066400000000000000000000172551411311634200207360ustar00rootroot00000000000000In the *First Steps* section you saw how to add help for a CLI app/command by adding it to a function's docstring. Here's how that last example looked like: ```Python {!../docs_src/first_steps/tutorial006.py!} ``` Now that you also know how to use `typer.Argument()`, let's use it to add documentation specific for a *CLI argument*. ## Add a `help` text for a *CLI argument* You can use the `help` parameter to add a help text for a *CLI argument*: ```Python hl_lines="4" {!../docs_src/arguments/help/tutorial001.py!} ``` And it will be used in the automatic `--help` option:
```console $ python main.py --help // Check the section with Arguments below πŸš€ Usage: main.py [OPTIONS] NAME Arguments: NAME The name of the user to greet [required] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
## Combine help text and docstrings And of course, you can also combine that `help` with the docstring: ```Python hl_lines="4 5 6 7" {!../docs_src/arguments/help/tutorial002.py!} ``` And the `--help` option will combine all the information:
```console $ python main.py --help // Notice that we have the help text from the docstring and also the Arguments πŸ“ Usage: main.py [OPTIONS] NAME Say hi to NAME very gently, like Dirk. Arguments: NAME The name of the user to greet [required] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
## Help with defaults If you have a *CLI argument* with a default value, like `"World"`: ```Python hl_lines="4" {!../docs_src/arguments/help/tutorial003.py!} ``` It will show that default value in the help text:
```console $ python main.py --help // Notice the [default: World] πŸ” Usage: main.py [OPTIONS] [NAME] Say hi to NAME very gently, like Dirk. Arguments: [NAME] Who to greet [default: World] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
But you can disable that if you want to, with `show_default=False`: ```Python hl_lines="4" {!../docs_src/arguments/help/tutorial004.py!} ``` And then it won't show the default value:
```console $ python main.py --help // Notice the there's no [default: World] now πŸ”₯ Usage: main.py [OPTIONS] [NAME] Say hi to NAME very gently, like Dirk. Arguments: [NAME] Who to greet Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! note "Technical Details" In Click applications the default values are hidden by default. πŸ™ˆ In **Typer** these default values are shown by default. πŸ‘€ ## Custom default string You can use the same `show_default` to pass a custom string (instead of a `bool`) to customize the default value to be shown in the help text: ```Python hl_lines="6" {!../docs_src/arguments/help/tutorial005.py!} ``` And it will be used in the help text:
```console $ python main.py --help Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] Who to greet [default: (Deadpoolio the amazing's name)] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // See it shows "(Deadpoolio the amazing's name)" instead of the actual default of "Wade Wilson" ```
## Custom help name (`metavar`) You can also customize the text used in the generated help text to represent a *CLI argument*. By default, it will be the same name you declared, in uppercase letters. So, if you declare it as: ```Python name: str ``` It will be shown as: ``` NAME ``` But you can customize it with the `metavar` parameter for `typer.Argument()`. For example, let's say you don't want to have the default of `NAME`, you want to have `username`, in lowercase, and you really want ✨ emojis ✨ everywhere: ```Python hl_lines="4" {!../docs_src/arguments/help/tutorial006.py!} ``` Now the generated help text will have `✨username✨` instead of `NAME`:
```console $ python main.py --help Usage: main.py [OPTIONS] ✨username✨ Arguments: ✨username✨ [default: World] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
## Hide a *CLI argument* from the help text If you want, you can make a *CLI argument* **not** show up in the `Arguments` section in the help text. You will probably not want to do this normally, but it's possible: ```Python hl_lines="4" {!../docs_src/arguments/help/tutorial007.py!} ``` Check it:
```console $ python main.py --help // Notice there's no Arguments section at all πŸ”₯ Usage: main.py [OPTIONS] [NAME] Say hi to NAME very gently, like Dirk. Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! info Have in mind that the *CLI argument* will still show up in the first line with `Usage`. But it won't show up in the main help text under the `Arguments` section. ### Help text for *CLI arguments* in Click Click itself doesn't support adding help for *CLI arguments*, and it doesn't generate help for them as in the "`Arguments:`" sections in the examples above. Not supporting `help` in *CLI arguments* is an intentional design decision in Click: > This is to follow the general convention of Unix tools of using arguments for only the most necessary things, and to document them in the command help text by referring to them by name. So, in Click applications, you are expected to write all the documentation for *CLI arguments* by hand in the docstring. --- Nevertheless, **Typer supports `help` for *CLI arguments***. ✨ πŸ€·β€β™‚ **Typer** doesn't follow that convention and instead supports `help` to make it easier to have consistent help texts with a consistent format for your CLI programs. 🎨 This is also to help you create CLI programs that are ✨ awesome ✨ *by default*. With very little code. If you want to keep Click's convention in a **Typer** app, you can do it with the `hidden` parameter as described above. !!! note "Technical Details" To support `help` in *CLI arguments* **Typer** does a lot of internal work in its own sub-classes of Click's internal classes. typer-0.4.0/docs/tutorial/arguments/index.md000066400000000000000000000002551411311634200211050ustar00rootroot00000000000000In the next few sections we'll see some ways to modify how *CLI arguments* work. We'll create optional *CLI arguments*, we'll add integrated help for *CLI arguments*, etc. typer-0.4.0/docs/tutorial/arguments/optional.md000066400000000000000000000113171411311634200216240ustar00rootroot00000000000000We said before that *by default*: * *CLI options* are **optional** * *CLI arguments* are **required** Again, that's how they work *by default*, and that's the convention in many CLI programs and systems. But you can change that. In fact, it's very common to have **optional** *CLI arguments*, it's way more common than having **required** *CLI options*. As an example of how it could be useful, let's see how the `ls` CLI program works.
```console // If you just type $ ls // ls will "list" the files and directories in the current directory typer tests README.md LICENSE // But it also receives an optional CLI argument $ ls ./tests/ // And then ls will list the files and directories inside of that directory from the CLI argument __init__.py test_tutorial ```
### An alternative *CLI argument* declaration In the [First Steps](../first-steps.md#add-a-cli-argument){.internal-link target=_blank} you saw how to add a *CLI argument*: ```Python hl_lines="4" {!../docs_src/first_steps/tutorial002.py!} ``` Now let's see an alternative way to create the same *CLI argument*: ```Python hl_lines="4" {!../docs_src/arguments/optional/tutorial001.py!} ``` Before, you had this function parameter: ```Python name: str ``` And because `name` didn't have any default value it would be a **required parameter** for the Python function, in Python terms. **Typer** does the same and makes it a **required** *CLI argument*. And then we changed it to: ```Python name: str = typer.Argument(...) ``` But now as `typer.Argument()` is the "default value" of the function's parameter, it would mean that "it is no longer required" (in Python terms). As we no longer have the Python function default value (or its absence) to tell if something is required or not and what is the default value, the first parameter to `typer.Argument()` serves the same purpose of defining that default value, or making it required. To make it *required*, we pass `...` as the first function argument passed to `typer.Argument(...)`. !!! info If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis". All we did there achieves the same thing as before, a **required** *CLI argument*:
```console $ python main.py Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing argument 'NAME'. ```
It's still not very useful, but it works correctly. And being able to declare a **required** *CLI argument* using `name: str = typer.Argument(...)` that works exactly the same as `name: str` will come handy later. ### Make an optional *CLI argument* Now, finally what we came for, an optional *CLI argument*. To make a *CLI argument* optional, use `typer.Argument()` and pass a different "default" as the first parameter to `typer.Argument()`, for example `None`: ```Python hl_lines="6" {!../docs_src/arguments/optional/tutorial002.py!} ``` Now we have: ```Python name: Optional[str] = typer.Argument(None) ``` Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*). And because the first parameter passed to `typer.Argument(None)` (the new "default" value) is `None`, **Typer** knows that this is an **optional** *CLI argument*, if no value is provided when calling it in the command line, it will have that default value of `None`. !!! tip By using `Optional` your editor will be able to know that the value *could* be `None`, and will be able to warn you if you do something assuming it is a `str` that would break if it was `None`. Check the help:
```console // First check the help $ python main.py --help Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! tip Notice that `NAME` is still a *CLI argument*, it's shown up there in the "`Usage: main.py` ...". Also notice that now `[NAME]` has brackets ("`[`" and "`]`") around (before it was just `NAME`) to denote that it's **optional**, not **required**. Now run it and test it:
```console // With no CLI argument $ python main.py Hello World! // With one optional CLI argument $ python main.py Camila Hello Camila ```
!!! tip Notice that "`Camila`" here is an optional *CLI argument*, not a *CLI option*, because we didn't use something like "`--name Camila`", we just passed "`Camila`" directly to the program. typer-0.4.0/docs/tutorial/arguments/other-uses.md000066400000000000000000000002431411311634200220710ustar00rootroot00000000000000`typer.Argument()` has several other use cases. Such as for data validation, to enable other features, etc. You will see about these use cases later in the docs. typer-0.4.0/docs/tutorial/commands/000077500000000000000000000000001411311634200172465ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/commands/arguments.md000066400000000000000000000020741411311634200216000ustar00rootroot00000000000000The same way as with a CLI application with a single command, subcommands (or just "commands") can also have their own *CLI arguments*: ```Python hl_lines="7 12" {!../docs_src/commands/arguments/tutorial001.py!} ```
```console // Check the help for create $ python main.py create --help Usage: main.py create [OPTIONS] USERNAME Options: --help Show this message and exit. // Call it with a CLI argument $ python main.py create Camila Creating user: Camila // The same for delete $ python main.py delete Camila Deleting user: Camila ```
!!! tip Everything to the *right* of the *command* are *CLI parameters* (*CLI arguments* and *CLI options*) for that command. !!! note "Technical Details" Actually, it's everything to the right of that command, *before any subcommand*. It's possible to have groups of *subcommands*, it's like if one *command* also had *subcommands*. And then those *subcommands* could have their own *CLI parameters*, taking their own *CLI parameters*. You will see about them later in another section. typer-0.4.0/docs/tutorial/commands/callback.md000066400000000000000000000113451411311634200213300ustar00rootroot00000000000000When you create an `app = typer.Typer()` it works as a group of commands. And you can create multiple commands with it. Each of those commands can have their own *CLI parameters*. But as those *CLI parameters* are handled by each of those commands, they don't allow us to create *CLI parameters* for the main CLI application itself. But we can use `@app.callback()` for that. It's very similar to `@app.command()`, but it declares the *CLI parameters* for the main CLI application (before the commands): ```Python hl_lines="25 26 27 28 29 30 31 32" {!../docs_src/commands/callback/tutorial001.py!} ``` Here we create a `callback` with a `--verbose` *CLI option*. !!! tip After getting the `--verbose` flag, we modify a global `state`, and we use it in the other commands. There are other ways to achieve the same, but this will suffice for this example. And as we added a docstring to the callback function, by default it will be extracted and used as the help text. Check it:
```console // Check the help $ python main.py --help // Notice the main help text, extracted from the callback function: "Manage users in the awesome CLI app." Usage: main.py [OPTIONS] COMMAND [ARGS]... Manage users in the awesome CLI app. Options: --verbose / --no-verbose [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create delete // Check the new top level CLI option --verbose // Try it normally $ python main.py create Camila Creating user: Camila // And now with --verbose $ python main.py --verbose create Camila Will write verbose output About to create a user Creating user: Camila Just created a user // Notice that --verbose belongs to the callback, it has to go before create or delete ⛔️ $ python main.py create --verbose Camila Usage: main.py create [OPTIONS] USERNAME Try "main.py create --help" for help. Error: no such option: --verbose ```
## Adding a callback on creation It's also possible to add a callback when creating the `typer.Typer()` app: ```Python hl_lines="4 5 8" {!../docs_src/commands/callback/tutorial002.py!} ``` That achieves the same as with `@app.callback()`. Check it:
```console $ python main.py create Camila Running a command Creating user: Camila ```
## Overriding a callback If you added a callback when creating the `typer.Typer()` app, it's possible to override it with `@app.callback()`: ```Python hl_lines="11 12 13" {!../docs_src/commands/callback/tutorial003.py!} ``` Now `new_callback()` will be the one used. Check it:
```console $ python main.py create Camila // Notice that the message is the one from new_callback() Override callback, running a command Creating user: Camila ```
## Adding a callback only for documentation You can also add a callback just to add the documentation in the docstring. It can be convenient especially if you have several lines of text, as the indentation will be automatically handled for you: ```Python hl_lines="8 9 10 11 12 13 14 15 16" {!../docs_src/commands/callback/tutorial004.py!} ``` Now the callback will be used mainly to extract the docstring for the help text. Check it:
```console $ python main.py --help // Notice all the help text extracted from the callback docstring Usage: main.py [OPTIONS] COMMAND [ARGS]... Manage users CLI app. Use it with the create command. A new user with the given NAME will be created. Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create // And it just works as normally $ python main.py create Camila Creating user: Camila ```
## Click Group If you come from Click, this **Typer** callback is the equivalent of the function in a Click Group. For example: ```Python import click @click.group() def cli(): pass ``` The original function `cli` would be the equivalent of a Typer callback. !!! note "Technical Details" When using Click, it converts that `cli` variable to a Click `Group` object. And then the original function no longer exists in that variable. **Typer** doesn't do that, the callback function is not modified, only registered in the `typer.Typer` app. This is intentional, it's part of **Typer**'s design, to allow having editor auto completion and type checks. typer-0.4.0/docs/tutorial/commands/context.md000066400000000000000000000067351411311634200212670ustar00rootroot00000000000000When you create a **Typer** application it uses Click underneath. And every Click application has a special object called a "Context" that is normally hidden. But you can access the context by declaring a function parameter of type `typer.Context`. You might have read it in [CLI Option Callback and Context](../options/callback-and-context.md){.internal-link target=_blank}. The same way, in commands or in the main `Typer` callback you can access the context by declaring a function parameter of type `typer.Context`. ## Getting the context For example, let's say that you want to execute some logic in a `Typer` callback depending on the subcommand that is being called. You can get the name of the subcommand from the context: ```Python hl_lines="17 21" {!../docs_src/commands/context/tutorial001.py!} ``` Check it:
```console $ python main.py create Camila // We get the message from the callback About to execute command: create Creating user: Camila $ python main.py delete Camila // We get the message from the callback, this time with delete About to execute command: delete Deleting user: Camila ```
## Executable callback By default, the callback is only executed right before executing a command. And if no command is provided, the help message is shown. But we could make it run even without a subcommand with `invoke_without_command=True`: ```Python hl_lines="16" {!../docs_src/commands/context/tutorial002.py!} ``` Check it:
```console $ python main.py // The callback is executed, we don't get the default help message Initializing database // Try with a command $ python main.py create Camila // The callback is still executed Initializing database Creating user: Camila ```
## Exclusive executable callback We might not want the callback to be executed if there's already other command that will be executed. For that, we can get the `typer.Context` and check if there's an invoked command in `ctx.invoked_subcommand`. If it's `None`, it means that we are not calling a subcommand but the main program (the callback) directly: ```Python hl_lines="17 21" {!../docs_src/commands/context/tutorial003.py!} ``` Check it:
```console $ python main.py // The callback is executed Initializing database // Check it with a subcommand $ python main.py create Camila // This time the callback is not executed Creating user: Camila ```
## Configuring the context You can pass configurations for the context when creating a command or callback. To read more about the available configurations check the docs for Click's `Context`. For example, you could keep additional *CLI parameters* not declared in your CLI program with `ignore_unknown_options` and `allow_extra_args`. Then you can access those extra raw *CLI parameters* as a `list` of `str` in `ctx.args`: ```Python hl_lines="7 9 10" {!../docs_src/commands/context/tutorial004.py!} ```
```console $ python main.py --name Camila --city Berlin Got extra arg: --name Got extra arg: Camila Got extra arg: --city Got extra arg: Berlin ```
!!! tip Notice that it saves all the extra *CLI parameters* as a raw `list` of `str`, including the *CLI option* names and values, everything together. typer-0.4.0/docs/tutorial/commands/help.md000066400000000000000000000062021411311634200205200ustar00rootroot00000000000000The same as before, you can add help for the commands in the docstrings and the *CLI options*. And the `typer.Typer()` application receives a parameter `help` that you can pass with the main help text for your CLI program: ```Python hl_lines="3 8 9 10 20 23 24 25 26 27 39 42 43 44 45 46 55 56 57" {!../docs_src/commands/help/tutorial001.py!} ``` Check it:
```console // Check the new help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Awesome CLI user manager. Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create Create a new user with USERNAME. delete Delete a user with USERNAME. delete-all Delete ALL users in the database. init Initialize the users database. // Now the commands have inline help πŸŽ‰ // Check the help for create $ python main.py create --help Usage: main.py create [OPTIONS] USERNAME Create a new user with USERNAME. Options: --help Show this message and exit. // Check the help for delete $ python main.py delete --help Usage: main.py delete [OPTIONS] USERNAME Delete a user with USERNAME. If --force is not used, will ask for confirmation. Options: --force / --no-force Force deletion without confirmation. [required] --help Show this message and exit. // Check the help for delete-all $ python main.py delete-all --help Usage: main.py delete-all [OPTIONS] Delete ALL users in the database. If --force is not used, will ask for confirmation. Options: --force / --no-force Force deletion without confirmation. [required] --help Show this message and exit. // Check the help for init $ python main.py init --help Usage: main.py init [OPTIONS] Initialize the users database. Options: --help Show this message and exit. ```
!!! tip `typer.Typer()` receives several other parameters for other things, we'll see that later. You will also see how to use "Callbacks" later, and those include a way to add this same help message in a function docstring. ## Overwrite command help You will probably be better adding the help text as a docstring to your functions, but if for some reason you wanted to overwrite it, you can use the `help` function argument passed to `@app.command()`: ```Python hl_lines="6 14" {!../docs_src/commands/help/tutorial002.py!} ``` Check it:
```console // Check the help $ python main.py --help // Notice it uses the help passed to @app.command() Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create Create a new user with USERNAME. delete Delete a user with USERNAME. // It uses "Create a new user with USERNAME." instead of "Some internal utility function to create." ```
typer-0.4.0/docs/tutorial/commands/index.md000066400000000000000000000144761411311634200207130ustar00rootroot00000000000000We have seen how to create a CLI program with possibly several *CLI options* and *CLI arguments*. But **Typer** allows you to create CLI programs with several commands (also known as subcommands). For example, the program `git` has several commands. One command of `git` is `git push`. And `git push` in turn takes its own *CLI arguments* and *CLI options*. For example:
```console // The push command with no parameters $ git push ---> 100% // The push command with one CLI option --set-upstream and 2 CLI arguments $ git push --set-upstream origin master ---> 100% ```
Another command of `git` is `git pull`, it also has some *CLI parameters*. It's like if the same big program `git` had several small programs inside. !!! tip A command looks the same as a *CLI argument*, it's just some name without a preceding `--`. But commands have a predefined name, and are used to group different sets of functionalities into the same CLI application. ## Command or subcommand It's common to call a CLI program a "command". But when one of these programs have subcommands, those subcommands are also frequently called just "commands". Have that in mind so you don't get confused. Here I'll use **CLI application** or **program** to refer to the program you are building in Python with Typer, and **command** to refer to one of these "subcommands" of your program. ## Explicit application Before creating CLI applications with multiple commands/subcommands we need to understand how to create an explicit `typer.Typer()` application. In the *CLI options* and *CLI argument* tutorials you have seen how to create a single function and then pass that function to `typer.run()`. For example: ```Python hl_lines="9" {!../docs_src/first_steps/tutorial002.py!} ``` But that is actually a shortcut. Under the hood, **Typer** converts that to a CLI application with `typer.Typer()` and executes it. All that inside of `typer.run()`. There's also a more explicit way to achieve the same: ```Python hl_lines="3 6 12" {!../docs_src/commands/index/tutorial001.py!} ``` When you use `typer.run()`, **Typer** is doing more or less the same as above, it will: * Create a new `typer.Typer()` "application". * Create a new "`command`" with your function. * Call the same "application" as if it was a function with "`app()`". !!! info "`@decorator` Info" That `@something` syntax in Python is called a "decorator". You put it on top of a function. Like a pretty decorative hat (I guess that's where the term came from). A "decorator" takes the function below and does something with it. In our case, this decorator tells **Typer** that the function below is a "`command`". Both ways, with `typer.run()` and creating the explicit application, achieve the same. !!! tip If your use case is solved with just `typer.run()`, that's fine, you don't have to create the explicit `app` and use `@app.command()`, etc. You might want to do that later when your app needs the extra features, but if it doesn't need them yet, that's fine. If you run the second example, with the explicit `app`, it works exactly the same:
```console // Without a CLI argument $ python main.py Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing argument 'NAME'. // With the NAME CLI argument $ python main.py Camila Hello Camila // Asking for help $ python main.py --help Usage: main.py [OPTIONS] NAME Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
## A CLI application with multiple commands Coming back to the CLI applications with multiple commands/subcommands, **Typer** allows creating CLI applications with multiple of them. Now that you know how to create an explicit `typer.Typer()` application and add one command, let's see how to add multiple commands. Let's say that we have a CLI application to manage users. We'll have a command to `create` users and another command to `delete` them. To begin, let's say it can only create and delete one single predefined user: ```Python hl_lines="6 11" {!../docs_src/commands/index/tutorial002.py!} ``` Now we have a CLI application with 2 commands, `create` and `delete`:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create delete // Test them $ python main.py create Creating user: Hiro Hamada $ python main.py delete Deleting user: Hiro Hamada // Now we have 2 commands! πŸŽ‰ ```
Notice that the help text now shows the 2 commands: `create` and `delete`. !!! tip By default, the names of the commands are generated from the function name. ## Click Group If you come from Click, a `typer.Typer` app with subcommands is more or less the equivalent of a Click Group. !!! note "Technical Details" A `typer.Typer` app is *not* a Click Group, but it provides the equivalent functionality. And it creates a Click Group when calling it. It is not directly a Group because **Typer** doesn't modify the functions in your code to convert them to another type of object, it only registers them. ## Decorator Technical Details When you use `@app.command()` the function under the decorator is registered in the **Typer** application and is then used later by the application. But Typer doesn't modify that function itself, the function is left as is. That means that if your function is simple enough that you could create it without using `typer.Option()` or `typer.Argument()`, you could use the same function for a **Typer** application and a **FastAPI** application putting both decorators on top, or similar tricks. !!! note "Click Technical Details" This behavior is a design difference with Click. In Click, when you add a `@click.command()` decorator it actually modifies the function underneath and replaces it with an object. typer-0.4.0/docs/tutorial/commands/name.md000066400000000000000000000022121411311634200205050ustar00rootroot00000000000000By default, the command names are generated from the function name. So, if your function is something like: ```Python def create(username: str): ... ``` Then the command name will be `create`. But if you already had a function called `create()` somewhere in your code, you would have to name your CLI function differently. And what if you wanted the command to still be named `create`? For this, you can set the name of the command in the first parameter for the `@app.command()` decorator: ```Python hl_lines="6 11" {!../docs_src/commands/name/tutorial001.py!} ``` Now, even though the functions are named `cli_create_user()` and `cli_delete_user()`, the commands will still be named `create` and `delete`:
```console $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create delete // Test it $ python main.py create Camila Creating user: Camila ```
typer-0.4.0/docs/tutorial/commands/one-or-multiple.md000066400000000000000000000065761411311634200226360ustar00rootroot00000000000000You might have noticed that if you create a single command, as in the first example: ```Python hl_lines="3 6 12" {!../docs_src/commands/index/tutorial001.py!} ``` **Typer** is smart enough to create a CLI application with that single function as the main CLI application, not as a command/subcommand:
```console // Without a CLI argument $ python main.py Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing argument 'NAME'. // With the NAME CLI argument $ python main.py Camila Hello Camila // Asking for help $ python main.py Usage: main.py [OPTIONS] NAME Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! tip Notice that it doesn't show a command `main`, even though the function name is `main`. But if you add multiple commands, **Typer** will create one *CLI command* for each one of them: ```Python hl_lines="6 11" {!../docs_src/commands/index/tutorial002.py!} ``` Here we have 2 commands `create` and `delete`:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create delete // Test the commands $ python main.py create Creating user: Hiro Hamada $ python main.py delete Deleting user: Hiro Hamada ```
## One command and one callback If you want to create a CLI app with one single command but you still want it to be a command/subcommand you can just add a callback: ```Python hl_lines="11 12 13" {!../docs_src/commands/one_or_multiple/tutorial001.py!} ``` And now your CLI program will have a single command. Check it:
```console // Check the help $ python main.py --help // Notice the single command create Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create // Try it $ python main.py create Creating user: Hiro Hamada ```
## Using the callback to document Now that you are using a callback just to have a single command, you might as well use it to add documentation for your app: ```Python hl_lines="11 12 13 14 15 16 17" {!../docs_src/commands/one_or_multiple/tutorial002.py!} ``` And now the docstring from the callback will be used as the help text:
```console $ python main.py --help // Notice the help text from the docstring Usage: main.py [OPTIONS] COMMAND [ARGS]... Creates a single user Hiro Hamada. In the next version it will create 5 users more. Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create // And it still works the same, the callback does nothing $ python main.py create Creating user: Hiro Hamada ```
typer-0.4.0/docs/tutorial/commands/options.md000066400000000000000000000037741411311634200212760ustar00rootroot00000000000000Commands can also have their own *CLI options*. In fact, each command can have different *CLI arguments* and *CLI options*: ```Python hl_lines="7 13 14 24 33" {!../docs_src/commands/options/tutorial001.py!} ``` Here we have multiple commands, with different *CLI parameters*: * `create`: * `username`: a *CLI argument*. * `delete`: * `username`: a *CLI argument*. * `--force`: a *CLI option*, if not provided, it's prompted. * `delete-all`: * `--force`: a *CLI option*, if not provided, it's prompted. * `init`: * Doesn't take any *CLI parameters*.
```console // Check the help python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: create delete delete-all info ```
!!! tip Check the command `delete-all`, by default command names are generated from the function name, replacing `_` with `-`. Test it:
```console // Check the command create $ python main.py create Camila Creating user: Camila // Now test the command delete $ python main.py delete Camila # Are you sure you want to delete the user? [y/N]: $ y Deleting user: Camila $ python main.py delete Wade # Are you sure you want to delete the user? [y/N]: $ n Operation cancelled // And finally, the command delete-all // Notice it doesn't have CLI arguments, only a CLI option $ python main.py delete-all # Are you sure you want to delete ALL users? [y/N]: $ y Deleting all users $ python main.py delete-all # Are you sure you want to delete ALL users? [y/N]: $ n Operation cancelled // And if you pass the --force CLI option, it doesn't need to confirm $ python main.py delete-all --force Deleting all users // And init that doesn't take any CLI parameter $ python main.py init Initializing user database ```
typer-0.4.0/docs/tutorial/first-steps.md000066400000000000000000000333501411311634200202560ustar00rootroot00000000000000## The simplest example The simplest **Typer** file could look like this: ```Python {!../docs_src/first_steps/tutorial001.py!} ``` !!! tip You will learn more about `typer.echo()` later in the docs. Copy that to a file `main.py`. Test it:
```console $ python main.py Hello World // It just prints "Hello World". // Now check the --help $ python main.py --help Usage: main.py [OPTIONS] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
...but this program is still not very useful. Let's extend it. ## What is a **CLI argument** Here we will use the word **CLI argument** to refer to **CLI parameters** passed in some specific order to the CLI application. By default, they are *required*. If you go to your terminal and type:
```bash $ ls ./myproject first-steps.md intro.md ```
`ls` will show the contents of the directory `./myproject`. * `ls` is the *program* (or "command", "CLI app"). * `./myproject` is a *CLI argument*, in this case it refers to the path of a directory. They are a bit different from **CLI options** that you will see later below. ## Add a CLI argument Update the previous example with an argument `name`: ```Python hl_lines="4 5" {!../docs_src/first_steps/tutorial002.py!} ```
```console $ python main.py // If you run it without the argument, it shows a nice error Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing argument 'NAME'. // Now pass that NAME CLI argument $ python main.py Camila Hello Camila // Here "Camila" is the CLI argument // To pass a name with spaces for the same CLI argument, use quotes $ python main.py "Camila GutiΓ©rrez" Hello Camila GutiΓ©rrez ```
!!! tip If you need to pass a single value that contains spaces to a *CLI argument*, use quotes (`"`) around it. ## Two CLI arguments Now let's say we want to have the name and last name separated. So, extend that to have 2 arguments, `name` and `lastname`: ```Python hl_lines="4 5" {!../docs_src/first_steps/tutorial003.py!} ```
```console // Check the main --help $ python main.py --help Usage: main.py [OPTIONS] NAME LASTNAME Arguments: NAME [required] LASTNAME [required] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // There are now 2 CLI arguments, name and lastname // Now pass a single name argument $ python main.py Camila Usage: main.py [OPTIONS] NAME LASTNAME Try "main.py --help" for help. Error: Missing argument 'LASTNAME'. // These 2 arguments are required, so, pass both: $ python main.py Camila GutiΓ©rrez Hello Camila GutiΓ©rrez ```
!!! tip Notice that the order is important. The last name has to go after the first name. If you called it with: ``` $ python main.py GutiΓ©rrez Camila ``` your app wouldn't have a way to know which is the `name` and which the `lastname`. It expects the first *CLI argument* to be the `name` and the second *CLI argument* to be the `lastname`. ## What is a **CLI option** Here we will use the word **CLI option** to refer to *CLI parameters* passed to the CLI application with a specific name. For example, if you go to your terminal and type:
```console $ ls ./myproject --size 12 first-steps.md 4 intro.md ```
`ls` will show the contents of the directory `./myproject` with their `size`. * `ls` is the *program* (or "command", "CLI app"). * `./myproject` is a *CLI argument*. * `--size` is an optional *CLI option*. The program knows it has to show the size because it sees `--size`, not because of the order. A *CLI option* like `--size` doesn't depend on the order like a *CLI argument*. So, if you put the `--size` *before* the *CLI argument*, it still works (in fact, that's the most common way of doing it):
```console $ ls --size ./myproject 12 first-steps.md 4 intro.md ```
The main visual difference between a *CLI option* and and a *CLI argument* is that the *CLI option* has `--` prepended to the name, like in "`--size`". A *CLI option* doesn't depend on the order because it has a predefined name (here it's `--size`). This is because the CLI app is looking specifically for a literal `--size` parameter (also known as "flag" or "switch"), with that specific "name" (here the specific name is "`--size`"). The CLI app will check if you typed it or not, it will be actively looking for `--size` even if you didn't type it (to check if it's there or not). In contrast, the CLI app is not actively looking for the *CLI argument* with a text "`./myproject`", it has no way to know if you would type `./myproject` or `./my-super-awesome-project` or anything else. It's just waiting to get whatever you give it. The only way to know that you refer to a specific *CLI argument* is because of the order. The same way that it knows that the first *CLI argument* was the `name` and the second was the `lastname`, but if you mixed the order, it wouldn't be able to handle it. Instead, with a *CLI option*, the order doesn't matter. Also, by default, a *CLI option* is *optional* (not *required*). So, by default: * A *CLI argument* is **required** * A *CLI option* is **optional** But the *required* and *optional* defaults can be changed. So, the main and **most important** difference is that: * *CLI options* **start with `--`** and don't depend on the order * *CLI arguments* depend on the **sequence order** !!! tip In this example above the *CLI option* `--size` is just a "flag" or "switch" that will contain a boolean value, `True` or `False`, depending on if it was added to the command or not. This one doesn't receive any values. But *CLI options* can also receive values like *CLI arguments*. You'll see how later. ## Add one *CLI option* Now add a `--formal` *CLI option*: ```Python hl_lines="4 5" {!../docs_src/first_steps/tutorial004.py!} ``` Here `formal` is a `bool` that is `False` by default.
```console // Get the help $ python main.py --help Usage: main.py [OPTIONS] NAME LASTNAME Arguments: NAME [required] LASTNAME [required] Options: --formal / --no-formal [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! tip Notice that it automatically creates a `--formal` and a `--no-formal` because it detected that `formal` is a `bool`. Now call it normally:
```console $ python main.py Camila GutiΓ©rrez Hello Camila GutiΓ©rrez // But if you pass --formal $ python main.py Camila GutiΓ©rrez --formal Good day Ms. Camila GutiΓ©rrez. // And as --formal is a CLI option you can put it anywhere in this command $ python main.py Camila --formal GutiΓ©rrez Good day Ms. Camila GutiΓ©rrez. $ python main.py --formal Camila GutiΓ©rrez Good day Ms. Camila GutiΓ©rrez. ```
## A *CLI option* with a value To convert the `lastname` from a *CLI argument* to a *CLI option*, give it a default value of `""`: ```Python hl_lines="4" {!../docs_src/first_steps/tutorial005.py!} ``` As `lastname` now has a default value of `""` (an empty string) it is no longer required in the function, and **Typer** will now by default make it an optional *CLI option*.
```console $ python main.py --help Usage: main.py [OPTIONS] NAME Arguments: NAME [required] Options: --lastname TEXT [default: ] --formal / --no-formal [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! tip Notice the `--lastname`, and notice that it takes a textual value. A *CLI option* with a value like `--lastname` (contrary to a *CLI option* without a value, a `bool` flag, like `--formal` or `--size`) takes as its value whatever is at the *right side* of the *CLI option*.
```console // Call it without a --lastname $ python main.py Camila Hello Camila // Pass the --lastname $ python main.py Camila --lastname GutiΓ©rrez Hello Camila GutiΓ©rrez ```
!!! tip Notice that "`GutiΓ©rrez`" is at the right side of `--lastname`. A *CLI option* with a value takes as its value whatever is at the *right side*. And as `--lastname` is now a *CLI option* that doesn't depend on the order, you can pass it before the name:
```console $ python main.py --lastname GutiΓ©rrez Camila // and it will still work normally Hello Camila GutiΓ©rrez ```
## Document your CLI app If you add a docstring to your function it will be used in the help text: ```Python hl_lines="5 6 7 8 9" {!../docs_src/first_steps/tutorial006.py!} ``` Now see it with the `--help` option:
```console $ python main.py --help Usage: main.py [OPTIONS] NAME Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. Arguments: NAME [required] Options: --lastname TEXT [default: ] --formal / --no-formal [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. ```
!!! tip There is another place to document the specific *CLI options* and *CLI arguments* that will show up next to them in the help text as with `--install-completion` or `--help`, you will learn that later in the tutorial. ## Arguments, options, parameters, optional, required Be aware that these terms refer to multiple things depending on the context, and sadly, those "contexts" mix frequently, so it's easy to get confused. ### In Python In Python, the names of the variables in a function, like `name` and `lastname`: ```Python def main(name: str, lastname: str = ""): pass ``` are called "Python function parameters" or "Python function arguments". !!! note "Technical Details" There's actually a very small distinction in Python between "parameter" and "argument". It's quite technical... and somewhat pedantic. One refers to the variable name in a function *declaration*. Like: ``` def bring_person(name: str, lastname: str = ""): pass ``` The other refers to the value passed when *calling* a function. Like: ``` person = bring_person("Camila", lastname="Gutiérrez") ``` ...but you will probably see them used interchangeably in most of the places (including here). #### Python default values In Python, in a function, a parameter with a *default value* like `lastname` in: ```Python def main(name: str, lastname: str = ""): pass ``` is considered an "optional parameter" (or "optional argument"). The default value can be anything, like `""` or `None`. And a parameter like `name`, that doesn't have a default value, is considered *required*. ### In CLIs When talking about command line interface applications, the words **"argument"** and **"parameter"** are commonly used to refer to that data passed to a CLI app, those parameters. But those words **don't imply** anything about the data being required, needing to be passed in a certain order, nor having a flag like `--lastname`. The parameters that come with a name like `--lastname` (and optionally a value) are commonly optional, not required. So, when talking about CLIs it's common to call them **optional arguments** or **optional parameters**. Sometimes these *optional parameters* that start with `--` are also called a **flag** or a **switch**. In reality, the parameters that require an order can be made *optional* too. And the ones that come with a flag (like `--lastname`) can be *required* too. ### In **Typer** To try and make it a bit easier, we'll normally use the words "parameter" or "argument" to refer to Python functions. We'll use ***CLI argument*** to refer to those *CLI parameters* that depend on the specific order. That are **required** by default. And we'll use ***CLI option*** to refer to those *CLI parameters* that depend on a name that starts with `--` (like `--lastname`). That are **optional** by default. We will use ***CLI parameter*** to refer to both, *CLI arguments* and *CLI options*. ## **Typer CLI** Now that you know the basics of **Typer**, you might want to install and use [Typer CLI](../typer-cli.md){.internal-link target=_blank}. **Typer CLI** is a tool to run your **Typer** scripts giving you ✨ auto completion ✨ in your terminal. As an alternative to running with Python:
```console $ python main.py Hello World ```
You can run with **Typer CLI**:
```console $ typer main.py run Hello World ```
...and it will give you auto completion in your terminal when you hit TAB for all your code. So you can use it to have auto completion for your own scripts as you continue with the tutorial. !!! tip Your CLI application built with **Typer** won't need [Typer CLI](../typer-cli.md){.internal-link target=_blank} to have auto completion once you create a Python package. But for short scripts and for learning, before creating a Python package, it might be useful. typer-0.4.0/docs/tutorial/index.md000066400000000000000000000045051411311634200171020ustar00rootroot00000000000000## Python types If you need a refreshed about how to use Python type hints, check the first part of FastAPI's Python types intro. You can also check the mypy cheat sheet. In short (very short), you can declare a function with parameters like: ```Python from typing import Optional def type_example(name: str, formal: bool = False, intro: Optional[str] = None): pass ``` And your editor (and **Typer**) will know that: * `name` is of type `str` and is a required parameter. * `formal` is a `bool` and is by default `False`. * `intro` is an optional `str`, by default is `None`. These type hints are what give you autocomplete in your editor and several other features. **Typer** is based on these type hints. ## Intro This tutorial shows you how to use **Typer** with all its features, step by step. Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific CLI needs. It is also built to work as a future reference. So you can come back and see exactly what you need. ## Run the code All the code blocks can be copied and used directly (they are tested Python files). To run any of the examples, copy the code to a file `main.py`, and run it:
```console $ python main.py ✨ The magic happens here ✨ ```
It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally. Using it in your editor is what really shows you the benefits of **Typer**, seeing how little code you have to write, all the type checks, autocompletion, etc. And running the examples is what will really help you understand what is going on. You can learn a lot more by running some examples and playing around with them than by reading all the docs here. --- ## Install **Typer** The first step is to install **Typer**. For the tutorial, you might want to install it with all the optional dependencies and features:
```console $ pip install typer[all] ---> 100% Successfully installed typer click colorama shellingham ```
...that also includes `colorama` and `shellingham`. typer-0.4.0/docs/tutorial/launch.md000066400000000000000000000017221411311634200172430ustar00rootroot00000000000000You can launch applications from your CLI program with `typer.launch()`. It will launch the appropriate application depending on the URL or file type you pass it: ```Python hl_lines="6" {!../docs_src/launch/tutorial001.py!} ``` Check it:
```console $ python main.py Opening Typer docs // Opens browser with Typer's docs ```
## Locating a file You can also make the operating system open the file browser indicating where a file is located with `locate=True`: ```Python hl_lines="17" {!../docs_src/launch/tutorial002.py!} ``` !!! tip The rest of the code in this example is just making sure the app directory exists and creating the config file. But the most important part is the `typer.launch(config_file_str, locate=True)` with the argument `locate=True`. Check it:
```console $ python main.py Opening config directory // Opens a file browser indicating where the config file is located ```
typer-0.4.0/docs/tutorial/multiple-values/000077500000000000000000000000001411311634200205755ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/multiple-values/arguments-with-multiple-values.md000066400000000000000000000035241411311634200272270ustar00rootroot00000000000000*CLI arguments* can also receive multiple values. You can define the type of a *CLI argument* using `typing.List`. ```Python hl_lines="7" {!../docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py!} ``` And then you can pass it as many *CLI arguments* of that type as you want:
```console $ python main.py ./index.md ./first-steps.md woohoo! This file exists: index.md woohoo! This file exists: first-steps.md woohoo! ```
!!! tip We also declared a final *CLI argument* `celebration`, and it's correctly used even if we pass an arbitrary number of `files` first. !!! info A `List` can only be used in the last command (if there are subcommands), as this will take anything to the right and assume it's part of the expected *CLI arguments*. ## *CLI arguments* with tuples If you want a specific number of values and types, you can use a tuple, and it can even have default values: ```Python hl_lines="7 8" {!../docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py!} ``` Check it:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] [NAMES]... Arguments: [NAMES]... Select 3 characters to play with [default: Harry, Hermione, Ron] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Use it with its defaults $ python main.py Hello Harry Hello Hermione Hello Ron // If you pass an invalid number of arguments you will get an error $ python main.py Draco Hagrid Error: argument names takes 3 values // And if you pass the exact number of values it will work correctly $ python main.py Draco Hagrid Dobby Hello Draco Hello Hagrid Hello Dobby ```
typer-0.4.0/docs/tutorial/multiple-values/index.md000066400000000000000000000002051411311634200222230ustar00rootroot00000000000000There are several ways to declare multiple values for *CLI options* and *CLI arguments*. We'll see them in the next short sections. typer-0.4.0/docs/tutorial/multiple-values/multiple-options.md000066400000000000000000000023171411311634200244460ustar00rootroot00000000000000You can declare a *CLI option* that can be used multiple times, and then get all the values. For example, let's say you want to accept several users in a single execution. For this, use the standard Python `typing.List` to declare it as a `list` of `str`: ```Python hl_lines="1 6" {!../docs_src/multiple_values/multiple_options/tutorial001.py!} ``` You will receive the values as you declared them, as a `list` of `str`. Check it:
```console $ python main.py No provided users Aborted! // Now pass a user $ python main.py --user Camila Processing user: Camila // And now try with several users $ python main.py --user Camila --user Rick --user Morty Processing user: Camila Processing user: Rick Processing user: Morty ```
## Multiple `float` The same way, you can use other types and they will be converted by **Typer** to their declared type: ```Python hl_lines="6" {!../docs_src/multiple_values/multiple_options/tutorial002.py!} ``` Check it:
```console $ python main.py The sum is 0 // Try with some numbers $ python main.py --number 2 The sum is 2.0 // Try with some numbers $ python main.py --number 2 --number 3 --number 4.5 The sum is 9.5 ```
typer-0.4.0/docs/tutorial/multiple-values/options-with-multiple-values.md000066400000000000000000000044131411311634200267130ustar00rootroot00000000000000You can also declare a *CLI option* that takes several values of different types. You can set the number of values and types to anything you want, but it has to be a fixed number of values. For this, use the standard Python `typing.Tuple`: ```Python hl_lines="1 6" {!../docs_src/multiple_values/options_with_multiple_values/tutorial001.py!} ``` Each of the internal types defines the type of each value in the tuple. So: ```Python user: Tuple[str, int, bool] ``` means that the parameter `user` is a tuple of 3 values. * The first value is a `str`. * The second value is an `int`. * The third value is a `bool`. Later we do: ```Python username, coins, is_wizard = user ``` If you hadn't seen that, it means that `user` is a tuple with 3 values, and we are assigning each of the values to a new variable: * The first value in the tuple `user` (a `str`) goes to the variable `username`. * The second value in the tuple `user` (an `int`) goes to the variable `coins`. * The third value in the tuple `user` (a `bool`) goes to the variable `is_wizard`. So, this: ```Python username, coins, is_wizard = user ``` is equivalent to this: ```Python username = user[0] coins = user[1] is_wizard = user[2] ``` !!! tip Notice that the default is a tuple with `(None, None, None)`. You cannot simply use `None` here as the default because Click doesn't support it. ## Check it Now let's see how this works in the terminal:
```console // check the help $ python main.py --help // Notice the <TEXT INTEGER BOOLEAN> Usage: main.py [OPTIONS] Options: --user <TEXT INTEGER BOOLEAN>... --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Now try it $ python main.py --user Camila 50 yes The username Camila has 50 coins And this user is a wizard! // With other values $ python main.py --user Morty 3 no The username Morty has 3 coins // Try with invalid values (not enough) $ python main.py --user Camila 50 Error: --user option requires 3 arguments ```
typer-0.4.0/docs/tutorial/options/000077500000000000000000000000001411311634200171405ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/options/autocompletion.md000066400000000000000000000303721411311634200225310ustar00rootroot00000000000000As you have seen, apps built with **Typer** have completion in your shell that works when you create a Python package or using **Typer CLI**. It normally completes *CLI options*, *CLI arguments*, and subcommands (that you will learn about later). But you can also provide auto completion for the **values** of *CLI options* and *CLI arguments*. We will learn about that here. ## Review completion Before checking how to provide custom completions, let's check again how it works. After installing completion (for your own Python package or for **Typer CLI**), when you use your CLI program and start adding a *CLI option* with `--` an then hit TAB, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc). To check it quickly without creating a new Python package, install [Typer CLI](../../typer-cli.md){.internal-link target=_blank}. Then let's create small example script: ```Python {!../docs_src/options/autocompletion/tutorial001.py!} ``` And let's try it with **Typer CLI** to get completion:
```console // Hit the TAB key in your keyboard below where you see the: [TAB] $ typer ./main.py [TAB][TAB] // Depending on your terminal/shell you will get some completion like this ✨ run -- Run the provided Typer app. utils -- Extra utility commands for Typer apps. // Then try with "run" and -- $ typer ./main.py run --[TAB][TAB] // You will get completion for --name, depending on your terminal it will look something like this --name -- The name to say hi to. // And you can run it as if it was with Python directly $ typer ./main.py run --name Camila Hello Camila ```
## Custom completion for values Right now we get completion for the *CLI option* names, but not for the values. We can provide completion for the values creating an `autocompletion` function, similar to the `callback` functions from [CLI Option Callback and Context](./callback-and-context.md){.internal-link target=_blank}: ```Python hl_lines="4 5 10" {!../docs_src/options/autocompletion/tutorial002.py!} ``` We return a `list` of strings from the `complete_name()` function. And then we get those values when using completion:
```console $ typer ./main.py run --name [TAB][TAB] // We get the values returned from the function πŸŽ‰ Camila Carlos Sebastian ```
We got the basics working. Now let's improve it. ## Check the incomplete value Right now, we always return those values, even if users start typing `Sebast` and then hit TAB, they will also get the completion for `Camila` and `Carlos` (depending on the shell), while we should only get completion for `Sebastian`. But we can fix that so that it always works correctly. Modify the `complete_name()` function to receive a parameter of type `str`, it will contain the incomplete value. Then we can check and return only the values that start with the incomplete value from the command line: ```Python hl_lines="6 7 8 9 10 11" {!../docs_src/options/autocompletion/tutorial003.py!} ``` Now let's try it:
```console $ typer ./main.py run --name Ca[TAB][TAB] // We get the values returned from the function that start with Ca πŸŽ‰ Camila Carlos ```
Now we are only returning the valid values, that start with `Ca`, we are no longer returning `Sebastian` as a completion option. !!! tip You have to declare the incomplete value of type `str` and that's what you will receive in the function. No matter if the actual value will be an `int`, or something else, when doing completion, you will only get a `str` as the incomplete value. And the same way, you can only return `str`, not `int`, etc. ## Add help to completions Right now we are returning a `list` of `str`. But some shells (Zsh, Fish, PowerShell) are capable of showing extra help text for completion. We can provide that extra help text so that those shells can show it. In the `complete_name()` function, instead of providing one `str` per completion element, we provide a `tuple` with 2 items. The first item is the actual completion string, and the second item is the help text. So, in the end, we return a `list` of `tuples` of `str`: ```Python hl_lines="3 4 5 6 7 10 11 12 13 14 15 16" {!../docs_src/options/autocompletion/tutorial004.py!} ``` !!! tip If you want to have help text for each item, make sure each item in the list is a `tuple`. Not a `list`. Click checks specifically for a `tuple` when extracting the help text. So in the end, the return will be a `list` (or other iterable) of `tuples` of 2 `str`. !!! info The help text will be visible in Zsh, Fish, and PowerShell. Bash doesn't support showing the help text, but completion will still work the same. If you have a shell like Zsh, it would look like:
```console $ typer ./main.py run --name [TAB][TAB] // We get the completion items with their help text πŸŽ‰ Camila -- The reader of books. Carlos -- The writer of scripts. Sebastian -- The type hints guy. ```
## Simplify with `yield` Instead of creating and returning a list with values (`str` or `tuple`), we can use `yield` with each value that we want in the completion. That way our function will be a generator that **Typer** (actually Click) can iterate: ```Python hl_lines="10 11 12 13" {!../docs_src/options/autocompletion/tutorial005.py!} ``` That simplifies our code a bit and works the same. !!! tip If all the `yield` part seems complex for you, don't worry, you can just use the version with the `list` above. In the end, that's just to save us a couple of lines of code. !!! info The function can use `yield`, so it doesn't have to return strictly a `list`, it just has to be iterable. But each of the elements for completion has to be a `str` or a `tuple` (when containing a help text). ## Access other *CLI parameters* with the Context Let's say that now we want to modify the program to be able to "say hi" to multiple people at the same time. So, we will allow multiple `--name` *CLI options*. !!! tip You will learn more about *CLI parameters* with multiple values later in the tutorial. So, for now, take this as a sneak peek πŸ˜‰. For this we use a `List` of `str`: ```Python hl_lines="6 7 8" {!../docs_src/options/autocompletion/tutorial006.py!} ``` And then we can use it like:
```console $ typer ./main.py run --name Camila --name Sebastian Hello Camila Hello Sebastian ```
### Getting completion for multiple values And the same way as before, we want to provide **completion** for those names. But we don't want to provide the **same names** for completion if they were already given in previous parameters. For that, we will access and use the "Context". When you create a **Typer** application it uses Click underneath. And every Click application has a special object called a "Context" that is normally hidden. But you can access the context by declaring a function parameter of type `typer.Context`. And from that context you can get the current values for each parameter. ```Python hl_lines="12 13 15" {!../docs_src/options/autocompletion/tutorial007.py!} ``` We are getting the `names` already provided with `--name` in the command line before this completion was triggered. If there's no `--name` in the command line, it will be `None`, so we use `or []` to make sure we have a `list` (even if empty) to check its contents later. Then, when we have a completion candidate, we check if each `name` was already provided with `--name` by checking if it's in that list of `names` with `name not in names`. And then we `yield` each item that has not being used yet. Check it:
```console $ typer ./main.py run --name [TAB][TAB] // The first time we trigger completion, we get all the names Camila -- The reader of books. Carlos -- The writer of scripts. Sebastian -- The type hints guy. // Add a name and trigger completion again $ typer ./main.py run --name Sebastian --name Ca[TAB][TAB] // Now we get completion only for the names we haven't used πŸŽ‰ Camila -- The reader of books. Carlos -- The writer of scripts. // And if we add another of the available names: $ typer ./main.py run --name Sebastian --name Camila --name [TAB][TAB] // We get completion for the only available one Carlos -- The writer of scripts. ```
!!! tip It's quite possible that if there's only one option left, your shell will complete it right away instead of showing the option with the help text, to save you more typing. ## Getting the raw *CLI parameters* You can also get the raw *CLI parameters*, just a `list` of `str` with everything passed in the command line before the incomplete value. For example, something like `["typer", "main.py", "run", "--name"]`. !!! tip This would be for advanced scenarios, in most use cases you would be better off using the context. But it's still possible if you need it. As a simple example, let's show it on the screen before completion. Because completion is based on the output printed by your program (handled internally by **Typer**), during completion we can't just print something else as we normally do. ### Printing to "standard error" !!! tip If you need a refresher about what is "standard output" and "standard error" check the section in [Printing and Colors: "Standard Output" and "Standard Error"](../printing.md#standard-output-and-standard-error){.internal-link target=_blank}. The completion system only reads from "standard output", so, printing to "standard error" won't break completion. πŸš€ You can print to "standard error" with `typer.echo("some text", err=True)`. Using `err=True` tells **Typer** (actually Click) that the output should be shown in "standard error". ```Python hl_lines="12 13" {!../docs_src/options/autocompletion/tutorial008.py!} ``` We get all the *CLI parameters* as a raw `list` of `str` by declaring a parameter with type `List[str]`, here it's named `args`. !!! tip Here we name the list of all the raw *CLI parameters* `args` because that's the convention with Click. But it doesn't contain only *CLI arguments*, it has everything, including *CLI options* and values, as a raw `list` of `str`. And then we just print it to "standard error".
```console $ typer ./main.py run --name [TAB][TAB] // First we see the raw CLI parameters ['./main.py', 'run', '--name'] // And then we see the actual completion Camila -- The reader of books. Carlos -- The writer of scripts. Sebastian -- The type hints guy. ```
!!! tip This is a very simple (and quite useless) example, just so you know how it works and that you can use it. But it's probably useful only in very advanced use cases. ## Getting the Context and the raw *CLI parameters* Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, and the incomplete `str`: ```Python hl_lines="12" {!../docs_src/options/autocompletion/tutorial009.py!} ``` Check it:
```console $ typer ./main.py run --name [TAB][TAB] // First we see the raw CLI parameters ['./main.py', 'run', '--name'] // And then we see the actual completion Camila -- The reader of books. Carlos -- The writer of scripts. Sebastian -- The type hints guy. $ typer ./main.py run --name Sebastian --name Ca[TAB][TAB] // Again, we see the raw CLI parameters ['./main.py', 'run', '--name', 'Sebastian', '--name'] // And then we see the rest of the valid completion items Camila -- The reader of books. Carlos -- The writer of scripts. ```
## Types, types everywhere **Typer** uses the type declarations to detect what it has to provide to your `autocompletion` function. You can declare function parameters of these types: * `str`: for the incomplete value. * `typer.Context`: for the current context. * `List[str]`: for the raw *CLI parameters*. It doesn't matter how you name them, in which order, or which ones of the 3 options you declare. It will all "**just work**" ✨ typer-0.4.0/docs/tutorial/options/callback-and-context.md000066400000000000000000000157101411311634200234440ustar00rootroot00000000000000In some occasions you might want to have some custom logic for a specific *CLI parameter* (for a *CLI option* or *CLI argument*) that is executed with the value received from the terminal. In those cases you can use a *CLI parameter* callback function. ## Validate *CLI parameters* For example, you could do some validation before the rest of the code is executed. ```Python hl_lines="4 5 6 7 10" {!../docs_src/options/callback/tutorial001.py!} ``` Here you pass a function to `typer.Option()` or `typer.Argument()` with the keyword argument `callback`. The function receives the value from the command line. It can do anything with it, and then return the value. In this case, if the `--name` is not `Camila` we raise a `typer.BadParameter()` exception. The `BadParameter` exception is special, it shows the error with the parameter that generated it. Check it:
```console $ python main.py --name Camila Hello Camila $ python main.py --name Rick Usage: main.py [OPTIONS] // We get the error from the callback Error: Invalid value for '--name': Only Camila is allowed ```
## Handling completion There's something to be aware of with callbacks and completion that requires some small special handling. But first let's just use completion in your shell (Bash, Zsh, Fish, or PowerShell). After installing completion (for your own Python package or for **Typer CLI**), when you use your CLI program and start adding a *CLI option* with `--` an then hit TAB, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc). To check it quickly without creating a new Python package, install [Typer CLI](../../typer-cli.md){.internal-link target=_blank} and use it with the previous script:
```console // Hit the TAB key in your keyboard below where you see the: [TAB] $ typer ./main.py [TAB][TAB] // Depending on your terminal/shell you will get some completion like this ✨ run -- Run the provided Typer app. utils -- Extra utility commands for Typer apps. // Then try with "run" and --help $ typer ./main.py run --help // You get a help text with your CLI options as you normally would Usage: typer run [OPTIONS] Run the provided Typer app. Options: --name TEXT [required] --help Show this message and exit. // Then try completion with your program $ typer ./main.py run --[TAB][TAB] // You get completion for CLI options --help -- Show this message and exit. --name // And you can run it as if it was with Python directly $ typer ./main.py run --name Camila Hello Camila ```
### How shell completion works The way it works internally is that the shell/terminal will call your CLI program with some special environment variables (that hold the current *CLI parameters*, etc) and your CLI program will print some special values that the shell will use to present completion. All this is handled for you by **Typer** behind the scenes. But the main **important point** is that it is all based on values printed by your program that the shell reads. ### Breaking completion in a callback Let's say that when the callback is running, we want to show a message saying that it's validating the name: ```Python hl_lines="5" {!../docs_src/options/callback/tutorial002.py!} ``` And because the callback will be called when the shell calls your program asking for completion, that message `"Validating name"` will be printed and it will break completion. It will look something like:
```console // Run it normally $ typer ./main.py run --name Camila // See the extra message "Validating name" Validating name Hello Camila $ typer ./main.py run --[TAB][TAB] // Some weird broken error message ⛔️ (eval):1: command not found: Validating rutyper ./main.pyed Typer app. ```
### Fix completion - using the `Context` When you create a **Typer** application it uses Click underneath. And every Click application has a special object called a "Context" that is normally hidden. But you can access the context by declaring a function parameter of type `typer.Context`. The "context" has some additional data about the current execution of your program: ```Python hl_lines="4 5 6" {!../docs_src/options/callback/tutorial003.py!} ``` The `ctx.resilient_parsing` will be `True` when handling completion, so you can just return without printing anything else. But it will be `False` when calling the program normally. So you can continue the execution of your previous code. That's all is needed to fix completion πŸš€ Check it:
```console $ typer ./main.py run --[TAB][TAB] // Now it works correctly πŸŽ‰ --help -- Show this message and exit. --name // And you can call it normally $ typer ./main.py run --name Camila Validating name Hello Camila ```
## Using the `CallbackParam` object The same way you can access the `typer.Context` by declaring a function parameter with its value, you can declare another function parameter with type `typer.CallbackParam` to get the specific Click `Parameter` object. ```Python hl_lines="4 7" {!../docs_src/options/callback/tutorial004.py!} ``` It's probably not very common, but you could do it if you need it. For example if you had a callback that could be used by several *CLI parameters*, that way the callback could know which parameter is each time. Check it:
```console $ python main.py --name Camila Validating param: name Hello Camila ```
## Technical Details Because you get the relevant data in the callback function based on standard Python type annotations, you get type checks and autocompletion in your editor for free. And **Typer** will make sure you get the function parameters you want. You don't have to worry about their names, their order, etc. As it's based on standard Python types, it "**just works**". ✨ ### Click's `Parameter` The `typer.CallbackParam` is actually just a sub-class of Click's `Parameter`, so you get all the right completion in your editor. ### Callback with type annotations You can get the `typer.Context` and the `typer.CallbackParam` simply by declaring a function parameter of each type. The order doesn't matter, the name of the function parameters doesn't matter. You could also get only the `typer.CallbackParam` and not the `typer.Context`, or vice versa, it will still work. ### `value` function parameter The `value` function parameter in the callback can also have any name (e.g. `lastname`) and any type, but it should have the same type annotation as in the main function, because that's what it will receive. It's also possible to not declare its type. It will still work. And it's possible to not declare the `value` parameter at all, and, for example, only get the `typer.Context`. That will also work. typer-0.4.0/docs/tutorial/options/help.md000066400000000000000000000045621411311634200204210ustar00rootroot00000000000000You already saw how to add a help text for *CLI arguments* with the `help` parameter. Let's now do the same for *CLI options*: ```Python hl_lines="6 7" {!../docs_src/options/help/tutorial001.py!} ``` We are replacing the default values we had before with `typer.Option()`. As we no longer have a default value there, the first parameter to `typer.Option()` serves the same purpose of defining that default value. So, if we had: ```Python lastname: str = "" ``` now we write: ```Python lastname: str = typer.Option("") ``` And both forms achieve the same: a *CLI option* with a default value of an empty string (`""`). And then we can pass the `help` keyword parameter: ```Python lastname: str = typer.Option("", help="this option does this and that") ``` to create the help for that *CLI option*. Copy that example from above to a file `main.py`. Test it:
```console $ python main.py --help Usage: main.py [OPTIONS] NAME Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. Arguments: NAME [required] Options: --lastname TEXT Last name of person to greet. [default: ] --formal / --no-formal Say hi formally. [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Now you have a help text for the --lastname and --formal CLI options πŸŽ‰ ```
## Hide default from help You can tell Typer to not show the default value in the help text with `show_default=False`: ```Python hl_lines="4" {!../docs_src/options/help/tutorial002.py!} ``` And it will no longer show the default value in the help text:
```console $ python main.py Hello Wade Wilson // Show the help $ python main.py --help Usage: main.py [OPTIONS] Options: --fullname TEXT --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Notice there's no [default: Wade Wilson] πŸ”₯ ```
!!! note "Technical Details" In Click applications the default values are hidden by default. πŸ™ˆ In **Typer** these default values are shown by default. πŸ‘€ typer-0.4.0/docs/tutorial/options/index.md000066400000000000000000000003121411311634200205650ustar00rootroot00000000000000In the next short sections we will see how to modify *CLI options* using `typer.Option()`. `typer.Option()` works very similarly to `typer.Argument()`, but has some extra features that we'll see next. typer-0.4.0/docs/tutorial/options/name.md000066400000000000000000000213171411311634200204060ustar00rootroot00000000000000By default **Typer** will create a *CLI option* name from the function parameter. So, if you have a function with: ```Python def main(user_name: Optional[str] = None): pass ``` or ```Python def main(user_name: Optional[str] = typer.Option(None)): pass ``` **Typer** will create a *CLI option*: ``` --user-name ``` But you can customize it if you want to. Let's say the function parameter name is `user_name` as above, but you want the *CLI option* to be just `--name`. You can pass the *CLI option* name that you want to have in the next positional argument passed to `typer.Option()`: ```Python hl_lines="4" {!../docs_src/options/name/tutorial001.py!} ``` Here you are passing the string `"--name"` as the second positional argument to `typer.Option()`. !!! info "Positional" means that it's not a function argument with a keyword name. For example `show_default=True` is a keyword argument. "`show_default`" is the keyword. But in `"--name"` there's no `option_name="--name"` or something similar, it's just the string value `"--name"` that goes in `typer.Option()` after the `...` value passed in the first position. That's a "positional argument" in a function. Check it:
```console $ python main.py --help // Notice the --name instead of --user-name Usage: main.py [OPTIONS] Options: --name TEXT [required] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it $ python --name Camila Hello Camila ```
## *CLI option* short names A short name is a *CLI option* name with a single dash (`-`) instead of 2 (`--`) and a single letter, like `-n` instead of `--name`. For example, the `ls` program has a *CLI option* named `--size`, and the same *CLI option* also has a short name `-s`:
```console // With the long name --size $ ls ./myproject --size 12 first-steps.md 4 intro.md // With the short name -s $ ls ./myproject -s 12 first-steps.md 4 intro.md // Both CLI option names do the same ```
### *CLI option* short names together Short names have another feature, when they have a single letter, as in `-s`, you can put several of these *CLI options* together, with a single dash. For example, the `ls` program has these 2 *CLI options* (among others): * `--size`: show the sizes of the listed files. * `--human`: show a human-readable format, like `1MB` instead of just `1024`. And these 2 *CLI options* have short versions too: * `--size`: short version `-s`. * `--human`: short version `-h`. So, you can put them together with `-sh` or `-hs`:
```console // Call ls with long CLI options $ ls --size --human 12K first-steps.md 4.0K intro.md // Now with short versions $ ls -s -h 12K first-steps.md 4.0K intro.md // And with short versions together $ ls -sh 12K first-steps.md 4.0K intro.md // Order in short versions doesn't matter $ ls -hs 12K first-steps.md 4.0K intro.md // They all work the same πŸŽ‰ ```
### *CLI option* short names with values When you use *CLI options* with short names, you can put them together if they are just boolean flags, like `--size` or `--human`. But if you have a *CLI option* `--file` with a short name `-f` that takes a value, if you put it with other short names for *CLI options*, you have to put it as the last letter, so that it can receive the value that comes right after. For example, let's say you are decompressing/extracting a file `myproject.tar.gz` with the program `tar`. You can pass these *CLI option* short names to `tar`: * `-x`: means "e`X`tract", to decompress and extract the contents. * `-v`: means "`V`erbose", to print on the screen what it is doing, so you can know that it's decompressing each file and can entertain yourself while you wait. * `-f`: means "`F`ile", this one requires a value, the compressed file to extract (in our example, this is `myproject.tar.gz`). * So if you use all the short names together, this `-f` has to come last, to receive the value that comes next to it. For example:
```console $ tar -xvf myproject.tar.gz myproject/ myproject/first-steps.md myproject/intro.md // But if you put the -f before $ tar -fxv myproject.tar.gz // You get an ugly error tar: You must specify one of the blah, blah, error, error ```
### Defining *CLI option* short names In **Typer** you can also define *CLI option* short names the same way you can customize the long names. `typer.Option()` receives as a first function argument the default value, e.g. `None`, and all the next *positional* values are to define the *CLI option* name(s). !!! tip Remember the *positional* function arguments are those that don't have a keyword. All the other function arguments/parameters you pass to `typer.Option()` like `prompt=True` and `help="This option blah, blah"` require the keyword. You can overwrite the *CLI option* name to use as in the previous example, but you can also declare extra alternatives, including short names. For example, extending the previous example, let's add a *CLI option* short name `-n`: ```Python hl_lines="4" {!../docs_src/options/name/tutorial002.py!} ``` Here we are overwriting the *CLI option* name that by default would be `--user-name`, and we are defining it to be `--name`. And we are also declaring a *CLI option* short name of `-n`. Check it:
```console // Check the help $ python main.py --help // Notice the two CLI option names -n and --name Usage: main.py [OPTIONS] Options: -n, --name TEXT [required] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try the short version $ python main.py -n Camila Hello Camila ```
### *CLI option* only short name If you only declare a short name like `-n` then that will be the only *CLI option* name. And neither `--name` nor `--user-name` will be available. ```Python hl_lines="4" {!../docs_src/options/name/tutorial003.py!} ``` Check it:
```console $ python main.py --help // Notice there's no --name nor --user-name, only -n Usage: main.py [OPTIONS] Options: -n TEXT [required] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it $ python main.py -n Camila Hello Camila ```
### *CLI option* short name and default Continuing with the example above, as **Typer** allows you to declare a *CLI option* as having only a short name, if you want to have the default long name plus a short name, you have to declare both explicitly: ```Python hl_lines="4" {!../docs_src/options/name/tutorial004.py!} ``` Check it:
```console $ python main.py --help // Notice that we have the long version --user-name back // and we also have the short version -n Usage: main.py [OPTIONS] Options: -n, --user-name TEXT [required] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it $ python main.py --user-name Camila Hello Camila // And try the short version $ python main.py -n Camila ```
### *CLI option* short names together You can create multiple short names and use them together. You don't have to do anything special for it to work (apart from declaring those short versions): ```Python hl_lines="5 6" {!../docs_src/options/name/tutorial005.py!} ``` !!! tip Notice that, again, we are declaring the long and short version of the *CLI option* names. Check it:
```console $ python main.py --help // We now have short versions -n and -f // And also long versions --name and --formal Usage: main.py [OPTIONS] Options: -n, --name TEXT [required] -f, --formal --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try the short versions $ python main.py -n Camila -f Good day Ms. Camila. // And try the 2 short versions together // See how -n has to go last, to be able to get the value $ python main.py -fn Camila Good day Ms. Camila. ```
typer-0.4.0/docs/tutorial/options/password.md000066400000000000000000000024201411311634200213220ustar00rootroot00000000000000Apart from having a prompt, you can make a *CLI option* have a `confirmation_prompt=True`: ```Python hl_lines="5" {!../docs_src/options/password/tutorial001.py!} ``` And the CLI program will ask for confirmation:
```console $ python main.py Camila // It prompts for the email # Email: $ camila@example.com # Repeat for confirmation: $ camila@example.com Hello Camila, your email is camila@example.com ```
## A Password prompt When receiving a password, it is very common (in most shells) to not show anything on the screen while typing the password. The program will still receive the password, but nothing will be shown on screen, not even `****`. You can achieve the same using `hide_input=True`. And if you combine it with `confirmation_prompt=True` you can easily receive a password with double confirmation: ```Python hl_lines="6 7 8" {!../docs_src/options/password/tutorial002.py!} ``` Check it:
```console $ python main.py Camila // It prompts for the password, but doesn't show anything when you type # Password: $ # Repeat for confirmation: $ // Let's imagine the password typed was "typerrocks" Hello Camila. Doing something very secure with password. ...just kidding, here it is, very insecure: typerrocks ```
typer-0.4.0/docs/tutorial/options/prompt.md000066400000000000000000000035441411311634200210110ustar00rootroot00000000000000It's also possible to, instead of just showing an error, ask for the missing value with `prompt=True`: ```Python hl_lines="4" {!../docs_src/options/prompt/tutorial001.py!} ``` And then your program will ask the user for it in the terminal:
```console // Call it with the NAME CLI argument $ python main.py Camila // It asks for the missing CLI option --lastname # Lastname: $ GutiΓ©rrez Hello Camila GutiΓ©rrez ```
## Customize the prompt You can also set a custom prompt, passing the string that you want to use instead of just `True`: ```Python hl_lines="5" {!../docs_src/options/prompt/tutorial002.py!} ``` And then your program will ask for it using with your custom prompt:
```console // Call it with the NAME CLI argument $ python main.py Camila // It uses the custom prompt # Please tell me your last name: $ GutiΓ©rrez Hello Camila GutiΓ©rrez ```
## Confirmation prompt In some cases you could want to prompt for something and then ask the user to confirm it by typing it twice. You can do it passing the parameter `confirmation_prompt=True`. Let's say it's a CLI app to delete a project: ```Python hl_lines="4" {!../docs_src/options/prompt/tutorial003.py!} ``` And it will prompt the user for a value and then for the confirmation:
```console $ python main.py // Your app will first prompt for the project name, and then for the confirmation # Project name: $ Old Project # Repeat for confirmation: $ Old Project Deleting project Old Project // If the user doesn't type the same, receives an error and a new prompt $ python main.py # Project name: $ Old Project # Repeat for confirmation: $ New Spice Error: the two entered values do not match # Project name: $ Old Project # Repeat for confirmation: $ Old Project Deleting project Old Project // Now it works πŸŽ‰ ```
typer-0.4.0/docs/tutorial/options/required.md000066400000000000000000000031061411311634200213020ustar00rootroot00000000000000We said before that *by default*: * *CLI options* are **optional** * *CLI arguments* are **required** Well, that's how they work *by default*, and that's the convention in many CLI programs and systems. But if you really want, you can change that. To make a *CLI option* required, pass `...` to `typer.Option()`. !!! info If you hadn't seen that `...` before: it is a a special single value, it is part of Python and is called "Ellipsis". That will tell **Typer** that it's still a *CLI option*, but it doesn't have a default value, and it's required. Let's make `--lastname` a required *CLI option*: ```Python hl_lines="4" {!../docs_src/options/required/tutorial001.py!} ``` And test it:
```console // Pass the NAME CLI argument $ python main.py Camila // We didn't pass the now required --lastname CLI option Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Missing option '--lastname'. // Now update it to pass the required --lastname CLI option $ python main.py Camila --lastname GutiΓ©rrez Hello Camila GutiΓ©rrez // And if you check the help $ python main.py --help Usage: main.py [OPTIONS] NAME Options: --lastname TEXT [required] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // It now tells you that --lastname is required πŸŽ‰ ```
typer-0.4.0/docs/tutorial/options/version.md000066400000000000000000000065551411311634200211620ustar00rootroot00000000000000You could use a callback to implement a `--version` *CLI option*. It would show the version of your CLI program and then it would terminate it. Even before any other *CLI parameter* is processed. ## First version of `--version` Let's see a first version of how it could look like: ```Python hl_lines="8-11 16-18" {!../docs_src/options/version/tutorial001.py!} ``` !!! tip Notice that we don't have to get the `typer.Context` and check for `ctx.resilient_parsing` for completion to work, because we only print and modify the program when `--version` is passed, otherwise, nothing is printed or changed from the callback. If the `--version` *CLI option* is passed, we get a value `True` in the callback. Then we can print the version and raise `typer.Exit()` to make sure the program is terminated before anything else is executed. We also declare the explicit *CLI option* name `--version`, because we don't want an automatic `--no-version`, it would look awkward. Check it:
```console $ python main.py --help // We get a --version, and don't get an awkward --no-version πŸŽ‰ Usage: main.py [OPTIONS] Options: --version --name TEXT --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // We can call it normally $ python main.py --name Camila Hello Camila // And we can get the version $ python main.py --version Awesome CLI Version: 0.1.0 // Because we exit in the callback, we don't get a "Hello World" message after the version πŸš€ ```
## Previous parameters and `is_eager` But now let's say that the `--name` *CLI option* that we declared before `--version` is required, and it has a callback that could exit the program: ```Python hl_lines="14-16 21-23" {!../docs_src/options/version/tutorial002.py!} ``` Then our CLI program could not work as expected in some cases as it is *right now*, because if we use `--version` after `--name` then the callback for `--name` will be processed before and we can get its error:
```console $ python main.py --name Rick --version Only Camila is allowed Aborted! ```
!!! tip We don't have to check for `ctx.resilient_parsing` in the `name_callback()` for completion to work, because we are not using `typer.echo()`, instead we are raising a `typer.BadParameter`. !!! note "Technical Details" `typer.BadParameter` prints the error to "standard error", not to "standard output", and because the completion system only reads from "standard output", it won't break completion. !!! info If you need a refresher about what is "standard output" and "standard error" check the section in [Printing and Colors: "Standard Output" and "Standard Error"](../printing.md#standard-output-and-standard-error){.internal-link target=_blank}. ### Fix with `is_eager` For those cases, we can mark a *CLI parameter* (a *CLI option* or *CLI argument*) with `is_eager=True`. That will tell **Typer** (actually Click) that it should process this *CLI parameter* before the others: ```Python hl_lines="22-24" {!../docs_src/options/version/tutorial003.py!} ``` Check it:
```console $ python main.py --name Rick --version // Now we only get the version, and the name is not used Awesome CLI Version: 0.1.0 ```
typer-0.4.0/docs/tutorial/package.md000066400000000000000000000504441411311634200173710ustar00rootroot00000000000000When you create a CLI program with **Typer** you probably want to create your own Python package. That's what allows your users to install it and have it as an independent program that they can use in their terminal. And that's also required for shell auto completion to work (unless you use your program through [Typer CLI](../typer-cli.md){.internal-link target=_blank}). Nowadays, there are several ways and tools to create Python packages (what you install with `pip install something`). You might even have your favorite already. Here's a very opinionated, short guide, showing one of the alternative ways of creating a Python package with a **Typer** app, from scratch. !!! tip If you already have a favorite way of creating Python packages, feel free to skip this. ## Prerequisites For this guide we'll use Poetry. Poetry's docs are great, so go ahead, check them and install it. ## Create a project Let's say we want to create a CLI application called `portal-gun`. To make sure your package doesn't collide with the package created by someone else, we'll name it with a prefix of your name. So, if your name is Rick, we'll call it `rick-portal-gun`. Create a project with Poetry:
```console $ poetry new rick-portal-gun Created package rick_portal_gun in rick-portal-gun // Enter the new project directory cd ./rick-portal-gun ```
## Dependencies and environment Add `typer[all]` to your dependencies:
```console $ poetry add typer[all] // It creates a virtual environment for your project Creating virtualenv rick-portal-gun-w31dJa0b-py3.6 in /home/rick/.cache/pypoetry/virtualenvs Using version ^0.1.0 for typer Updating dependencies Resolving dependencies... (1.2s) Writing lock file ---> 100% Package operations: 15 installs, 0 updates, 0 removals - Installing zipp (3.1.0) - Installing importlib-metadata (1.5.0) - Installing pyparsing (2.4.6) - Installing six (1.14.0) - Installing attrs (19.3.0) - Installing click (7.1.1) - Installing colorama (0.4.3) - Installing more-itertools (8.2.0) - Installing packaging (20.3) - Installing pluggy (0.13.1) - Installing py (1.8.1) - Installing shellingham (1.3.2) - Installing wcwidth (0.1.8) - Installing pytest (5.4.1) - Installing typer (0.0.11) // Activate that new virtual environment $ poetry shell Spawning shell within /home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.6 // Open an editor using this new environment, for example VS Code $ code ./ ```
You can see that you have a generated project structure that looks like: ``` . β”œβ”€β”€ poetry.lock β”œβ”€β”€ pyproject.toml β”œβ”€β”€ README.rst β”œβ”€β”€ rick_portal_gun β”‚Β Β  └── __init__.py └── tests β”œβ”€β”€ __init__.py └── test_rick_portal_gun.py ``` ## Create your app Now let's create an extremely simple **Typer** app. Create a file `rick_portal_gun/main.py` with: ```Python import typer app = typer.Typer() @app.callback() def callback(): """ Awesome Portal Gun """ @app.command() def shoot(): """ Shoot the portal gun """ typer.echo("Shooting portal gun") @app.command() def load(): """ Load the portal gun """ typer.echo("Loading portal gun") ``` !!! tip As we are creating an installable Python package, there's no need to add a section with `if __name__ == "__main__:`. ## Modify the README Let's change the README. By default it's a file `README.rst`. Let's change it to `README.md`. So, change the extension from `.rst` to `.md`. So that we can use Markdown instead of reStructuredText. And change the file to have something like: ```Markdown # Portal Gun The awesome Portal Gun ``` ## Modify your project metadata Edit your file `pyproject.toml`. It would look something like: ```TOML [tool.poetry] name = "rick-portal-gun" version = "0.1.0" description = "" authors = ["Rick Sanchez "] [tool.poetry.dependencies] python = "^3.6" typer = {extras = ["all"], version = "^0.1.0"} [tool.poetry.dev-dependencies] pytest = "^5.2" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" ``` We changed the default README, so let's make it use the new `README.md`. Add the line: ```TOML hl_lines="6" [tool.poetry] name = "rick-portal-gun" version = "0.1.0" description = "" authors = ["Rick Sanchez "] readme = "README.md" [tool.poetry.dependencies] python = "^3.6" typer = {extras = ["all"], version = "^0.1.0"} [tool.poetry.dev-dependencies] pytest = "^5.2" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" ``` ## Add a "script" We are creating a Python package that can be installed with `pip install`. But we want it to provide a CLI program that can be executed in the shell. To do that, we add a configuration to the `pyproject.toml` in the section `[tool.poetry.scripts]`: ```TOML hl_lines="8 9" [tool.poetry] name = "rick-portal-gun" version = "0.1.0" description = "" authors = ["Rick Sanchez "] readme = "README.md" [tool.poetry.scripts] rick-portal-gun = "rick_portal_gun.main:app" [tool.poetry.dependencies] python = "^3.6" typer = {extras = ["all"], version = "^0.1.0"} [tool.poetry.dev-dependencies] pytest = "^5.2" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" ``` Here's what that line means: `rick-portal-gun`: will be the name of the CLI program. That's how we will call it in the terminal once it is installed. Like:
```console $ rick-portal-gun // Something happens here ✨ ```
`rick_portal_gun.main`, in the part `"rick_portal_gun.main:app"`, with underscores, refers to the Python module to import. That's what someone would use in a section like: ```Python from rick_portal_gun.main import # something goes here ``` The `app` in `"rick_portal_gun.main:app"` is the thing to import from the module, and to call as a function, like: ```Python from rick_portal_gun.main import app app() ``` That config section tells Poetry that when this package is installed we want it to create a command line program called `rick-portal-gun`. And that the object to call (like a function) is the one in the variable `app` inside of the module `rick_portal_gun.main`. ## Install your package That's what we need to create a package. You can now install it:
```console $ poetry install Installing dependencies from lock file No dependencies to install or update - Installing rick-portal-gun (0.1.0) ```
## Try your CLI program Your package is installed in the environment created by Poetry, but you can already use it.
```console // You can use the which program to check which rick-portal-gun program is available (if any) $ which rick-portal-gun // You get the one from your environment /home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.6/bin/rick-portal-gun // Try it $ rick-portal-gun // You get all the standard help Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]... Awesome Portal Gun Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: load Load the portal gun shoot Shoot the portal gun ```
## Create a wheel package Python packages have a standard format called a "wheel". It's a file that ends in `.whl`. You can create a wheel with Poetry:
```console $ poetry build Building rick-portal-gun (0.1.0) - Building sdist - Built rick-portal-gun-0.1.0.tar.gz - Building wheel - Built rick_portal_gun-0.1.0-py3-none-any.whl ```
After that, if you check in your project directory, you should now have a couple of extra files at `./dist/`: ``` hl_lines="3 4" . β”œβ”€β”€ dist β”‚Β Β  β”œβ”€β”€ rick_portal_gun-0.1.0-py3-none-any.whl β”‚Β Β  └── rick-portal-gun-0.1.0.tar.gz β”œβ”€β”€ pyproject.toml β”œβ”€β”€ README.md β”œβ”€β”€ ... ``` The `.whl` is the wheel file. You can send that wheel file to anyone and they can use it to install your program (we'll see how to upload it to PyPI in a bit). ## Test your wheel package Now you can open another terminal and install that package from the file for your own user with:
```console $ pip install --user /home/rock/code/rick-portal-gun/dist/rick_portal_gun-0.1.0-py3-none-any.whl ---> 100% ```
!!! warning The `--user` is important, that ensures you install it in your user's directory and not in the global system. If you installed it in the global system (e.g. with `sudo`) you could install a version of a library (e.g. a sub-dependency) that is incompatible with your system. !!! tip Bonus points if you use `pipx` to install it while keeping an isolated environment for your Python CLI programs πŸš€ Now you have your CLI program installed. And you can use it freely:
```console $ rick-portal-gun shoot // It works πŸŽ‰ Shooting portal gun ```
Having it installed globally (and not in a single environment), you can now install completion globally for it:
```console $ rick-portal-gun --install-completion zsh completion installed in /home/user/.zshrc. Completion will take effect once you restart the terminal. ```
!!! tip If you want to remove completion you can just delete the added line in that file. And after you restart the terminal you will get completion for your new CLI program:
```console $ rick-portal-gun [TAB][TAB] // You get completion for your CLI program ✨ load -- Load the portal gun shoot -- Shoot the portal gun ```
## Support `python -m` (optional) You may have seen that you can call many Python modules as scripts with `python -m some-module`. For example, one way to call `pip` is:
```console $ pip install fastapi ```
But you can also call Python with the `-m` *CLI Option* and pass a module for it to execute as if it was a script, like:
```console $ python -m pip install fastapi ```
Here we pass `pip` as the value for `-m`, so, Python will execute the module `pip` as if it was a script. And then it will pass the rest of the *CLI Parameters* (`install fastapi`) to it. These two are more or less equivalent, the `install fastapi` will be passed to `pip`. !!! tip In the case of `pip`, in many occasions it's actually recommended that you run it with `python -m`, because if you create a virtual environment with its own `python`, that will ensure that you use the `pip` from *that* environment. ### Add a `__main__.py` You can support that same style of calling the package/module for your own package, simply by adding a file `__main__.py`. Python will look for that file and execute it. The file would live right beside `__init__.py`: ``` hl_lines="7" . β”œβ”€β”€ poetry.lock β”œβ”€β”€ pyproject.toml β”œβ”€β”€ README.rst β”œβ”€β”€ rick_portal_gun β”‚ β”œβ”€β”€ __init__.py β”‚ └── __main__.py └── tests β”œβ”€β”€ __init__.py └── test_rick_portal_gun.py ``` No other file has to import it, you don't have to reference it in your `pyproject.toml` or anything else, it just works by default, as it is standard Python behavior. Then in that file you can execute your **Typer** program: ```Python from .main import app app() ``` Now, after installing your package, if you call it with `python -m` it will work (for the main part):
```console $ python -m rick_portal_gun Usage: __main__.py [OPTIONS] COMMAND [ARGS]... Awesome Portal Gun Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: load Load the portal gun shoot Shoot the portal gun ```
!!! tip Notice that you have to pass the importable version of the package name, so `rick_portal_gun` instead of `rick-portal-gun`. That works! πŸš€ Sort of... πŸ€” See the `__main__.py` in the help instead of `rick-portal-gun`? We'll fix that next. ### Set a program name in `__main__.py` We are setting the program name in the file `pyproject.toml` in the line like: ```TOML [tool.poetry.scripts] rick-portal-gun = "rick_portal_gun.main:app" ``` But when Python runs our package as a script with `python -m`, it doesn't have the information of the program name. So, to fix the help text to use the correct program name when called with `python -m`, we can pass it to the app in `__main__.py`: ```Python from .main import app app(prog_name="rick-portal-gun") ``` !!! tip You can pass all the arguments and keyword arguments you could pass to a Click application, including `prog_name`.
```console $ python -m rick_portal_gun Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]... Awesome Portal Gun Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: load Load the portal gun shoot Shoot the portal gun ```
Great! That works correctly! πŸŽ‰ βœ… Notice that now it uses `rick-portal-gun` instead of `__main__.py` in the help. ### Autocompletion and `python -m` Have in mind that TAB completion (shell auto-completion) won't work when using `python -m`. Auto-completion depends on the name of the program called, it's tied to each specific program name. So, to have shell completion for `rick-portal-gun` you would have to call it directly:
```console $ rick-portal-gun [TAB][TAB] ```
But you can still support `python -m` for the cases where it's useful. ## Publish to PyPI (optional) You can publish that new package to PyPI to make it public, so others can install it easily. So, go ahead and create an account there (it's free). ### PyPI API token To do it, you first need to configure a PyPI auth token. Login to PyPI. And then go to https://pypi.org/manage/account/token/ to create a new token. Let's say your new API token is: ``` pypi-wubalubadubdub-deadbeef1234 ``` Now configure Poetry to use this token with the command `poetry config pypi-token.pypi`:
```console $ poetry config pypi-token.pypi pypi-wubalubadubdub-deadbeef1234 // It won't show any output, but it's already configured ```
### Publish to PyPI Now you can publish your package with Poetry. You could build the package (as we did above) and then publish later, or you could tell poetry to build it before publishing in one go:
```console $ poetry publish --build # There are 2 files ready for publishing. Build anyway? (yes/no) [no] $ yes ---> 100% Building rick-portal-gun (0.1.0) - Building sdist - Built rick-portal-gun-0.1.0.tar.gz - Building wheel - Built rick_portal_gun-0.1.0-py3-none-any.whl Publishing rick-portal-gun (0.1.0) to PyPI - Uploading rick-portal-gun-0.1.0.tar.gz 100% - Uploading rick_portal_gun-0.1.0-py3-none-any.whl 100% ```
Now you can go to PyPI and check your projects at https://pypi.org/manage/projects/. You should now see your new "rick-portal-gun" package. ### Install from PyPI Now to see that we can install it form PyPI, open another terminal, and uninstall the currently installed package.
```console $ pip uninstall rick-portal-gun Found existing installation: rick-portal-gun 0.1.0 Uninstalling rick-portal-gun-0.1.0: Would remove: /home/user/.local/bin/rick-portal-gun /home/user/.local/lib/python3.6/site-packages/rick_portal_gun-0.1.0.dist-info/* /home/user/.local/lib/python3.6/site-packages/rick_portal_gun/* # Proceed (y/n)? $ y Successfully uninstalled rick-portal-gun-0.1.0 ```
And now install it again, but this time using just the name, so that `pip` pulls it from PyPI:
```console $ pip install --user rick-portal-gun // Notice that it says "Downloading" πŸš€ Collecting rick-portal-gun Downloading rick_portal_gun-0.1.0-py3-none-any.whl (1.8 kB) Requirement already satisfied: typer[all]<0.0.12,>=0.0.11 in ./.local/lib/python3.6/site-packages (from rick-portal-gun) (0.0.11) Requirement already satisfied: click<7.2.0,>=7.1.1 in ./anaconda3/lib/python3.6/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (7.1.1) Requirement already satisfied: colorama; extra == "all" in ./anaconda3/lib/python3.6/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (0.4.3) Requirement already satisfied: shellingham; extra == "all" in ./anaconda3/lib/python3.6/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (1.3.1) Installing collected packages: rick-portal-gun Successfully installed rick-portal-gun-0.1.0 ```
And now test the newly installed package from PyPI:
```console $ rick-portal-gun load // It works! πŸŽ‰ Loading portal gun ```
## Generate docs with **Typer CLI** (optional) You can install and use [Typer CLI](../typer-cli.md){.internal-link target=_blank} to generate docs for your package. After installing it, you can use it to generate a new `README.md`:
```console $ typer rick_portal_gun.main utils docs --output README.md --name rick-portal-gun Docs saved to: README.md ```
You just have to pass it the module to import (`rick_portal_gun.main`) and it will detect the `typer.Typer` app automatically. By specifying the `--name` of the program it will be able to use it while generating the docs. ### Publish a new version with the docs Now you can publish a new version with the updated docs. For that you need to first increase the version in `pyproject.toml`: ```TOML hl_lines="3" [tool.poetry] name = "rick-portal-gun" version = "0.2.0" description = "" authors = ["Rick Sanchez "] readme = "README.md" [tool.poetry.scripts] rick-portal-gun = "rick_portal_gun.main:app" [tool.poetry.dependencies] python = "^3.6" typer = {extras = ["all"], version = "^0.1.0"} [tool.poetry.dev-dependencies] pytest = "^5.2" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" ``` And in the file `rick_portal_gun/__init__.py`: ```Python __version__ = '0.2.0' ``` And then build and publish again:
```console $ poetry publish --build ---> 100% Building rick-portal-gun (0.2.0) - Building sdist - Built rick-portal-gun-0.2.0.tar.gz - Building wheel - Built rick_portal_gun-0.2.0-py3-none-any.whl Publishing rick-portal-gun (0.2.0) to PyPI - Uploading rick-portal-gun-0.2.0.tar.gz 100% - Uploading rick_portal_gun-0.2.0-py3-none-any.whl 100% ```
And now you can go to PyPI, to the project page, and reload it, and it will now have your new generated docs. ## What's next This is a very simple guide. You could add many more steps. For example, you should use Git, the version control system, to save your code. You can add a lot of extra metadata to your `pyproject.toml`, check the docs for Poetry: Libraries. You could use `pipx` to manage your installed CLI Python programs in isolated environments. Maybe use automatic formatting with Black. You'll probably want to publish your code as open source to GitHub. And then you could integrate a CI tool to run your tests and deploy your package automatically. And there's a long etc. But now you have the basics and you can continue on your own πŸš€. typer-0.4.0/docs/tutorial/parameter-types/000077500000000000000000000000001411311634200205675ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/parameter-types/bool.md000066400000000000000000000077241411311634200220560ustar00rootroot00000000000000We have seen some examples of *CLI options* with `bool`, and how **Typer** creates `--something` and `--no-something` automatically. But we can customize those names. ## Only `--force` Let's say that we want a `--force` *CLI option* only, we want to discard `--no-force`. We can do that by specifying the exact name we want: ```Python hl_lines="4" {!../docs_src/parameter_types/bool/tutorial001.py!} ``` Now there's only a `--force` *CLI option*:
```console // Check the help $ python main.py --help // Notice there's only --force, we no longer have --no-force Usage: main.py [OPTIONS] Options: --force [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it: $ python main.py Not forcing // Now add --force $ python main.py --force Forcing operation // And --no-force no longer exists ⛔️ $ python main.py --no-force Usage: main.py [OPTIONS] Try "main.py --help" for help. Error: no such option: --no-force ```
## Alternative names Now let's imagine we have a *CLI option* `--accept`. And we want to allow setting `--accept` or the contrary, but `--no-accept` looks ugly. We might want to instead have `--accept` and `--reject`. We can do that by passing a single `str` with the 2 names for the `bool` *CLI option* separated by `/`: ```Python hl_lines="6" {!../docs_src/parameter_types/bool/tutorial002.py!} ``` Check it:
```console // Check the help $ python main.py --help // Notice the --accept / --reject Usage: main.py [OPTIONS] Options: --accept / --reject --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it $ python main.py I don't know what you want yet // Now pass --accept $ python main.py --accept Accepting! // And --reject $ python main.py --reject Rejecting! ```
## Short names The same way, you can declare short versions of the names for these *CLI options*. For example, let's say we want `-f` for `--force` and `-F` for `--no-force`: ```Python hl_lines="4" {!../docs_src/parameter_types/bool/tutorial003.py!} ``` Check it:
```console // Check the help $ python main.py --help // Notice the -f, --force / -F, --no-force Usage: main.py [OPTIONS] Options: -f, --force / -F, --no-force [default: False] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try with the short name -f $ python main.py -f Forcing operation // Try with the short name -F $ python main.py -F Not forcing ```
## Only names for `False` If you want to (although it might not be a good idea), you can declare only *CLI option* names to set the `False` value. To do that, use a space and a single `/` and pass the negative name after: ```Python hl_lines="4" {!../docs_src/parameter_types/bool/tutorial004.py!} ``` !!! tip Have in mind that it's a string with a preceding space and then a `/`. So, it's `" /-S"` not `"/-S"`. Check it:
```console // Check the help $ python main.py --help // Notice the / -d, --demo Usage: main.py [OPTIONS] Options: / -d, --demo [default: True] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it $ python main.py Running in production // Now pass --demo $ python main.py --demo Running demo // And the short version $ python main.py -d Running demo ```
typer-0.4.0/docs/tutorial/parameter-types/datetime.md000066400000000000000000000045571411311634200227200ustar00rootroot00000000000000You can specify a *CLI parameter* as a Python `datetime`. Your function will receive a standard Python `datetime` object, and again, your editor will give you completion, etc. ```Python hl_lines="1 6 7 8" {!../docs_src/parameter_types/datetime/tutorial001.py!} ``` Typer will accept any string from the following formats: * `%Y-%m-%d` * `%Y-%m-%dT%H:%M:%S` * `%Y-%m%d %H:%M:%S` Check it:
```console $ python main.py --help Usage: main.py [OPTIONS] BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S] Arguments: BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S][required] Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Pass a datetime $ python main.py 1956-01-31T10:00:00 Interesting day to be born: 1956-01-31 10:00:00 Birth hour: 10 // An invalid date $ python main.py july-19-1989 Usage: main.py [OPTIONS] [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d%H:%M:%S] Error: Invalid value for '[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': invalid datetime format: july-19-1989. (choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S) ```
## Custom date format You can also customize the formats received for the `datetime` with the `formats` parameter. `formats` receives a list of strings with the date formats that would be passed to datetime.strptime(). For example, let's imagine that you want to accept an ISO formatted datetime, but for some strange reason, you also want to accept a format with: * first the month * then the day * then the year * separated with "`/`" ...It's a crazy example, but let's say you also needed that strange format: ```Python hl_lines="8" {!../docs_src/parameter_types/datetime/tutorial002.py!} ``` !!! tip Notice the last string in `formats`: `"%m/%d/%Y"`. Check it:
```console // ISO dates work $ python main.py 1969-10-29 Launch will be at: 1969-10-29 00:00:00 // But the strange custom format also works $ python main.py 10/29/1969 Launch will be at: 1969-10-29 00:00:00 ```
typer-0.4.0/docs/tutorial/parameter-types/enum.md000066400000000000000000000033771411311634200220670ustar00rootroot00000000000000To define a *CLI parameter* that can take a value from a predefined set of values you can use a standard Python `enum.Enum`: ```Python hl_lines="1 6 7 8 9 12 13" {!../docs_src/parameter_types/enum/tutorial001.py!} ``` !!! tip Notice that the function parameter `network` will be an `Enum`, not a `str`. To get the `str` value in your function's code use `network.value`. Check it:
```console $ python main.py --help // Notice the predefined values [simple|conv|lstm] Usage: main.py [OPTIONS] Options: --network [simple|conv|lstm] [default: simple] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Try it $ python main.py --network conv Training neural network of type: conv // Invalid value $ python main.py --network capsule Usage: main.py [OPTIONS] Try "main.py --help" for help. Error: Invalid value for '--network': invalid choice: capsule. (choose from simple, conv, lstm) ```
### Case insensitive Enum choices You can make an `Enum` (choice) *CLI parameter* be case-insensitive with the `case_sensitive` parameter: ```Python hl_lines="13" {!../docs_src/parameter_types/enum/tutorial002.py!} ``` And then the values of the `Enum` will be checked no matter if lower case, upper case, or a mix:
```console // Notice the upper case CONV $ python main.py --network CONV Training neural network of type: conv // A mix also works $ python main.py --network LsTm Training neural network of type: lstm ```
typer-0.4.0/docs/tutorial/parameter-types/file.md000066400000000000000000000164531411311634200220410ustar00rootroot00000000000000Apart from `Path` *CLI parameters* you can also declare some types of "files". !!! tip In most of the cases you are probably fine just using `Path`. You can read and write data with `Path` the same way. The difference is that these types will give you a Python file-like object instead of a Python Path. A "file-like object" is the same type of object returned by `open()` as in: ```Python with open('file.txt') as f: # Here f is the file-like object read_data = f.read() print(read_data) ``` But in some special use cases you might want to use these special types. For example if you are migrating an existing application. ## `FileText` reading `typer.FileText` gives you a file-like object for reading text, you will get `str` data from it. This means that even if your file has text written in a non-english language, e.g. a `text.txt` file with: ``` la cigΓΌeΓ±a trae al niΓ±o ``` You will have a `str` with the text inside, e.g.: ```Python content = "la cigΓΌeΓ±a trae al niΓ±o" ``` instead of having `bytes`, e.g.: ```Python content = b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o" ``` You will get all the correct editor support, attributes, methods, etc for the file-like object: ```Python hl_lines="4" {!../docs_src/parameter_types/file/tutorial001.py!} ``` Check it:
```console // Create a quick text config $ echo "some settings" > config.txt // Add another line to the config to test it $ echo "some more settings" >> config.txt // Now run your program $ python main.py --config config.txt Config line: some settings Config line: some more settings ```
## `FileTextWrite` For writing text, you can use `typer.FileTextWrite`: ```Python hl_lines="4 5" {!../docs_src/parameter_types/file/tutorial002.py!} ``` This would be for writing human text, like: ``` some settings la cigΓΌeΓ±a trae al niΓ±o ``` ...not to write binary `bytes`. Check it:
```console $ python main.py --config text.txt Config written // Check the contents of the file $ cat text.txt Some config written by the app ```
!!! info "Technical Details" `typer.FileTextWrite` is a just a convenience class. It's the same as using `typer.FileText` and setting `mode="w"`. You will learn about `mode` later below. ## `FileBinaryRead` To read binary data you can use `typer.FileBinaryRead`. You will receive `bytes` from it. It's useful for reading binary files like images: ```Python hl_lines="4" {!../docs_src/parameter_types/file/tutorial003.py!} ``` Check it:
```console $ python main.py --file lena.jpg Processed bytes total: 512 Processed bytes total: 1024 Processed bytes total: 1536 Processed bytes total: 2048 ```
## `FileBinaryWrite` To write binary data you can use `typer.FileBinaryWrite`. You would write `bytes` to it. It's useful for writing binary files like images. Have in mind that you have to pass `bytes` to its `.write()` method, not `str`. If you have a `str`, you have to encode it first to get `bytes`. ```Python hl_lines="4" {!../docs_src/parameter_types/file/tutorial004.py!} ```
```console $ python main.py --file binary.dat Binary file written // Check the binary file was created $ ls ./binary.dat ./binary.dat ```
## File *CLI parameter* configurations You can use several configuration parameters for these types (classes) in `typer.Option()` and `typer.Argument()`: * `mode`: controls the "mode" to open the file with. * It's automatically set for you by using the classes above. * Read more about it below. * `encoding`: to force a specific encoding, e.g. `"utf-8"`. * `lazy`: delay I/O operations. Automatic by default. * By default, when writing files, Click will generate a file-like object that is not yet the actual file. Once you start writing, it will go, open the file and start writing to it, but not before. This is mainly useful to avoid creating the file until you start writing to it. It's normally safe to leave this automatic. But you can overwrite it setting `lazy=False`. By default, it's `lazy=True` for writing and `lazy=False` for reading. * `atomic`: if true, all writes will actually go to a temporal file and then moved to the final destination after completing. This is useful with files modified frequently by several users/programs. ## Advanced `mode` By default, **Typer** will configure the `mode` for you: * `typer.FileText`: `mode="r"`, to read text. * `typer.FileTextWrite`: `mode="w"`, to write text. * `typer.FileBinaryRead`: `mode="rb"`, to read binary data. * `typer.FileBinaryWrite`: `mode="wb"`, to write binary data. ### Note about `FileTextWrite` `typer.FileTextWrite` is actually just a convenience class. It's the same as using `typer.FileText` with `mode="w"`. But it's probably shorter and more intuitive as you can get it with autocompletion in your editor by just starting to type `typer.File`... just like the other classes. ### Customize `mode` You can override the `mode` from the defaults above. For example, you could use `mode="a"` to write "appending" to the same file: ```Python hl_lines="4" {!../docs_src/parameter_types/file/tutorial005.py!} ``` !!! tip As you are manually setting `mode="a"`, you can use `typer.FileText` or `typer.FileTextWrite`, both will work. Check it:
```console $ python main.py --config config.txt Config line written // Run your program a couple more times to see how it appends instead of overwriting $ python main.py --config config.txt Config line written $ python main.py --config config.txt Config line written // Check the contents of the file, it should have each of the 3 lines appended $ cat config.txt This is a single line This is a single line This is a single line ```
## About the different types !!! info These are technical details about why the different types/classes provided by **Typer**. But you don't need this information to be able to use them. You can skip it. **Typer** provides you these different types (classes) because they inherit directly from the actual Python implementation that will be provided underneath for each case. This way your editor will give you the right type checks and completion for each type. Even if you use `lazy`. When you use `lazy` Click creates a especial object to delay writes, and serves as a "proxy" to the actual file that will be written. But this especial proxy object doesn't expose the attributes and methods needed for type checks and completion in the editor. If you access those attributes or call the methods, the "proxy" lazy object will call them in the final object and it will all work. But you wouldn't get autocompletion for them. But because these **Typer** classes inherit from the actual implementation that will be provided underneath (not the lazy object), you will get all the autocompletion and type checks in the editor. typer-0.4.0/docs/tutorial/parameter-types/index.md000066400000000000000000000042171411311634200222240ustar00rootroot00000000000000You can use several data types for the *CLI options* and *CLI arguments*, and you can add data validation requirements too. ## Data conversion When you declare a *CLI parameter* with some type **Typer** will convert the data received in the command line to that data type. For example: ```Python hl_lines="4" {!../docs_src/parameter_types/index/tutorial001.py!} ``` In this example, the value received for the *CLI argument* `NAME` will be treated as `str`. The value for the *CLI option* `--age` will be converted to an `int` and `--height-meters` will be converted to a `float`. And as `female` is a `bool` *CLI option*, **Typer** will convert it to a "flag" `--female` and the counterpart `--no-female`. And here's how it looks like:
```console $ python main.py --help // Notice how --age is an INTEGER and --height-meters is a FLOAT Usage: main.py [OPTIONS] NAME Arguments: NAME [required] Options: --age INTEGER [default: 20] --height-meters FLOAT [default: 1.89] --female / --no-female [default: True] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Call it with CLI parameters $ python main.py Camila --age 15 --height-meters 1.70 --female // All the data has the correct Python type NAME is Camila, of type: class 'str' --age is 15, of type: class 'int' --height-meters is 1.7, of type: class 'float' --female is True, of type: class 'bool' // And if you pass an incorrect type $ python main.py Camila --age 15.3 Usage: main.py [OPTIONS] NAME Try "main.py --help" for help. Error: Invalid value for '--age': 15.3 is not a valid integer // Because 15.3 is not an INTEGER (it's a float) ```
## Watch next See more about specific types and validations in the next sections... !!! info "Technical Details" All the types you will see in the next sections are handled underneath by Click's Parameter Types. typer-0.4.0/docs/tutorial/parameter-types/number.md000066400000000000000000000062511411311634200224050ustar00rootroot00000000000000You can define numeric validations with `max` and `min` values for `int` and `float` *CLI parameters*: ```Python hl_lines="5 6 7" {!../docs_src/parameter_types/number/tutorial001.py!} ``` *CLI arguments* and *CLI options* can both use these validations. You can specify `min`, `max` or both. Check it:
```console $ python main.py --help // Notice the extra RANGE in the help text for --age and --score Usage: main.py [OPTIONS] ID Arguments: ID [required] Options: --age INTEGER RANGE [default: 20] --score FLOAT RANGE [default: 0] --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. // Pass all the CLI parameters $ python main.py 5 --age 20 --score 90 ID is 5 --age is 20 --score is 90.0 // Pass an invalid ID $ python main.py 1002 Usage: main.py [OPTIONS] ID Try "main.py --help" for help. Error: Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000. // Pass an invalid age $ python main.py 5 --age 15 Usage: main.py [OPTIONS] ID Try "main.py --help" for help. Error: Invalid value for '--age': 15 is smaller than the minimum valid value 18. // Pass an invalid score $ python main.py 5 --age 20 --score 100.5 Usage: main.py [OPTIONS] ID Try "main.py --help" for help. Error: Invalid value for '--score': 100.5 is bigger than the maximum valid value 100. // But as we didn't specify a minimum score, this is accepted $ python main.py 5 --age 20 --score -5 ID is 5 --age is 20 --score is -5.0 ```
## Clamping numbers You might want to, instead of showing an error, use the closest minimum or maximum valid values. You can do it with the `clamp` parameter: ```Python hl_lines="5 6 7" {!../docs_src/parameter_types/number/tutorial002.py!} ``` And then, when you pass data that is out of the valid range, it will be "clamped", the closest valid value will be used:
```console // ID doesn't have clamp, so it shows an error $ python main.py 1002 Usage: main.py [OPTIONS] ID Try "main.py --help" for help. Error: Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000. // But --rank and --score use clamp $ python main.py 5 --rank 11 --score -5 ID is 5 --rank is 10 --score is 0 ```
## Counter *CLI options* You can make a *CLI option* work as a counter with the `counter` parameter: ```Python hl_lines="4" {!../docs_src/parameter_types/number/tutorial003.py!} ``` It means that the *CLI option* will be like a boolean flag, e.g. `--verbose`. And the value you receive in the function will be the amount of times that `--verbose` was added:
```console // Check it $ python main.py Verbose level is 0 // Now use one --verbose $ python main.py --verbose Verbose level is 1 // Now 3 --verbose $ python main.py --verbose --verbose --verbose Verbose level is 3 // And with the short name $ python main.py -v Verbose level is 1 // And with the short name 3 times $ python main.py -v -v -v Verbose level is 3 // As short names can be put together, this also works $ python main.py -vvv Verbose level is 3 ```
typer-0.4.0/docs/tutorial/parameter-types/path.md000066400000000000000000000065041411311634200220520ustar00rootroot00000000000000You can declare a *CLI parameter* to be a standard Python `pathlib.Path`. This is what you would do for directory paths, file paths, etc: ```Python hl_lines="1 7" {!../docs_src/parameter_types/path/tutorial001.py!} ``` And again, as you receive a standard Python `Path` object the same as the type annotation, your editor will give you autocompletion for all its attributes and methods. Check it:
```console // No config $ python main.py No config file Aborted! // Pass a config that doesn't exist $ python main.py --config config.txt The config doesn't exist // Now create a quick config $ echo "some settings" > config.txt // And try again $ python main.py --config config.txt Config file contents: some settings // And with a directory $ python main.py --config ./ Config is a directory, will use all its config files ```
## Path validations You can perform several validations for `Path` *CLI parameters*: * `exists`: if set to true, the file or directory needs to exist for this value to be valid. If this is not required and a file does indeed not exist, then all further checks are silently skipped. * `file_okay`: controls if a file is a possible value. * `dir_okay`: controls if a directory is a possible value. * `writable`: if true, a writable check is performed. * `readable`: if true, a readable check is performed. * `resolve_path`: if this is true, then the path is fully resolved before the value is passed onwards. This means that it’s absolute and symlinks are resolved. !!! note "Technical Details" It will not expand a tilde-prefix (something with `~`, like `~/Documents/`), as this is supposed to be done by the shell only. !!! tip All these parameters come directly from Click. For example: ```Python hl_lines="9 10 11 12 13 14" {!../docs_src/parameter_types/path/tutorial002.py!} ``` Check it:
```console $ python main.py --config config.txt Usage: main.py [OPTIONS] Try "main.py --help" for help. Error: Invalid value for '--config': File 'config.txt' does not exist. // Now create a quick config $ echo "some settings" > config.txt // And try again $ python main.py --config config.txt Config file contents: some settings // And with a directory $ python main.py --config ./ Usage: main.py [OPTIONS] Try "main.py --help" for help. Error: Invalid value for '--config': File './' is a directory. ```
### Advanced `Path` configurations !!! warning "Advanced Details" You probably won't need these configurations at first, you may want to skip it. They are used for more advanced use cases. * `allow_dash`: If this is set to True, a single dash to indicate standard streams is permitted. * `path_type`: optionally a string type that should be used to represent the path. The default is None which means the return value will be either bytes or unicode depending on what makes most sense given the input data Click deals with. typer-0.4.0/docs/tutorial/parameter-types/uuid.md000066400000000000000000000033231411311634200220600ustar00rootroot00000000000000!!! info A UUID is a "Universally Unique Identifier". It's a standard format for identifiers, like passport numbers, but for anything, not just people in countries. They look like this: ``` d48edaa6-871a-4082-a196-4daab372d4a1 ``` The way they are generated makes them sufficiently long and random that you could assume that every UUID generated is unique. Even if it was generated by a different application, database, or system. So, if your system uses UUIDs to identify your data, you could mix it with the data from some other system that also uses UUIDs with some confidence that their IDs (UUIDs) won't clash with yours. This wouldn't be true if you just used `int`s as identifiers, as most databases do. You can declare a *CLI parameter* as a UUID: ```Python hl_lines="1 6 7 8" {!../docs_src/parameter_types/uuid/tutorial001.py!} ``` Your Python code will receive a standard Python `UUID` object with all its attributes and methods, and as you are annotating your function parameter with that type, you will have type checks, autocompletion in your editor, etc. Check it:
```console // Pass a valid UUID v4 $ python main.py d48edaa6-871a-4082-a196-4daab372d4a1 USER_ID is d48edaa6-871a-4082-a196-4daab372d4a1 UUID version is: 4 // An invalid value $ python main.py 7479706572-72756c6573 Usage: main.py [OPTIONS] USER_ID Try "main.py --help" for help. Error: Invalid value for 'USER_ID': 7479706572-72756c6573 is not a valid UUID value ```
typer-0.4.0/docs/tutorial/printing.md000066400000000000000000000121121411311634200176160ustar00rootroot00000000000000You can use `typer.echo()` to print to the screen: ```Python hl_lines="5" {!../docs_src/first_steps/tutorial001.py!} ``` The reason to use `typer.echo()` instead of just `print()` is that it applies some error corrections in case the terminal is misconfigured, and it will properly output color if it's supported. !!! info `typer.echo()` comes directly from Click, you can read more about it in Click's docs. Check it:
```console $ python main.py Hello World ```
## Color !!! info For colors to work correctly on Windows you need to also install `colorama`. You don't need to call `colorama.init()`. Typer (actually Click) will handle it underneath. And make sure you use `typer.echo()` instead of `print()`. !!! note "Technical Details" The way color works in terminals is by using some codes (ASCII codes) as part of the text. So, a colored text is still just a `str`. You can create colored strings to output to the terminal with `typer.style()`, that gives you `str`s that you can then pass to `typer.echo()`: ```Python hl_lines="7 9" {!../docs_src/printing/tutorial001.py!} ``` !!! tip The parameters `fg` and `bg` receive strings with the color names for the "**f**ore**g**round" and "**b**ack**g**round" colors. You could simply pass `fg="green"` and `bg="red"`. But **Typer** provides them all as variables like `typer.colors.GREEN` just so you can use autocompletion while selecting them. Check it:
python main.py everything is good python main.py --no-good everything is bad
You can pass these function arguments to `typer.style()`: * `fg`: the foreground color. * `bg`: the background color. * `bold`: enable or disable bold mode. * `dim`: enable or disable dim mode. This is badly supported. * `underline`: enable or disable underline. * `blink`: enable or disable blinking. * `reverse`: enable or disable inverse rendering (foreground becomes background and the other way round). * `reset`: by default a reset-all code is added at the end of the string which means that styles do not carry over. This can be disabled to compose styles. !!! info You can read more about it in Click's docs about `style()` ## `typer.secho()` - style and print There's a shorter form to style and print at the same time with `typer.secho()` it's like `typer.echo()` but also adds style like `typer.style()`: ```Python hl_lines="5" {!../docs_src/printing/tutorial002.py!} ``` Check it:
python main.py Camila Welcome here Camila
## "Standard Output" and "Standard Error" The way printing works underneath is that the **operating system** (Linux, Windows, macOS) treats what we print as if our CLI program was **writing text** to a "**virtual file**" called "**standard output**". When our code "prints" things it is actually "writing" to this "virtual file" of "standard output". This might seem strange, but that's how the CLI program and the operating system interact with each other. And then the operating system **shows on the screen** whatever our CLI program "**wrote**" to that "**virtual file**" called "**standard output**". ### Standard Error And there's another "**virtual file**" called "**standard error**" that is normally only used for errors. But we can also "print" to "standard error". And both are shown on the terminal to the users. !!! info If you use PowerShell it's quite possible that what you print to "standard error" won't be shown in the terminal. In PowerShell, to see "standard error" you would have to check the variable `$Error`. But it will work normally in Bash, Zsh, and Fish. ### Printing to "standard error" You can print to "standard error" with `typer.echo("some text", err=True)`. Using `err=True` tells **Typer** (actually Click) that the output should be shown in "standard error". ```Python hl_lines="5" {!../docs_src/printing/tutorial003.py!} ``` When you try it in the terminal, it will probably just look the same:
```console $ python main.py Here is something written to standard error ```
### "Standard Input" As a final detail, when you type text in your keyboard to your terminal, the operating system also considers it another "**virtual file**" that you are writing text to. This virtual file is called "**standard input**". ### What is this for Right now this probably seems quite useless πŸ€·β€β™‚. But understanding that will come handy in the future, for example for autocompletion and testing. typer-0.4.0/docs/tutorial/progressbar.md000066400000000000000000000066151411311634200203300ustar00rootroot00000000000000If you are executing an operation that can take some time, you can inform it to the user with a progress bar. For this, you can use `typer.progressbar()`: ```Python hl_lines="8" {!../docs_src/progressbar/tutorial001.py!} ``` You use `typer.progressbar()` with a `with` statement, as in: ```Python with typer.progressbar(something) as progress: pass ``` And you pass as function argument to `typer.progressbar()` the thing that you would normally iterate over. So, if you have a list of users, this could be: ```Python users = ["Camila", "Rick", "Morty"] with typer.progressbar(users) as progress: pass ``` And the `with` statement using `typer.progressbar()` gives you an object that you can iterate over, just like if it was the same thing that you would iterate over normally. But by iterating over this object **Typer** (actually Click) will know to update the progress bar: ```Python users = ["Camila", "Rick", "Morty"] with typer.progressbar(users) as progress: for user in progress: typer.echo(user) ``` !!! tip Notice that there are 2 levels of code blocks. One for the `with` statement and one for the `for` statement. !!! info This is mostly useful for operations that take some time. In the example above we are faking it with `time.sleep()`. Check it:
```console $ python main.py ---> 100% Processed 100 things. ```
## Setting a Progress Bar `length` The progress bar is generated from the length of the iterable (e.g. the list of users). But if the length is not available (for example, with something that fetches a new user from a web API each time) you can pass an explicit `length` to `typer.progressbar()`. ```Python hl_lines="14" {!../docs_src/progressbar/tutorial002.py!} ``` Check it:
```console $ python main.py ---> 100% Processed 100 user IDs. ```
### About the function with `yield` If you hadn't seen something like that `yield` above, that's a "generator". You can iterate over that function with a `for` and at each iteration it will give you the value at `yield`. `yield` is like a `return` that gives values multiple times and let's you use the function in a `for` loop. For example: ```Python def iterate_user_ids(): # Let's imagine this is a web API, not a range() for i in range(100): yield i for i in iterate_user_ids(): print(i) ``` would print each of the "user IDs" (here it's just the numbers from `0` to `99`). ## Add a `label` You can also set a `label`: ```Python hl_lines="8" {!../docs_src/progressbar/tutorial003.py!} ``` Check it:
python main.py Processed 100 things.
## Iterate manually If you need to manually iterate over something and update the progress bar irregularly, you can do it by not passing an iterable but just a `length` to `typer.progressbar()`. And then calling the `.update()` method in the object from the `with` statement: ```Python hl_lines="8 12" {!../docs_src/progressbar/tutorial004.py!} ``` Check it:
python main.py Processed 100 things in batches.
typer-0.4.0/docs/tutorial/prompt.md000066400000000000000000000027571411311634200173230ustar00rootroot00000000000000When you need to ask the user for info interactively you should normally use [*CLI Option*s with Prompt](options/prompt.md){.internal-link target=_blank}, because they allow using the CLI program in a non-interactive way (for example, a Bash script could use it). But if you absolutely need to ask for interactive information without using a *CLI option*, you can use `typer.prompt()`: ```Python hl_lines="5" {!../docs_src/prompt/tutorial001.py!} ``` Check it:
```console $ python main.py # What's your name?:$ Camila Hello Camila ```
## Confirm There's also an alternative to ask for confirmation. Again, if possible, you should use a [*CLI Option* with a confirmation prompt](options/prompt.md){.internal-link target=_blank}: ```Python hl_lines="5" {!../docs_src/prompt/tutorial002.py!} ``` Check it:
```console $ python main.py # Are you sure you want to delete it? [y/N]:$ y Deleting it! // This time cancel it $ python main.py # Are you sure you want to delete it? [y/N]:$ n Not deleting Aborted! ```
## Confirm or abort As it's very common to abort if the user doesn't confirm, there's an integrated parameter `abort` that does it automatically: ```Python hl_lines="5" {!../docs_src/prompt/tutorial003.py!} ```
```console $ python main.py # Are you sure you want to delete it? [y/N]:$ y Deleting it! // This time cancel it $ python main.py # Are you sure you want to delete it? [y/N]:$ n Aborted! ```
typer-0.4.0/docs/tutorial/subcommands/000077500000000000000000000000001411311634200177605ustar00rootroot00000000000000typer-0.4.0/docs/tutorial/subcommands/add-typer.md000066400000000000000000000073231411311634200222000ustar00rootroot00000000000000We'll start with the core idea. To add a `typer.Typer()` app inside of another. ## Manage items Let's imagine that you are creating a *CLI program* to manage items in some distant land. It could be in an `items.py` file with this: ```Python {!../docs_src/subcommands/tutorial001/items.py!} ``` And you would use it like:
```console $ python items.py create Wand Creating item: Wand ```
## Manage users But then you realize that you also have to manage users from your *CLI app*. It could be a file `users.py` with something like: ```Python {!../docs_src/subcommands/tutorial001/users.py!} ``` And you would use it like:
```console $ python users.py create Camila Creating user: Camila ```
## Put them together Both parts are similar. In fact, `items.py` and `users.py` both have commands `create` and `delete`. But we need them to be part of the same *CLI program*. In this case, as with `git remote`, we can put them together as subcommands in another `typer.Typer()` *CLI program*. Now create a `main.py` with: ```Python hl_lines="3 4 7 8" {!../docs_src/subcommands/tutorial001/main.py!} ``` Here's what we do in `main.py`: * Import the other Python modules (the files `users.py` and `items.py`). * Create the main `typer.Typer()` application. * Use `app.add_typer()` to include the `app` from `items.py` and `users.py`, each of those 2 was also created with `typer.Typer()`. * Define a `name` with the command that will be used for each of these "sub-Typers" to group their own commands. And now your *CLI program* has 2 commands: * `users`: with all of the commands (subcommands) in the `app` from `users.py`. * `items` with all the commands (subcommands) in the `app` from `items.py`. Check it:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: items users ```
Now you have a *CLI program* with commands `items` and `users`, and they in turn have their own commands (subcommands). Let's check the `items` command:
```console // Check the help for items $ python main.py items --help // It shows its own commands (subcommands): create, delete, sell Usage: main.py items [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: create delete sell // Try it $ python main.py items create Wand Creating item: Wand $ python main.py items sell Vase Selling item: Vase ```
!!! tip Notice that we are still calling `$ python main.py` but now we are using the command `items`. And now check the command `users`, with all its subcommands:
```console $ python main.py users --help Usage: main.py users [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: create delete // Try it $ python main.py users create Camila Creating user: Camila ```
## Recap That's the core idea. You can just create `typer.Typer()` apps and add them inside one another. And you can do that with any levels of commands that you want. Do you need sub-sub-sub-subcommands? Go ahead, create all the `typer.Typer()`s you need and put them together with `app.add_typer()`. In the next sections we'll update this with more features, but you already have the core idea. This way, in the same spirit of Click, **Typer** applications are composable, each `typer.Typer()` can be a *CLI app* by itself, but it can also be added as a command group to another Typer app. typer-0.4.0/docs/tutorial/subcommands/callback-override.md000066400000000000000000000056471411311634200236670ustar00rootroot00000000000000When creating a **Typer** app you can define a callback function, it always executes and defines the *CLI arguments* and *CLI options* that go before a command. When adding a Typer app inside of another, the sub-Typer can also have its own callback. It can handle any *CLI parameters* that go before its own commands and execute any extra code: ```Python hl_lines="9 10 11" {!../docs_src/subcommands/callback_override/tutorial001.py!} ``` In this case it doesn't define any *CLI parameters*, it just writes a message. Check it:
```console $ python main.py users create Camila // Notice the first message is not created by the command function but by the callback Running a users command Creating user: Camila ```
## Add a callback on creation It's also possible to add a callback when creating the `typer.Typer()` app that will be added to another Typer app: ```Python hl_lines="6 7 10" {!../docs_src/subcommands/callback_override/tutorial002.py!} ``` This achieves exactly the same as above, it's just another place to add the callback. Check it:
```console $ python main.py users create Camila Running a users command Creating user: Camila ```
## Overriding the callback on creation If a callback was added when creating the `typer.Typer()` app, it's possible to override it with a new one using `@app.callback()`. This is the same information you saw on the section about [Commands - Typer Callback](../commands/callback.md){.internal-link target=_blank}, and it applies the same for sub-Typer apps: ```Python hl_lines="6 7 10 14 15 16" {!../docs_src/subcommands/callback_override/tutorial003.py!} ``` Here we had defined a callback when creating the `typer.Typer()` sub-app, but then we override it with a new callback with the function `user_callback()`. As `@app.callback()` takes precedence over `typer.Typer(callback=some_function)`, now our CLI app will use this new callback. Check it:
```console $ python main.py users create Camila // Notice the message from the new callback Callback override, running users command Creating user: Camila ```
## Overriding the callback when adding a sub-Typer Lastly, you can override the callback defined anywhere else when adding a sub-Typer with `app.add_typer()` using the `callback` parameter. This has the highest priority: ```Python hl_lines="13 14 17" {!../docs_src/subcommands/callback_override/tutorial004.py!} ``` Notice that the precedence goes to `app.add_typer()` and is not affected by the order of execution. There's another callback defined below, but the one from `app.add_typer()` wins. Now when you use the CLI program it will use the new callback function `callback_for_add_typer()`. Check it:
```console $ python users create Camila // Notice the message from the callback added in add_typer() I have the high land! Running users command Creating user: Camila ```
typer-0.4.0/docs/tutorial/subcommands/index.md000066400000000000000000000022611411311634200214120ustar00rootroot00000000000000You read before how to create a program with [Commands](../commands/index.md){.internal-link target=_blank}. Now we'll see how to create a *CLI program* with commands that have their own subcommands. Also known as command groups. For example, the *CLI program* `git` has a command `remote`. But `git remote`, in turn, has its own subcommands, like `add`:
```console // git remote alone shows the current remote repositories $ git remote origin // Use -v to make it verbose and show more info $ git remote -v origin git@github.com:yourusername/typer.git (fetch) origin git@github.com:yourusername/typer.git (push) // git remote add takes 2 CLI arguments, a name and URL $ git remote add upstream https://github.com/tiangolo/typer.git // Doesn't output anything, but now you have another remote repository called upstream // Now check again $ git remote -v origin git@github.com:yourusername/typer.git (fetch) origin git@github.com:yourusername/typer.git (push) upstream https://github.com/tiangolo/typer.git (fetch) upstream https://github.com/tiangolo/typer.git (push) ```
In the next sections we'll see how to create subcommands like these. typer-0.4.0/docs/tutorial/subcommands/name-and-help.md000066400000000000000000000303451411311634200227150ustar00rootroot00000000000000When adding a Typer app to another we have seen how to set the `name` to use for the command. For example to set the command to `users`: ```Python app.add_typer(users.app, name="users") ``` ## Add a help text We can also set the `help` while adding a Typer: ```Python hl_lines="6" {!../docs_src/subcommands/name_help/tutorial001.py!} ``` And then we get that help text for that command in the *CLI program*:
```console // Check the main help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: users Manage users in the app. // Check the help for the users command $ python main.py users --help Usage: main.py users [OPTIONS] COMMAND [ARGS]... Manage users in the app. Options: --help Show this message and exit. Commands: create ```
We can set the `name` and `help` in several places, each one taking precedence over the other, overriding the previous value. Let's see those locations. !!! tip There are other attributes that can be set in that same way in the same places we'll see next. But those are documented later in another section. ## Inferring name and help from callback ### Inferring a command's name and help When you create a command with `@app.command()`, by default, it generates the name from the function name. And by default, the help text is extracted from the function's docstring. For example: ```Python @app.command() def create(item: str): """ Create an item. """ typer.echo(f"Creating item: {item}") ``` ...will create a command `create` with a help text of `Create an item`. ### Inferring name and help from `@app.callback()` The same way, if you define a callback in a `typer.Typer()`, the help text is extracted from the callback function's docstring. And if that Typer app is added to another Typer app, the default name of the command is generated from the name of the callback function. Here's an example: ```Python hl_lines="6 9 10 11 12 13" {!../docs_src/subcommands/name_help/tutorial002.py!} ``` Notice that now we added the sub-Typer without specifying a `name` nor a `help`. They are now inferred from the callback function. The command name will be the same callback function's name: `users`. And the help text for that `users` command will be the callback function's docstring: `Manage users in the app.`. Check it:
```console // Check the main help $ python main.py --help // Notice the command name "users" and the help text "Manage users in the app." Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: users Manage users in the app. // Check the help for the users command $ python main.py users --help // Notice the main description: "Manage users in the app." Usage: main.py users [OPTIONS] COMMAND [ARGS]... Manage users in the app. Options: --help Show this message and exit. Commands: create ```
### Name and help from callback parameter in `typer.Typer()` If you pass a `callback` parameter while creating a `typer.Typer(callback=some_function)` it will be used to infer the name and help text. This has the lowest priority, we'll see later what has a higher priority and can override it. Check the code: ```Python hl_lines="6 7 8 9 12" {!../docs_src/subcommands/name_help/tutorial003.py!} ``` This achieves exactly the same as the previous example. Check it:
```console // Check the main help $ python main.py --help // Notice the command name "users" and the help text "Manage users in the app." Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: users Manage users in the app. // Check the help for the users command $ python main.py users --help // Notice the main description: "Manage users in the app." Usage: main.py users [OPTIONS] COMMAND [ARGS]... Manage users in the app. Options: --help Show this message and exit. Commands: create ```
### Override a callback set in `typer.Typer()` with `@app.callback()` The same as with normal **Typer** apps, if you pass a `callback` to `typer.Typer(callback=some_function)` and then override it with `@app.callback()`, the name and help text will be inferred from the new callback: ```Python hl_lines="16 17 18 19 20" {!../docs_src/subcommands/name_help/tutorial004.py!} ``` Now the name of the command will be `users` instead of `old-callback`, and the help text will be `Manage users in the app.` instead of `Old callback help.`. Check it:
```console // Check the main help $ python main.py --help // Notice the command name "users" and the help text "Manage users in the app." Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: users Manage users in the app. // Check the help for the users command $ python main.py users --help // Notice the main description: "Manage users in the app." Usage: main.py users [OPTIONS] COMMAND [ARGS]... Manage users in the app. Options: --help Show this message and exit. Commands: create ```
### Infer name and help from callback in `app.add_typer()` If you override the callback in `app.add_typer()` when including a sub-app, the name and help will be inferred from this callback function. This takes precedence over inferring the name and help from a callback set in `@sub_app.callback()` and `typer.Typer(callback=sub_app_callback)`. Check the code: ```Python hl_lines="15 16 17 18 21" {!../docs_src/subcommands/name_help/tutorial005.py!} ``` Now the command will be `new-users` instead of `users`. And the help text will be `I have the highland! Create some users.` instead of the previous ones. Check it:
```console // Check the main help $ python main.py --help // Check the command new-users and its help text Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: new-users I have the highland! Create some users. // Now check the help for the new-users command $ python main.py new-users --help // Notice the help text Usage: main.py new-users [OPTIONS] COMMAND [ARGS]... I have the highland! Create some users. Options: --help Show this message and exit. Commands: create ```
### Enough inferring So, when inferring a name and help text, the precedence order from lowest priority to highest is: * `sub_app = typer.Typer(callback=some_function)` * `@sub_app.callback()` * `app.add_typer(sub_app, callback=new_function)` That's for inferring the name and help text from functions. But if you set the name and help text explicitly, that has a higher priority than these. ## Set the name and help Let's now see the places where you can set the command name and help text, from lowest priority to highest. !!! tip Setting the name and help text explicitly always has a higher precedence than inferring from a callback function. ### Name and help in `typer.Typer()` You could have all the callbacks and overrides we defined before, but the name and help text was inferred from the function name and docstring. If you set it explicitly, that takes precedence over inferring. You can set it when creating a new `typer.Typer()`: ```Python hl_lines="12" {!../docs_src/subcommands/name_help/tutorial006.py!} ``` !!! info The rest of the callbacks and overrides are there only to show you that they don't affect the name and help text when you set it explicitly. We set an explicit name `exp-users`, and an explicit help `Explicit help.`. So that will take precedence now. Check it:
```console // Check the main help $ python main.py --help // Notice the command name is exp-users and the help text is "Explicit help." Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: exp-users Explicit help. // Check the help for the exp-users command $ python main.py exp-users --help // Notice the main help text Usage: main.py exp-users [OPTIONS] COMMAND [ARGS]... Explicit help. Options: --help Show this message and exit. Commands: create ```
### Name and help in `@app.callback()` Any parameter that you use when creating a `typer.Typer()` app can be overridden in the parameters of `@app.callback()`. Continuing with the previous example, we now override the values in `@user_app.callback()`: ```Python hl_lines="24" {!../docs_src/subcommands/name_help/tutorial007.py!} ``` And now the command name will be `call-users` and the help text will be `Help from callback for users.`. Check it:
```console // Check the help $ python main.py --help // The command name now is call-users and the help text is "Help from callback for users.". Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: call-users Help from callback for users. // Check the call-users command help $ python main.py call-users --help // Notice the main help text Usage: main.py call-users [OPTIONS] COMMAND [ARGS]... Help from callback for users. Options: --help Show this message and exit. Commands: create ```
### Name and help in `app.add_typer()` And finally, with the highest priority, you can override all that by explicitly setting the `name` and `help` in `app.add_typer()`, just like we did on the first example above: ```Python hl_lines="21" {!../docs_src/subcommands/name_help/tutorial008.py!} ``` And now, with the highest priorities of them all, the command name will now be `cake-sith-users` and the help text will be `Unlimited powder! Eh, users.`. Check it:
```console // Check the help $ python main.py --help // Notice the command name cake-sith-users and the new help text "Unlimited powder! Eh, users." Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: cake-sith-users Unlimited powder! Eh, users. // And check the help for the command cake-sith-users $ python main.py cake-sith-users --help // Notice the main help text Usage: main.py cake-sith-users [OPTIONS] COMMAND [ARGS]... Unlimited powder! Eh, users. Options: --help Show this message and exit. Commands: create ```
## Recap The precedence to generate a command's name and help, from lowest priority to highest, is: * Implicitly inferred from `sub_app = typer.Typer(callback=some_function)` * Implicitly inferred from the callback function under `@sub_app.callback()` * Implicitly inferred from `app.add_typer(sub_app, callback=some_function)` * Explicitly set on `sub_app = typer.Typer(name="some-name", help="Some help.")` * Explicitly set on `@sub_app.callback("some-name", help="Some help.")` * Explicitly set on `app.add_typer(sub_app, name="some-name", help="Some help.")` So, `app.add_typer(sub_app, name="some-name", help="Some help.")` always wins. typer-0.4.0/docs/tutorial/subcommands/nested-subcommands.md000066400000000000000000000136751411311634200241110ustar00rootroot00000000000000We'll now see how these same ideas can be extended for deeply nested commands. Let's imagine that the same *CLI program* from the previous examples now needs to handle `lands`. But a land could be a `reign` or `town`. And each of those could have their own commands, like `create` and `delete`. ## A CLI app for reigns Let's start with a file `reigns.py`: ```Python {!../docs_src/subcommands/tutorial003/reigns.py!} ``` This is already a simple *CLI program* to manage reigns:
```console // Check the help $ python reigns.py --help Usage: reigns.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: conquer destroy // Try it $ python reigns.py conquer Cintra Conquering reign: Cintra $ python reigns.py destroy Mordor Destroying reign: Mordor ```
## A CLI app for towns And now the equivalent for managing towns in `towns.py`: ```Python {!../docs_src/subcommands/tutorial003/towns.py!} ``` With it, you can manage towns:
```console // Check the help $ python towns.py --help Usage: towns.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: burn found // Try it $ python towns.py found "New Asgard" Founding town: New Asgard $ python towns.py burn Vizima Burning town: Vizima ```
## Manage the land in a CLI app Now let's put the `reigns` and `towns` together in the same *CLI program* in `lands.py`: ```Python {!../docs_src/subcommands/tutorial003/lands.py!} ``` And now we have a single *CLI program* with a command (or command group) `reigns` that has its own commands. And another command `towns` with its own subcommands. Check it:
```console // Check the help $ python lands.py --help Usage: lands.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: reigns towns // We still have the help for reigns $ python lands.py reigns --help Usage: lands.py reigns [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: conquer destroy // And the help for towns $ python lands.py towns --help Usage: lands.py towns [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: burn found ```
Now try it, manage the lands through the CLI:
```console // Try the reigns command $ python lands.py reigns conquer Gondor Conquering reign: Gondor $ python lands.py reigns destroy Nilfgaard Destroying reign: Nilfgaard // Try the towns command $ python lands.py towns found Springfield Founding town: Springfield $ python lands.py towns burn Atlantis Burning town: Atlantis ```
## Deeply nested subcommands Now let's say that all these commands in the `lands.py` *CLI program* should be part of the previous *CLI program* we built in the first example. We want our *CLI program* to have these commands/command groups: * `users`: * `create` * `delete` * `items`: * `create` * `delete` * `sell` * `lands`: * `reigns`: * `conquer` * `destroy` * `towns`: * `found` * `burn` This already is a quite deeply nested "tree" of commands/command groups. But to achieve that, we just have to add the `lands` **Typer** app to the same `main.py` file we already had: ```Python hl_lines="4 10" {!../docs_src/subcommands/tutorial003/main.py!} ``` And now we have everything in a single *CLI program*:
```console // Check the main help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: items lands users // Try some users commands $ python main.py users create Camila Creating user: Camila // Now try some items commands $ python main.py items create Sword Creating item: Sword // And now some lands commands for reigns $ python main.py lands reigns conquer Gondor Conquering reign: Gondor // And for towns $ python main.py lands towns found Cartagena Founding town: Cartagena ```
## Review the files Here are all the files if you want to review/copy them: `reigns.py`: ```Python {!../docs_src/subcommands/tutorial003/reigns.py!} ``` `towns.py`: ```Python {!../docs_src/subcommands/tutorial003/towns.py!} ``` `lands.py`: ```Python {!../docs_src/subcommands/tutorial003/lands.py!} ``` `users.py`: ```Python {!../docs_src/subcommands/tutorial003/users.py!} ``` `items.py`: ```Python {!../docs_src/subcommands/tutorial003/items.py!} ``` `main.py`: ```Python {!../docs_src/subcommands/tutorial003/main.py!} ``` !!! tip All these files have an `if __name__ == "__main__"` block just to demonstrate how each of them can also be an independent *CLI app*. But for your final application, only `main.py` would need it. ## Recap That's it, you can just add **Typer** applications one inside another as much as you want and create complex *CLI programs* while writing simple code. You can probably achieve a simpler *CLI program* design that's easier to use than the example here. But if your requirements are complex, **Typer** helps you build your *CLI app* easily. !!! tip Auto completion helps a lot, specially with complex programs. Check the docs about adding auto completion to your *CLI apps*. typer-0.4.0/docs/tutorial/subcommands/single-file.md000066400000000000000000000052511411311634200225030ustar00rootroot00000000000000In some cases, it's possible that your application code needs to live on a single file. You can still use the same ideas: ```Python {!../docs_src/subcommands/tutorial002/main.py!} ``` There are several things to notice here... ## Apps at the top First, you can create `typer.Typer()` objects and add them to another one at the top. It doesn't have to be done after creating the subcommands: ```Python hl_lines="4 5 6 7" {!../docs_src/subcommands/tutorial002/main.py!} ``` You can add the commands (subcommands) to each `typer.Typer()` app later and it will still work. ## Function names As you now have subcommands like `create` for `users` and for `items`, you can no longer call the functions with just the name, like `def create()`, because they would overwrite each other. So we use longer names: ```Python hl_lines="11 16 21 26 31" {!../docs_src/subcommands/tutorial002/main.py!} ``` ## Command name We are naming the functions with longer names so that they don't overwrite each other. But we still want the subcommands to be `create`, `delete`, etc. To call them like:
```console // We want this βœ”οΈ $ python main.py items create ```
instead of:
```console // We don't want this ⛔️ $ python main.py items items-create ```
So we pass the name we want to use for each subcommand as the function argument to the decorator: ```Python hl_lines="10 15 20 25 30" {!../docs_src/subcommands/tutorial002/main.py!} ``` ## Check it It still works the same:
```console // Check the help $ python main.py --help Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --install-completion Install completion for the current shell. --show-completion Show completion for the current shell, to copy it or customize the installation. --help Show this message and exit. Commands: items users ```
Check the `items` command:
```console // Check the help for items $ python main.py items --help // It shows its own commands (subcommands): create, delete, sell Usage: main.py items [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: create delete sell // Try it $ python main.py items create Wand Creating item: Wand $ python main.py items sell Vase Selling item: Vase ```
And the same for the `users` command:
```console $ python main.py users --help Usage: main.py users [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: create delete // Try it $ python main.py users create Camila Creating user: Camila ```
typer-0.4.0/docs/tutorial/terminating.md000066400000000000000000000057101411311634200203130ustar00rootroot00000000000000There are some cases where you might want to terminate a command at some point, and stop all subsequent execution. It could be that your code determined that the program completed successfully, or it could be an operation aborted. ## `Exit` a CLI program You can normally just let the code of your CLI program finish its execution, but in some scenarios, you might want to terminate at some point in the middle of it. And prevent any subsequent code to run. This doesn't have to mean that there's an error, just that nothing else needs to be executed. In that case, you can raise a `typer.Exit()` exception: ```Python hl_lines="9" {!../docs_src/terminating/tutorial001.py!} ``` There are several things to see in this example. * The CLI program is the function `main()`, not the others. This is the one that takes a *CLI argument*. * The function `maybe_create_user()` can terminate the program by raising `typer.Exit()`. * If the program is terminated by `maybe_create_user()` then `send_new_user_notification()` will never execute inside of `main()`. Check it:
```console $ python main.py Camila User created: Camila Notification sent for new user: Camila // Try with an existing user $ python main.py rick The user already exists // Notice that the notification code was never run, the second message is not printed ```
!!! tip Even though you are rasing an exception, it doesn't necessarily mean there's an error. This is done with an exception because it works as an "error" and stops all execution. But then **Typer** (actually Click) catches it and just terminates the program normally. ## Exit with an error `typer.Exit()` takes an optional `code` parameter. By default, `code` is `0`, meaning there was no error. You can pass a `code` with a number other than `0` to tell the terminal that there was an error in the execution of the program: ```Python hl_lines="7" {!../docs_src/terminating/tutorial002.py!} ``` Check it:
```console $ python main.py Camila New user created: Camila // Print the result code of the last program executed $ echo $? 0 // Now make it exit with an error $ python main.py root The root user is reserved // Print the result code of the last program executed $ echo $? 1 // 1 means there was an error, 0 means no errors. ```
!!! tip The error code might be used by other programs (for example a Bash script) that execute your CLI program. ## Abort There's a special exception that you can use to "abort" a program. It works more or less the same as `typer.Exit()` but will print `"Aborted!"` to the screen and can be useful in certain cases later to make it explicit that the execution was aborted: ```Python hl_lines="7" {!../docs_src/terminating/tutorial003.py!} ``` Check it:
```console $ python main.py Camila New user created: Camila // Now make it exit with an error $ python main.py root The root user is reserved Aborted! ```
typer-0.4.0/docs/tutorial/testing.md000066400000000000000000000136201411311634200174460ustar00rootroot00000000000000Testing **Typer** applications is very easy with pytest. Let's say you have an application `app/main.py` with: ```Python {!../docs_src/testing/app01/main.py!} ``` So, you would use it like:
```console $ python main.py Camila --city Berlin Hello Camila Let's have a coffee in Berlin ```
And the directory also has an empty `app/__init__.py` file. So, the `app` is a "Python package". ## Test the app ### Import and create a `CliRunner` Create another file/module `app/test_main.py`. Import `CliRunner` and create a `runner` object. This runner is what will "invoke" or "call" your command line application. ```Python hl_lines="1 5" {!../docs_src/testing/app01/test_main.py!} ``` !!! tip It's important that the name of the file starts with `test_`, that way pytest will be able to detect it and use it automatically. ### Call the app Then create a function `test_app()`. And inside of the function, use the `runner` to `invoke` the application. The first parameter to `runner.invoke()` is a `Typer` app. The second parameter is a `list` of `str`, with all the text you would pass in the command line, right as you would pass it: ```Python hl_lines="8 9" {!../docs_src/testing/app01/test_main.py!} ``` !!! tip The name of the function has to start with `test_`, that way pytest can detect it and use it automatically. ### Check the result Then, inside of the test function, add `assert` statements to ensure that everything in the result of the call is as it should be. ```Python hl_lines="10 11 12" {!../docs_src/testing/app01/test_main.py!} ``` Here we are checking that the exit code is 0, as it is for programs that exit without errors. Then we check that the text printed to "standard output" contains the text that our CLI program prints. !!! tip You could also check `result.stderr` for "standard error". !!! info If you need a refresher about what is "standard output" and "standard error" check the section in [Printing and Colors: "Standard Output" and "Standard Error"](printing.md#standard-output-and-standard-error){.internal-link target=_blank}. ### Call `pytest` Then you can call `pytest` in your directory and it will run your tests:
```console $ pytest ================ test session starts ================ platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 rootdir: /home/user/code/superawesome-cli/app plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 collected 1 item ---> 100% test_main.py . [100%] ================= 1 passed in 0.03s ================= ```
## Testing input If you have a CLI with prompts, like: ```Python hl_lines="7" {!../docs_src/testing/app02/main.py!} ``` That you would use like:
```console $ python main.py Camila # Email: $ camila@example.com Hello Camila, your email is: camila@example.com ```
You can test the input typed in the terminal using `input="camila@example.com\n"`. This is because what you type in the terminal goes to "**standard input**" and is handled by the operating system as if it was a "virtual file". !!! info If you need a refresher about what is "standard output", "standard error", and "standard input" check the section in [Printing and Colors: "Standard Output" and "Standard Error"](printing.md#standard-output-and-standard-error){.internal-link target=_blank}. When you hit the ENTER key after typing the email, that is just a "new line character". And in Python that is represented with `"\n"`. So, if you use `input="camila@example.com\n"` it means: "type `camila@example.com` in the terminal, then hit the ENTER key": ```Python hl_lines="9" {!../docs_src/testing/app02/test_main.py!} ``` ## Test a function If you have a script and you never created an explicit `typer.Typer` app, like: ```Python hl_lines="9" {!../docs_src/testing/app03/main.py!} ``` ...you can still test it, by creating an app during testing: ```Python hl_lines="6 7 13" {!../docs_src/testing/app03/test_main.py!} ``` Of course, if you are testing that script, it's probably easier/cleaner to just create the explicit `typer.Typer` app in `main.py` instead of creating it just during the test. But if you want to keep it that way, e.g. because it's a simple example in documentation, then you can use that trick. ### About the `app.command` decorator Notice the `app.command()(main)`. If it's not obvious what it's doing, continue reading... You would normally write something like: ```Python @app.command() def main(name: str = "World"): # Some code here ``` But `@app.command()` is just a decorator. That's equivalent to: ```Python def main(name: str = "World"): # Some code here decorator = app.command() new_main = decorator(main) main = new_main ``` `app.command()` returns a function (`decorator`) that takes another function as it's only parameter (`main`). And by using the `@something` you normally tell Python to replace the thing below (the function `main`) with the return of the `decorator` function (`new_main`). Now, in the specific case of **Typer**, the decorator doesn't change the original function. It registers it internally and returns it unmodified. So, `new_main` is actually the same original `main`. So, in the case of **Typer**, as it doesn't really modify the decorated function, that would be equivalent to: ```Python def main(name: str = "World"): # Some code here decorator = app.command() decorator(main) ``` But then we don't need to create the variable `decorator` to use it below, we can just use it directly: ```Python def main(name: str = "World"): # Some code here app.command()(main) ``` ...that's it. It's still probably simpler to just create the explicit `typer.Typer` in the `main.py` file πŸ˜…. typer-0.4.0/docs/tutorial/using-click.md000066400000000000000000000141251411311634200202020ustar00rootroot00000000000000!!! warning This is a more advanced topic, if you are starting with **Typer**, feel free to skip it. It will be mostly useful for people that already work with Click and have questions around it. **Typer** is powered by Click. It does all the work underneath. Here is some more information related to using both together. ## A single app with both Click and **Typer** If you already have a Click application and want to migrate to **Typer**, or to add some Typer components, you can get a Click `Command` from your Typer application and then use Click directly. ### How Click works Before knowing how to combine Click and **Typer**, let's first check a little about how Click works. #### Click `Command` Any Click application has an object of class `Command`. That's, more or less, the most basic Click object. A `Command` can have its own *CLI arguments* and *CLI options*, and it has a function that it calls. For example, in this Click app: ```Python hl_lines="7 14" {!../docs_src/using_click/tutorial001.py!} ``` The original `hello` variable is converted by Click from a function to a `Command` object. And the original `hello` function is used by that `Command` internally, but it is no longer named `hello` (as `hello` is now a Click `Command`). #### Click `Group` Then Click also has a `Group` class, it **inherits from `Command`**. So, a `Group` object is *also* a `Command`. A `Group` can also have its own *CLI arguments* and *CLI options*. A `Group` can have subcommands of class `Command` or sub groups of class `Group` as well. And a `Group` can also have a function that it calls, right before calling the function for any specific subcommand. For example: ```Python hl_lines="5 19 20" {!../docs_src/using_click/tutorial002.py!} ``` The `cli` variable is converted by Click from a function to a `Group` object. And the original `cli` function is used by that `Group` internally. !!! tip The original `cli` function would be the equivalent of a [Typer Callback](./commands/callback.md){.internal-link target=_blank}. Then the `cli` variable, that now is a `Group` object, is used to add sub-commands. ### How **Typer** works Typer doesn't modify the functions. You create an explicit variable of class `typer.Typer` and use it to *register* those functions. And then, when you call the app, Typer goes and creates a Click `Command` (or `Group`), and then calls it. If your app only has one command, then when you call it, **Typer** creates a single Click `Command` object and calls it. But **Typer** creates a Click `Group` object if your app has any of: * More than one command. * A callback. * Sub-Typer apps (sub commands). !!! tip If you want to learn more about this check the section [One or Multiple Commands](./commands/one-or-multiple.md){.internal-link target=_blank}. ### Combine Click and **Typer** **Typer** uses an internal function `typer.main.get_command()` to generate a Click `Command` (or `Group`) from a `typer.Typer` object. You can use it directly, and use the Click object with other Click applications. ### Including a Click app in a **Typer** app For example, you could have a **Typer** app, generate a Click `Group` from it, and then include other Click apps in it: ```Python hl_lines="15 16 29 31 34" {!../docs_src/using_click/tutorial003.py!} ``` Notice that we add a callback that does nothing (only document the CLI program), to make sure **Typer** creates a Click `Group`. That way we can add sub-commands to that Click `Group`. Then we generate a Click object from our `typer.Typer` app (`typer_click_object`), and then we can include another Click object (`hello`) in this Click `Group`. And that way, our **Typer** app will have a subcommand `top` built with Typer, and a subcommand `hello` built with Click. Check it:
```console $ python main.py // Notice we have both subcommands, top and hello Usage: main.py [OPTIONS] COMMAND [ARGS]... Error: Missing command. // Call the Typer part $ python main.py top The Typer app is at the top level // Call the Click part $ python main.py hello --name Camila Hello Camila! ```
### Including a **Typer** app in a Click app The same way, you can do the contrary and include a **Typer** sub app in a bigger Click app: ```Python hl_lines="31 33 36" {!../docs_src/using_click/tutorial004.py!} ``` Notice that we don't have to add a callback or more commands, we can just create a **Typer** app that generates a single Click `Command`, as we don't need to include anything under the Typer app. Then we generate a Click object from our `typer.Typer` app (`typer_click_object`), and then we use **the Click `cli` to include** our Click object from our Typer app. In this case, the original Click app includes the **Typer** app. And then we call the *original Click* app, not the Typer app. Check it:
```console $ python main.py // We get our Typer app down there in the sub command Usage: main.py [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: dropdb initdb sub A single-command Typer sub app // Use the Click part $ python main.py initdb Initialized the database // And use the Typer part $ python main.py sub Typer is now below Click, the Click app is the top level ```
## About Click decorators Typer apps don't work with Click decorators directly. This is because **Typer** doesn't modify functions to add metadata or to convert them to another object like Click does. So, things like `@click.pass_context` won't work. Most of the functionality provided by decorators in Click has an alternative way of doing it in **Typer**. For example, to access the context, you can just declare a function parameter of type `typer.Context`. !!! tip You can read more about using the context in the docs: [Commands: Using the Context](commands/context.md){.internal-link target=_blank} But if you need to use something based on Click decorators, you can always generate a Click object using the methods described above, and use it as you would normally use Click. typer-0.4.0/docs/typer-cli.md000066400000000000000000000227471411311634200160500ustar00rootroot00000000000000# Typer CLI

Run Typer scripts with completion, without having to create a package, using Typer CLI.

Build Status Coverage Package version

There is an optional utility tool called **Typer CLI**, additional to **Typer** itself. It's main feature is to provide ✨ completion ✨ in the Terminal for your own small programs built with **Typer**. ...without you having to create a complete installable Python package. It's probably most useful if you have a small custom Python script using **Typer** (maybe as part of some project), for some small tasks, and it's not complex/important enough to create a whole installable Python package for it (something to be installed with `pip`). In that case, you can install **Typer CLI**, and run your program with the `typer` command in your Terminal, and it will provide completion for your script. You can also use **Typer CLI** to generate Markdown documentation for your own **Typer** programs πŸ“. --- **Documentation**: https://typer.tiangolo.com/typer-cli/ **Source Code for Typer CLI**: https://github.com/tiangolo/typer-cli --- ## **Typer** or **Typer CLI** **Typer** is a library for building CLIs (Command Line Interface applications). You use **Typer** in your Python scripts. Like in: ```Python import typer def main(): typer.echo("Hello World") if __name__ == "__main__": typer.run(main) ``` **Typer CLI** is a command line application to run simple programs created with **Typer**, with completion in your terminal πŸš€. You use **Typer CLI** in your terminal, to run your scripts (as an alternative to calling `python` directly). Like in:
```console $ typer my_script.py run Hello World ```
But you never import anything from **Typer CLI** in your own scripts. ## Usage ### Install Install **Typer CLI**:
```console $ python -m pip install typer-cli ---> 100% Successfully installed typer-cli ```
That creates a `typer` command you can call in your terminal, much like `python`, `git`, or `echo`. You can then install completion for it:
```console $ typer --install-completion zsh completion installed in /home/user/.bashrc. Completion will take effect once you restart the terminal. ```
### Sample script Let's say you have a script that uses **Typer** in `my_custom_script.py`: ```Python from typing import Optional import typer app = typer.Typer() @app.command() def hello(name: Optional[str] = None): if name: typer.echo(f"Hello {name}") else: typer.echo("Hello World!") @app.command() def bye(name: Optional[str] = None): if name: typer.echo(f"Bye {name}") else: typer.echo("Goodbye!") if __name__ == "__main__": app() ``` For it to work, you would also install **Typer**:
```console $ python -m pip install typer ---> 100% Successfully installed typer ```
### Run with Python Then you could run your script with normal Python:
```console $ python my_custom_script.py hello Hello World! $ python my_custom_script.py hello --name Camila Hello Camila! $ python my_custom_script.py bye --name Camila Bye Camila ```
There's nothing wrong with using Python directly to run it. And, in fact, if some other code or program uses your script, that would probably be the best way to do it. ⛔️ But in your terminal, you won't get completion when hitting TAB for any of the subcommands or options, like `hello`, `bye`, and `--name`. ### Run with **Typer CLI** Here's where **Typer CLI** is useful. You can also run the same script with the `typer` command you get after installing `typer-cli`:
```console $ typer my_custom_script.py run hello Hello World! $ typer my_custom_script.py run hello --name Camila Hello Camila! $ typer my_custom_script.py run bye --name Camila Bye Camila ```
* Instead of using `python` directly you use the `typer` command. * After the name of the file, add the subcommand `run`. βœ”οΈ If you installed completion for **Typer CLI** (for the `typer` command) as described above, when you hit TAB you will have ✨ completion for everything ✨, including all the subcommands and options of your script, like `hello`, `bye`, and `--name` πŸš€. ## If main Because **Typer CLI** won't use the block with: ```Python if __name__ == "__main__": app() ``` ...you can also remove it if you are calling that script only with **Typer CLI** (using the `typer` command). ## Run other files **Typer CLI** can run any script with **Typer**, but the script doesn't even have to use **Typer** at all. **Typer CLI** could even run a file with a function that could be used with `typer.run()`, even if the script doesn't use `typer.run()` or anything else. For example, a file `main.py` like this will still work: ```Python def main(name: str = "World"): """ Say hi to someone, by default to the World. """ print(f"Hello {name}") ``` Then you can call it with:
```console $ typer main.py run --help Usage: typer run [OPTIONS] Say hi to someone, by default to the World. Options: --name TEXT --help Show this message and exit. $ typer main.py run --name Camila Hello Camila ```
And it will also have completion for things like the `--name` *CLI Option*. ## Run a package or module Instead of a file path you can pass a module (possibly in a package) to import. For example:
```console $ typer my_package.main run --help Usage: typer run [OPTIONS] Options: --name TEXT --help Show this message and exit. $ typer my_package.main run --name Camila Hello Camila ```
## Options You can specify one of the following **CLI options**: * `--app`: the name of the variable with a `Typer()` object to run as the main app. * `--func`: the name of the variable with a function that would be used with `typer.run()`. ### Defaults When your run a script with the **Typer CLI** (the `typer` command) it will use the app from the following priority: * An app object from the `--app` *CLI Option*. * A function to convert to a **Typer** app from `--func` *CLI Option* (like when using `typer.run()`). * A **Typer** app in a variable with a name of `app`, `cli`, or `main`. * The first **Typer** app available in the file, with any name. * A function in a variable with a name of `main`, `cli`, or `app`. * The first function in the file, with any name. ## Generate docs **Typer CLI** can also generate Markdown documentation for your **Typer** application. ### Sample script with docs For example, you could have a script like: ```Python {!../docs_src/commands/help/tutorial001.py!} ``` ### Generate docs with Typer CLI Then you could generate docs for it with **Typer CLI**. You can use the subcommand `utils`. And then the subcommand `docs`.
```console $ typer some_script.py utils docs ```
**Options**: * `--name TEXT`: The name of the CLI program to use in docs. * `--output FILE`: An output file to write docs to, like README.md. For example:
```console $ typer my_package.main utils docs --name awesome-cli --output README.md Docs saved to: README.md ```
### Sample docs output For example, for the previous script, the generated docs would look like: --- ## `awesome-cli` Awesome CLI user manager. **Usage**: ```console $ awesome-cli [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--install-completion`: Install completion for the current shell. * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. * `--help`: Show this message and exit. **Commands**: * `create`: Create a new user with USERNAME. * `delete`: Delete a user with USERNAME. * `delete-all`: Delete ALL users in the database. * `init`: Initialize the users database. ## `awesome-cli create` Create a new user with USERNAME. **Usage**: ```console $ awesome-cli create [OPTIONS] USERNAME ``` **Options**: * `--help`: Show this message and exit. ## `awesome-cli delete` Delete a user with USERNAME. If --force is not used, will ask for confirmation. **Usage**: ```console $ awesome-cli delete [OPTIONS] USERNAME ``` **Options**: * `--force / --no-force`: Force deletion without confirmation. [required] * `--help`: Show this message and exit. ## `awesome-cli delete-all` Delete ALL users in the database. If --force is not used, will ask for confirmation. **Usage**: ```console $ awesome-cli delete-all [OPTIONS] ``` **Options**: * `--force / --no-force`: Force deletion without confirmation. [required] * `--help`: Show this message and exit. ## `awesome-cli init` Initialize the users database. **Usage**: ```console $ awesome-cli init [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. --- ## License **Typer CLI**, the same as **Typer**, is licensed under the terms of the MIT license. typer-0.4.0/docs_src/000077500000000000000000000000001411311634200144515ustar00rootroot00000000000000typer-0.4.0/docs_src/app_dir/000077500000000000000000000000001411311634200160675ustar00rootroot00000000000000typer-0.4.0/docs_src/app_dir/tutorial001.py000066400000000000000000000004731411311634200205310ustar00rootroot00000000000000from pathlib import Path import typer APP_NAME = "my-super-cli-app" def main(): app_dir = typer.get_app_dir(APP_NAME) config_path: Path = Path(app_dir) / "config.json" if not config_path.is_file(): typer.echo("Config file doesn't exist yet") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/000077500000000000000000000000001411311634200164565ustar00rootroot00000000000000typer-0.4.0/docs_src/arguments/default/000077500000000000000000000000001411311634200201025ustar00rootroot00000000000000typer-0.4.0/docs_src/arguments/default/tutorial001.py000066400000000000000000000002251411311634200225370ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("Wade Wilson")): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/default/tutorial002.py000066400000000000000000000003611411311634200225410ustar00rootroot00000000000000import random import typer def get_name(): return random.choice(["Deadpool", "Rick", "Morty", "Hiro"]) def main(name: str = typer.Argument(get_name)): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/envvar/000077500000000000000000000000001411311634200177575ustar00rootroot00000000000000typer-0.4.0/docs_src/arguments/envvar/tutorial001.py000066400000000000000000000002521411311634200224140ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar="AWESOME_NAME")): typer.echo(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/envvar/tutorial002.py000066400000000000000000000002701411311634200224150ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar=["AWESOME_NAME", "GOD_NAME"])): typer.echo(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/envvar/tutorial003.py000066400000000000000000000002751411311634200224230ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar="AWESOME_NAME", show_envvar=False)): typer.echo(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/000077500000000000000000000000001411311634200174065ustar00rootroot00000000000000typer-0.4.0/docs_src/arguments/help/tutorial001.py000066400000000000000000000002611411311634200220430ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(..., help="The name of the user to greet")): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/tutorial002.py000066400000000000000000000003541411311634200220470ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(..., help="The name of the user to greet")): """ Say hi to NAME very gently, like Dirk. """ typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/tutorial003.py000066400000000000000000000003371411311634200220510ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", help="Who to greet")): """ Say hi to NAME very gently, like Dirk. """ typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/tutorial004.py000066400000000000000000000003631411311634200220510ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", help="Who to greet", show_default=False)): """ Say hi to NAME very gently, like Dirk. """ typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/tutorial005.py000066400000000000000000000003541411311634200220520ustar00rootroot00000000000000import typer def main( name: str = typer.Argument( "Wade Wilson", help="Who to greet", show_default="Deadpoolio the amazing's name" ) ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/tutorial006.py000066400000000000000000000002511411311634200220470ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", metavar="✨username✨")): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/help/tutorial007.py000066400000000000000000000003271411311634200220540ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", hidden=True)): """ Say hi to NAME very gently, like Dirk. """ typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/optional/000077500000000000000000000000001411311634200203035ustar00rootroot00000000000000typer-0.4.0/docs_src/arguments/optional/tutorial001.py000066400000000000000000000002131411311634200227350ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(...)): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/arguments/optional/tutorial002.py000066400000000000000000000003711411311634200227430ustar00rootroot00000000000000from typing import Optional import typer def main(name: Optional[str] = typer.Argument(None)): if name is None: typer.echo("Hello World!") else: typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/commands/000077500000000000000000000000001411311634200162525ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/arguments/000077500000000000000000000000001411311634200202575ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/arguments/tutorial001.py000066400000000000000000000003731411311634200227200ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): typer.echo(f"Creating user: {username}") @app.command() def delete(username: str): typer.echo(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/callback/000077500000000000000000000000001411311634200200065ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/callback/tutorial001.py000066400000000000000000000013641411311634200224500ustar00rootroot00000000000000import typer app = typer.Typer() state = {"verbose": False} @app.command() def create(username: str): if state["verbose"]: typer.echo("About to create a user") typer.echo(f"Creating user: {username}") if state["verbose"]: typer.echo("Just created a user") @app.command() def delete(username: str): if state["verbose"]: typer.echo("About to delete a user") typer.echo(f"Deleting user: {username}") if state["verbose"]: typer.echo("Just deleted a user") @app.callback() def main(verbose: bool = False): """ Manage users in the awesome CLI app. """ if verbose: typer.echo("Will write verbose output") state["verbose"] = True if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/callback/tutorial002.py000066400000000000000000000003421411311634200224440ustar00rootroot00000000000000import typer def callback(): typer.echo("Running a command") app = typer.Typer(callback=callback) @app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/callback/tutorial003.py000066400000000000000000000004771411311634200224560ustar00rootroot00000000000000import typer def callback(): typer.echo("Running a command") app = typer.Typer(callback=callback) @app.callback() def new_callback(): typer.echo("Override callback, running a command") @app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/callback/tutorial004.py000066400000000000000000000005041411311634200224460ustar00rootroot00000000000000import typer app = typer.Typer() @app.callback() def callback(): """ Manage users CLI app. Use it with the create command. A new user with the given NAME will be created. """ @app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/context/000077500000000000000000000000001411311634200177365ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/context/tutorial001.py000066400000000000000000000006521411311634200223770ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): typer.echo(f"Creating user: {username}") @app.command() def delete(username: str): typer.echo(f"Deleting user: {username}") @app.callback() def main(ctx: typer.Context): """ Manage users in the awesome CLI app. """ typer.echo(f"About to execute command: {ctx.invoked_subcommand}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/context/tutorial002.py000066400000000000000000000006251411311634200224000ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): typer.echo(f"Creating user: {username}") @app.command() def delete(username: str): typer.echo(f"Deleting user: {username}") @app.callback(invoke_without_command=True) def main(): """ Manage users in the awesome CLI app. """ typer.echo("Initializing database") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/context/tutorial003.py000066400000000000000000000007221411311634200223770ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): typer.echo(f"Creating user: {username}") @app.command() def delete(username: str): typer.echo(f"Deleting user: {username}") @app.callback(invoke_without_command=True) def main(ctx: typer.Context): """ Manage users in the awesome CLI app. """ if ctx.invoked_subcommand is None: typer.echo("Initializing database") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/context/tutorial004.py000066400000000000000000000004321411311634200223760ustar00rootroot00000000000000import typer app = typer.Typer() @app.command( context_settings={"allow_extra_args": True, "ignore_unknown_options": True} ) def main(ctx: typer.Context): for extra_arg in ctx.args: typer.echo(f"Got extra arg: {extra_arg}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/help/000077500000000000000000000000001411311634200172025ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/help/tutorial001.py000066400000000000000000000023121411311634200216360ustar00rootroot00000000000000import typer app = typer.Typer(help="Awesome CLI user manager.") @app.command() def create(username: str): """ Create a new user with USERNAME. """ typer.echo(f"Creating user: {username}") @app.command() def delete( username: str, force: bool = typer.Option( ..., prompt="Are you sure you want to delete the user?", help="Force deletion without confirmation.", ), ): """ Delete a user with USERNAME. If --force is not used, will ask for confirmation. """ if force: typer.echo(f"Deleting user: {username}") else: typer.echo("Operation cancelled") @app.command() def delete_all( force: bool = typer.Option( ..., prompt="Are you sure you want to delete ALL users?", help="Force deletion without confirmation.", ) ): """ Delete ALL users in the database. If --force is not used, will ask for confirmation. """ if force: typer.echo("Deleting all users") else: typer.echo("Operation cancelled") @app.command() def init(): """ Initialize the users database. """ typer.echo("Initializing user database") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/help/tutorial002.py000066400000000000000000000007011411311634200216370ustar00rootroot00000000000000import typer app = typer.Typer() @app.command(help="Create a new user with USERNAME.") def create(username: str): """ Some internal utility function to create. """ typer.echo(f"Creating user: {username}") @app.command(help="Delete a user with USERNAME.") def delete(username: str): """ Some internal utility function to delete. """ typer.echo(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/index/000077500000000000000000000000001411311634200173615ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/index/tutorial001.py000066400000000000000000000002171411311634200220170ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str): typer.echo(f"Hello {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/index/tutorial002.py000066400000000000000000000003411411311634200220160ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): typer.echo("Creating user: Hiro Hamada") @app.command() def delete(): typer.echo("Deleting user: Hiro Hamada") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/name/000077500000000000000000000000001411311634200171725ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/name/tutorial001.py000066400000000000000000000004351411311634200216320ustar00rootroot00000000000000import typer app = typer.Typer() @app.command("create") def cli_create_user(username: str): typer.echo(f"Creating user: {username}") @app.command("delete") def cli_delete_user(username: str): typer.echo(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/one_or_multiple/000077500000000000000000000000001411311634200214465ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/one_or_multiple/tutorial001.py000066400000000000000000000003001411311634200240750ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): typer.echo("Creating user: Hiro Hamada") @app.callback() def callback(): pass if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/one_or_multiple/tutorial002.py000066400000000000000000000004441411311634200241070ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): typer.echo("Creating user: Hiro Hamada") @app.callback() def callback(): """ Creates a single user Hiro Hamada. In the next version it will create 5 users more. """ if __name__ == "__main__": app() typer-0.4.0/docs_src/commands/options/000077500000000000000000000000001411311634200177455ustar00rootroot00000000000000typer-0.4.0/docs_src/commands/options/tutorial001.py000066400000000000000000000013231411311634200224020ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): typer.echo(f"Creating user: {username}") @app.command() def delete( username: str, force: bool = typer.Option(..., prompt="Are you sure you want to delete the user?"), ): if force: typer.echo(f"Deleting user: {username}") else: typer.echo("Operation cancelled") @app.command() def delete_all( force: bool = typer.Option(..., prompt="Are you sure you want to delete ALL users?") ): if force: typer.echo("Deleting all users") else: typer.echo("Operation cancelled") @app.command() def init(): typer.echo("Initializing user database") if __name__ == "__main__": app() typer-0.4.0/docs_src/first_steps/000077500000000000000000000000001411311634200170165ustar00rootroot00000000000000typer-0.4.0/docs_src/first_steps/tutorial001.py000066400000000000000000000001521411311634200214520ustar00rootroot00000000000000import typer def main(): typer.echo("Hello World") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/first_steps/tutorial002.py000066400000000000000000000001651411311634200214570ustar00rootroot00000000000000import typer def main(name: str): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/first_steps/tutorial003.py000066400000000000000000000002171411311634200214560ustar00rootroot00000000000000import typer def main(name: str, lastname: str): typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/first_steps/tutorial004.py000066400000000000000000000003711411311634200214600ustar00rootroot00000000000000import typer def main(name: str, lastname: str, formal: bool = False): if formal: typer.echo(f"Good day Ms. {name} {lastname}.") else: typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/first_steps/tutorial005.py000066400000000000000000000003761411311634200214660ustar00rootroot00000000000000import typer def main(name: str, lastname: str = "", formal: bool = False): if formal: typer.echo(f"Good day Ms. {name} {lastname}.") else: typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/first_steps/tutorial006.py000066400000000000000000000005601411311634200214620ustar00rootroot00000000000000import typer def main(name: str, lastname: str = "", formal: bool = False): """ Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. """ if formal: typer.echo(f"Good day Ms. {name} {lastname}.") else: typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/launch/000077500000000000000000000000001411311634200157235ustar00rootroot00000000000000typer-0.4.0/docs_src/launch/tutorial001.py000066400000000000000000000002421411311634200203570ustar00rootroot00000000000000import typer def main(): typer.echo("Opening Typer's docs") typer.launch("https://typer.tiangolo.com") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/launch/tutorial002.py000066400000000000000000000010241411311634200203570ustar00rootroot00000000000000from pathlib import Path import typer APP_NAME = "my-super-cli-app" def main(): app_dir = typer.get_app_dir(APP_NAME) app_dir_path = Path(app_dir) app_dir_path.mkdir(parents=True, exist_ok=True) config_path: Path = Path(app_dir) / "config.json" if not config_path.is_file(): config_path.write_text('{"version": "1.0.0"}') config_file_str = str(config_path) typer.echo("Opening config directory") typer.launch(config_file_str, locate=True) if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/multiple_values/000077500000000000000000000000001411311634200176635ustar00rootroot00000000000000typer-0.4.0/docs_src/multiple_values/arguments_with_multiple_values/000077500000000000000000000000001411311634200262155ustar00rootroot00000000000000typer-0.4.0/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py000066400000000000000000000004601411311634200306530ustar00rootroot00000000000000from pathlib import Path from typing import List import typer def main(files: List[Path], celebration: str): for path in files: if path.is_file(): typer.echo(f"This file exists: {path.name}") typer.echo(celebration) if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py000066400000000000000000000004501411311634200306530ustar00rootroot00000000000000from typing import Tuple import typer def main( names: Tuple[str, str, str] = typer.Argument( ("Harry", "Hermione", "Ron"), help="Select 3 characters to play with" ) ): for name in names: typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/multiple_values/multiple_options/000077500000000000000000000000001411311634200232715ustar00rootroot00000000000000typer-0.4.0/docs_src/multiple_values/multiple_options/tutorial001.py000066400000000000000000000004611411311634200257300ustar00rootroot00000000000000from typing import List, Optional import typer def main(user: Optional[List[str]] = typer.Option(None)): if not user: typer.echo("No provided users") raise typer.Abort() for u in user: typer.echo(f"Processing user: {u}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/multiple_values/multiple_options/tutorial002.py000066400000000000000000000002671411311634200257350ustar00rootroot00000000000000from typing import List import typer def main(number: List[float] = typer.Option([])): typer.echo(f"The sum is {sum(number)}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/multiple_values/options_with_multiple_values/000077500000000000000000000000001411311634200257035ustar00rootroot00000000000000typer-0.4.0/docs_src/multiple_values/options_with_multiple_values/tutorial001.py000066400000000000000000000006421411311634200303430ustar00rootroot00000000000000from typing import Tuple import typer def main(user: Tuple[str, int, bool] = typer.Option((None, None, None))): username, coins, is_wizard = user if not username: typer.echo("No user provided") raise typer.Abort() typer.echo(f"The username {username} has {coins} coins") if is_wizard: typer.echo("And this user is a wizard!") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/000077500000000000000000000000001411311634200161445ustar00rootroot00000000000000typer-0.4.0/docs_src/options/autocompletion/000077500000000000000000000000001411311634200212065ustar00rootroot00000000000000typer-0.4.0/docs_src/options/autocompletion/tutorial001.py000066400000000000000000000002541411311634200236450ustar00rootroot00000000000000import typer def main(name: str = typer.Option("World", help="The name to say hi to.")): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial002.py000066400000000000000000000004421411311634200236450ustar00rootroot00000000000000import typer def complete_name(): return ["Camila", "Carlos", "Sebastian"] def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial003.py000066400000000000000000000007101411311634200236440ustar00rootroot00000000000000import typer valid_names = ["Camila", "Carlos", "Sebastian"] def complete_name(incomplete: str): completion = [] for name in valid_names: if name.startswith(incomplete): completion.append(name) return completion def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial004.py000066400000000000000000000012021411311634200236420ustar00rootroot00000000000000import typer valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] def complete_name(incomplete: str): completion = [] for name, help_text in valid_completion_items: if name.startswith(incomplete): completion_item = (name, help_text) completion.append(completion_item) return completion def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial005.py000066400000000000000000000010351411311634200236470ustar00rootroot00000000000000import typer valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] def complete_name(incomplete: str): for name, help_text in valid_completion_items: if name.startswith(incomplete): yield (name, help_text) def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial006.py000066400000000000000000000003611411311634200236510ustar00rootroot00000000000000from typing import List import typer def main(name: List[str] = typer.Option(["World"], help="The name to say hi to.")): for each_name in name: typer.echo(f"Hello {each_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial007.py000066400000000000000000000012451411311634200236540ustar00rootroot00000000000000from typing import List import typer valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] def complete_name(ctx: typer.Context, incomplete: str): names = ctx.params.get("name") or [] for name, help_text in valid_completion_items: if name.startswith(incomplete) and name not in names: yield (name, help_text) def main( name: List[str] = typer.Option( ["World"], help="The name to say hi to.", autocompletion=complete_name ) ): for n in name: typer.echo(f"Hello {n}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial008.py000066400000000000000000000012071411311634200236530ustar00rootroot00000000000000from typing import List import typer valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] def complete_name(args: List[str], incomplete: str): typer.echo(f"{args}", err=True) for name, help_text in valid_completion_items: if name.startswith(incomplete): yield (name, help_text) def main( name: List[str] = typer.Option( ["World"], help="The name to say hi to.", autocompletion=complete_name ) ): for n in name: typer.echo(f"Hello {n}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/autocompletion/tutorial009.py000066400000000000000000000013321411311634200236530ustar00rootroot00000000000000from typing import List import typer valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] def complete_name(ctx: typer.Context, args: List[str], incomplete: str): typer.echo(f"{args}", err=True) names = ctx.params.get("name") or [] for name, help_text in valid_completion_items: if name.startswith(incomplete) and name not in names: yield (name, help_text) def main( name: List[str] = typer.Option( ["World"], help="The name to say hi to.", autocompletion=complete_name ) ): for n in name: typer.echo(f"Hello {n}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/callback/000077500000000000000000000000001411311634200177005ustar00rootroot00000000000000typer-0.4.0/docs_src/options/callback/tutorial001.py000066400000000000000000000004501411311634200223350ustar00rootroot00000000000000import typer def name_callback(value: str): if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: str = typer.Option(..., callback=name_callback)): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/callback/tutorial002.py000066400000000000000000000005121411311634200223350ustar00rootroot00000000000000import typer def name_callback(value: str): typer.echo("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: str = typer.Option(..., callback=name_callback)): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/callback/tutorial003.py000066400000000000000000000006131411311634200223400ustar00rootroot00000000000000import typer def name_callback(ctx: typer.Context, value: str): if ctx.resilient_parsing: return typer.echo("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: str = typer.Option(..., callback=name_callback)): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/callback/tutorial004.py000066400000000000000000000006671411311634200223520ustar00rootroot00000000000000import typer def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str): if ctx.resilient_parsing: return typer.echo(f"Validating param: {param.name}") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: str = typer.Option(..., callback=name_callback)): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/help/000077500000000000000000000000001411311634200170745ustar00rootroot00000000000000typer-0.4.0/docs_src/options/help/tutorial001.py000066400000000000000000000007321411311634200215340ustar00rootroot00000000000000import typer def main( name: str, lastname: str = typer.Option("", help="Last name of person to greet."), formal: bool = typer.Option(False, help="Say hi formally."), ): """ Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. """ if formal: typer.echo(f"Good day Ms. {name} {lastname}.") else: typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/help/tutorial002.py000066400000000000000000000002571411311634200215370ustar00rootroot00000000000000import typer def main(fullname: str = typer.Option("Wade Wilson", show_default=False)): typer.echo(f"Hello {fullname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/name/000077500000000000000000000000001411311634200170645ustar00rootroot00000000000000typer-0.4.0/docs_src/options/name/tutorial001.py000066400000000000000000000002351411311634200215220ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--name")): typer.echo(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/name/tutorial002.py000066400000000000000000000002431411311634200215220ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--name", "-n")): typer.echo(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/name/tutorial003.py000066400000000000000000000002311411311634200215200ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "-n")): typer.echo(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/name/tutorial004.py000066400000000000000000000002501411311634200215220ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--user-name", "-n")): typer.echo(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/name/tutorial005.py000066400000000000000000000004431411311634200215270ustar00rootroot00000000000000import typer def main( name: str = typer.Option(..., "--name", "-n"), formal: bool = typer.Option(False, "--formal", "-f"), ): if formal: typer.echo(f"Good day Ms. {name}.") else: typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/password/000077500000000000000000000000001411311634200200065ustar00rootroot00000000000000typer-0.4.0/docs_src/options/password/tutorial001.py000066400000000000000000000003311411311634200224410ustar00rootroot00000000000000import typer def main( name: str, email: str = typer.Option(..., prompt=True, confirmation_prompt=True) ): typer.echo(f"Hello {name}, your email is {email}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/password/tutorial002.py000066400000000000000000000005371411311634200224520ustar00rootroot00000000000000import typer def main( name: str, password: str = typer.Option( ..., prompt=True, confirmation_prompt=True, hide_input=True ), ): typer.echo(f"Hello {name}. Doing something very secure with password.") typer.echo(f"...just kidding, here it is, very insecure: {password}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/prompt/000077500000000000000000000000001411311634200174655ustar00rootroot00000000000000typer-0.4.0/docs_src/options/prompt/tutorial001.py000066400000000000000000000002601411311634200221210ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option(..., prompt=True)): typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/prompt/tutorial002.py000066400000000000000000000003211411311634200221200ustar00rootroot00000000000000import typer def main( name: str, lastname: str = typer.Option(..., prompt="Please tell me your last name") ): typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/prompt/tutorial003.py000066400000000000000000000003131411311634200221220ustar00rootroot00000000000000import typer def main(project_name: str = typer.Option(..., prompt=True, confirmation_prompt=True)): typer.echo(f"Deleting project {project_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/required/000077500000000000000000000000001411311634200177645ustar00rootroot00000000000000typer-0.4.0/docs_src/options/required/tutorial001.py000066400000000000000000000002431411311634200224210ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option(...)): typer.echo(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/version/000077500000000000000000000000001411311634200176315ustar00rootroot00000000000000typer-0.4.0/docs_src/options/version/tutorial001.py000066400000000000000000000006701411311634200222720ustar00rootroot00000000000000from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool): if value: typer.echo(f"Awesome CLI Version: {__version__}") raise typer.Exit() def main( name: str = typer.Option("World"), version: Optional[bool] = typer.Option( None, "--version", callback=version_callback ), ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/version/tutorial002.py000066400000000000000000000011001411311634200222600ustar00rootroot00000000000000from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool): if value: typer.echo(f"Awesome CLI Version: {__version__}") raise typer.Exit() def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") def main( name: str = typer.Option(..., callback=name_callback), version: Optional[bool] = typer.Option( None, "--version", callback=version_callback ), ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/options/version/tutorial003.py000066400000000000000000000011371411311634200222730ustar00rootroot00000000000000from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool): if value: typer.echo(f"Awesome CLI Version: {__version__}") raise typer.Exit() def name_callback(name: str): if name != "Camila": raise typer.BadParameter("Only Camila is allowed") return name def main( name: str = typer.Option(..., callback=name_callback), version: Optional[bool] = typer.Option( None, "--version", callback=version_callback, is_eager=True ), ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/000077500000000000000000000000001411311634200176555ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/bool/000077500000000000000000000000001411311634200206105ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/bool/__init__.py000066400000000000000000000000001411311634200227070ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/bool/tutorial001.py000066400000000000000000000003321411311634200232440ustar00rootroot00000000000000import typer def main(force: bool = typer.Option(False, "--force")): if force: typer.echo("Forcing operation") else: typer.echo("Not forcing") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/bool/tutorial002.py000066400000000000000000000005221411311634200232460ustar00rootroot00000000000000from typing import Optional import typer def main(accept: Optional[bool] = typer.Option(None, "--accept/--reject")): if accept is None: typer.echo("I don't know what you want yet") elif accept: typer.echo("Accepting!") else: typer.echo("Rejecting!") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/bool/tutorial003.py000066400000000000000000000003561411311634200232540ustar00rootroot00000000000000import typer def main(force: bool = typer.Option(False, "--force/--no-force", "-f/-F")): if force: typer.echo("Forcing operation") else: typer.echo("Not forcing") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/bool/tutorial004.py000066400000000000000000000003531411311634200232520ustar00rootroot00000000000000import typer def main(in_prod: bool = typer.Option(True, " /--demo", " /-d")): if in_prod: typer.echo("Running in production") else: typer.echo("Running demo") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/datetime/000077500000000000000000000000001411311634200214515ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/datetime/__init__.py000066400000000000000000000000001411311634200235500ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/datetime/tutorial001.py000066400000000000000000000003351411311634200241100ustar00rootroot00000000000000from datetime import datetime import typer def main(birth: datetime): typer.echo(f"Interesting day to be born: {birth}") typer.echo(f"Birth hour: {birth.hour}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/datetime/tutorial002.py000066400000000000000000000004521411311634200241110ustar00rootroot00000000000000from datetime import datetime import typer def main( launch_date: datetime = typer.Argument( ..., formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%m/%d/%Y"] ) ): typer.echo(f"Launch will be at: {launch_date}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/enum/000077500000000000000000000000001411311634200206215ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/enum/__init__.py000066400000000000000000000000001411311634200227200ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/enum/tutorial001.py000066400000000000000000000004601411311634200232570ustar00rootroot00000000000000from enum import Enum import typer class NeuralNetwork(str, Enum): simple = "simple" conv = "conv" lstm = "lstm" def main(network: NeuralNetwork = NeuralNetwork.simple): typer.echo(f"Training neural network of type: {network.value}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/enum/tutorial002.py000066400000000000000000000005321411311634200232600ustar00rootroot00000000000000from enum import Enum import typer class NeuralNetwork(str, Enum): simple = "simple" conv = "conv" lstm = "lstm" def main( network: NeuralNetwork = typer.Option(NeuralNetwork.simple, case_sensitive=False) ): typer.echo(f"Training neural network of type: {network.value}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/file/000077500000000000000000000000001411311634200205745ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/file/__init__.py000066400000000000000000000000001411311634200226730ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/file/tutorial001.py000066400000000000000000000002711411311634200232320ustar00rootroot00000000000000import typer def main(config: typer.FileText = typer.Option(...)): for line in config: typer.echo(f"Config line: {line}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/file/tutorial002.py000066400000000000000000000003171411311634200232340ustar00rootroot00000000000000import typer def main(config: typer.FileTextWrite = typer.Option(...)): config.write("Some config written by the app") typer.echo("Config written") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/file/tutorial003.py000066400000000000000000000005061411311634200232350ustar00rootroot00000000000000import typer def main(file: typer.FileBinaryRead = typer.Option(...)): processed_total = 0 for bytes_chunk in file: # Process the bytes in bytes_chunk processed_total += len(bytes_chunk) typer.echo(f"Processed bytes total: {processed_total}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/file/tutorial004.py000066400000000000000000000010501411311634200232310ustar00rootroot00000000000000import typer def main(file: typer.FileBinaryWrite = typer.Option(...)): first_line_str = "some settings\n" # You cannot write str directly to a binary file, you have to encode it to get bytes first_line_bytes = first_line_str.encode("utf-8") # Then you can write the bytes file.write(first_line_bytes) # This is already bytes, it starts with b" second_line = b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o" file.write(second_line) typer.echo("Binary file written") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/file/tutorial005.py000066400000000000000000000003221411311634200232330ustar00rootroot00000000000000import typer def main(config: typer.FileText = typer.Option(..., mode="a")): config.write("This is a single line\n") typer.echo("Config line written") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/index/000077500000000000000000000000001411311634200207645ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/index/__init__.py000066400000000000000000000000001411311634200230630ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/index/tutorial001.py000066400000000000000000000006361411311634200234270ustar00rootroot00000000000000import typer def main(name: str, age: int = 20, height_meters: float = 1.89, female: bool = True): typer.echo(f"NAME is {name}, of type: {type(name)}") typer.echo(f"--age is {age}, of type: {type(age)}") typer.echo(f"--height-meters is {height_meters}, of type: {type(height_meters)}") typer.echo(f"--female is {female}, of type: {type(female)}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/number/000077500000000000000000000000001411311634200211455ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/number/__init__.py000066400000000000000000000000001411311634200232440ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/number/tutorial001.py000066400000000000000000000004751411311634200236110ustar00rootroot00000000000000import typer def main( id: int = typer.Argument(..., min=0, max=1000), age: int = typer.Option(20, min=18), score: float = typer.Option(0, max=100), ): typer.echo(f"ID is {id}") typer.echo(f"--age is {age}") typer.echo(f"--score is {score}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/number/tutorial002.py000066400000000000000000000005361411311634200236100ustar00rootroot00000000000000import typer def main( id: int = typer.Argument(..., min=0, max=1000), rank: int = typer.Option(0, max=10, clamp=True), score: float = typer.Option(0, min=0, max=100, clamp=True), ): typer.echo(f"ID is {id}") typer.echo(f"--rank is {rank}") typer.echo(f"--score is {score}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/number/tutorial003.py000066400000000000000000000002671411311634200236120ustar00rootroot00000000000000import typer def main(verbose: int = typer.Option(0, "--verbose", "-v", count=True)): typer.echo(f"Verbose level is {verbose}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/path/000077500000000000000000000000001411311634200206115ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/path/__init__.py000066400000000000000000000000001411311634200227100ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/path/tutorial001.py000066400000000000000000000010461411311634200232500ustar00rootroot00000000000000from pathlib import Path from typing import Optional import typer def main(config: Optional[Path] = typer.Option(None)): if config is None: typer.echo("No config file") raise typer.Abort() if config.is_file(): text = config.read_text() typer.echo(f"Config file contents: {text}") elif config.is_dir(): typer.echo("Config is a directory, will use all its config files") elif not config.exists(): typer.echo("The config doesn't exist") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/path/tutorial002.py000066400000000000000000000005701411311634200232520ustar00rootroot00000000000000from pathlib import Path import typer def main( config: Path = typer.Option( ..., exists=True, file_okay=True, dir_okay=False, writable=False, readable=True, resolve_path=True, ) ): text = config.read_text() typer.echo(f"Config file contents: {text}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/parameter_types/uuid/000077500000000000000000000000001411311634200206235ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/uuid/__init__.py000066400000000000000000000000001411311634200227220ustar00rootroot00000000000000typer-0.4.0/docs_src/parameter_types/uuid/tutorial001.py000066400000000000000000000003161411311634200232610ustar00rootroot00000000000000from uuid import UUID import typer def main(user_id: UUID): typer.echo(f"USER_ID is {user_id}") typer.echo(f"UUID version is: {user_id.version}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/printing/000077500000000000000000000000001411311634200163035ustar00rootroot00000000000000typer-0.4.0/docs_src/printing/tutorial001.py000066400000000000000000000005551411311634200207460ustar00rootroot00000000000000import typer def main(good: bool = True): message_start = "everything is " if good: ending = typer.style("good", fg=typer.colors.GREEN, bold=True) else: ending = typer.style("bad", fg=typer.colors.WHITE, bg=typer.colors.RED) message = message_start + ending typer.echo(message) if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/printing/tutorial002.py000066400000000000000000000002261411311634200207420ustar00rootroot00000000000000import typer def main(name: str): typer.secho(f"Welcome here {name}", fg=typer.colors.MAGENTA) if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/printing/tutorial003.py000066400000000000000000000002251411311634200207420ustar00rootroot00000000000000import typer def main(): typer.echo(f"Here is something written to standard error", err=True) if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/progressbar/000077500000000000000000000000001411311634200170025ustar00rootroot00000000000000typer-0.4.0/docs_src/progressbar/tutorial001.py000066400000000000000000000004761411311634200214470ustar00rootroot00000000000000import time import typer def main(): total = 0 with typer.progressbar(range(100)) as progress: for value in progress: # Fake processing time time.sleep(0.01) total += 1 typer.echo(f"Processed {total} things.") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/progressbar/tutorial002.py000066400000000000000000000007141411311634200214430ustar00rootroot00000000000000import time import typer def iterate_user_ids(): # Let's imagine this is a web API, not a range() for i in range(100): yield i def main(): total = 0 with typer.progressbar(iterate_user_ids(), length=100) as progress: for value in progress: # Fake processing time time.sleep(0.01) total += 1 typer.echo(f"Processed {total} user IDs.") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/progressbar/tutorial003.py000066400000000000000000000005221411311634200214410ustar00rootroot00000000000000import time import typer def main(): total = 0 with typer.progressbar(range(100), label="Processing") as progress: for value in progress: # Fake processing time time.sleep(0.01) total += 1 typer.echo(f"Processed {total} things.") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/progressbar/tutorial004.py000066400000000000000000000005251411311634200214450ustar00rootroot00000000000000import time import typer def main(): total = 1000 with typer.progressbar(length=total) as progress: for batch in range(4): # Fake processing time time.sleep(1) progress.update(250) typer.echo(f"Processed {total} things in batches.") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/prompt/000077500000000000000000000000001411311634200157725ustar00rootroot00000000000000typer-0.4.0/docs_src/prompt/tutorial001.py000066400000000000000000000002471411311634200204330ustar00rootroot00000000000000import typer def main(): person_name = typer.prompt("What's your name?") typer.echo(f"Hello {person_name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/prompt/tutorial002.py000066400000000000000000000003771411311634200204400ustar00rootroot00000000000000import typer def main(): delete = typer.confirm("Are you sure you want to delete it?") if not delete: typer.echo("Not deleting") raise typer.Abort() typer.echo("Deleting it!") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/prompt/tutorial003.py000066400000000000000000000002711411311634200204320ustar00rootroot00000000000000import typer def main(): delete = typer.confirm("Are you sure you want to delete it?", abort=True) typer.echo("Deleting it!") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/subcommands/000077500000000000000000000000001411311634200167645ustar00rootroot00000000000000typer-0.4.0/docs_src/subcommands/callback_override/000077500000000000000000000000001411311634200224175ustar00rootroot00000000000000typer-0.4.0/docs_src/subcommands/callback_override/tutorial001.py000066400000000000000000000004721411311634200250600ustar00rootroot00000000000000import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app, name="users") @users_app.callback() def users_callback(): typer.echo("Running a users command") @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/callback_override/tutorial002.py000066400000000000000000000004741411311634200250630ustar00rootroot00000000000000import typer app = typer.Typer() def users_callback(): typer.echo("Running a users command") users_app = typer.Typer(callback=users_callback) app.add_typer(users_app, name="users") @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/callback_override/tutorial003.py000066400000000000000000000006501411311634200250600ustar00rootroot00000000000000import typer app = typer.Typer() def default_callback(): typer.echo("Running a users command") users_app = typer.Typer(callback=default_callback) app.add_typer(users_app, name="users") @users_app.callback() def user_callback(): typer.echo("Callback override, running users command") @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/callback_override/tutorial004.py000066400000000000000000000010511411311634200250550ustar00rootroot00000000000000import typer app = typer.Typer() def default_callback(): typer.echo("Running a users command") users_app = typer.Typer(callback=default_callback) def callback_for_add_typer(): typer.echo("I have the high land! Running users command") app.add_typer(users_app, name="users", callback=callback_for_add_typer) @users_app.callback() def user_callback(): typer.echo("Callback override, running users command") @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/000077500000000000000000000000001411311634200207145ustar00rootroot00000000000000typer-0.4.0/docs_src/subcommands/name_help/tutorial001.py000066400000000000000000000004031411311634200233470ustar00rootroot00000000000000import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app, name="users", help="Manage users in the app.") @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial002.py000066400000000000000000000004461411311634200233570ustar00rootroot00000000000000import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app) @users_app.callback() def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial003.py000066400000000000000000000004371411311634200233600ustar00rootroot00000000000000import typer app = typer.Typer() def users(): """ Manage users in the app. """ users_app = typer.Typer(callback=users) app.add_typer(users_app) @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial004.py000066400000000000000000000005711411311634200233600ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback) app.add_typer(users_app) @users_app.callback() def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial005.py000066400000000000000000000007361411311634200233640ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback) def new_users(): """ I have the highland! Create some users. """ app.add_typer(users_app, callback=new_users) @users_app.callback() def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial006.py000066400000000000000000000010071411311634200233550ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback, name="exp-users", help="Explicit help.") def new_users(): """ I have the highland! Create some users. """ app.add_typer(users_app, callback=new_users) @users_app.callback() def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial007.py000066400000000000000000000010711411311634200233570ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback, name="exp-users", help="Explicit help.") def new_users(): """ I have the highland! Create some users. """ app.add_typer(users_app, callback=new_users) @users_app.callback("call-users", help="Help from callback for users.") def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/name_help/tutorial008.py000066400000000000000000000012111411311634200233540ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback, name="exp-users", help="Explicit help.") def new_users(): """ I have the highland! Create some users. """ app.add_typer( users_app, callback=new_users, name="cake-sith-users", help="Unlimited powder! Eh, users.", ) @users_app.callback("call-users", help="Help from callback for users.") def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): typer.echo(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial001/000077500000000000000000000000001411311634200210505ustar00rootroot00000000000000typer-0.4.0/docs_src/subcommands/tutorial001/items.py000066400000000000000000000004711411311634200225450ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(item: str): typer.echo(f"Creating item: {item}") @app.command() def delete(item: str): typer.echo(f"Deleting item: {item}") @app.command() def sell(item: str): typer.echo(f"Selling item: {item}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial001/main.py000066400000000000000000000002611411311634200223450ustar00rootroot00000000000000import typer import items import users app = typer.Typer() app.add_typer(users.app, name="users") app.add_typer(items.app, name="items") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial001/users.py000066400000000000000000000003771411311634200225720ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(user_name: str): typer.echo(f"Creating user: {user_name}") @app.command() def delete(user_name: str): typer.echo(f"Deleting user: {user_name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial002/000077500000000000000000000000001411311634200210515ustar00rootroot00000000000000typer-0.4.0/docs_src/subcommands/tutorial002/main.py000066400000000000000000000013231411311634200223460ustar00rootroot00000000000000import typer app = typer.Typer() items_app = typer.Typer() app.add_typer(items_app, name="items") users_app = typer.Typer() app.add_typer(users_app, name="users") @items_app.command("create") def items_create(item: str): typer.echo(f"Creating item: {item}") @items_app.command("delete") def items_delete(item: str): typer.echo(f"Deleting item: {item}") @items_app.command("sell") def items_sell(item: str): typer.echo(f"Selling item: {item}") @users_app.command("create") def users_create(user_name: str): typer.echo(f"Creating user: {user_name}") @users_app.command("delete") def users_delete(user_name: str): typer.echo(f"Deleting user: {user_name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial003/000077500000000000000000000000001411311634200210525ustar00rootroot00000000000000typer-0.4.0/docs_src/subcommands/tutorial003/items.py000066400000000000000000000004711411311634200225470ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(item: str): typer.echo(f"Creating item: {item}") @app.command() def delete(item: str): typer.echo(f"Deleting item: {item}") @app.command() def sell(item: str): typer.echo(f"Selling item: {item}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial003/lands.py000066400000000000000000000002641411311634200225270ustar00rootroot00000000000000import typer import reigns import towns app = typer.Typer() app.add_typer(reigns.app, name="reigns") app.add_typer(towns.app, name="towns") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial003/main.py000066400000000000000000000003451411311634200223520ustar00rootroot00000000000000import typer import items import lands import users app = typer.Typer() app.add_typer(users.app, name="users") app.add_typer(items.app, name="items") app.add_typer(lands.app, name="lands") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial003/reigns.py000066400000000000000000000003631411311634200227150ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def conquer(name: str): typer.echo(f"Conquering reign: {name}") @app.command() def destroy(name: str): typer.echo(f"Destroying reign: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial003/towns.py000066400000000000000000000003471411311634200226020ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def found(name: str): typer.echo(f"Founding town: {name}") @app.command() def burn(name: str): typer.echo(f"Burning town: {name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/subcommands/tutorial003/users.py000066400000000000000000000003771411311634200225740ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(user_name: str): typer.echo(f"Creating user: {user_name}") @app.command() def delete(user_name: str): typer.echo(f"Deleting user: {user_name}") if __name__ == "__main__": app() typer-0.4.0/docs_src/terminating/000077500000000000000000000000001411311634200167725ustar00rootroot00000000000000typer-0.4.0/docs_src/terminating/tutorial001.py000066400000000000000000000011451411311634200214310ustar00rootroot00000000000000import typer existing_usernames = ["rick", "morty"] def maybe_create_user(username: str): if username in existing_usernames: typer.echo("The user already exists") raise typer.Exit() else: typer.echo(f"User created: {username}") def send_new_user_notification(username: str): # Somehow send a notification here for the new user, maybe an email typer.echo(f"Notification sent for new user: {username}") def main(username: str): maybe_create_user(username=username) send_new_user_notification(username=username) if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/terminating/tutorial002.py000066400000000000000000000003651411311634200214350ustar00rootroot00000000000000import typer def main(username: str): if username == "root": typer.echo("The root user is reserved") raise typer.Exit(code=1) typer.echo(f"New user created: {username}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/terminating/tutorial003.py000066400000000000000000000003601411311634200214310ustar00rootroot00000000000000import typer def main(username: str): if username == "root": typer.echo("The root user is reserved") raise typer.Abort() typer.echo(f"New user created: {username}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/testing/000077500000000000000000000000001411311634200161265ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app01/000077500000000000000000000000001411311634200170475ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app01/__init__.py000066400000000000000000000000001411311634200211460ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app01/main.py000066400000000000000000000004121411311634200203420ustar00rootroot00000000000000from typing import Optional import typer app = typer.Typer() @app.command() def main(name: str, city: Optional[str] = None): typer.echo(f"Hello {name}") if city: typer.echo(f"Let's have a coffee in {city}") if __name__ == "__main__": app() typer-0.4.0/docs_src/testing/app01/test_main.py000066400000000000000000000004531411311634200214060ustar00rootroot00000000000000from typer.testing import CliRunner from .main import app runner = CliRunner() def test_app(): result = runner.invoke(app, ["Camila", "--city", "Berlin"]) assert result.exit_code == 0 assert "Hello Camila" in result.stdout assert "Let's have a coffee in Berlin" in result.stdout typer-0.4.0/docs_src/testing/app02/000077500000000000000000000000001411311634200170505ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app02/__init__.py000066400000000000000000000000001411311634200211470ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app02/main.py000066400000000000000000000003241411311634200203450ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str, email: str = typer.Option(..., prompt=True)): typer.echo(f"Hello {name}, your email is: {email}") if __name__ == "__main__": app() typer-0.4.0/docs_src/testing/app02/test_main.py000066400000000000000000000004341411311634200214060ustar00rootroot00000000000000from typer.testing import CliRunner from .main import app runner = CliRunner() def test_app(): result = runner.invoke(app, ["Camila"], input="camila@example.com\n") assert result.exit_code == 0 assert "Hello Camila, your email is: camila@example.com" in result.stdout typer-0.4.0/docs_src/testing/app03/000077500000000000000000000000001411311634200170515ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app03/__init__.py000066400000000000000000000000001411311634200211500ustar00rootroot00000000000000typer-0.4.0/docs_src/testing/app03/main.py000066400000000000000000000001771411311634200203540ustar00rootroot00000000000000import typer def main(name: str = "World"): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.4.0/docs_src/testing/app03/test_main.py000066400000000000000000000004341411311634200214070ustar00rootroot00000000000000import typer from typer.testing import CliRunner from .main import main app = typer.Typer() app.command()(main) runner = CliRunner() def test_app(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.stdout typer-0.4.0/docs_src/using_click/000077500000000000000000000000001411311634200167435ustar00rootroot00000000000000typer-0.4.0/docs_src/using_click/tutorial001.py000066400000000000000000000005631411311634200214050ustar00rootroot00000000000000import click @click.command() @click.option("--count", default=1, help="Number of greetings.") @click.option("--name", prompt="Your name", help="The person to greet.") def hello(count, name): """Simple program that greets NAME for a total of COUNT times.""" for x in range(count): click.echo("Hello %s!" % name) if __name__ == "__main__": hello() typer-0.4.0/docs_src/using_click/tutorial002.py000066400000000000000000000004371411311634200214060ustar00rootroot00000000000000import click @click.group() def cli(): pass @click.command() def initdb(): click.echo("Initialized the database") @click.command() def dropdb(): click.echo("Dropped the database") cli.add_command(initdb) cli.add_command(dropdb) if __name__ == "__main__": cli() typer-0.4.0/docs_src/using_click/tutorial003.py000066400000000000000000000011671411311634200214100ustar00rootroot00000000000000import click import typer app = typer.Typer() @app.command() def top(): """ Top level command, form Typer """ typer.echo("The Typer app is at the top level") @app.callback() def callback(): """ Typer app, including Click subapp """ @click.command() @click.option("--name", prompt="Your name", help="The person to greet.") def hello(name): """Simple program that greets NAME for a total of COUNT times.""" click.echo("Hello %s!" % name) typer_click_object = typer.main.get_command(app) typer_click_object.add_command(hello, "hello") if __name__ == "__main__": typer_click_object() typer-0.4.0/docs_src/using_click/tutorial004.py000066400000000000000000000010041411311634200213770ustar00rootroot00000000000000import click import typer @click.group() def cli(): pass @cli.command() def initdb(): click.echo("Initialized the database") @cli.command() def dropdb(): click.echo("Dropped the database") app = typer.Typer() @app.command() def sub(): """ A single-command Typer sub app """ typer.echo("Typer is now below Click, the Click app is the top level") typer_click_object = typer.main.get_command(app) cli.add_command(typer_click_object, "sub") if __name__ == "__main__": cli() typer-0.4.0/mkdocs.yml000066400000000000000000000130501411311634200146540ustar00rootroot00000000000000site_name: Typer site_description: Typer, build great CLIs. Easy to code. Based on Python type hints. site_url: https://typer.tiangolo.com/ theme: name: 'material' palette: primary: 'black' accent: 'teal' icon: repo: fontawesome/brands/github-alt logo: 'img/icon-white.svg' favicon: 'img/favicon.png' repo_name: tiangolo/typer repo_url: https://github.com/tiangolo/typer edit_uri: '' google_analytics: - 'UA-155009830-1' - 'auto' nav: - Typer: 'index.md' - Features: 'features.md' - Tutorial - User Guide: - Tutorial - User Guide - Intro: 'tutorial/index.md' - First Steps: 'tutorial/first-steps.md' - Printing and Colors: 'tutorial/printing.md' - Terminating: 'tutorial/terminating.md' - CLI Arguments: - CLI Arguments Intro: 'tutorial/arguments/index.md' - Optional CLI Arguments: 'tutorial/arguments/optional.md' - CLI Arguments with Default: 'tutorial/arguments/default.md' - CLI Arguments with Help: 'tutorial/arguments/help.md' - CLI Arguments with Environment Variables: 'tutorial/arguments/envvar.md' - Other uses: 'tutorial/arguments/other-uses.md' - CLI Options: - CLI Options Intro: 'tutorial/options/index.md' - CLI Options with Help: 'tutorial/options/help.md' - Required CLI Options: 'tutorial/options/required.md' - CLI Option Prompt: 'tutorial/options/prompt.md' - Password CLI Option and Confirmation Prompt: 'tutorial/options/password.md' - CLI Option Name: 'tutorial/options/name.md' - CLI Option Callback and Context: 'tutorial/options/callback-and-context.md' - Version CLI Option, is_eager: 'tutorial/options/version.md' - CLI Option autocompletion: 'tutorial/options/autocompletion.md' - Commands: - Commands Intro: 'tutorial/commands/index.md' - Command CLI Arguments: 'tutorial/commands/arguments.md' - Command CLI Options: 'tutorial/commands/options.md' - Command Help: 'tutorial/commands/help.md' - Custom Command Name: 'tutorial/commands/name.md' - Typer Callback: 'tutorial/commands/callback.md' - One or Multiple Commands: 'tutorial/commands/one-or-multiple.md' - Using the Context: 'tutorial/commands/context.md' - CLI Parameter Types: - CLI Parameter Types Intro: 'tutorial/parameter-types/index.md' - Number: 'tutorial/parameter-types/number.md' - Boolean CLI Options: 'tutorial/parameter-types/bool.md' - UUID: 'tutorial/parameter-types/uuid.md' - DateTime: 'tutorial/parameter-types/datetime.md' - Enum - Choices: 'tutorial/parameter-types/enum.md' - Path: 'tutorial/parameter-types/path.md' - File: 'tutorial/parameter-types/file.md' - SubCommands - Command Groups: - SubCommands - Command Groups - Intro: 'tutorial/subcommands/index.md' - Add Typer: 'tutorial/subcommands/add-typer.md' - SubCommands in a Single File: 'tutorial/subcommands/single-file.md' - Nested SubCommands: 'tutorial/subcommands/nested-subcommands.md' - Sub-Typer Callback Override: 'tutorial/subcommands/callback-override.md' - SubCommand Name and Help: 'tutorial/subcommands/name-and-help.md' - Multiple Values: - Multiple Values Intro: 'tutorial/multiple-values/index.md' - Multiple CLI Options: 'tutorial/multiple-values/multiple-options.md' - CLI Options with Multiple Values: 'tutorial/multiple-values/options-with-multiple-values.md' - CLI Arguments with Multiple Values: 'tutorial/multiple-values/arguments-with-multiple-values.md' - Ask with Prompt: 'tutorial/prompt.md' - Progress Bar: 'tutorial/progressbar.md' - CLI Application Directory: 'tutorial/app-dir.md' - Launching Applications: 'tutorial/launch.md' - Testing: 'tutorial/testing.md' - Using Click: 'tutorial/using-click.md' - Building a Package: 'tutorial/package.md' - Typer CLI - completion for small scripts: 'typer-cli.md' - Alternatives, Inspiration and Comparisons: 'alternatives.md' - Help Typer - Get Help: 'help-typer.md' - Development - Contributing: 'contributing.md' - Release Notes: release-notes.md markdown_extensions: - toc: permalink: true - markdown.extensions.codehilite: guess_lang: false - markdown_include.include: base_path: docs - admonition - codehilite - extra - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_div_format - pymdownx.tabbed extra: social: - icon: fontawesome/brands/github-alt link: 'https://github.com/tiangolo/typer' - icon: fontawesome/brands/twitter link: 'https://twitter.com/tiangolo' - icon: fontawesome/brands/linkedin link: 'https://www.linkedin.com/in/tiangolo' - icon: fontawesome/brands/dev link: 'https://dev.to/tiangolo' - icon: fontawesome/brands/medium link: 'https://medium.com/@tiangolo' - icon: fontawesome/solid/globe link: 'https://tiangolo.com' extra_css: - 'css/termynal.css' - 'css/custom.css' extra_javascript: - 'https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js' - 'js/termynal.js' - 'js/custom.js' typer-0.4.0/mypy.ini000066400000000000000000000001401411311634200143440ustar00rootroot00000000000000[mypy] ignore_missing_imports = True [mypy-typer.*] disallow_untyped_defs = True strict = True typer-0.4.0/pyproject.toml000066400000000000000000000037661411311634200156020ustar00rootroot00000000000000[build-system] requires = ["flit_core >=2,<3"] build-backend = "flit_core.buildapi" [tool.flit.metadata] module = "typer" author = "SebastiÑn Ramírez" author-email = "tiangolo@gmail.com" home-page = "https://github.com/tiangolo/typer" classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries", "Topic :: Software Development", "Typing :: Typed", "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "License :: OSI Approved :: MIT License" ] requires = [ "click >= 7.1.1, <9.0.0" ] description-file = "README.md" requires-python = ">=3.6" [tool.flit.metadata.urls] Documentation = "https://typer.tiangolo.com/" [tool.flit.metadata.requires-extra] test = [ "shellingham >=1.3.0,<2.0.0", "pytest >=4.4.0,<5.4.0", "pytest-cov >=2.10.0,<3.0.0", "coverage >=5.2,<6.0", "pytest-xdist >=1.32.0,<2.0.0", "pytest-sugar >=0.9.4,<0.10.0", "mypy ==0.910", "black >=19.10b0,<20.0b0", "isort >=5.0.6,<6.0.0" ] doc = [ "mkdocs >=1.1.2,<2.0.0", "mkdocs-material >=5.4.0,<6.0.0", "markdown-include >=0.5.1,<0.6.0" ] dev = [ "autoflake >=1.3.1,<2.0.0", "flake8 >=3.8.3,<4.0.0", ] all = [ "colorama >=0.4.3,<0.5.0", "shellingham >=1.3.0,<2.0.0" ] [tool.isort] profile = "black" known_third_party = ["typer", "click"] skip_glob = [ "docs_src/subcommands/tutorial001/main.py", "docs_src/subcommands/tutorial003/lands.py", "docs_src/subcommands/tutorial003/main.py", ] typer-0.4.0/scripts/000077500000000000000000000000001411311634200143415ustar00rootroot00000000000000typer-0.4.0/scripts/build-docs.sh000077500000000000000000000001141411311634200167210ustar00rootroot00000000000000#!/usr/bin/env bash python -m mkdocs build cp ./docs/index.md ./README.md typer-0.4.0/scripts/clean.sh000077500000000000000000000001421411311634200157570ustar00rootroot00000000000000#!/bin/sh -e if [ -d 'dist' ] ; then rm -r dist fi if [ -d 'site' ] ; then rm -r site fi typer-0.4.0/scripts/docs-live.sh000077500000000000000000000001041411311634200165600ustar00rootroot00000000000000#!/usr/bin/env bash set -e mkdocs serve --dev-addr 127.0.0.1:8008 typer-0.4.0/scripts/format-imports.sh000077500000000000000000000002471411311634200176660ustar00rootroot00000000000000#!/bin/sh -e set -x # Sort imports one per line, so autoflake can remove unused imports isort --force-single-line-imports typer tests docs_src sh ./scripts/format.sh typer-0.4.0/scripts/format.sh000077500000000000000000000003151411311634200161670ustar00rootroot00000000000000#!/bin/sh -e set -x autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place docs_src typer tests --exclude=__init__.py black typer tests docs_src isort typer tests docs_src typer-0.4.0/scripts/get-pwsh-activate.sh000066400000000000000000000001601411311634200202260ustar00rootroot00000000000000curl https://raw.githubusercontent.com/python/cpython/main/Lib/venv/scripts/common/Activate.ps1 -o Activate.ps1 typer-0.4.0/scripts/lint.sh000077500000000000000000000001721411311634200156460ustar00rootroot00000000000000#!/usr/bin/env bash set -e set -x mypy typer black typer tests docs_src --check isort typer tests docs_src --check-only typer-0.4.0/scripts/netlify-docs.sh000077500000000000000000000005231411311634200173000ustar00rootroot00000000000000#!/usr/bin/env bash set -x set -e # Install pip cd /tmp curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python3.6 get-pip.py --user cd - # Install Flit to be able to install all python3.6 -m pip install --user flit # Install with Flit python3.6 -m flit install --user --deps develop # Finally, run mkdocs python3.6 -m mkdocs build typer-0.4.0/scripts/publish.sh000077500000000000000000000000521411311634200163430ustar00rootroot00000000000000#!/usr/bin/env bash set -e flit publish typer-0.4.0/scripts/test-cov-html.sh000077500000000000000000000001201411311634200173770ustar00rootroot00000000000000#!/usr/bin/env bash set -e set -x bash scripts/test.sh --cov-report=html ${@} typer-0.4.0/scripts/test-files.sh000077500000000000000000000005361411311634200167630ustar00rootroot00000000000000#!/usr/bin/env bash set -e set -x # Check README.md is up to date diff --brief docs/index.md README.md # Check copy paste errors in tutorials if grep -r --include "*.md" "Usage: tutorial" ./docs ; then echo "Incorrect console demo"; exit 1 ; fi if grep -r --include "*.md" "python tutorial" ./docs ; then echo "Incorrect console demo"; exit 1 ; fi typer-0.4.0/scripts/test.sh000077500000000000000000000005121411311634200156550ustar00rootroot00000000000000#!/usr/bin/env bash set -e set -x bash ./scripts/test-files.sh # Use xdist-pytest --forked to ensure modified sys.path to import relative modules in examples keeps working pytest --cov=typer --cov=tests --cov=docs_src --cov-report=term-missing --cov-report=xml -o console_output_style=progress --forked --numprocesses=auto ${@} typer-0.4.0/tests/000077500000000000000000000000001411311634200140145ustar00rootroot00000000000000typer-0.4.0/tests/__init__.py000066400000000000000000000000001411311634200161130ustar00rootroot00000000000000typer-0.4.0/tests/assets/000077500000000000000000000000001411311634200153165ustar00rootroot00000000000000typer-0.4.0/tests/assets/__init__.py000066400000000000000000000000001411311634200174150ustar00rootroot00000000000000typer-0.4.0/tests/assets/compat_click7_8.py000066400000000000000000000012501411311634200206340ustar00rootroot00000000000000from typing import List import click import typer app = typer.Typer() def shell_complete( ctx: click.Context, param: click.Parameter, incomplete: str ) -> List[str]: return ["Jonny"] @app.command(context_settings={"auto_envvar_prefix": "TEST"}) def main( name: str = typer.Option("John", hidden=True), lastname: str = typer.Option("Doe", "/lastname", show_default="Mr. Doe"), age: int = typer.Option(lambda: 42, show_default=True), nickname: str = typer.Option("", shell_complete=shell_complete), ): """ Say hello. """ typer.echo(f"Hello {name} {lastname}, it seems you have {age}, {nickname}") if __name__ == "__main__": app() typer-0.4.0/tests/assets/completion_no_types.py000066400000000000000000000010431411311634200217570ustar00rootroot00000000000000import typer app = typer.Typer() def complete(ctx, args, incomplete): typer.echo(f"info name is: {ctx.info_name}", err=True) typer.echo(f"args is: {args}", err=True) typer.echo(f"incomplete is: {incomplete}", err=True) return [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] @app.command() def main(name: str = typer.Option("World", autocompletion=complete)): typer.echo(f"Hello {name}") if __name__ == "__main__": app() typer-0.4.0/tests/assets/completion_no_types_order.py000066400000000000000000000010431411311634200231520ustar00rootroot00000000000000import typer app = typer.Typer() def complete(args, incomplete, ctx): typer.echo(f"info name is: {ctx.info_name}", err=True) typer.echo(f"args is: {args}", err=True) typer.echo(f"incomplete is: {incomplete}", err=True) return [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] @app.command() def main(name: str = typer.Option("World", autocompletion=complete)): typer.echo(f"Hello {name}") if __name__ == "__main__": app() typer-0.4.0/tests/assets/prog_name.py000066400000000000000000000002401411311634200176330ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(i: int): # pragma: no cover pass if __name__ == "__main__": app(prog_name="custom-name") typer-0.4.0/tests/test_compat/000077500000000000000000000000001411311634200163365ustar00rootroot00000000000000typer-0.4.0/tests/test_compat/__init__.py000066400000000000000000000000001411311634200204350ustar00rootroot00000000000000typer-0.4.0/tests/test_compat/test_option_get_help.py000066400000000000000000000022141411311634200231250ustar00rootroot00000000000000import os import subprocess from typer.testing import CliRunner from tests.assets import compat_click7_8 as mod runner = CliRunner() def test_hidden_option(): result = runner.invoke(mod.app, ["--help"]) assert result.exit_code == 0 assert "Say hello" in result.output assert "--name" not in result.output assert "/lastname" in result.output assert "TEST_LASTNAME" in result.output assert "(dynamic)" in result.output def test_coverage_call(): result = runner.invoke(mod.app) assert result.exit_code == 0 assert "Hello John Doe, it seems you have 42" in result.output def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_COMPAT_CLICK7_8.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "compat_click7_8.py --nickname ", "_TYPER_COMPLETE_TESTING": "True", }, ) # TODO: when deprecating Click 7, remove second option assert "Jonny" in result.stdout or "_files" in result.stdout typer-0.4.0/tests/test_completion/000077500000000000000000000000001411311634200172245ustar00rootroot00000000000000typer-0.4.0/tests/test_completion/__init__.py000066400000000000000000000000001411311634200213230ustar00rootroot00000000000000typer-0.4.0/tests/test_completion/test_completion.py000066400000000000000000000120351411311634200230070ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from docs_src.first_steps import tutorial001 as mod def test_show_completion(): result = subprocess.run( [ "bash", "-c", f"{sys.executable} -m coverage run {mod.__file__} --show-completion", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "SHELL": "/bin/bash", "_TYPER_COMPLETE_TESTING": "True"}, ) assert "_TUTORIAL001.PY_COMPLETE=complete_bash" in result.stdout def test_install_completion(): bash_completion_path: Path = Path.home() / ".bashrc" text = "" if bash_completion_path.is_file(): # pragma: nocover text = bash_completion_path.read_text() result = subprocess.run( [ "bash", "-c", f"{sys.executable} -m coverage run {mod.__file__} --install-completion", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "SHELL": "/bin/bash", "_TYPER_COMPLETE_TESTING": "True"}, ) new_text = bash_completion_path.read_text() bash_completion_path.write_text(text) assert "source" in new_text assert ".bash_completions/tutorial001.py.sh" in new_text assert "completion installed in" in result.stdout assert "Completion will take effect once you restart the terminal" in result.stdout def test_completion_invalid_instruction(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "sourcebash", "_TYPER_COMPLETE_TESTING": "True", }, ) assert result.returncode != 0 assert "Invalid completion instruction." in result.stderr def test_completion_source_bash(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "source_bash", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( "complete -o default -F _tutorial001py_completion tutorial001.py" in result.stdout ) def test_completion_source_invalid_shell(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "source_xxx", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "Shell xxx not supported." in result.stderr def test_completion_source_invalid_instruction(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "explode_bash", "_TYPER_COMPLETE_TESTING": "True", }, ) assert 'Completion instruction "explode" not supported.' in result.stderr def test_completion_source_zsh(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "source_zsh", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "compdef _tutorial001py_completion tutorial001.py" in result.stdout def test_completion_source_fish(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "source_fish", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "complete --command tutorial001.py --no-files" in result.stdout def test_completion_source_powershell(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "source_powershell", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( "Register-ArgumentCompleter -Native -CommandName tutorial001.py -ScriptBlock $scriptblock" in result.stdout ) def test_completion_source_pwsh(): result = subprocess.run( ["coverage", "run", mod.__file__], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "source_pwsh", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( "Register-ArgumentCompleter -Native -CommandName tutorial001.py -ScriptBlock $scriptblock" in result.stdout ) typer-0.4.0/tests/test_completion/test_completion_complete.py000066400000000000000000000126571411311634200247110ustar00rootroot00000000000000import os import subprocess from docs_src.commands.help import tutorial001 as mod def test_completion_complete_subcommand_bash(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial001.py del", "COMP_CWORD": "1", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "delete\ndelete-all" in result.stdout def test_completion_complete_subcommand_bash_invalid(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial001.py del", "COMP_CWORD": "42", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "create\ndelete\ndelete-all\ninit" in result.stdout def test_completion_complete_subcommand_zsh(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial001.py del", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( """_arguments '*: :(("delete":"Delete a user with USERNAME."\n""" """\"delete-all":"Delete ALL users in the database."))'""" ) in result.stdout def test_completion_complete_subcommand_zsh_files(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial001.py delete ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ("_files") in result.stdout def test_completion_complete_subcommand_fish(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_fish", "_TYPER_COMPLETE_ARGS": "tutorial001.py del", "_TYPER_COMPLETE_FISH_ACTION": "get-args", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( "delete\tDelete a user with USERNAME.\ndelete-all\tDelete ALL users in the database." in result.stdout ) def test_completion_complete_subcommand_fish_should_complete(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_fish", "_TYPER_COMPLETE_ARGS": "tutorial001.py del", "_TYPER_COMPLETE_FISH_ACTION": "is-args", "_TYPER_COMPLETE_TESTING": "True", }, ) assert result.returncode == 0 def test_completion_complete_subcommand_fish_should_complete_no(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_fish", "_TYPER_COMPLETE_ARGS": "tutorial001.py delete ", "_TYPER_COMPLETE_FISH_ACTION": "is-args", "_TYPER_COMPLETE_TESTING": "True", }, ) assert result.returncode != 0 def test_completion_complete_subcommand_powershell(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_powershell", "_TYPER_COMPLETE_ARGS": "tutorial001.py del", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( "delete:::Delete a user with USERNAME.\ndelete-all:::Delete ALL users in the database." ) in result.stdout def test_completion_complete_subcommand_pwsh(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_pwsh", "_TYPER_COMPLETE_ARGS": "tutorial001.py del", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ( "delete:::Delete a user with USERNAME.\ndelete-all:::Delete ALL users in the database." ) in result.stdout def test_completion_complete_subcommand_noshell(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL001.PY_COMPLETE": "complete_noshell", "_TYPER_COMPLETE_ARGS": "tutorial001.py del", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ("") in result.stdout typer-0.4.0/tests/test_completion/test_completion_complete_no_help.py000066400000000000000000000040761411311634200264110ustar00rootroot00000000000000import os import subprocess from docs_src.commands.index import tutorial002 as mod def test_completion_complete_subcommand_zsh(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL002.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial002.py ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "create" in result.stdout assert "delete" in result.stdout def test_completion_complete_subcommand_fish(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL002.PY_COMPLETE": "complete_fish", "_TYPER_COMPLETE_ARGS": "tutorial002.py ", "_TYPER_COMPLETE_FISH_ACTION": "get-args", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "create\ndelete" in result.stdout def test_completion_complete_subcommand_powershell(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL002.PY_COMPLETE": "complete_powershell", "_TYPER_COMPLETE_ARGS": "tutorial002.py ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ("create::: \ndelete::: ") in result.stdout def test_completion_complete_subcommand_pwsh(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL002.PY_COMPLETE": "complete_pwsh", "_TYPER_COMPLETE_ARGS": "tutorial002.py ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert ("create::: \ndelete::: ") in result.stdout typer-0.4.0/tests/test_completion/test_completion_install.py000066400000000000000000000126421411311634200245410ustar00rootroot00000000000000import os import subprocess from pathlib import Path from unittest import mock import shellingham import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion_install_no_shell(): result = subprocess.run( ["coverage", "run", mod.__file__, "--install-completion"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) # TODO: when deprecating Click 7, remove second option assert ( "Error: Option '--install-completion' requires an argument" in result.stderr or "Error: --install-completion option requires an argument" in result.stderr ) def test_completion_install_bash(): bash_completion_path: Path = Path.home() / ".bashrc" text = "" if bash_completion_path.is_file(): text = bash_completion_path.read_text() result = subprocess.run( ["coverage", "run", mod.__file__, "--install-completion", "bash"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) new_text = bash_completion_path.read_text() bash_completion_path.write_text(text) install_source = ".bash_completions/tutorial001.py.sh" assert install_source not in text assert install_source in new_text assert "completion installed in" in result.stdout assert "Completion will take effect once you restart the terminal" in result.stdout install_source_path = Path.home() / install_source assert install_source_path.is_file() install_content = install_source_path.read_text() install_source_path.unlink() assert ( "complete -o default -F _tutorial001py_completion tutorial001.py" in install_content ) def test_completion_install_zsh(): completion_path: Path = Path.home() / ".zshrc" text = "" if not completion_path.is_file(): # pragma: nocover completion_path.write_text('echo "custom .zshrc"') if completion_path.is_file(): text = completion_path.read_text() result = subprocess.run( ["coverage", "run", mod.__file__, "--install-completion", "zsh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) new_text = completion_path.read_text() completion_path.write_text(text) zfunc_fragment = "fpath+=~/.zfunc" assert zfunc_fragment in new_text assert "completion installed in" in result.stdout assert "Completion will take effect once you restart the terminal" in result.stdout install_source_path = Path.home() / ".zfunc/_tutorial001.py" assert install_source_path.is_file() install_content = install_source_path.read_text() install_source_path.unlink() assert "compdef _tutorial001py_completion tutorial001.py" in install_content def test_completion_install_fish(): script_path = Path(mod.__file__) completion_path: Path = Path.home() / f".config/fish/completions/{script_path.name}.fish" result = subprocess.run( ["coverage", "run", mod.__file__, "--install-completion", "fish"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) new_text = completion_path.read_text() completion_path.unlink() assert "complete --command tutorial001.py" in new_text assert "completion installed in" in result.stdout assert "Completion will take effect once you restart the terminal" in result.stdout runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion_install_powershell(): completion_path: Path = Path.home() / f".config/powershell/Microsoft.PowerShell_profile.ps1" completion_path_bytes = f"{completion_path}\n".encode("windows-1252") text = "" if completion_path.is_file(): # pragma: nocover text = completion_path.read_text() with mock.patch.object( shellingham, "detect_shell", return_value=("pwsh", "/usr/bin/pwsh") ): with mock.patch.object( subprocess, "run", return_value=subprocess.CompletedProcess( ["pwsh"], returncode=0, stdout=completion_path_bytes ), ): result = runner.invoke(app, ["--install-completion"]) install_script = "Register-ArgumentCompleter -Native -CommandName mocked-typer-testing-app -ScriptBlock $scriptblock" parent: Path = completion_path.parent parent.mkdir(parents=True, exist_ok=True) completion_path.write_text(install_script) new_text = completion_path.read_text() completion_path.write_text(text) assert install_script not in text assert install_script in new_text assert "completion installed in" in result.stdout assert "Completion will take effect once you restart the terminal" in result.stdout typer-0.4.0/tests/test_completion/test_completion_show.py000066400000000000000000000063241411311634200240530ustar00rootroot00000000000000import os import subprocess from docs_src.first_steps import tutorial001 as mod def test_completion_show_no_shell(): result = subprocess.run( ["coverage", "run", mod.__file__, "--show-completion"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) # TODO: when deprecating Click 7, remove second option assert ( "Error: Option '--show-completion' requires an argument" in result.stderr or "Error: --show-completion option requires an argument" in result.stderr ) def test_completion_show_bash(): result = subprocess.run( ["coverage", "run", mod.__file__, "--show-completion", "bash"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) assert ( "complete -o default -F _tutorial001py_completion tutorial001.py" in result.stdout ) def test_completion_source_zsh(): result = subprocess.run( ["coverage", "run", mod.__file__, "--show-completion", "zsh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) assert "compdef _tutorial001py_completion tutorial001.py" in result.stdout def test_completion_source_fish(): result = subprocess.run( ["coverage", "run", mod.__file__, "--show-completion", "fish"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) assert "complete --command tutorial001.py --no-files" in result.stdout def test_completion_source_powershell(): result = subprocess.run( ["coverage", "run", mod.__file__, "--show-completion", "powershell"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) assert ( "Register-ArgumentCompleter -Native -CommandName tutorial001.py -ScriptBlock $scriptblock" in result.stdout ) def test_completion_source_pwsh(): result = subprocess.run( ["coverage", "run", mod.__file__, "--show-completion", "pwsh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TYPER_COMPLETE_TESTING": "True", "_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION": "True", }, ) assert ( "Register-ArgumentCompleter -Native -CommandName tutorial001.py -ScriptBlock $scriptblock" in result.stdout ) typer-0.4.0/tests/test_others.py000066400000000000000000000160561411311634200167410ustar00rootroot00000000000000import os import subprocess from pathlib import Path from typing import Optional from unittest import mock import click import pytest import shellingham import typer import typer.completion from typer.main import solve_typer_info_defaults, solve_typer_info_help from typer.models import TyperInfo from typer.testing import CliRunner runner = CliRunner() def test_optional(): app = typer.Typer() @app.command() def opt(user: Optional[str] = None): if user: typer.echo(f"User: {user}") else: typer.echo("No user") result = runner.invoke(app) assert result.exit_code == 0 assert "No user" in result.output result = runner.invoke(app, ["--user", "Camila"]) assert result.exit_code == 0 assert "User: Camila" in result.output def test_no_type(): app = typer.Typer() @app.command() def no_type(user): typer.echo(f"User: {user}") result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "User: Camila" in result.output def test_help_from_info(): # Mainly for coverage/completeness value = solve_typer_info_help(TyperInfo()) assert value is None def test_defaults_from_info(): # Mainly for coverage/completeness value = solve_typer_info_defaults(TyperInfo()) assert value def test_install_invalid_shell(): app = typer.Typer() @app.command() def main(): typer.echo("Hello World") with mock.patch.object( shellingham, "detect_shell", return_value=("xshell", "/usr/bin/xshell") ): result = runner.invoke(app, ["--install-completion"]) assert "Shell xshell is not supported." in result.stdout result = runner.invoke(app) assert "Hello World" in result.stdout def test_callback_too_many_parameters(): app = typer.Typer() def name_callback(ctx, param, val1, val2): pass # pragma: nocover @app.command() def main(name: str = typer.Option(..., callback=name_callback)): pass # pragma: nocover with pytest.raises(click.ClickException) as exc_info: runner.invoke(app, ["--name", "Camila"]) assert ( exc_info.value.message == "Too many CLI parameter callback function parameters" ) def test_callback_2_untyped_parameters(): app = typer.Typer() def name_callback(ctx, value): typer.echo(f"info name is: {ctx.info_name}") typer.echo(f"value is: {value}") @app.command() def main(name: str = typer.Option(..., callback=name_callback)): typer.echo("Hello World") result = runner.invoke(app, ["--name", "Camila"]) assert "info name is: main" in result.stdout assert "value is: Camila" in result.stdout def test_callback_3_untyped_parameters(): app = typer.Typer() def name_callback(ctx, param, value): typer.echo(f"info name is: {ctx.info_name}") typer.echo(f"param name is: {param.name}") typer.echo(f"value is: {value}") @app.command() def main(name: str = typer.Option(..., callback=name_callback)): typer.echo("Hello World") result = runner.invoke(app, ["--name", "Camila"]) assert "info name is: main" in result.stdout assert "param name is: name" in result.stdout assert "value is: Camila" in result.stdout def test_completion_untyped_parameters(): file_path = Path(__file__).parent / "assets/completion_no_types.py" result = subprocess.run( ["coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_COMPLETION_NO_TYPES.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "completion_no_types.py --name Sebastian --name Ca", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "info name is: completion_no_types.py" in result.stderr # TODO: when deprecating Click 7, remove second option assert ( "args is: []" in result.stderr or "args is: ['--name', 'Sebastian', '--name']" in result.stderr ) assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout result = subprocess.run( ["coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Hello World" in result.stdout def test_completion_untyped_parameters_different_order_correct_names(): file_path = Path(__file__).parent / "assets/completion_no_types_order.py" result = subprocess.run( ["coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_COMPLETION_NO_TYPES_ORDER.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "completion_no_types_order.py --name Sebastian --name Ca", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "info name is: completion_no_types_order.py" in result.stderr # TODO: when deprecating Click 7, remove second option assert ( "args is: []" in result.stderr or "args is: ['--name', 'Sebastian', '--name']" in result.stderr ) assert "incomplete is: Ca" in result.stderr assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout result = subprocess.run( ["coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Hello World" in result.stdout def test_autocompletion_too_many_parameters(): app = typer.Typer() def name_callback(ctx, args, incomplete, val2): pass # pragma: nocover @app.command() def main(name: str = typer.Option(..., autocompletion=name_callback)): pass # pragma: nocover with pytest.raises(click.ClickException) as exc_info: runner.invoke(app, ["--name", "Camila"]) assert exc_info.value.message == "Invalid autocompletion callback parameters: val2" def test_forward_references(): app = typer.Typer() @app.command() def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False): typer.echo(f"arg1: {type(arg1)} {arg1}") typer.echo(f"arg2: {type(arg2)} {arg2}") typer.echo(f"arg3: {type(arg3)} {arg3}") typer.echo(f"arg4: {type(arg4)} {arg4}") typer.echo(f"arg5: {type(arg5)} {arg5}") result = runner.invoke(app, ["Hello", "2", "invalid"]) # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.stdout or "Error: Invalid value for 'ARG3': invalid is not a valid integer" in result.stdout ) result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"]) assert ( "arg1: Hello\narg2: 2\narg3: 3\narg4: True\narg5: True\n" in result.stdout ) typer-0.4.0/tests/test_prog_name.py000066400000000000000000000005711411311634200173770ustar00rootroot00000000000000import subprocess from pathlib import Path def test_custom_prog_name(): file_path = Path(__file__).parent / "assets/prog_name.py" result = subprocess.run( ["coverage", "run", str(file_path), "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage: custom-name [OPTIONS] I" in result.stdout typer-0.4.0/tests/test_tutorial/000077500000000000000000000000001411311634200167165ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/__init__.py000066400000000000000000000000001411311634200210150ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/000077500000000000000000000000001411311634200217625ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/__init__.py000066400000000000000000000000001411311634200240610ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_default/000077500000000000000000000000001411311634200244455ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_default/__init__.py000066400000000000000000000000001411311634200265440ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_default/test_tutorial001.py000066400000000000000000000016641411311634200301510ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.default import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Arguments:" in result.output assert "[default: Wade Wilson]" in result.output def test_call_no_arg(): result = runner.invoke(app) assert result.exit_code == 0 assert "Hello Wade Wilson" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_default/test_tutorial002.py000066400000000000000000000020611411311634200301420ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.default import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Arguments:" in result.output assert "[default: (dynamic)]" in result.output def test_call_no_arg(): greetings = ["Hello Deadpool", "Hello Rick", "Hello Morty", "Hello Hiro"] for i in range(3): result = runner.invoke(app) assert result.exit_code == 0 assert any(greet in result.output for greet in greetings) def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_envvar/000077500000000000000000000000001411311634200243225ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_envvar/__init__.py000066400000000000000000000000001411311634200264210ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py000066400000000000000000000022671411311634200300260ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.envvar import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Arguments:" in result.output assert "[env var: AWESOME_NAME;default: World]" in result.output def test_call_arg(): result = runner.invoke(app, ["Wednesday"]) assert result.exit_code == 0 assert "Hello Mr. Wednesday" in result.output def test_call_env_var(): result = runner.invoke(app, env={"AWESOME_NAME": "Wednesday"}) assert result.exit_code == 0 assert "Hello Mr. Wednesday" in result.output def test_call_env_var_arg(): result = runner.invoke(app, ["Czernobog"], env={"AWESOME_NAME": "Wednesday"}) assert result.exit_code == 0 assert "Hello Mr. Czernobog" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002.py000066400000000000000000000022461411311634200300240ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.envvar import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Arguments:" in result.output assert "[env var: AWESOME_NAME, GOD_NAME;default: World]" in result.output def test_call_arg(): result = runner.invoke(app, ["Wednesday"]) assert result.exit_code == 0 assert "Hello Mr. Wednesday" in result.output def test_call_env_var1(): result = runner.invoke(app, env={"AWESOME_NAME": "Wednesday"}) assert result.exit_code == 0 assert "Hello Mr. Wednesday" in result.output def test_call_env_var2(): result = runner.invoke(app, env={"GOD_NAME": "Anubis"}) assert result.exit_code == 0 assert "Hello Mr. Anubis" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003.py000066400000000000000000000023521411311634200300230ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.envvar import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Arguments:" in result.output assert "[env var: AWESOME_NAME;default: World]" not in result.output assert "[default: World]" in result.output def test_call_arg(): result = runner.invoke(app, ["Wednesday"]) assert result.exit_code == 0 assert "Hello Mr. Wednesday" in result.output def test_call_env_var(): result = runner.invoke(app, env={"AWESOME_NAME": "Wednesday"}) assert result.exit_code == 0 assert "Hello Mr. Wednesday" in result.output def test_call_env_var_arg(): result = runner.invoke(app, ["Czernobog"], env={"AWESOME_NAME": "Wednesday"}) assert result.exit_code == 0 assert "Hello Mr. Czernobog" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/000077500000000000000000000000001411311634200237515ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_help/__init__.py000066400000000000000000000000001411311634200260500ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial001.py000066400000000000000000000015671411311634200274570ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] NAME" in result.output assert "Arguments:" in result.output assert "NAME" in result.output assert "The name of the user to greet" in result.output assert "[required]" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial002.py000066400000000000000000000016741411311634200274570ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] NAME" in result.output assert "Say hi to NAME very gently, like Dirk." in result.output assert "Arguments:" in result.output assert "NAME" in result.output assert "The name of the user to greet" in result.output assert "[required]" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial003.py000066400000000000000000000016631411311634200274560ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Say hi to NAME very gently, like Dirk." in result.output assert "Arguments:" in result.output assert "NAME" in result.output assert "Who to greet" in result.output assert "[default: World]" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial004.py000066400000000000000000000016671411311634200274630ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Say hi to NAME very gently, like Dirk." in result.output assert "Arguments:" in result.output assert "NAME" in result.output assert "Who to greet" in result.output assert "[default: World]" not in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial005.py000066400000000000000000000015451411311634200274570ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial005 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Arguments:" in result.output assert "Who to greet" in result.output assert "[default: (Deadpoolio the amazing's name)]" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial006.py000066400000000000000000000015251411311634200274560ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial006 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] ✨username✨" in result.output assert "Arguments:" in result.output assert "✨username✨" in result.output assert "[default: World]" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_help/test_tutorial007.py000066400000000000000000000015551411311634200274620ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial007 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output assert "Say hi to NAME very gently, like Dirk." in result.output assert "Arguments:" not in result.output assert "[default: World]" not in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_optional/000077500000000000000000000000001411311634200246465ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_optional/__init__.py000066400000000000000000000000001411311634200267450ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py000066400000000000000000000013261411311634200303450ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.optional import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_call_no_arg(): result = runner.invoke(app) assert result.exit_code != 0 assert "Error: Missing argument 'NAME'." in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial002.py000066400000000000000000000015221411311634200303440ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.arguments.optional import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAME]" in result.output def test_call_no_arg(): result = runner.invoke(app) assert result.exit_code == 0 assert "Hello World!" in result.output def test_call_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/000077500000000000000000000000001411311634200215565ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/__init__.py000066400000000000000000000000001411311634200236550ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_arguments/000077500000000000000000000000001411311634200246225ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_arguments/__init__.py000066400000000000000000000000001411311634200267210ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_arguments/test_tutorial001.py000066400000000000000000000020251411311634200303160ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.arguments import tutorial001 as mod app = mod.app runner = CliRunner() def test_help_create(): result = runner.invoke(app, ["create", "--help"]) assert result.exit_code == 0 assert "create [OPTIONS] USERNAME" in result.output def test_help_delete(): result = runner.invoke(app, ["delete", "--help"]) assert result.exit_code == 0 assert "delete [OPTIONS] USERNAME" in result.output def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_callback/000077500000000000000000000000001411311634200243515ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_callback/__init__.py000066400000000000000000000000001411311634200264500ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py000066400000000000000000000036151411311634200300530ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.callback import tutorial001 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Manage users in the awesome CLI app." in result.output assert "--verbose / --no-verbose" in result.output def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_create_verbose(): result = runner.invoke(app, ["--verbose", "create", "Camila"]) assert result.exit_code == 0 assert "Will write verbose output" in result.output assert "About to create a user" in result.output assert "Creating user: Camila" in result.output assert "Just created a user" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_delete_verbose(): result = runner.invoke(app, ["--verbose", "delete", "Camila"]) assert result.exit_code == 0 assert "Will write verbose output" in result.output assert "About to delete a user" in result.output assert "Deleting user: Camila" in result.output assert "Just deleted a user" in result.output def test_wrong_verbose(): result = runner.invoke(app, ["delete", "--verbose", "Camila"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: No such option: --verbose" in result.output or "Error: no such option: --verbose" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_callback/test_tutorial002.py000066400000000000000000000011171411311634200300470ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.callback import tutorial002 as mod app = mod.app runner = CliRunner() def test_app(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Running a command" in result.output assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_callback/test_tutorial003.py000066400000000000000000000013041411311634200300460ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.callback import tutorial003 as mod app = mod.app runner = CliRunner() def test_app(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Override callback, running a command" in result.output assert "Running a command" not in result.output assert "Creating user: Camila" in result.output def test_for_coverage(): mod.callback() def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_callback/test_tutorial004.py000066400000000000000000000014771411311634200300620ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.callback import tutorial004 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Manage users CLI app." in result.output assert "Use it with the create command." in result.output assert "A new user with the given NAME will be created." in result.output def test_app(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_context/000077500000000000000000000000001411311634200243015ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_context/__init__.py000066400000000000000000000000001411311634200264000ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_context/test_tutorial001.py000066400000000000000000000014771411311634200300070ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.context import tutorial001 as mod app = mod.app runner = CliRunner() def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "About to execute command: create" in result.output assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "About to execute command: delete" in result.output assert "Deleting user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_context/test_tutorial002.py000066400000000000000000000016651411311634200300070ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.context import tutorial002 as mod app = mod.app runner = CliRunner() def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Initializing database" in result.output assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "Initializing database" in result.output assert "Deleting user: Camila" in result.output def test_callback(): result = runner.invoke(app) assert result.exit_code == 0 assert "Initializing database" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_context/test_tutorial003.py000066400000000000000000000016751411311634200300110ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.context import tutorial003 as mod app = mod.app runner = CliRunner() def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Initializing database" not in result.output assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "Initializing database" not in result.output assert "Deleting user: Camila" in result.output def test_callback(): result = runner.invoke(app) assert result.exit_code == 0 assert "Initializing database" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_context/test_tutorial004.py000066400000000000000000000013141411311634200300000ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.context import tutorial004 as mod app = mod.app runner = CliRunner() def test_1(): result = runner.invoke(app, ["--name", "Camila", "--city", "Berlin"]) assert result.exit_code == 0 assert "Got extra arg: --name" in result.output assert "Got extra arg: Camila" in result.output assert "Got extra arg: --city" in result.output assert "Got extra arg: Berlin" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_help/000077500000000000000000000000001411311634200235455ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_help/__init__.py000066400000000000000000000000001411311634200256440ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_help/test_tutorial001.py000066400000000000000000000100461411311634200272430ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.help import tutorial001 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Awesome CLI user manager." in result.output assert "create" in result.output assert "Create a new user with USERNAME." in result.output assert "delete" in result.output assert "Delete a user with USERNAME." in result.output assert "delete-all" in result.output assert "Delete ALL users in the database." in result.output assert "init" in result.output assert "Initialize the users database." in result.output def test_help_create(): result = runner.invoke(app, ["create", "--help"]) assert result.exit_code == 0 assert "create [OPTIONS] USERNAME" in result.output assert "Create a new user with USERNAME." in result.output def test_help_delete(): result = runner.invoke(app, ["delete", "--help"]) assert result.exit_code == 0 assert "delete [OPTIONS] USERNAME" in result.output assert "Delete a user with USERNAME." in result.output assert "--force / --no-force" in result.output assert "Force deletion without confirmation." in result.output def test_help_delete_all(): result = runner.invoke(app, ["delete-all", "--help"]) assert result.exit_code == 0 assert "delete-all [OPTIONS]" in result.output assert "Delete ALL users in the database." in result.output assert "If --force is not used, will ask for confirmation." in result.output assert "[required]" in result.output assert "--force / --no-force" in result.output assert "Force deletion without confirmation." in result.output def test_help_init(): result = runner.invoke(app, ["init", "--help"]) assert result.exit_code == 0 assert "init [OPTIONS]" in result.output assert "Initialize the users database." in result.output def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"], input="y\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete the user? [y/n]:" in result.output or "Are you sure you want to delete the user? [y/N]:" in result.output ) assert "Deleting user: Camila" in result.output def test_no_delete(): result = runner.invoke(app, ["delete", "Camila"], input="n\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete the user? [y/n]:" in result.output or "Are you sure you want to delete the user? [y/N]:" in result.output ) assert "Operation cancelled" in result.output def test_delete_all(): result = runner.invoke(app, ["delete-all"], input="y\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete ALL users? [y/n]:" in result.output or "Are you sure you want to delete ALL users? [y/N]:" in result.output ) assert "Deleting all users" in result.output def test_no_delete_all(): result = runner.invoke(app, ["delete-all"], input="n\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete ALL users? [y/n]:" in result.output or "Are you sure you want to delete ALL users? [y/N]:" in result.output ) assert "Operation cancelled" in result.output def test_init(): result = runner.invoke(app, ["init"]) assert result.exit_code == 0 assert "Initializing user database" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_help/test_tutorial002.py000066400000000000000000000031561411311634200272500ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.help import tutorial002 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "create" in result.output assert "Create a new user with USERNAME." in result.output assert "delete" in result.output assert "Delete a user with USERNAME." in result.output assert "Some internal utility function to create." not in result.output assert "Some internal utility function to delete." not in result.output def test_help_create(): result = runner.invoke(app, ["create", "--help"]) assert result.exit_code == 0 assert "Create a new user with USERNAME." in result.output assert "Some internal utility function to create." not in result.output def test_help_delete(): result = runner.invoke(app, ["delete", "--help"]) assert result.exit_code == 0 assert "Delete a user with USERNAME." in result.output assert "Some internal utility function to delete." not in result.output def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_index/000077500000000000000000000000001411311634200237245ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_index/__init__.py000066400000000000000000000000001411311634200260230ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_index/test_tutorial001.py000066400000000000000000000012351411311634200274220ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.index import tutorial001 as mod app = mod.app runner = CliRunner() def test_no_arg(): result = runner.invoke(app) assert result.exit_code != 0 assert "Error: Missing argument 'NAME'." in result.output def test_arg(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_index/test_tutorial002.py000066400000000000000000000017011411311634200274210ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.index import tutorial002 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output def test_create(): result = runner.invoke(app, ["create"]) assert result.exit_code == 0 assert "Creating user: Hiro Hamada" in result.output def test_delete(): result = runner.invoke(app, ["delete"]) assert result.exit_code == 0 assert "Deleting user: Hiro Hamada" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_name/000077500000000000000000000000001411311634200235355ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_name/__init__.py000066400000000000000000000000001411311634200256340ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_name/test_tutorial001.py000066400000000000000000000016201411311634200272310ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.name import tutorial001 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_one_or_multiple/000077500000000000000000000000001411311634200260115ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_one_or_multiple/__init__.py000066400000000000000000000000001411311634200301100ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial001.py000066400000000000000000000013221411311634200315040ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.one_or_multiple import tutorial001 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "create" in result.output def test_command(): result = runner.invoke(app, ["create"]) assert result.exit_code == 0 assert "Creating user: Hiro Hamada" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial002.py000066400000000000000000000015421411311634200315110ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.one_or_multiple import tutorial002 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Creates a single user Hiro Hamada." in result.output assert "In the next version it will create 5 users more." in result.output assert "Commands:" in result.output assert "create" in result.output def test_command(): result = runner.invoke(app, ["create"]) assert result.exit_code == 0 assert "Creating user: Hiro Hamada" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_commands/test_options/000077500000000000000000000000001411311634200243105ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_options/__init__.py000066400000000000000000000000001411311634200264070ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_commands/test_options/test_tutorial001.py000066400000000000000000000057141411311634200300140ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.commands.options import tutorial001 as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "delete-all" in result.output assert "init" in result.output def test_create(): result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_delete(): result = runner.invoke(app, ["delete", "Camila"], input="y\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete the user? [y/n]:" in result.output or "Are you sure you want to delete the user? [y/N]:" in result.output ) assert "Deleting user: Camila" in result.output def test_no_delete(): result = runner.invoke(app, ["delete", "Camila"], input="n\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete the user? [y/n]:" in result.output or "Are you sure you want to delete the user? [y/N]:" in result.output ) assert "Operation cancelled" in result.output def test_delete_all(): result = runner.invoke(app, ["delete-all"], input="y\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete ALL users? [y/n]:" in result.output or "Are you sure you want to delete ALL users? [y/N]:" in result.output ) assert "Deleting all users" in result.output def test_no_delete_all(): result = runner.invoke(app, ["delete-all"], input="n\n") assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete ALL users? [y/n]:" in result.output or "Are you sure you want to delete ALL users? [y/N]:" in result.output ) assert "Operation cancelled" in result.output def test_delete_all_force(): result = runner.invoke(app, ["delete-all", "--force"]) assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Are you sure you want to delete ALL users? [y/n]:" not in result.output or "Are you sure you want to delete ALL users? [y/N]:" not in result.output ) assert "Deleting all users" in result.output def test_init(): result = runner.invoke(app, ["init"]) assert result.exit_code == 0 assert "Initializing user database" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_first_steps/000077500000000000000000000000001411311634200223225ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_first_steps/__init__.py000066400000000000000000000000001411311634200244210ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_first_steps/test_tutorial001.py000066400000000000000000000010201411311634200260100ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial001 as mod runner = CliRunner() def test_cli(): app = typer.Typer() app.command()(mod.main) result = runner.invoke(app, []) assert result.output == "Hello World\n" def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_first_steps/test_tutorial002.py000066400000000000000000000013011411311634200260130ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, []) assert result.exit_code != 0 assert "Error: Missing argument 'NAME'" in result.output def test_2(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_first_steps/test_tutorial003.py000066400000000000000000000013461411311634200260250ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, ["Camila"]) assert result.exit_code != 0 assert "Error: Missing argument 'LASTNAME'" in result.output def test_2(): result = runner.invoke(app, ["Camila", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_first_steps/test_tutorial004.py000066400000000000000000000026341411311634200260270ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Arguments:" in result.output assert "NAME [required]" in result.output assert "LASTNAME [required]" in result.output assert "--formal / --no-formal" in result.output def test_1(): result = runner.invoke(app, ["Camila", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_formal_1(): result = runner.invoke(app, ["Camila", "Gutiérrez", "--formal"]) assert result.exit_code == 0 assert "Good day Ms. Camila Gutiérrez." in result.output def test_formal_2(): result = runner.invoke(app, ["Camila", "--formal", "Gutiérrez"]) assert result.exit_code == 0 assert "Good day Ms. Camila Gutiérrez." in result.output def test_formal_3(): result = runner.invoke(app, ["--formal", "Camila", "Gutiérrez"]) assert result.exit_code == 0 assert "Good day Ms. Camila Gutiérrez." in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_first_steps/test_tutorial005.py000066400000000000000000000026201411311634200260230ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial005 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Arguments:" in result.output assert "NAME [required]" in result.output assert "--lastname TEXT" in result.output assert "--formal / --no-formal" in result.output def test_1(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_option_lastname(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_option_lastname_2(): result = runner.invoke(app, ["--lastname", "Gutiérrez", "Camila"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_formal_1(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez", "--formal"]) assert result.exit_code == 0 assert "Good day Ms. Camila Gutiérrez." in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_first_steps/test_tutorial006.py000066400000000000000000000025521411311634200260300ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.first_steps import tutorial006 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Say hi to NAME, optionally with a --lastname." in result.output assert "If --formal is used, say hi very formally." in result.output def test_1(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_option_lastname(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_option_lastname_2(): result = runner.invoke(app, ["--lastname", "Gutiérrez", "Camila"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_formal_1(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez", "--formal"]) assert result.exit_code == 0 assert "Good day Ms. Camila Gutiérrez." in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_multiple_values/000077500000000000000000000000001411311634200231675ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/__init__.py000066400000000000000000000000001411311634200252660ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/000077500000000000000000000000001411311634200325605ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/__init__.py000066400000000000000000000000001411311634200346570ustar00rootroot00000000000000test_tutorial001.py000066400000000000000000000013241411311634200361760ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_valuesimport subprocess import typer from typer.testing import CliRunner from docs_src.multiple_values.arguments_with_multiple_values import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app, ["README.md", "pyproject.toml", "woohoo!"]) assert result.exit_code == 0 assert "This file exists: README.md\nwoohoo!" in result.output assert "This file exists: pyproject.toml\nwoohoo!" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout test_tutorial002.py000066400000000000000000000027261411311634200362060ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_valuesimport subprocess import typer from typer.testing import CliRunner from docs_src.multiple_values.arguments_with_multiple_values import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] [NAMES]..." in result.output assert "Arguments:" in result.output assert "[default: Harry, Hermione, Ron]" in result.output def test_defaults(): result = runner.invoke(app) assert result.exit_code == 0 assert "Hello Harry" in result.output assert "Hello Hermione" in result.output assert "Hello Ron" in result.output def test_invalid_args(): result = runner.invoke(app, ["Draco", "Hagrid"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Argument 'names' takes 3 values" in result.stdout or "Error: argument names takes 3 values" in result.stdout ) def test_valid_args(): result = runner.invoke(app, ["Draco", "Hagrid", "Dobby"]) assert result.exit_code == 0 assert "Hello Draco" in result.stdout assert "Hello Hagrid" in result.stdout assert "Hello Dobby" in result.stdout def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_multiple_values/test_multiple_options/000077500000000000000000000000001411311634200276345ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_multiple_options/__init__.py000066400000000000000000000000001411311634200317330ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001.py000066400000000000000000000021061411311634200333300ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.multiple_values.multiple_options import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app) assert result.exit_code != 0 assert "No provided users" in result.output assert "Aborted!" in result.output def test_1_user(): result = runner.invoke(app, ["--user", "Camila"]) assert result.exit_code == 0 assert "Processing user: Camila" in result.output def test_3_user(): result = runner.invoke( app, ["--user", "Camila", "--user", "Rick", "--user", "Morty"] ) assert result.exit_code == 0 assert "Processing user: Camila" in result.output assert "Processing user: Rick" in result.output assert "Processing user: Morty" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002.py000066400000000000000000000016161411311634200333360ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.multiple_values.multiple_options import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app) assert result.exit_code == 0 assert "The sum is 0" in result.output def test_1_number(): result = runner.invoke(app, ["--number", "2"]) assert result.exit_code == 0 assert "The sum is 2.0" in result.output def test_2_number(): result = runner.invoke(app, ["--number", "2", "--number", "3", "--number", "4.5"]) assert result.exit_code == 0 assert "The sum is 9.5" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/000077500000000000000000000000001411311634200322465ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/__init__.py000066400000000000000000000000001411311634200343450ustar00rootroot00000000000000test_tutorial001.py000066400000000000000000000026501411311634200356670ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_valuesimport subprocess import typer from typer.testing import CliRunner from docs_src.multiple_values.options_with_multiple_values import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app) assert result.exit_code != 0 assert "No user provided" in result.output assert "Aborted!" in result.output def test_user_1(): result = runner.invoke(app, ["--user", "Camila", "50", "yes"]) assert result.exit_code == 0 assert "The username Camila has 50 coins" in result.output assert "And this user is a wizard!" in result.output def test_user_2(): result = runner.invoke(app, ["--user", "Morty", "3", "no"]) assert result.exit_code == 0 assert "The username Morty has 3 coins" in result.output assert "And this user is a wizard!" not in result.output def test_invalid_user(): result = runner.invoke(app, ["--user", "Camila", "50"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Option '--user' requires 3 arguments" in result.output or "Error: --user option requires 3 arguments" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/000077500000000000000000000000001411311634200214505ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/__init__.py000066400000000000000000000000001411311634200235470ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_callback/000077500000000000000000000000001411311634200242435ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_callback/__init__.py000066400000000000000000000000001411311634200263420ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_callback/test_tutorial001.py000066400000000000000000000013731411311634200277440ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.callback import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_2(): result = runner.invoke(app, ["--name", "rick"]) assert result.exit_code != 0 assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_callback/test_tutorial003.py000066400000000000000000000024071411311634200277450ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.callback import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Validating name" in result.output assert "Hello Camila" in result.output def test_2(): result = runner.invoke(app, ["--name", "rick"]) assert result.exit_code != 0 assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL003.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial003.py --", "COMP_CWORD": "1", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "--name" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_callback/test_tutorial004.py000066400000000000000000000024161411311634200277460ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.callback import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Validating param: name" in result.output assert "Hello Camila" in result.output def test_2(): result = runner.invoke(app, ["--name", "rick"]) assert result.exit_code != 0 assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL004.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial004.py --", "COMP_CWORD": "1", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "--name" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_completion/000077500000000000000000000000001411311634200246605ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_completion/__init__.py000066400000000000000000000000001411311634200267570ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_completion/test_tutorial002.py000066400000000000000000000021361411311634200303600ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.autocompletion import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL002.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial002.py --name ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "Camila" in result.stdout assert "Carlos" in result.stdout assert "Sebastian" in result.stdout def test_1(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_completion/test_tutorial003.py000066400000000000000000000021511411311634200303560ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.autocompletion import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL003.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial003.py --name Seb", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "Camila" not in result.stdout assert "Carlos" not in result.stdout assert "Sebastian" in result.stdout def test_1(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_completion/test_tutorial004.py000066400000000000000000000022521411311634200303610ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.autocompletion import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL004.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial004.py --name ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout def test_1(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_completion/test_tutorial007.py000066400000000000000000000024041411311634200303630ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.autocompletion import tutorial007 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL007.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial007.py --name Sebastian --name ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout def test_1(): result = runner.invoke(app, ["--name", "Camila", "--name", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_completion/test_tutorial008.py000066400000000000000000000025541411311634200303720ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.autocompletion import tutorial008 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL008.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial008.py --name ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' in result.stdout # TODO: when deprecating Click 7, remove second option assert "[]" in result.stderr or "['--name']" in result.stderr def test_1(): result = runner.invoke(app, ["--name", "Camila", "--name", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_completion/test_tutorial009.py000066400000000000000000000026301411311634200303660ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.autocompletion import tutorial009 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL009.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial009.py --name Sebastian --name ", "_TYPER_COMPLETE_TESTING": "True", }, ) assert '"Camila":"The reader of books."' in result.stdout assert '"Carlos":"The writer of scripts."' in result.stdout assert '"Sebastian":"The type hints guy."' not in result.stdout # TODO: when deprecating Click 7, remove second option assert "[]" in result.stderr or "['--name', 'Sebastian', '--name']" in result.stderr def test_1(): result = runner.invoke(app, ["--name", "Camila", "--name", "Sebastian"]) assert result.exit_code == 0 assert "Hello Camila" in result.output assert "Hello Sebastian" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_help/000077500000000000000000000000001411311634200234375ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_help/__init__.py000066400000000000000000000000001411311634200255360ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_help/test_tutorial001.py000066400000000000000000000024251411311634200271370ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.help import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Say hi to NAME, optionally with a --lastname." in result.output assert "If --formal is used, say hi very formally." in result.output assert "Last name of person to greet." in result.output assert "Say hi formally." in result.output def test_1(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_option_lastname(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_formal(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez", "--formal"]) assert result.exit_code == 0 assert "Good day Ms. Camila Gutiérrez." in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_help/test_tutorial002.py000066400000000000000000000013631411311634200271400ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.help import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_call(): result = runner.invoke(app) assert result.exit_code == 0 assert "Hello Wade Wilson" in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--fullname TEXT" in result.output assert "[default: Wade Wilson]" not in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_name/000077500000000000000000000000001411311634200234275ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_name/__init__.py000066400000000000000000000000001411311634200255260ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_name/test_tutorial001.py000066400000000000000000000013741411311634200271310ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.name import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--name TEXT" in result.output assert "--user-name" not in result.output def test_call(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_name/test_tutorial002.py000066400000000000000000000016261411311634200271320ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.name import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "-n, --name TEXT" in result.output assert "--user-name" not in result.output def test_call(): result = runner.invoke(app, ["-n", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_call_long(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_name/test_tutorial003.py000066400000000000000000000014351411311634200271310ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.name import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "-n TEXT" in result.output assert "--user-name" not in result.output assert "--name" not in result.output def test_call(): result = runner.invoke(app, ["-n", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_name/test_tutorial004.py000066400000000000000000000016331411311634200271320ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.name import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "-n, --user-name TEXT" in result.output assert "--name" not in result.output def test_call(): result = runner.invoke(app, ["-n", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_call_long(): result = runner.invoke(app, ["--user-name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_name/test_tutorial005.py000066400000000000000000000023071411311634200271320ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.name import tutorial005 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "-n, --name TEXT" in result.output assert "-f, --formal" in result.output def test_call(): result = runner.invoke(app, ["-n", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_call_formal(): result = runner.invoke(app, ["-n", "Camila", "-f"]) assert result.exit_code == 0 assert "Good day Ms. Camila." in result.output def test_call_formal_condensed(): result = runner.invoke(app, ["-fn", "Camila"]) assert result.exit_code == 0 assert "Good day Ms. Camila." in result.output def test_call_condensed_wrong_order(): result = runner.invoke(app, ["-nf", "Camila"]) assert result.exit_code != 0 def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_prompt/000077500000000000000000000000001411311634200240305ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_prompt/__init__.py000066400000000000000000000000001411311634200261270ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_prompt/test_tutorial001.py000066400000000000000000000020031411311634200275200ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.prompt import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_lastname(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_option_lastname_prompt(): result = runner.invoke(app, ["Camila"], input="Gutiérrez") assert result.exit_code == 0 assert "Lastname: " in result.output assert "Hello Camila Gutiérrez" in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_prompt/test_tutorial002.py000066400000000000000000000020301411311634200275210ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.prompt import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_option_lastname(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_option_lastname_prompt(): result = runner.invoke(app, ["Camila"], input="Gutiérrez") assert result.exit_code == 0 assert "Please tell me your last name: " in result.output assert "Hello Camila Gutiérrez" in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py000066400000000000000000000027021411311634200275300ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.prompt import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_prompt(): result = runner.invoke(app, input="Old Project\nOld Project\n") assert result.exit_code == 0 assert "Deleting project Old Project" in result.output def test_prompt_not_equal(): result = runner.invoke( app, input="Old Project\nNew Spice\nOld Project\nOld Project\n" ) assert result.exit_code == 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: The two entered values do not match" in result.output or "Error: the two entered values do not match" in result.output ) assert "Deleting project Old Project" in result.output def test_option(): result = runner.invoke(app, ["--project-name", "Old Project"]) assert result.exit_code == 0 assert "Deleting project Old Project" in result.output assert "Project name: " not in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--project-name TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_required/000077500000000000000000000000001411311634200243275ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_required/__init__.py000066400000000000000000000000001411311634200264260ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_required/test_tutorial002.py000066400000000000000000000016771411311634200300400ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.options.required import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, ["Camila"]) assert result.exit_code != 0 assert "Error: Missing option '--lastname'." in result.output def test_option_lastname(): result = runner.invoke(app, ["Camila", "--lastname", "Gutiérrez"]) assert result.exit_code == 0 assert "Hello Camila Gutiérrez" in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_options/test_version/000077500000000000000000000000001411311634200241745ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_version/__init__.py000066400000000000000000000000001411311634200262730ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_options/test_version/test_tutorial003.py000066400000000000000000000026231411311634200276760ustar00rootroot00000000000000import os import subprocess import typer from typer.testing import CliRunner from docs_src.options.version import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_1(): result = runner.invoke(app, ["--name", "Rick", "--version"]) assert result.exit_code == 0 assert "Awesome CLI Version: 0.1.0" in result.output def test_2(): result = runner.invoke(app, ["--name", "rick"]) assert result.exit_code != 0 assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output def test_3(): result = runner.invoke(app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout def test_completion(): result = subprocess.run( ["coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL003.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial003.py --name Rick --v", "COMP_CWORD": "3", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "--version" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/000077500000000000000000000000001411311634200231615ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/__init__.py000066400000000000000000000000001411311634200252600ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_bool/000077500000000000000000000000001411311634200251535ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_bool/__init__.py000066400000000000000000000000001411311634200272520ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py000066400000000000000000000022631411311634200306530ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--force" in result.output assert "--no-force" not in result.output def test_no_force(): result = runner.invoke(app) assert result.exit_code == 0 assert "Not forcing" in result.output def test_force(): result = runner.invoke(app, ["--force"]) assert result.exit_code == 0 assert "Forcing operation" in result.output def test_invalid_no_force(): result = runner.invoke(app, ["--no-force"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: No such option: --no-force" in result.output or "Error: no such option: --no-force" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py000066400000000000000000000025331411311634200306540ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--accept / --reject" in result.output assert "--no-accept" not in result.output def test_main(): result = runner.invoke(app) assert result.exit_code == 0 assert "I don't know what you want yet" in result.output def test_accept(): result = runner.invoke(app, ["--accept"]) assert result.exit_code == 0 assert "Accepting!" in result.output def test_reject(): result = runner.invoke(app, ["--reject"]) assert result.exit_code == 0 assert "Rejecting!" in result.output def test_invalid_no_accept(): result = runner.invoke(app, ["--no-accept"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: No such option: --no-accept" in result.output or "Error: no such option: --no-accept" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003.py000066400000000000000000000015421411311634200306540ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "-f, --force / -F, --no-force" in result.output def test_force(): result = runner.invoke(app, ["-f"]) assert result.exit_code == 0 assert "Forcing operation" in result.output def test_no_force(): result = runner.invoke(app, ["-F"]) assert result.exit_code == 0 assert "Not forcing" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004.py000066400000000000000000000017331411311634200306570ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "/ -d, --demo" in result.output def test_main(): result = runner.invoke(app) assert result.exit_code == 0 assert "Running in production" in result.output def test_demo(): result = runner.invoke(app, ["--demo"]) assert result.exit_code == 0 assert "Running demo" in result.output def test_short_demo(): result = runner.invoke(app, ["-d"]) assert result.exit_code == 0 assert "Running demo" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_datetime/000077500000000000000000000000001411311634200260145ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_datetime/__init__.py000066400000000000000000000000001411311634200301130ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py000066400000000000000000000026561411311634200315220ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.datetime import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]" in result.output def test_main(): result = runner.invoke(app, ["1956-01-31T10:00:00"]) assert result.exit_code == 0 assert "Interesting day to be born: 1956-01-31 10:00:00" in result.output assert "Birth hour: 10" in result.output def test_invalid(): result = runner.invoke(app, ["july-19-1989"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': 'july-19-1989' does not match the formats '%Y-%m-%d', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S'" in result.output or "Error: Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': invalid datetime format: july-19-1989. (choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S)" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002.py000066400000000000000000000014271411311634200315160ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.datetime import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app, ["1969-10-29"]) assert result.exit_code == 0 assert "Launch will be at: 1969-10-29 00:00:00" in result.output def test_usa_weird_date_format(): result = runner.invoke(app, ["10/29/1969"]) assert result.exit_code == 0 assert "Launch will be at: 1969-10-29 00:00:00" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_enum/000077500000000000000000000000001411311634200251645ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_enum/__init__.py000066400000000000000000000000001411311634200272630ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py000066400000000000000000000022721411311634200306640ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--network [simple|conv|lstm]" in result.output def test_main(): result = runner.invoke(app, ["--network", "conv"]) assert result.exit_code == 0 assert "Training neural network of type: conv" in result.output def test_invalid(): result = runner.invoke(app, ["--network", "capsule"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for '--network': 'capsule' is not one of 'simple', 'conv', 'lstm'" in result.output or "Error: Invalid value for '--network': invalid choice: capsule. (choose from simple, conv, lstm)" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002.py000066400000000000000000000014161411311634200306640ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_upper(): result = runner.invoke(app, ["--network", "CONV"]) assert result.exit_code == 0 assert "Training neural network of type: conv" in result.output def test_mix(): result = runner.invoke(app, ["--network", "LsTm"]) assert result.exit_code == 0 assert "Training neural network of type: lstm" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/000077500000000000000000000000001411311634200251375ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/__init__.py000066400000000000000000000000001411311634200272360ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001.py000066400000000000000000000015011411311634200306310ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(tmpdir): config_file = Path(tmpdir) / "config.txt" config_file.write_text("some settings\nsome more settings") result = runner.invoke(app, ["--config", f"{config_file}"]) config_file.unlink() assert result.exit_code == 0 assert "Config line: some settings" in result.output assert "Config line: some more settings" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002.py000066400000000000000000000015331411311634200306370ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(tmpdir): config_file = Path(tmpdir) / "config.txt" if config_file.exists(): # pragma no cover config_file.unlink() result = runner.invoke(app, ["--config", f"{config_file}"]) text = config_file.read_text() config_file.unlink() assert result.exit_code == 0 assert "Config written" in result.output assert "Some config written by the app" in text def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003.py000066400000000000000000000014121411311634200306340ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(tmpdir): binary_file = Path(tmpdir) / "config.txt" binary_file.write_bytes(b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o") result = runner.invoke(app, ["--file", f"{binary_file}"]) binary_file.unlink() assert result.exit_code == 0 assert "Processed bytes total:" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py000066400000000000000000000015751411311634200306470ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial004 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(tmpdir): binary_file = Path(tmpdir) / "config.txt" if binary_file.exists(): # pragma no cover binary_file.unlink() result = runner.invoke(app, ["--file", f"{binary_file}"]) text = binary_file.read_text() binary_file.unlink() assert result.exit_code == 0 assert "Binary file written" in result.output assert "some settings" in text assert "la cigüeña trae al niño" in text def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005.py000066400000000000000000000020271411311634200306410ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial005 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(tmpdir): config_file = Path(tmpdir) / "config.txt" if config_file.exists(): # pragma no cover config_file.unlink() config_file.write_text("") result = runner.invoke(app, ["--config", f"{config_file}"]) result = runner.invoke(app, ["--config", f"{config_file}"]) result = runner.invoke(app, ["--config", f"{config_file}"]) text = config_file.read_text() config_file.unlink() assert result.exit_code == 0 assert "Config line written" assert "This is a single line\nThis is a single line\nThis is a single line" in text def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_index/000077500000000000000000000000001411311634200253275ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_index/__init__.py000066400000000000000000000000001411311634200274260ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py000066400000000000000000000026771411311634200310400ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.index import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--age INTEGER" in result.output assert "--height-meters FLOAT" in result.output def test_params(): result = runner.invoke( app, ["Camila", "--age", "15", "--height-meters", "1.70", "--female"] ) assert result.exit_code == 0 assert "NAME is Camila, of type: " in result.output assert "--age is 15, of type: " in result.output assert "--height-meters is 1.7, of type: " in result.output assert "--female is True, of type: " in result.output def test_invalid(): result = runner.invoke(app, ["Camila", "--age", "15.3"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for '--age': '15.3' is not a valid integer" in result.output or "Error: Invalid value for '--age': 15.3 is not a valid integer" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_number/000077500000000000000000000000001411311634200255105ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_number/__init__.py000066400000000000000000000000001411311634200276070ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py000066400000000000000000000045571411311634200312200ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.number import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--age INTEGER RANGE" in result.output assert "--score FLOAT RANGE" in result.output def test_params(): result = runner.invoke(app, ["5", "--age", "20", "--score", "90"]) assert result.exit_code == 0 assert "ID is 5" in result.output assert "--age is 20" in result.output assert "--score is 90.0" in result.output def test_invalid_id(): result = runner.invoke(app, ["1002"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( ( "Error: Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." in result.output ) or "Error: Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000." in result.output ) def test_invalid_age(): result = runner.invoke(app, ["5", "--age", "15"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for '--age': 15 is not in the range x>=18" in result.output or "Error: Invalid value for '--age': 15 is smaller than the minimum valid value 18." in result.output ) def test_invalid_score(): result = runner.invoke(app, ["5", "--age", "20", "--score", "100.5"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for '--score': 100.5 is not in the range x<=100." in result.output or "Error: Invalid value for '--score': 100.5 is bigger than the maximum valid value 100." in result.output ) def test_negative_score(): result = runner.invoke(app, ["5", "--age", "20", "--score", "-5"]) assert result.exit_code == 0 assert "ID is 5" in result.output assert "--age is 20" in result.output assert "--score is -5.0" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py000066400000000000000000000020701411311634200312050ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.number import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_invalid_id(): result = runner.invoke(app, ["1002"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for 'ID': 1002 is not in the range 0<=x<=1000" in result.output or "Error: Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000." in result.output ) def test_clamped(): result = runner.invoke(app, ["5", "--rank", "11", "--score", "-5"]) assert result.exit_code == 0 assert "ID is 5" in result.output assert "--rank is 10" in result.output assert "--score is 0" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003.py000066400000000000000000000025431411311634200312130ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.number import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app) assert result.exit_code == 0 assert "Verbose level is 0" in result.output def test_verbose_1(): result = runner.invoke(app, ["--verbose"]) assert result.exit_code == 0 assert "Verbose level is 1" in result.output def test_verbose_3(): result = runner.invoke(app, ["--verbose", "--verbose", "--verbose"]) assert result.exit_code == 0 assert "Verbose level is 3" in result.output def test_verbose_short_1(): result = runner.invoke(app, ["-v"]) assert result.exit_code == 0 assert "Verbose level is 1" in result.output def test_verbose_short_3(): result = runner.invoke(app, ["-v", "-v", "-v"]) assert result.exit_code == 0 assert "Verbose level is 3" in result.output def test_verbose_short_3_condensed(): result = runner.invoke(app, ["-vvv"]) assert result.exit_code == 0 assert "Verbose level is 3" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_path/000077500000000000000000000000001411311634200251545ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_path/__init__.py000066400000000000000000000000001411311634200272530ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001.py000066400000000000000000000026671411311634200306640ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.path import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_no_path(tmpdir): Path(tmpdir) / "config.txt" result = runner.invoke(app) assert result.exit_code == 1 assert "No config file" in result.output assert "Aborted!" in result.output def test_not_exists(tmpdir): config_file = Path(tmpdir) / "config.txt" if config_file.exists(): # pragma no cover config_file.unlink() result = runner.invoke(app, ["--config", f"{config_file}"]) assert result.exit_code == 0 assert "The config doesn't exist" in result.output def test_exists(tmpdir): config_file = Path(tmpdir) / "config.txt" config_file.write_text("some settings") result = runner.invoke(app, ["--config", f"{config_file}"]) config_file.unlink() assert result.exit_code == 0 assert "Config file contents: some settings" in result.output def test_dir(): result = runner.invoke(app, ["--config", "./"]) assert result.exit_code == 0 assert "Config is a directory, will use all its config files" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002.py000066400000000000000000000025061411311634200306550ustar00rootroot00000000000000import subprocess from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.path import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_not_exists(tmpdir): config_file = Path(tmpdir) / "config.txt" if config_file.exists(): # pragma no cover config_file.unlink() result = runner.invoke(app, ["--config", f"{config_file}"]) assert result.exit_code != 0 assert "Error: Invalid value for '--config': File" in result.output assert "does not exist" in result.output def test_exists(tmpdir): config_file = Path(tmpdir) / "config.txt" config_file.write_text("some settings") result = runner.invoke(app, ["--config", f"{config_file}"]) config_file.unlink() assert result.exit_code == 0 assert "Config file contents: some settings" in result.output def test_dir(): result = runner.invoke(app, ["--config", "./"]) assert result.exit_code != 0 assert ( "Error: Invalid value for '--config': File './' is a directory." in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_parameter_types/test_uuid/000077500000000000000000000000001411311634200251665ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_uuid/__init__.py000066400000000000000000000000001411311634200272650ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py000066400000000000000000000021401411311634200306600ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.parameter_types.uuid import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_main(): result = runner.invoke(app, ["d48edaa6-871a-4082-a196-4daab372d4a1"]) assert result.exit_code == 0 assert "USER_ID is d48edaa6-871a-4082-a196-4daab372d4a1" in result.output assert "UUID version is: 4" in result.output def test_invalid_uuid(): result = runner.invoke(app, ["7479706572-72756c6573"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Error: Invalid value for 'USER_ID': '7479706572-72756c6573' is not a valid UUID" in result.output or "Error: Invalid value for 'USER_ID': 7479706572-72756c6573 is not a valid UUID value" in result.output ) def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_prompt/000077500000000000000000000000001411311634200212765ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_prompt/__init__.py000066400000000000000000000000001411311634200233750ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_prompt/test_tutorial001.py000066400000000000000000000011431411311634200247720ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.prompt import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_cli(): result = runner.invoke(app, input="Camila\n") assert result.exit_code == 0 assert "What's your name?:" in result.output assert "Hello Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_prompt/test_tutorial002.py000066400000000000000000000015701411311634200247770ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.prompt import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_cli(): result = runner.invoke(app, input="y\n") assert result.exit_code == 0 assert "Are you sure you want to delete it? [y/N]:" in result.output assert "Deleting it!" in result.output def test_no_confirm(): result = runner.invoke(app, input="n\n") assert result.exit_code == 1 assert "Are you sure you want to delete it? [y/N]:" in result.output assert "Not deleting" in result.output assert "Aborted!" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_prompt/test_tutorial003.py000066400000000000000000000015151411311634200247770ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.prompt import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_cli(): result = runner.invoke(app, input="y\n") assert result.exit_code == 0 assert "Are you sure you want to delete it? [y/N]:" in result.output assert "Deleting it!" in result.output def test_no_confirm(): result = runner.invoke(app, input="n\n") assert result.exit_code == 1 assert "Are you sure you want to delete it? [y/N]:" in result.output assert "Aborted!" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/000077500000000000000000000000001411311634200222705ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_subcommands/__init__.py000066400000000000000000000000001411311634200243670ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_subcommands/test_callback_override/000077500000000000000000000000001411311634200267625ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_subcommands/test_callback_override/__init__.py000066400000000000000000000000001411311634200310610ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial001.py000066400000000000000000000011521411311634200324560ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.callback_override import tutorial001 as mod runner = CliRunner() app = mod.app def test_cli(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Running a users command" in result.output assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial002.py000066400000000000000000000011521411311634200324570ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.callback_override import tutorial002 as mod runner = CliRunner() app = mod.app def test_cli(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Running a users command" in result.output assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial003.py000066400000000000000000000013531411311634200324630ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.callback_override import tutorial003 as mod runner = CliRunner() app = mod.app def test_cli(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Running a users command" not in result.output assert "Callback override, running users command" in result.output assert "Creating user: Camila" in result.output def test_for_coverage(): mod.default_callback() def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial004.py000066400000000000000000000015211411311634200324610ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.callback_override import tutorial004 as mod runner = CliRunner() app = mod.app def test_cli(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Running a users command" not in result.output assert "Callback override, running users command" not in result.output assert "I have the high land! Running users command" in result.output assert "Creating user: Camila" in result.output def test_for_coverage(): mod.default_callback() mod.user_callback() def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/000077500000000000000000000000001411311634200252575ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/__init__.py000066400000000000000000000000001411311634200273560ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial001.py000066400000000000000000000016731411311634200307630ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial001 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "users" in result.output assert "Manage users in the app." in result.output def test_command_help(): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "Manage users in the app." in result.output def test_command(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial002.py000066400000000000000000000016731411311634200307640ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial002 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "users" in result.output assert "Manage users in the app." in result.output def test_command_help(): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "Manage users in the app." in result.output def test_command(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial003.py000066400000000000000000000016731411311634200307650ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial003 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "users" in result.output assert "Manage users in the app." in result.output def test_command_help(): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "Manage users in the app." in result.output def test_command(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial004.py000066400000000000000000000016731411311634200307660ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial004 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "users" in result.output assert "Manage users in the app." in result.output def test_command_help(): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "Manage users in the app." in result.output def test_command(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial005.py000066400000000000000000000017451411311634200307670ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial005 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "new-users" in result.output assert "I have the highland! Create some users." in result.output def test_command_help(): result = runner.invoke(app, ["new-users", "--help"]) assert result.exit_code == 0 assert "I have the highland! Create some users." in result.output def test_command(): result = runner.invoke(app, ["new-users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial006.py000066400000000000000000000016631411311634200307670ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial006 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "exp-users" in result.output assert "Explicit help." in result.output def test_command_help(): result = runner.invoke(app, ["exp-users", "--help"]) assert result.exit_code == 0 assert "Explicit help." in result.output def test_command(): result = runner.invoke(app, ["exp-users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial007.py000066400000000000000000000017241411311634200307660ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial007 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "call-users" in result.output assert "Help from callback for users." in result.output def test_command_help(): result = runner.invoke(app, ["call-users", "--help"]) assert result.exit_code == 0 assert "Help from callback for users." in result.output def test_command(): result = runner.invoke(app, ["call-users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial008.py000066400000000000000000000017411411311634200307660ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.name_help import tutorial008 as mod runner = CliRunner() app = mod.app def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Commands:" in result.output assert "cake-sith-users" in result.output assert "Unlimited powder! Eh, users." in result.output def test_command_help(): result = runner.invoke(app, ["cake-sith-users", "--help"]) assert result.exit_code == 0 assert "Unlimited powder! Eh, users." in result.output def test_command(): result = runner.invoke(app, ["cake-sith-users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_tutorial001.py000066400000000000000000000050171411311634200257700ustar00rootroot00000000000000import subprocess import pytest from typer.testing import CliRunner from docs_src.subcommands import tutorial001 runner = CliRunner() @pytest.fixture() def mod(monkeypatch): with monkeypatch.context() as m: monkeypatch.syspath_prepend(list(tutorial001.__path__)[0]) from docs_src.subcommands.tutorial001 import main return main @pytest.fixture() def app(mod): return mod.app def test_help(app): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "items" in result.output assert "users" in result.output def test_help_items(app): result = runner.invoke(app, ["items", "--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "sell" in result.output def test_items_create(app): result = runner.invoke(app, ["items", "create", "Wand"]) assert result.exit_code == 0 assert "Creating item: Wand" in result.output def test_items_sell(app): result = runner.invoke(app, ["items", "sell", "Vase"]) assert result.exit_code == 0 assert "Selling item: Vase" in result.output def test_items_delete(app): result = runner.invoke(app, ["items", "delete", "Vase"]) assert result.exit_code == 0 assert "Deleting item: Vase" in result.output def test_help_users(app): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "sell" not in result.output def test_users_create(app): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_users_delete(app): result = runner.invoke(app, ["users", "delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_scripts(mod): from docs_src.subcommands.tutorial001 import items, users for module in [mod, items, users]: result = subprocess.run( ["coverage", "run", module.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_tutorial002.py000066400000000000000000000041441411311634200257710ustar00rootroot00000000000000import subprocess from typer.testing import CliRunner from docs_src.subcommands.tutorial002 import main as mod app = mod.app runner = CliRunner() def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "items" in result.output assert "users" in result.output def test_help_items(): result = runner.invoke(app, ["items", "--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "sell" in result.output def test_items_create(): result = runner.invoke(app, ["items", "create", "Wand"]) assert result.exit_code == 0 assert "Creating item: Wand" in result.output def test_items_sell(): result = runner.invoke(app, ["items", "sell", "Vase"]) assert result.exit_code == 0 assert "Selling item: Vase" in result.output def test_items_delete(): result = runner.invoke(app, ["items", "delete", "Vase"]) assert result.exit_code == 0 assert "Deleting item: Vase" in result.output def test_help_users(): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "sell" not in result.output def test_users_create(): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_users_delete(): result = runner.invoke(app, ["users", "delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_subcommands/test_tutorial003.py000066400000000000000000000104051411311634200257670ustar00rootroot00000000000000import subprocess import pytest from typer.testing import CliRunner from docs_src.subcommands import tutorial003 runner = CliRunner() @pytest.fixture() def mod(monkeypatch): with monkeypatch.context() as m: m.syspath_prepend(list(tutorial003.__path__)[0]) from docs_src.subcommands.tutorial003 import main return main @pytest.fixture() def app(mod): return mod.app def test_help(app): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "items" in result.output assert "users" in result.output assert "lands" in result.output def test_help_items(app): result = runner.invoke(app, ["items", "--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "sell" in result.output def test_items_create(app): result = runner.invoke(app, ["items", "create", "Wand"]) assert result.exit_code == 0 assert "Creating item: Wand" in result.output def test_items_sell(app): result = runner.invoke(app, ["items", "sell", "Vase"]) assert result.exit_code == 0 assert "Selling item: Vase" in result.output def test_items_delete(app): result = runner.invoke(app, ["items", "delete", "Vase"]) assert result.exit_code == 0 assert "Deleting item: Vase" in result.output def test_help_users(app): result = runner.invoke(app, ["users", "--help"]) assert result.exit_code == 0 assert "[OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "create" in result.output assert "delete" in result.output assert "sell" not in result.output def test_users_create(app): result = runner.invoke(app, ["users", "create", "Camila"]) assert result.exit_code == 0 assert "Creating user: Camila" in result.output def test_users_delete(app): result = runner.invoke(app, ["users", "delete", "Camila"]) assert result.exit_code == 0 assert "Deleting user: Camila" in result.output def test_help_lands(app): result = runner.invoke(app, ["lands", "--help"]) assert result.exit_code == 0 assert "lands [OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "reigns" in result.output assert "towns" in result.output def test_help_lands_reigns(app): result = runner.invoke(app, ["lands", "reigns", "--help"]) assert result.exit_code == 0 assert "lands reigns [OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "conquer" in result.output assert "destroy" in result.output def test_lands_reigns_conquer(app): result = runner.invoke(app, ["lands", "reigns", "conquer", "Gondor"]) assert result.exit_code == 0 assert "Conquering reign: Gondor" in result.output def test_lands_reigns_destroy(app): result = runner.invoke(app, ["lands", "reigns", "destroy", "Mordor"]) assert result.exit_code == 0 assert "Destroying reign: Mordor" in result.output def test_help_lands_towns(app): result = runner.invoke(app, ["lands", "towns", "--help"]) assert result.exit_code == 0 assert "lands towns [OPTIONS] COMMAND [ARGS]..." in result.output assert "Commands:" in result.output assert "burn" in result.output assert "found" in result.output def test_lands_towns_found(app): result = runner.invoke(app, ["lands", "towns", "found", "Cartagena"]) assert result.exit_code == 0 assert "Founding town: Cartagena" in result.output def test_lands_towns_burn(app): result = runner.invoke(app, ["lands", "towns", "burn", "New Asgard"]) assert result.exit_code == 0 assert "Burning town: New Asgard" in result.output def test_scripts(mod): from docs_src.subcommands.tutorial003 import items, lands, reigns, towns, users for module in [mod, items, lands, reigns, towns, users]: result = subprocess.run( ["coverage", "run", module.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_terminating/000077500000000000000000000000001411311634200222765ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_terminating/__init__.py000066400000000000000000000000001411311634200243750ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_terminating/test_tutorial001.py000066400000000000000000000015271411311634200260000ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.terminating import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_cli(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "User created: Camila" in result.output assert "Notification sent for new user: Camila" in result.output def test_existing(): result = runner.invoke(app, ["rick"]) assert result.exit_code == 0 assert "The user already exists" in result.output assert "Notification sent for new user" not in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_terminating/test_tutorial002.py000066400000000000000000000013231411311634200257730ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.terminating import tutorial002 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_cli(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "New user created: Camila" in result.output def test_root(): result = runner.invoke(app, ["root"]) assert result.exit_code == 1 assert "The root user is reserved" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_terminating/test_tutorial003.py000066400000000000000000000013721411311634200260000ustar00rootroot00000000000000import subprocess import typer from typer.testing import CliRunner from docs_src.terminating import tutorial003 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_cli(): result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "New user created: Camila" in result.output def test_root(): result = runner.invoke(app, ["root"]) assert result.exit_code == 1 assert "The root user is reserved" in result.output assert "Aborted!" in result.output def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_testing/000077500000000000000000000000001411311634200214325ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_testing/__init__.py000066400000000000000000000000001411311634200235310ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_testing/test_app01.py000066400000000000000000000006061411311634200237660ustar00rootroot00000000000000import subprocess from docs_src.testing.app01 import main as mod from docs_src.testing.app01.test_main import test_app def test_app01(): test_app() def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_testing/test_app02.py000066400000000000000000000006061411311634200237670ustar00rootroot00000000000000import subprocess from docs_src.testing.app02 import main as mod from docs_src.testing.app02.test_main import test_app def test_app02(): test_app() def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_testing/test_app03.py000066400000000000000000000006061411311634200237700ustar00rootroot00000000000000import subprocess from docs_src.testing.app03 import main as mod from docs_src.testing.app03.test_main import test_app def test_app03(): test_app() def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_using_click/000077500000000000000000000000001411311634200222475ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_using_click/__init__.py000066400000000000000000000000001411311634200243460ustar00rootroot00000000000000typer-0.4.0/tests/test_tutorial/test_using_click/test_tutorial003.py000066400000000000000000000015441411311634200257520ustar00rootroot00000000000000import subprocess from click.testing import CliRunner from docs_src.using_click import tutorial003 as mod runner = CliRunner() def test_cli(): result = runner.invoke(mod.typer_click_object, []) # TODO: when deprecating Click 7, remove second option assert "Error: Missing command" in result.stdout or "Usage" in result.stdout def test_typer(): result = runner.invoke(mod.typer_click_object, ["top"]) assert "The Typer app is at the top level" in result.stdout def test_click(): result = runner.invoke(mod.typer_click_object, ["hello", "--name", "Camila"]) assert "Hello Camila!" in result.stdout def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/tests/test_tutorial/test_using_click/test_tutorial004.py000066400000000000000000000016521411311634200257530ustar00rootroot00000000000000import subprocess from click.testing import CliRunner from docs_src.using_click import tutorial004 as mod runner = CliRunner() def test_cli(): result = runner.invoke(mod.cli, []) assert "Usage" in result.stdout assert "dropdb" in result.stdout assert "sub" in result.stdout def test_typer(): result = runner.invoke(mod.cli, ["sub"]) assert "Typer is now below Click, the Click app is the top level" in result.stdout def test_click_initdb(): result = runner.invoke(mod.cli, ["initdb"]) assert "Initialized the database" in result.stdout def test_click_dropdb(): result = runner.invoke(mod.cli, ["dropdb"]) assert "Dropped the database" in result.stdout def test_script(): result = subprocess.run( ["coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.4.0/typer/000077500000000000000000000000001411311634200140155ustar00rootroot00000000000000typer-0.4.0/typer/__init__.py000066400000000000000000000031071411311634200161270ustar00rootroot00000000000000"""Typer, build great CLIs. Easy to code. Based on Python type hints.""" __version__ = "0.4.0" from click.exceptions import Abort as Abort from click.exceptions import BadParameter as BadParameter from click.exceptions import Exit as Exit from click.termui import clear as clear from click.termui import confirm as confirm from click.termui import echo_via_pager as echo_via_pager from click.termui import edit as edit from click.termui import get_terminal_size as get_terminal_size from click.termui import getchar as getchar from click.termui import launch as launch from click.termui import pause as pause from click.termui import progressbar as progressbar from click.termui import prompt as prompt from click.termui import secho as secho from click.termui import style as style from click.termui import unstyle as unstyle from click.utils import echo as echo from click.utils import format_filename as format_filename from click.utils import get_app_dir as get_app_dir from click.utils import get_binary_stream as get_binary_stream from click.utils import get_text_stream as get_text_stream from click.utils import open_file as open_file from . import colors as colors from .main import Typer as Typer from .main import run as run from .models import CallbackParam as CallbackParam from .models import Context as Context from .models import FileBinaryRead as FileBinaryRead from .models import FileBinaryWrite as FileBinaryWrite from .models import FileText as FileText from .models import FileTextWrite as FileTextWrite from .params import Argument as Argument from .params import Option as Option typer-0.4.0/typer/_completion_click7.py000066400000000000000000000115351411311634200201400ustar00rootroot00000000000000import os import re import sys import click import click._bashcomplete from ._completion_shared import get_completion_script try: import shellingham except ImportError: # pragma: nocover shellingham = None _click_patched = False def do_bash_complete(cli: click.Command, prog_name: str) -> bool: cwords = click.parser.split_arg_string(os.getenv("COMP_WORDS", "")) cword = int(os.getenv("COMP_CWORD", 0)) args = cwords[1:cword] try: incomplete = cwords[cword] except IndexError: incomplete = "" for item in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): click.echo(item[0]) return True def do_zsh_complete(cli: click.Command, prog_name: str) -> bool: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:] if args and not completion_args.endswith(" "): incomplete = args[-1] args = args[:-1] else: incomplete = "" def escape(s: str) -> str: return ( s.replace('"', '""') .replace("'", "''") .replace("$", "\\$") .replace("`", "\\`") ) res = [] for item, help in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): if help: res.append(f'"{escape(item)}":"{escape(help)}"') else: res.append(f'"{escape(item)}"') if res: args_str = "\n".join(res) click.echo(f"_arguments '*: :(({args_str}))'") else: click.echo("_files") return True def do_fish_complete(cli: click.Command, prog_name: str) -> bool: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:] if args and not completion_args.endswith(" "): incomplete = args[-1] args = args[:-1] else: incomplete = "" show_args = [] for item, help in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): if help: formatted_help = re.sub(r"\s", " ", help) show_args.append(f"{item}\t{formatted_help}") else: show_args.append(item) if complete_action == "get-args": if show_args: for arg in show_args: click.echo(arg) elif complete_action == "is-args": if show_args: # Activate complete args (no files) sys.exit(0) else: # Deactivate complete args (allow files) sys.exit(1) return True def do_powershell_complete(cli: click.Command, prog_name: str) -> bool: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:] for item, help in click._bashcomplete.get_choices(cli, prog_name, args, incomplete): click.echo(f"{item}:::{help or ' '}") return True def do_shell_complete(*, cli: click.Command, prog_name: str, shell: str) -> bool: if shell == "bash": return do_bash_complete(cli, prog_name) elif shell == "zsh": return do_zsh_complete(cli, prog_name) elif shell == "fish": return do_fish_complete(cli, prog_name) elif shell in {"powershell", "pwsh"}: return do_powershell_complete(cli, prog_name) return False def handle_shell_complete( cli: click.Command, prog_name: str, complete_var: str, complete_instr: str ) -> bool: if "_" not in complete_instr: click.echo("Invalid completion instruction.", err=True) sys.exit(1) command, shell = complete_instr.split("_", 1) if command == "source": click.echo( get_completion_script( prog_name=prog_name, complete_var=complete_var, shell=shell ) ) return True elif command == "complete": return do_shell_complete(cli=cli, prog_name=prog_name, shell=shell) click.echo(f'Completion instruction "{command}" not supported.', err=True) return False def completion_init() -> None: global _click_patched if not _click_patched: testing = os.getenv("_TYPER_COMPLETE_TESTING") def testing_handle_shell_complete( cli: click.Command, prog_name: str, complete_var: str, complete_instr: str ) -> bool: result = handle_shell_complete(cli, prog_name, complete_var, complete_instr) if result: # Avoid fast_exit(1) in Click so Coverage can finish sys.exit(1) return result if testing: click._bashcomplete.bashcomplete = testing_handle_shell_complete else: click._bashcomplete.bashcomplete = handle_shell_complete _click_patched = True typer-0.4.0/typer/_completion_click8.py000066400000000000000000000150401411311634200201340ustar00rootroot00000000000000import os import re import sys from typing import Any, Dict, List, Tuple import click import click.parser import click.shell_completion from ._completion_shared import ( COMPLETION_SCRIPT_BASH, COMPLETION_SCRIPT_FISH, COMPLETION_SCRIPT_POWER_SHELL, COMPLETION_SCRIPT_ZSH, Shells, ) try: import shellingham except ImportError: # pragma: nocover shellingham = None class BashComplete(click.shell_completion.BashComplete): name = Shells.bash.value source_template = COMPLETION_SCRIPT_BASH def source_vars(self) -> Dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } def get_completion_args(self) -> Tuple[List[str], str]: cwords = click.parser.split_arg_string(os.environ["COMP_WORDS"]) cword = int(os.environ["COMP_CWORD"]) args = cwords[1:cword] try: incomplete = cwords[cword] except IndexError: incomplete = "" return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: # TODO: Explore replicating the new behavior from Click, with item types and # triggering completion for files and directories # return f"{item.type},{item.value}" return f"{item.value}" def complete(self) -> str: args, incomplete = self.get_completion_args() completions = self.get_completions(args, incomplete) out = [self.format_completion(item) for item in completions] return "\n".join(out) class ZshComplete(click.shell_completion.ZshComplete): name = Shells.zsh.value source_template = COMPLETION_SCRIPT_ZSH def source_vars(self) -> Dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } def get_completion_args(self) -> Tuple[List[str], str]: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:] if args and not completion_args.endswith(" "): incomplete = args[-1] args = args[:-1] else: incomplete = "" return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: def escape(s: str) -> str: return ( s.replace('"', '""') .replace("'", "''") .replace("$", "\\$") .replace("`", "\\`") ) # TODO: Explore replicating the new behavior from Click, pay attention to # the difference with and without escape # return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" if item.help: return f'"{escape(item.value)}":"{escape(item.help)}"' else: return f'"{escape(item.value)}"' def complete(self) -> str: args, incomplete = self.get_completion_args() completions = self.get_completions(args, incomplete) res = [self.format_completion(item) for item in completions] if res: args_str = "\n".join(res) return f"_arguments '*: :(({args_str}))'" else: return "_files" class FishComplete(click.shell_completion.FishComplete): name = Shells.fish.value source_template = COMPLETION_SCRIPT_FISH def source_vars(self) -> Dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } def get_completion_args(self) -> Tuple[List[str], str]: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:] if args and not completion_args.endswith(" "): incomplete = args[-1] args = args[:-1] else: incomplete = "" return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: # TODO: Explore replicating the new behavior from Click, pay attention to # the difference with and without formatted help # if item.help: # return f"{item.type},{item.value}\t{item.help}" # return f"{item.type},{item.value} if item.help: formatted_help = re.sub(r"\s", " ", item.help) return f"{item.value}\t{formatted_help}" else: return f"{item.value}" def complete(self) -> str: complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "") args, incomplete = self.get_completion_args() completions = self.get_completions(args, incomplete) show_args = [self.format_completion(item) for item in completions] if complete_action == "get-args": if show_args: return "\n".join(show_args) elif complete_action == "is-args": if show_args: # Activate complete args (no files) sys.exit(0) else: # Deactivate complete args (allow files) sys.exit(1) return "" # pragma: no cover class PowerShellComplete(click.shell_completion.ShellComplete): name = Shells.powershell.value source_template = COMPLETION_SCRIPT_POWER_SHELL def source_vars(self) -> Dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } def get_completion_args(self) -> Tuple[List[str], str]: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") cwords = click.parser.split_arg_string(completion_args) args = cwords[1:] return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: return f"{item.value}:::{item.help or ' '}" def completion_init() -> None: click.shell_completion.add_completion_class(BashComplete, Shells.bash.value) click.shell_completion.add_completion_class(ZshComplete, Shells.zsh.value) click.shell_completion.add_completion_class(FishComplete, Shells.fish.value) click.shell_completion.add_completion_class( PowerShellComplete, Shells.powershell.value ) click.shell_completion.add_completion_class(PowerShellComplete, Shells.pwsh.value) typer-0.4.0/typer/_completion_shared.py000066400000000000000000000205351411311634200202320ustar00rootroot00000000000000import os import re import subprocess import sys from enum import Enum from pathlib import Path from typing import Optional, Tuple import click try: import shellingham except ImportError: # pragma: nocover shellingham = None from typing import Optional class Shells(str, Enum): bash = "bash" zsh = "zsh" fish = "fish" powershell = "powershell" pwsh = "pwsh" COMPLETION_SCRIPT_BASH = """ %(complete_func)s() { local IFS=$'\n' COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\ COMP_CWORD=$COMP_CWORD \\ %(autocomplete_var)s=complete_bash $1 ) ) return 0 } complete -o default -F %(complete_func)s %(prog_name)s """ COMPLETION_SCRIPT_ZSH = """ #compdef %(prog_name)s %(complete_func)s() { eval $(env _TYPER_COMPLETE_ARGS="${words[1,$CURRENT]}" %(autocomplete_var)s=complete_zsh %(prog_name)s) } compdef %(complete_func)s %(prog_name)s """ COMPLETION_SCRIPT_FISH = 'complete --command %(prog_name)s --no-files --arguments "(env %(autocomplete_var)s=complete_fish _TYPER_COMPLETE_FISH_ACTION=get-args _TYPER_COMPLETE_ARGS=(commandline -cp) %(prog_name)s)" --condition "env %(autocomplete_var)s=complete_fish _TYPER_COMPLETE_FISH_ACTION=is-args _TYPER_COMPLETE_ARGS=(commandline -cp) %(prog_name)s"' COMPLETION_SCRIPT_POWER_SHELL = """ Import-Module PSReadLine Set-PSReadLineKeyHandler -Chord Tab -Function MenuComplete $scriptblock = { param($wordToComplete, $commandAst, $cursorPosition) $Env:%(autocomplete_var)s = "complete_powershell" $Env:_TYPER_COMPLETE_ARGS = $commandAst.ToString() $Env:_TYPER_COMPLETE_WORD_TO_COMPLETE = $wordToComplete %(prog_name)s | ForEach-Object { $commandArray = $_ -Split ":::" $command = $commandArray[0] $helpString = $commandArray[1] [System.Management.Automation.CompletionResult]::new( $command, $command, 'ParameterValue', $helpString) } $Env:%(autocomplete_var)s = "" $Env:_TYPER_COMPLETE_ARGS = "" $Env:_TYPER_COMPLETE_WORD_TO_COMPLETE = "" } Register-ArgumentCompleter -Native -CommandName %(prog_name)s -ScriptBlock $scriptblock """ _completion_scripts = { "bash": COMPLETION_SCRIPT_BASH, "zsh": COMPLETION_SCRIPT_ZSH, "fish": COMPLETION_SCRIPT_FISH, "powershell": COMPLETION_SCRIPT_POWER_SHELL, "pwsh": COMPLETION_SCRIPT_POWER_SHELL, } # TODO: Probably refactor this, copied from Click 7.x _invalid_ident_char_re = re.compile(r"[^a-zA-Z0-9_]") def get_completion_script(*, prog_name: str, complete_var: str, shell: str) -> str: cf_name = _invalid_ident_char_re.sub("", prog_name.replace("-", "_")) script = _completion_scripts.get(shell) if script is None: click.echo(f"Shell {shell} not supported.", err=True) sys.exit(1) return ( script % dict( complete_func="_{}_completion".format(cf_name), prog_name=prog_name, autocomplete_var=complete_var, ) ).strip() def install_bash(*, prog_name: str, complete_var: str, shell: str) -> Path: # Ref: https://github.com/scop/bash-completion#faq # It seems bash-completion is the official completion system for bash: # Ref: https://www.gnu.org/software/bash/manual/html_node/A-Programmable-Completion-Example.html # But installing in the locations from the docs doesn't seem to have effect completion_path = Path.home() / f".bash_completions/{prog_name}.sh" rc_path = Path.home() / ".bashrc" rc_path.parent.mkdir(parents=True, exist_ok=True) rc_content = "" if rc_path.is_file(): rc_content = rc_path.read_text() completion_init_lines = [f"source {completion_path}"] for line in completion_init_lines: if line not in rc_content: # pragma: nocover rc_content += f"\n{line}" rc_content += "\n" rc_path.write_text(rc_content) # Install completion completion_path.parent.mkdir(parents=True, exist_ok=True) script_content = get_completion_script( prog_name=prog_name, complete_var=complete_var, shell=shell ) completion_path.write_text(script_content) return completion_path def install_zsh(*, prog_name: str, complete_var: str, shell: str) -> Path: # Setup Zsh and load ~/.zfunc zshrc_path = Path.home() / ".zshrc" zshrc_path.parent.mkdir(parents=True, exist_ok=True) zshrc_content = "" if zshrc_path.is_file(): zshrc_content = zshrc_path.read_text() completion_init_lines = [ "autoload -Uz compinit", "compinit", "zstyle ':completion:*' menu select", "fpath+=~/.zfunc", ] for line in completion_init_lines: if line not in zshrc_content: # pragma: nocover zshrc_content += f"\n{line}" zshrc_content += "\n" zshrc_path.write_text(zshrc_content) # Install completion under ~/.zfunc/ path_obj = Path.home() / f".zfunc/_{prog_name}" path_obj.parent.mkdir(parents=True, exist_ok=True) script_content = get_completion_script( prog_name=prog_name, complete_var=complete_var, shell=shell ) path_obj.write_text(script_content) return path_obj def install_fish(*, prog_name: str, complete_var: str, shell: str) -> Path: path_obj = Path.home() / f".config/fish/completions/{prog_name}.fish" parent_dir: Path = path_obj.parent parent_dir.mkdir(parents=True, exist_ok=True) script_content = get_completion_script( prog_name=prog_name, complete_var=complete_var, shell=shell ) path_obj.write_text(f"{script_content}\n") return path_obj def install_powershell(*, prog_name: str, complete_var: str, shell: str) -> Path: subprocess.run( [ shell, "-Command", "Set-ExecutionPolicy", "Unrestricted", "-Scope", "CurrentUser", ] ) result = subprocess.run( [shell, "-NoProfile", "-Command", "echo", "$profile"], check=True, stdout=subprocess.PIPE, ) if result.returncode != 0: # pragma: nocover click.echo("Couldn't get PowerShell user profile", err=True) raise click.exceptions.Exit(result.returncode) path_str = "" if isinstance(result.stdout, str): # pragma: nocover path_str = result.stdout if isinstance(result.stdout, bytes): try: # PowerShell would be predominant in Windows path_str = result.stdout.decode("windows-1252") except UnicodeDecodeError: # pragma: nocover try: path_str = result.stdout.decode("utf8") except UnicodeDecodeError: click.echo("Couldn't decode the path automatically", err=True) raise click.exceptions.Exit(1) path_obj = Path(path_str.strip()) parent_dir: Path = path_obj.parent parent_dir.mkdir(parents=True, exist_ok=True) script_content = get_completion_script( prog_name=prog_name, complete_var=complete_var, shell=shell ) with path_obj.open(mode="a") as f: f.write(f"{script_content}\n") return path_obj def install( shell: Optional[str] = None, prog_name: Optional[str] = None, complete_var: Optional[str] = None, ) -> Tuple[str, Path]: prog_name = prog_name or click.get_current_context().find_root().info_name assert prog_name if complete_var is None: complete_var = "_{}_COMPLETE".format(prog_name.replace("-", "_").upper()) test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION") if shell is None and shellingham is not None and not test_disable_detection: shell, _ = shellingham.detect_shell() if shell == "bash": installed_path = install_bash( prog_name=prog_name, complete_var=complete_var, shell=shell ) return shell, installed_path elif shell == "zsh": installed_path = install_zsh( prog_name=prog_name, complete_var=complete_var, shell=shell ) return shell, installed_path elif shell == "fish": installed_path = install_fish( prog_name=prog_name, complete_var=complete_var, shell=shell ) return shell, installed_path elif shell in {"powershell", "pwsh"}: installed_path = install_powershell( prog_name=prog_name, complete_var=complete_var, shell=shell ) return shell, installed_path else: click.echo(f"Shell {shell} is not supported.") raise click.exceptions.Exit(1) typer-0.4.0/typer/colors.py000066400000000000000000000006561411311634200156770ustar00rootroot00000000000000# Variable names to colors, just for completion BLACK = "black" RED = "red" GREEN = "green" YELLOW = "yellow" BLUE = "blue" MAGENTA = "magenta" CYAN = "cyan" WHITE = "white" RESET = "reset" BRIGHT_BLACK = "bright_black" BRIGHT_RED = "bright_red" BRIGHT_GREEN = "bright_green" BRIGHT_YELLOW = "bright_yellow" BRIGHT_BLUE = "bright_blue" BRIGHT_MAGENTA = "bright_magenta" BRIGHT_CYAN = "bright_cyan" BRIGHT_WHITE = "bright_white" typer-0.4.0/typer/completion.py000066400000000000000000000115311411311634200165410ustar00rootroot00000000000000import os import sys from typing import Any, Dict, Tuple import click from ._completion_shared import Shells, get_completion_script, install from .models import ParamMeta from .params import Option from .utils import _get_click_major, get_params_from_function try: import shellingham except ImportError: # pragma: nocover shellingham = None _click_patched = False def get_completion_inspect_parameters() -> Tuple[ParamMeta, ParamMeta]: completion_init() test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION") if shellingham and not test_disable_detection: parameters = get_params_from_function(_install_completion_placeholder_function) else: parameters = get_params_from_function( _install_completion_no_auto_placeholder_function ) install_param, show_param = parameters.values() return install_param, show_param def install_callback(ctx: click.Context, param: click.Parameter, value: Any) -> Any: if not value or ctx.resilient_parsing: return value # pragma no cover if isinstance(value, str): shell, path = install(shell=value) else: shell, path = install() click.secho(f"{shell} completion installed in {path}", fg="green") click.echo("Completion will take effect once you restart the terminal") sys.exit(0) def show_callback(ctx: click.Context, param: click.Parameter, value: Any) -> Any: if not value or ctx.resilient_parsing: return value # pragma no cover prog_name = ctx.find_root().info_name assert prog_name complete_var = "_{}_COMPLETE".format(prog_name.replace("-", "_").upper()) shell = "" test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION") if isinstance(value, str): shell = value elif shellingham and not test_disable_detection: shell, _ = shellingham.detect_shell() script_content = get_completion_script( prog_name=prog_name, complete_var=complete_var, shell=shell ) click.echo(script_content) sys.exit(0) # Create a fake command function to extract the completion parameters def _install_completion_placeholder_function( install_completion: bool = Option( None, "--install-completion", is_flag=True, callback=install_callback, expose_value=False, help="Install completion for the current shell.", ), show_completion: bool = Option( None, "--show-completion", is_flag=True, callback=show_callback, expose_value=False, help="Show completion for the current shell, to copy it or customize the installation.", ), ) -> Any: pass # pragma no cover def _install_completion_no_auto_placeholder_function( install_completion: Shells = Option( None, callback=install_callback, expose_value=False, help="Install completion for the specified shell.", ), show_completion: Shells = Option( None, callback=show_callback, expose_value=False, help="Show completion for the specified shell, to copy it or customize the installation.", ), ) -> Any: pass # pragma no cover def completion_init() -> None: if _get_click_major() < 8: from ._completion_click7 import completion_init completion_init() else: from ._completion_click8 import completion_init completion_init() # Re-implement Click's shell_complete to add error message with: # Invalid completion instruction # To use 7.x instruction style for compatibility # And to add extra error messages, for compatibility with Typer in previous versions # This is only called in new Command method, only used by Click 8.x+ def shell_complete( cli: click.BaseCommand, ctx_args: Dict[str, Any], prog_name: str, complete_var: str, instruction: str, ) -> int: import click import click.shell_completion if "_" not in instruction: click.echo("Invalid completion instruction.", err=True) return 1 # Click 8 changed the order/style of shell instructions from e.g. # source_bash to bash_source # Typer override to preserve the old style for compatibility # Original in Click 8.x commented: # shell, _, instruction = instruction.partition("_") instruction, _, shell = instruction.partition("_") # Typer override end comp_cls = click.shell_completion.get_completion_class(shell) if comp_cls is None: click.echo(f"Shell {shell} not supported.", err=True) return 1 comp = comp_cls(cli, ctx_args, prog_name, complete_var) if instruction == "source": click.echo(comp.source()) return 0 if instruction == "complete": click.echo(comp.complete()) return 0 click.echo(f'Completion instruction "{instruction}" not supported.', err=True) return 1 typer-0.4.0/typer/core.py000066400000000000000000000372611411311634200153300ustar00rootroot00000000000000import inspect import os import sys from gettext import gettext as _ from typing import ( TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Tuple, Union, cast, ) import click import click.core import click.formatting import click.parser import click.types from .utils import _get_click_major if TYPE_CHECKING: # pragma: no cover import click.shell_completion # TODO: when deprecating Click 7, remove this def _typer_param_shell_complete( self: click.core.Parameter, ctx: click.Context, incomplete: str ) -> List["click.shell_completion.CompletionItem"]: if self._custom_shell_complete is not None: results = self._custom_shell_complete(ctx, self, incomplete) if results and isinstance(results[0], str): from click.shell_completion import CompletionItem results = [CompletionItem(c) for c in results] return cast(List["click.shell_completion.CompletionItem"], results) return self.type.shell_complete(ctx, self, incomplete) def _typer_param_setup_autocompletion_compat( self: click.Parameter, *, autocompletion: Optional[ Callable[[click.Context, List[str], str], List[Union[Tuple[str, str], str]]] ] = None, ) -> None: if autocompletion is not None and self._custom_shell_complete is None: import warnings warnings.warn( "'autocompletion' is renamed to 'shell_complete'. The old name is" " deprecated and will be removed in Click 8.1. See the docs about" " 'Parameter' for information about new behavior.", DeprecationWarning, stacklevel=2, ) def compat_autocompletion( ctx: click.Context, param: click.core.Parameter, incomplete: str ) -> List["click.shell_completion.CompletionItem"]: from click.shell_completion import CompletionItem out = [] for c in autocompletion(ctx, [], incomplete): # type: ignore if isinstance(c, tuple): c = CompletionItem(c[0], help=c[1]) elif isinstance(c, str): c = CompletionItem(c) if c.value.startswith(incomplete): out.append(c) return out self._custom_shell_complete = compat_autocompletion class TyperArgument(click.core.Argument): def __init__( self, *, # Parameter param_decls: List[str], type: Optional[Any] = None, required: Optional[bool] = None, default: Optional[Any] = None, callback: Optional[Callable[..., Any]] = None, nargs: Optional[int] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # TyperArgument show_default: Union[bool, str] = True, show_choices: bool = True, show_envvar: bool = True, help: Optional[str] = None, hidden: bool = False, ): self.help = help self.show_default = show_default self.show_choices = show_choices self.show_envvar = show_envvar self.hidden = hidden kwargs: Dict[str, Any] = { "param_decls": param_decls, "type": type, "required": required, "default": default, "callback": callback, "nargs": nargs, "metavar": metavar, "expose_value": expose_value, "is_eager": is_eager, "envvar": envvar, } if _get_click_major() > 7: kwargs["shell_complete"] = shell_complete else: kwargs["autocompletion"] = autocompletion super().__init__(**kwargs) if _get_click_major() > 7: _typer_param_setup_autocompletion_compat( self, autocompletion=autocompletion ) def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: # Modified version of click.core.Option.get_help_record() # to support Arguments if self.hidden: return None name = self.make_metavar() help = self.help or "" extra = [] if self.show_envvar: envvar = self.envvar # allow_from_autoenv is currently not supported in Typer for CLI Arguments if envvar is not None: var_str = ( ", ".join(str(d) for d in envvar) if isinstance(envvar, (list, tuple)) else envvar ) extra.append(f"env var: {var_str}") if self.default is not None and (self.show_default or ctx.show_default): if isinstance(self.show_default, str): default_string = f"({self.show_default})" elif isinstance(self.default, (list, tuple)): default_string = ", ".join(str(d) for d in self.default) elif inspect.isfunction(self.default): default_string = "(dynamic)" else: default_string = str(self.default) extra.append(f"default: {default_string}") if self.required: extra.append("required") if extra: extra_str = ";".join(extra) help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" return name, help def make_metavar(self) -> str: # Modified version of click.core.Argument.make_metavar() # to include Argument name if self.metavar is not None: return self.metavar var = (self.name or "").upper() if not self.required: var = "[{}]".format(var) type_var = self.type.get_metavar(self) if type_var: var += f":{type_var}" if self.nargs != 1: var += "..." return var def shell_complete( self, ctx: click.Context, incomplete: str ) -> List["click.shell_completion.CompletionItem"]: return _typer_param_shell_complete(self, ctx=ctx, incomplete=incomplete) class TyperOption(click.core.Option): def __init__( self, *, # Parameter param_decls: List[str], type: Optional[Union[click.types.ParamType, Any]] = None, required: Optional[bool] = None, default: Optional[Any] = None, callback: Optional[Callable[..., Any]] = None, nargs: Optional[int] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # Option show_default: Union[bool, str] = False, prompt: Union[bool, str] = False, confirmation_prompt: Union[bool, str] = False, prompt_required: bool = True, hide_input: bool = False, is_flag: Optional[bool] = None, flag_value: Optional[Any] = None, multiple: bool = False, count: bool = False, allow_from_autoenv: bool = True, help: Optional[str] = None, hidden: bool = False, show_choices: bool = True, show_envvar: bool = False, ): # TODO: when deprecating Click 7, remove custom kwargs with prompt_required # and call super().__init__() directly kwargs: Dict[str, Any] = { "param_decls": param_decls, "type": type, "required": required, "default": default, "callback": callback, "nargs": nargs, "metavar": metavar, "expose_value": expose_value, "is_eager": is_eager, "envvar": envvar, "show_default": show_default, "prompt": prompt, "confirmation_prompt": confirmation_prompt, "hide_input": hide_input, "is_flag": is_flag, "flag_value": flag_value, "multiple": multiple, "count": count, "allow_from_autoenv": allow_from_autoenv, "help": help, "hidden": hidden, "show_choices": show_choices, "show_envvar": show_envvar, } if _get_click_major() > 7: kwargs["prompt_required"] = prompt_required kwargs["shell_complete"] = shell_complete else: kwargs["autocompletion"] = autocompletion super().__init__(**kwargs) if _get_click_major() > 7: _typer_param_setup_autocompletion_compat( self, autocompletion=autocompletion ) def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: # Click 7.x was not breaking this use case, so in that case, re-use its logic if _get_click_major() < 8: return super().get_help_record(ctx) # Duplicate all of Click's logic only to modify a single line, to allow boolean # flags with only names for False values as it's currently supported by Typer # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false if self.hidden: return None any_prefix_is_slash = False def _write_opts(opts: Sequence[str]) -> str: nonlocal any_prefix_is_slash rv, any_slashes = click.formatting.join_options(opts) if any_slashes: any_prefix_is_slash = True if not self.is_flag and not self.count: rv += f" {self.make_metavar()}" return rv rv = [_write_opts(self.opts)] if self.secondary_opts: rv.append(_write_opts(self.secondary_opts)) help = self.help or "" extra = [] if self.show_envvar: envvar = self.envvar if envvar is None: if ( self.allow_from_autoenv and ctx.auto_envvar_prefix is not None and self.name is not None ): envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" if envvar is not None: var_str = ( envvar if isinstance(envvar, str) else ", ".join(str(d) for d in envvar) ) extra.append(_("env var: {var}").format(var=var_str)) # Temporarily enable resilient parsing to avoid type casting # failing for the default. Might be possible to extend this to # help formatting in general. resilient = ctx.resilient_parsing ctx.resilient_parsing = True try: default_value = self.get_default(ctx, call=False) finally: ctx.resilient_parsing = resilient show_default_is_str = isinstance(self.show_default, str) if show_default_is_str or ( default_value is not None and (self.show_default or ctx.show_default) ): if show_default_is_str: default_string = f"({self.show_default})" elif isinstance(default_value, (list, tuple)): default_string = ", ".join(str(d) for d in default_value) elif callable(default_value): default_string = _("(dynamic)") elif self.is_bool_flag and self.secondary_opts: # For boolean flags that have distinct True/False opts, # use the opt without prefix instead of the value. # Typer override, original commented # default_string = click.parser.split_opt( # (self.opts if self.default else self.secondary_opts)[0] # )[1] if self.default: if self.opts: default_string = click.parser.split_opt(self.opts[0])[1] else: default_string = str(default_value) else: default_string = click.parser.split_opt(self.secondary_opts[0])[1] # Typer override end elif self.is_bool_flag and not self.secondary_opts and not default_value: default_string = "" else: default_string = str(default_value) if default_string: extra.append(_("default: {default}").format(default=default_string)) if isinstance(self.type, click.types._NumberRangeBase): range_str = self.type._describe_range() if range_str: extra.append(range_str) if self.required: extra.append(_("required")) if extra: extra_str = "; ".join(extra) help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" return ("; " if any_prefix_is_slash else " / ").join(rv), help def shell_complete( self, ctx: click.Context, incomplete: str ) -> List["click.shell_completion.CompletionItem"]: return _typer_param_shell_complete(self, ctx=ctx, incomplete=incomplete) def _typer_format_options( self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter ) -> None: args = [] opts = [] for param in self.get_params(ctx): rv = param.get_help_record(ctx) if rv is not None: if param.param_type_name == "argument": args.append(rv) elif param.param_type_name == "option": opts.append(rv) # TODO: explore adding Click's gettext support, e.g.: # from gettext import gettext as _ # with formatter.section(_("Options")): # ... if args: with formatter.section("Arguments"): formatter.write_dl(args) if opts: with formatter.section("Options"): formatter.write_dl(opts) def _typer_main_shell_completion( self: click.core.Command, *, ctx_args: Dict[str, Any], prog_name: str, complete_var: Optional[str] = None, ) -> None: if complete_var is None: complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() instruction = os.environ.get(complete_var) if not instruction: return from .completion import shell_complete rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) sys.exit(rv) class TyperCommand(click.core.Command): def format_options( self, ctx: click.Context, formatter: click.HelpFormatter ) -> None: _typer_format_options(self, ctx=ctx, formatter=formatter) def _main_shell_completion( self, ctx_args: Dict[str, Any], prog_name: str, complete_var: Optional[str] = None, ) -> None: _typer_main_shell_completion( self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var ) class TyperGroup(click.core.Group): def format_options( self, ctx: click.Context, formatter: click.HelpFormatter ) -> None: _typer_format_options(self, ctx=ctx, formatter=formatter) self.format_commands(ctx, formatter) def _main_shell_completion( self, ctx_args: Dict[str, Any], prog_name: str, complete_var: Optional[str] = None, ) -> None: _typer_main_shell_completion( self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var ) typer-0.4.0/typer/main.py000066400000000000000000000770021411311634200153210ustar00rootroot00000000000000import inspect from datetime import datetime from enum import Enum from functools import update_wrapper from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID import click from .completion import get_completion_inspect_parameters from .core import TyperArgument, TyperCommand, TyperGroup, TyperOption from .models import ( AnyType, ArgumentInfo, CommandFunctionType, CommandInfo, Default, DefaultPlaceholder, FileBinaryRead, FileBinaryWrite, FileText, FileTextWrite, NoneType, OptionInfo, ParameterInfo, ParamMeta, Required, TyperInfo, ) from .utils import get_params_from_function def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: install_param, show_param = get_completion_inspect_parameters() click_install_param, _ = get_click_param(install_param) click_show_param, _ = get_click_param(show_param) return click_install_param, click_show_param class Typer: def __init__( self, *, name: Optional[str] = Default(None), cls: Optional[Type[click.Command]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command context_settings: Optional[Dict[Any, Any]] = Default(None), callback: Optional[Callable[..., Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), options_metavar: str = Default("[OPTIONS]"), add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), add_completion: bool = True, ): self._add_completion = add_completion self.info = TyperInfo( name=name, cls=cls, invoke_without_command=invoke_without_command, no_args_is_help=no_args_is_help, subcommand_metavar=subcommand_metavar, chain=chain, result_callback=result_callback, context_settings=context_settings, callback=callback, help=help, epilog=epilog, short_help=short_help, options_metavar=options_metavar, add_help_option=add_help_option, hidden=hidden, deprecated=deprecated, ) self.registered_groups: List[TyperInfo] = [] self.registered_commands: List[CommandInfo] = [] self.registered_callback: Optional[TyperInfo] = None def callback( self, name: Optional[str] = Default(None), *, cls: Optional[Type[click.Command]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command context_settings: Optional[Dict[Any, Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), options_metavar: str = Default("[OPTIONS]"), add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), ) -> Callable[[CommandFunctionType], CommandFunctionType]: def decorator(f: CommandFunctionType) -> CommandFunctionType: self.registered_callback = TyperInfo( name=name, cls=cls, invoke_without_command=invoke_without_command, no_args_is_help=no_args_is_help, subcommand_metavar=subcommand_metavar, chain=chain, result_callback=result_callback, context_settings=context_settings, callback=f, help=help, epilog=epilog, short_help=short_help, options_metavar=options_metavar, add_help_option=add_help_option, hidden=hidden, deprecated=deprecated, ) return f return decorator def command( self, name: Optional[str] = None, *, cls: Optional[Type[click.Command]] = None, context_settings: Optional[Dict[Any, Any]] = None, help: Optional[str] = None, epilog: Optional[str] = None, short_help: Optional[str] = None, options_metavar: str = "[OPTIONS]", add_help_option: bool = True, no_args_is_help: bool = False, hidden: bool = False, deprecated: bool = False, ) -> Callable[[CommandFunctionType], CommandFunctionType]: if cls is None: cls = TyperCommand def decorator(f: CommandFunctionType) -> CommandFunctionType: self.registered_commands.append( CommandInfo( name=name, cls=cls, context_settings=context_settings, callback=f, help=help, epilog=epilog, short_help=short_help, options_metavar=options_metavar, add_help_option=add_help_option, no_args_is_help=no_args_is_help, hidden=hidden, deprecated=deprecated, ) ) return f return decorator def add_typer( self, typer_instance: "Typer", *, name: Optional[str] = Default(None), cls: Optional[Type[click.Command]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command context_settings: Optional[Dict[Any, Any]] = Default(None), callback: Optional[Callable[..., Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), options_metavar: str = Default("[OPTIONS]"), add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), ) -> None: self.registered_groups.append( TyperInfo( typer_instance, name=name, cls=cls, invoke_without_command=invoke_without_command, no_args_is_help=no_args_is_help, subcommand_metavar=subcommand_metavar, chain=chain, result_callback=result_callback, context_settings=context_settings, callback=callback, help=help, epilog=epilog, short_help=short_help, options_metavar=options_metavar, add_help_option=add_help_option, hidden=hidden, deprecated=deprecated, ) ) def __call__(self, *args: Any, **kwargs: Any) -> Any: return get_command(self)(*args, **kwargs) def get_group(typer_instance: Typer) -> click.Command: group = get_group_from_info(TyperInfo(typer_instance)) return group def get_command(typer_instance: Typer) -> click.Command: if typer_instance._add_completion: click_install_param, click_show_param = get_install_completion_arguments() if ( typer_instance.registered_callback or typer_instance.info.callback or typer_instance.registered_groups or len(typer_instance.registered_commands) > 1 ): # Create a Group click_command = get_group(typer_instance) if typer_instance._add_completion: click_command.params.append(click_install_param) click_command.params.append(click_show_param) return click_command elif len(typer_instance.registered_commands) == 1: # Create a single Command click_command = get_command_from_info(typer_instance.registered_commands[0]) if typer_instance._add_completion: click_command.params.append(click_install_param) click_command.params.append(click_show_param) return click_command assert False, "Could not get a command for this Typer instance" # pragma no cover def get_group_name(typer_info: TyperInfo) -> Optional[str]: if typer_info.callback: # Priority 1: Callback passed in app.add_typer() return get_command_name(typer_info.callback.__name__) if typer_info.typer_instance: registered_callback = typer_info.typer_instance.registered_callback if registered_callback: if registered_callback.callback: # Priority 2: Callback passed in @subapp.callback() return get_command_name(registered_callback.callback.__name__) if typer_info.typer_instance.info.callback: return get_command_name(typer_info.typer_instance.info.callback.__name__) return None def solve_typer_info_help(typer_info: TyperInfo) -> str: # Priority 1: Explicit value was set in app.add_typer() if not isinstance(typer_info.help, DefaultPlaceholder): return inspect.cleandoc(typer_info.help or "") # Priority 2: Explicit value was set in sub_app.callback() try: callback_help = typer_info.typer_instance.registered_callback.help if not isinstance(callback_help, DefaultPlaceholder): return inspect.cleandoc(callback_help or "") except AttributeError: pass # Priority 3: Explicit value was set in sub_app = typer.Typer() try: instance_help = typer_info.typer_instance.info.help if not isinstance(instance_help, DefaultPlaceholder): return inspect.cleandoc(instance_help or "") except AttributeError: pass # Priority 4: Implicit inference from callback docstring in app.add_typer() if typer_info.callback: doc = inspect.getdoc(typer_info.callback) if doc: return doc # Priority 5: Implicit inference from callback docstring in @app.callback() try: callback = typer_info.typer_instance.registered_callback.callback if not isinstance(callback, DefaultPlaceholder): doc = inspect.getdoc(callback or "") if doc: return doc except AttributeError: pass # Priority 6: Implicit inference from callback docstring in typer.Typer() try: instance_callback = typer_info.typer_instance.info.callback if not isinstance(instance_callback, DefaultPlaceholder): doc = inspect.getdoc(instance_callback) if doc: return doc except AttributeError: pass # Value not set, use the default return typer_info.help.value def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: values: Dict[str, Any] = {} name = None for name, value in typer_info.__dict__.items(): # Priority 1: Value was set in app.add_typer() if not isinstance(value, DefaultPlaceholder): values[name] = value continue # Priority 2: Value was set in @subapp.callback() try: callback_value = getattr( typer_info.typer_instance.registered_callback, name # type: ignore ) if not isinstance(callback_value, DefaultPlaceholder): values[name] = callback_value continue except AttributeError: pass # Priority 3: Value set in subapp = typer.Typer() try: instance_value = getattr( typer_info.typer_instance.info, name # type: ignore ) if not isinstance(instance_value, DefaultPlaceholder): values[name] = instance_value continue except AttributeError: pass # Value not set, use the default values[name] = value.value if values["name"] is None: values["name"] = get_group_name(typer_info) values["help"] = solve_typer_info_help(typer_info) return TyperInfo(**values) def get_group_from_info(group_info: TyperInfo) -> click.Command: assert ( group_info.typer_instance ), "A Typer instance is needed to generate a Click Group" commands: Dict[str, click.Command] = {} for command_info in group_info.typer_instance.registered_commands: command = get_command_from_info(command_info=command_info) if command.name: commands[command.name] = command for sub_group_info in group_info.typer_instance.registered_groups: sub_group = get_group_from_info(sub_group_info) if sub_group.name: commands[sub_group.name] = sub_group solved_info = solve_typer_info_defaults(group_info) ( params, convertors, context_param_name, ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) cls = solved_info.cls or TyperGroup group = cls( # type: ignore name=solved_info.name or "", commands=commands, invoke_without_command=solved_info.invoke_without_command, no_args_is_help=solved_info.no_args_is_help, subcommand_metavar=solved_info.subcommand_metavar, chain=solved_info.chain, result_callback=solved_info.result_callback, context_settings=solved_info.context_settings, callback=get_callback( callback=solved_info.callback, params=params, convertors=convertors, context_param_name=context_param_name, ), params=params, # type: ignore help=solved_info.help, epilog=solved_info.epilog, short_help=solved_info.short_help, options_metavar=solved_info.options_metavar, add_help_option=solved_info.add_help_option, hidden=solved_info.hidden, deprecated=solved_info.deprecated, ) return group def get_command_name(name: str) -> str: return name.lower().replace("_", "-") def get_params_convertors_ctx_param_name_from_function( callback: Optional[Callable[..., Any]] ) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]: params = [] convertors = {} context_param_name = None if callback: parameters = get_params_from_function(callback) for param_name, param in parameters.items(): if lenient_issubclass(param.annotation, click.Context): context_param_name = param_name continue click_param, convertor = get_click_param(param) if convertor: convertors[param_name] = convertor params.append(click_param) return params, convertors, context_param_name def get_command_from_info(command_info: CommandInfo) -> click.Command: assert command_info.callback, "A command must have a callback function" name = command_info.name or get_command_name(command_info.callback.__name__) use_help = command_info.help if use_help is None: use_help = inspect.getdoc(command_info.callback) else: use_help = inspect.cleandoc(use_help) ( params, convertors, context_param_name, ) = get_params_convertors_ctx_param_name_from_function(command_info.callback) cls = command_info.cls or TyperCommand command = cls( name=name, context_settings=command_info.context_settings, callback=get_callback( callback=command_info.callback, params=params, convertors=convertors, context_param_name=context_param_name, ), params=params, # type: ignore help=use_help, epilog=command_info.epilog, short_help=command_info.short_help, options_metavar=command_info.options_metavar, add_help_option=command_info.add_help_option, no_args_is_help=command_info.no_args_is_help, hidden=command_info.hidden, deprecated=command_info.deprecated, ) return command def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: if value is not None: return Path(value) return None def generate_enum_convertor(enum: Type[Enum]) -> Callable[..., Any]: lower_val_map = {str(val.value).lower(): val for val in enum} def convertor(value: Any) -> Any: if value is not None: low = str(value).lower() if low in lower_val_map: key = lower_val_map[low] return enum(key) return convertor def generate_iter_convertor(convertor: Callable[[Any], Any]) -> Callable[..., Any]: def internal_convertor(value: Any) -> List[Any]: return [convertor(v) for v in value] return internal_convertor def get_callback( *, callback: Optional[Callable[..., Any]] = None, params: Sequence[click.Parameter] = [], convertors: Dict[str, Callable[[str], Any]] = {}, context_param_name: Optional[str] = None, ) -> Optional[Callable[..., Any]]: if not callback: return None parameters = get_params_from_function(callback) use_params: Dict[str, Any] = {} for param_name in parameters: use_params[param_name] = None for param in params: if param.name: use_params[param.name] = param.default def wrapper(**kwargs: Any) -> Any: for k, v in kwargs.items(): if k in convertors: use_params[k] = convertors[k](v) else: use_params[k] = v if context_param_name: use_params[context_param_name] = click.get_current_context() return callback(**use_params) # type: ignore update_wrapper(wrapper, callback) return wrapper def get_click_type( *, annotation: Any, parameter_info: ParameterInfo ) -> click.ParamType: if annotation == str: return click.STRING elif annotation == int: if parameter_info.min is not None or parameter_info.max is not None: min_ = None max_ = None if parameter_info.min is not None: min_ = int(parameter_info.min) if parameter_info.max is not None: max_ = int(parameter_info.max) return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp) else: return click.INT elif annotation == float: if parameter_info.min is not None or parameter_info.max is not None: return click.FloatRange( min=parameter_info.min, max=parameter_info.max, clamp=parameter_info.clamp, ) else: return click.FLOAT elif annotation == bool: return click.BOOL elif annotation == UUID: return click.UUID elif annotation == datetime: return click.DateTime(formats=parameter_info.formats) elif ( annotation == Path or parameter_info.allow_dash or parameter_info.path_type or parameter_info.resolve_path ): return click.Path( exists=parameter_info.exists, file_okay=parameter_info.file_okay, dir_okay=parameter_info.dir_okay, writable=parameter_info.writable, readable=parameter_info.readable, resolve_path=parameter_info.resolve_path, allow_dash=parameter_info.allow_dash, path_type=parameter_info.path_type, ) elif lenient_issubclass(annotation, FileTextWrite): return click.File( mode=parameter_info.mode or "w", encoding=parameter_info.encoding, errors=parameter_info.errors, lazy=parameter_info.lazy, atomic=parameter_info.atomic, ) elif lenient_issubclass(annotation, FileText): return click.File( mode=parameter_info.mode or "r", encoding=parameter_info.encoding, errors=parameter_info.errors, lazy=parameter_info.lazy, atomic=parameter_info.atomic, ) elif lenient_issubclass(annotation, FileBinaryRead): return click.File( mode=parameter_info.mode or "rb", encoding=parameter_info.encoding, errors=parameter_info.errors, lazy=parameter_info.lazy, atomic=parameter_info.atomic, ) elif lenient_issubclass(annotation, FileBinaryWrite): return click.File( mode=parameter_info.mode or "wb", encoding=parameter_info.encoding, errors=parameter_info.errors, lazy=parameter_info.lazy, atomic=parameter_info.atomic, ) elif lenient_issubclass(annotation, Enum): return click.Choice( [item.value for item in annotation], case_sensitive=parameter_info.case_sensitive, ) raise RuntimeError(f"Type not yet supported: {annotation}") # pragma no cover def lenient_issubclass( cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]] ) -> bool: return isinstance(cls, type) and issubclass(cls, class_or_tuple) def get_click_param( param: ParamMeta, ) -> Tuple[Union[click.Argument, click.Option], Any]: # First, find out what will be: # * ParamInfo (ArgumentInfo or OptionInfo) # * default_value # * required default_value = None required = False if isinstance(param.default, ParameterInfo): parameter_info = param.default if parameter_info.default == Required: required = True else: default_value = parameter_info.default elif param.default == Required or param.default == param.empty: required = True parameter_info = ArgumentInfo() else: default_value = param.default parameter_info = OptionInfo() annotation: Any = Any if not param.annotation == param.empty: annotation = param.annotation else: annotation = str main_type = annotation is_list = False parameter_type: Any = None is_flag = None origin = getattr(main_type, "__origin__", None) if origin is not None: # Handle Optional[SomeType] if origin is Union: types = [] for type_ in main_type.__args__: if type_ is NoneType: continue types.append(type_) assert len(types) == 1, "Typer Currently doesn't support Union types" main_type = types[0] origin = getattr(main_type, "__origin__", None) # Handle Tuples and Lists if lenient_issubclass(origin, List): main_type = main_type.__args__[0] assert not getattr( main_type, "__origin__", None ), "List types with complex sub-types are not currently supported" is_list = True elif lenient_issubclass(origin, Tuple): # type: ignore types = [] for type_ in main_type.__args__: assert not getattr( type_, "__origin__", None ), "Tuple types with complex sub-types are not currently supported" types.append( get_click_type(annotation=type_, parameter_info=parameter_info) ) parameter_type = tuple(types) if parameter_type is None: parameter_type = get_click_type( annotation=main_type, parameter_info=parameter_info ) convertor = None if lenient_issubclass(main_type, Path): convertor = param_path_convertor if lenient_issubclass(main_type, Enum): convertor = generate_enum_convertor(main_type) if convertor and is_list: convertor = generate_iter_convertor(convertor) # TODO: handle recursive conversion for tuples if isinstance(parameter_info, OptionInfo): if main_type is bool and not (parameter_info.is_flag is False): is_flag = True # Click doesn't accept a flag of type bool, only None, and then it sets it # to bool internally parameter_type = None default_option_name = get_command_name(param.name) if is_flag: default_option_declaration = ( f"--{default_option_name}/--no-{default_option_name}" ) else: default_option_declaration = f"--{default_option_name}" param_decls = [param.name] if parameter_info.param_decls: param_decls.extend(parameter_info.param_decls) else: param_decls.append(default_option_declaration) return ( TyperOption( # Option param_decls=param_decls, show_default=parameter_info.show_default, prompt=parameter_info.prompt, confirmation_prompt=parameter_info.confirmation_prompt, prompt_required=parameter_info.prompt_required, hide_input=parameter_info.hide_input, is_flag=is_flag, flag_value=parameter_info.flag_value, multiple=is_list, count=parameter_info.count, allow_from_autoenv=parameter_info.allow_from_autoenv, type=parameter_type, help=parameter_info.help, hidden=parameter_info.hidden, show_choices=parameter_info.show_choices, show_envvar=parameter_info.show_envvar, # Parameter required=required, default=default_value, callback=get_param_callback( callback=parameter_info.callback, convertor=convertor ), metavar=parameter_info.metavar, expose_value=parameter_info.expose_value, is_eager=parameter_info.is_eager, envvar=parameter_info.envvar, shell_complete=parameter_info.shell_complete, autocompletion=get_param_completion(parameter_info.autocompletion), ), convertor, ) elif isinstance(parameter_info, ArgumentInfo): param_decls = [param.name] nargs = None if is_list: nargs = -1 return ( TyperArgument( # Argument param_decls=param_decls, type=parameter_type, required=required, nargs=nargs, # TyperArgument show_default=parameter_info.show_default, show_choices=parameter_info.show_choices, show_envvar=parameter_info.show_envvar, help=parameter_info.help, hidden=parameter_info.hidden, # Parameter default=default_value, callback=get_param_callback( callback=parameter_info.callback, convertor=convertor ), metavar=parameter_info.metavar, expose_value=parameter_info.expose_value, is_eager=parameter_info.is_eager, envvar=parameter_info.envvar, autocompletion=get_param_completion(parameter_info.autocompletion), ), convertor, ) assert False, "A click.Parameter should be returned" # pragma no cover def get_param_callback( *, callback: Optional[Callable[..., Any]] = None, convertor: Optional[Callable[..., Any]] = None, ) -> Optional[Callable[..., Any]]: if not callback: return None parameters = get_params_from_function(callback) ctx_name = None click_param_name = None value_name = None untyped_names: List[str] = [] for param_name, param_sig in parameters.items(): if lenient_issubclass(param_sig.annotation, click.Context): ctx_name = param_name elif lenient_issubclass(param_sig.annotation, click.Parameter): click_param_name = param_name else: untyped_names.append(param_name) # Extract value param name first if untyped_names: value_name = untyped_names.pop() # If context and Click param were not typed (old/Click callback style) extract them if untyped_names: if ctx_name is None: ctx_name = untyped_names.pop(0) if click_param_name is None: if untyped_names: click_param_name = untyped_names.pop(0) if untyped_names: raise click.ClickException( "Too many CLI parameter callback function parameters" ) def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: use_params: Dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if click_param_name: use_params[click_param_name] = param if value_name: if convertor: use_value = convertor(value) else: use_value = value use_params[value_name] = use_value return callback(**use_params) # type: ignore update_wrapper(wrapper, callback) return wrapper def get_param_completion( callback: Optional[Callable[..., Any]] = None ) -> Optional[Callable[..., Any]]: if not callback: return None parameters = get_params_from_function(callback) ctx_name = None args_name = None incomplete_name = None unassigned_params = [param for param in parameters.values()] for param_sig in unassigned_params[:]: origin = getattr(param_sig.annotation, "__origin__", None) if lenient_issubclass(param_sig.annotation, click.Context): ctx_name = param_sig.name unassigned_params.remove(param_sig) elif lenient_issubclass(origin, List): args_name = param_sig.name unassigned_params.remove(param_sig) elif lenient_issubclass(param_sig.annotation, str): incomplete_name = param_sig.name unassigned_params.remove(param_sig) # If there are still unassigned parameters (not typed), extract by name for param_sig in unassigned_params[:]: if ctx_name is None and param_sig.name == "ctx": ctx_name = param_sig.name unassigned_params.remove(param_sig) elif args_name is None and param_sig.name == "args": args_name = param_sig.name unassigned_params.remove(param_sig) elif incomplete_name is None and param_sig.name == "incomplete": incomplete_name = param_sig.name unassigned_params.remove(param_sig) # Extract value param name first if unassigned_params: show_params = " ".join([param.name for param in unassigned_params]) raise click.ClickException( f"Invalid autocompletion callback parameters: {show_params}" ) def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: use_params: Dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if args_name: use_params[args_name] = args if incomplete_name: use_params[incomplete_name] = incomplete return callback(**use_params) # type: ignore update_wrapper(wrapper, callback) return wrapper def run(function: Callable[..., Any]) -> Any: app = Typer() app.command()(function) app() typer-0.4.0/typer/models.py000066400000000000000000000322541411311634200156600ustar00rootroot00000000000000import inspect import io from typing import ( TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Type, TypeVar, Union, ) import click if TYPE_CHECKING: # pragma: no cover import click.shell_completion from .main import Typer # noqa NoneType = type(None) AnyType = Type[Any] Required = ... class Context(click.Context): pass class FileText(io.TextIOWrapper): pass class FileTextWrite(FileText): pass class FileBinaryRead(io.BufferedReader): pass class FileBinaryWrite(io.BufferedWriter): pass class CallbackParam(click.Parameter): pass class DefaultPlaceholder: """ You shouldn't use this class directly. It's used internally to recognize when a default value has been overwritten, even if the new value is `None`. """ def __init__(self, value: Any): self.value = value def __bool__(self) -> bool: return bool(self.value) DefaultType = TypeVar("DefaultType") CommandFunctionType = TypeVar("CommandFunctionType", bound=Callable[..., Any]) def Default(value: DefaultType) -> DefaultType: """ You shouldn't use this function directly. It's used internally to recognize when a default value has been overwritten, even if the new value is `None`. """ return DefaultPlaceholder(value) # type: ignore class CommandInfo: def __init__( self, name: Optional[str] = None, *, cls: Optional[Type[click.Command]] = None, context_settings: Optional[Dict[Any, Any]] = None, callback: Optional[Callable[..., Any]] = None, help: Optional[str] = None, epilog: Optional[str] = None, short_help: Optional[str] = None, options_metavar: str = "[OPTIONS]", add_help_option: bool = True, no_args_is_help: bool = False, hidden: bool = False, deprecated: bool = False, ): self.name = name self.cls = cls self.context_settings = context_settings self.callback = callback self.help = help self.epilog = epilog self.short_help = short_help self.options_metavar = options_metavar self.add_help_option = add_help_option self.no_args_is_help = no_args_is_help self.hidden = hidden self.deprecated = deprecated class TyperInfo: def __init__( self, typer_instance: Optional["Typer"] = Default(None), *, name: Optional[str] = Default(None), cls: Optional[Type[click.Command]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command context_settings: Optional[Dict[Any, Any]] = Default(None), callback: Optional[Callable[..., Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), options_metavar: str = Default("[OPTIONS]"), add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), ): self.typer_instance = typer_instance self.name = name self.cls = cls self.invoke_without_command = invoke_without_command self.no_args_is_help = no_args_is_help self.subcommand_metavar = subcommand_metavar self.chain = chain self.result_callback = result_callback self.context_settings = context_settings self.callback = callback self.help = help self.epilog = epilog self.short_help = short_help self.options_metavar = options_metavar self.add_help_option = add_help_option self.hidden = hidden self.deprecated = deprecated class ParameterInfo: def __init__( self, *, default: Optional[Any] = None, param_decls: Optional[Sequence[str]] = None, callback: Optional[Callable[..., Any]] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # TyperArgument show_default: Union[bool, str] = True, show_choices: bool = True, show_envvar: bool = True, help: Optional[str] = None, hidden: bool = False, # Choice case_sensitive: bool = True, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime formats: Optional[Union[List[str]]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = "strict", lazy: Optional[bool] = None, atomic: bool = False, # Path exists: bool = False, file_okay: bool = True, dir_okay: bool = True, writable: bool = False, readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, ): self.default = default self.param_decls = param_decls self.callback = callback self.metavar = metavar self.expose_value = expose_value self.is_eager = is_eager self.envvar = envvar self.shell_complete = shell_complete self.autocompletion = autocompletion # TyperArgument self.show_default = show_default self.show_choices = show_choices self.show_envvar = show_envvar self.help = help self.hidden = hidden # Choice self.case_sensitive = case_sensitive # Numbers self.min = min self.max = max self.clamp = clamp # DateTime self.formats = formats # File self.mode = mode self.encoding = encoding self.errors = errors self.lazy = lazy self.atomic = atomic # Path self.exists = exists self.file_okay = file_okay self.dir_okay = dir_okay self.writable = writable self.readable = readable self.resolve_path = resolve_path self.allow_dash = allow_dash self.path_type = path_type class OptionInfo(ParameterInfo): def __init__( self, *, # ParameterInfo default: Optional[Any] = None, param_decls: Optional[Sequence[str]] = None, callback: Optional[Callable[..., Any]] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # Option show_default: bool = True, prompt: Union[bool, str] = False, confirmation_prompt: bool = False, prompt_required: bool = True, hide_input: bool = False, is_flag: Optional[bool] = None, flag_value: Optional[Any] = None, count: bool = False, allow_from_autoenv: bool = True, help: Optional[str] = None, hidden: bool = False, show_choices: bool = True, show_envvar: bool = True, # Choice case_sensitive: bool = True, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime formats: Optional[Union[List[str]]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = "strict", lazy: Optional[bool] = None, atomic: bool = False, # Path exists: bool = False, file_okay: bool = True, dir_okay: bool = True, writable: bool = False, readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, ): super().__init__( default=default, param_decls=param_decls, callback=callback, metavar=metavar, expose_value=expose_value, is_eager=is_eager, envvar=envvar, shell_complete=shell_complete, autocompletion=autocompletion, # TyperArgument show_default=show_default, show_choices=show_choices, show_envvar=show_envvar, help=help, hidden=hidden, # Choice case_sensitive=case_sensitive, # Numbers min=min, max=max, clamp=clamp, # DateTime formats=formats, # File mode=mode, encoding=encoding, errors=errors, lazy=lazy, atomic=atomic, # Path exists=exists, file_okay=file_okay, dir_okay=dir_okay, writable=writable, readable=readable, resolve_path=resolve_path, allow_dash=allow_dash, path_type=path_type, ) self.prompt = prompt self.confirmation_prompt = confirmation_prompt self.prompt_required = prompt_required self.hide_input = hide_input self.is_flag = is_flag self.flag_value = flag_value self.count = count self.allow_from_autoenv = allow_from_autoenv class ArgumentInfo(ParameterInfo): def __init__( self, *, # ParameterInfo default: Optional[Any] = None, param_decls: Optional[Sequence[str]] = None, callback: Optional[Callable[..., Any]] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # TyperArgument show_default: Union[bool, str] = True, show_choices: bool = True, show_envvar: bool = True, help: Optional[str] = None, hidden: bool = False, # Choice case_sensitive: bool = True, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime formats: Optional[Union[List[str]]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = "strict", lazy: Optional[bool] = None, atomic: bool = False, # Path exists: bool = False, file_okay: bool = True, dir_okay: bool = True, writable: bool = False, readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, ): super().__init__( default=default, param_decls=param_decls, callback=callback, metavar=metavar, expose_value=expose_value, is_eager=is_eager, envvar=envvar, shell_complete=shell_complete, autocompletion=autocompletion, # TyperArgument show_default=show_default, show_choices=show_choices, show_envvar=show_envvar, help=help, hidden=hidden, # Choice case_sensitive=case_sensitive, # Numbers min=min, max=max, clamp=clamp, # DateTime formats=formats, # File mode=mode, encoding=encoding, errors=errors, lazy=lazy, atomic=atomic, # Path exists=exists, file_okay=file_okay, dir_okay=dir_okay, writable=writable, readable=readable, resolve_path=resolve_path, allow_dash=allow_dash, path_type=path_type, ) class ParamMeta: empty = inspect.Parameter.empty def __init__( self, *, name: str, default: Any = inspect.Parameter.empty, annotation: Any = inspect.Parameter.empty, ) -> None: self.name = name self.default = default self.annotation = annotation typer-0.4.0/typer/params.py000066400000000000000000000131051411311634200156520ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union import click from .models import ArgumentInfo, OptionInfo if TYPE_CHECKING: # pragma: no cover import click.shell_completion def Option( # Parameter default: Optional[Any], *param_decls: str, callback: Optional[Callable[..., Any]] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # Option show_default: bool = True, prompt: Union[bool, str] = False, confirmation_prompt: bool = False, prompt_required: bool = True, hide_input: bool = False, is_flag: Optional[bool] = None, flag_value: Optional[Any] = None, count: bool = False, allow_from_autoenv: bool = True, help: Optional[str] = None, hidden: bool = False, show_choices: bool = True, show_envvar: bool = True, # Choice case_sensitive: bool = True, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime formats: Optional[Union[List[str]]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = "strict", lazy: Optional[bool] = None, atomic: bool = False, # Path exists: bool = False, file_okay: bool = True, dir_okay: bool = True, writable: bool = False, readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, ) -> Any: return OptionInfo( # Parameter default=default, param_decls=param_decls, callback=callback, metavar=metavar, expose_value=expose_value, is_eager=is_eager, envvar=envvar, shell_complete=shell_complete, autocompletion=autocompletion, # Option show_default=show_default, prompt=prompt, confirmation_prompt=confirmation_prompt, prompt_required=prompt_required, hide_input=hide_input, is_flag=is_flag, flag_value=flag_value, count=count, allow_from_autoenv=allow_from_autoenv, help=help, hidden=hidden, show_choices=show_choices, show_envvar=show_envvar, # Choice case_sensitive=case_sensitive, # Numbers min=min, max=max, clamp=clamp, # DateTime formats=formats, # File mode=mode, encoding=encoding, errors=errors, lazy=lazy, atomic=atomic, # Path exists=exists, file_okay=file_okay, dir_okay=dir_okay, writable=writable, readable=readable, resolve_path=resolve_path, allow_dash=allow_dash, path_type=path_type, ) def Argument( # Parameter default: Optional[Any], *, callback: Optional[Callable[..., Any]] = None, metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, envvar: Optional[Union[str, List[str]]] = None, shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], Union[List["click.shell_completion.CompletionItem"], List[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, # TyperArgument show_default: Union[bool, str] = True, show_choices: bool = True, show_envvar: bool = True, help: Optional[str] = None, hidden: bool = False, # Choice case_sensitive: bool = True, # Numbers min: Optional[Union[int, float]] = None, max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime formats: Optional[Union[List[str]]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, errors: Optional[str] = "strict", lazy: Optional[bool] = None, atomic: bool = False, # Path exists: bool = False, file_okay: bool = True, dir_okay: bool = True, writable: bool = False, readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, ) -> Any: return ArgumentInfo( # Parameter default=default, # Arguments can only have one param declaration # it will be generated from the param name param_decls=None, callback=callback, metavar=metavar, expose_value=expose_value, is_eager=is_eager, envvar=envvar, shell_complete=shell_complete, autocompletion=autocompletion, # TyperArgument show_default=show_default, show_choices=show_choices, show_envvar=show_envvar, help=help, hidden=hidden, # Choice case_sensitive=case_sensitive, # Numbers min=min, max=max, clamp=clamp, # DateTime formats=formats, # File mode=mode, encoding=encoding, errors=errors, lazy=lazy, atomic=atomic, # Path exists=exists, file_okay=file_okay, dir_okay=dir_okay, writable=writable, readable=readable, resolve_path=resolve_path, allow_dash=allow_dash, path_type=path_type, ) typer-0.4.0/typer/py.typed000066400000000000000000000000001411311634200155020ustar00rootroot00000000000000typer-0.4.0/typer/testing.py000066400000000000000000000015611411311634200160470ustar00rootroot00000000000000from typing import IO, Any, Mapping, Optional, Sequence, Text, Union from click.testing import CliRunner as ClickCliRunner # noqa from click.testing import Result from typer.main import Typer from typer.main import get_command as _get_command class CliRunner(ClickCliRunner): def invoke( # type: ignore self, app: Typer, args: Optional[Union[str, Sequence[str]]] = None, input: Optional[Union[bytes, Text, IO[Any]]] = None, env: Optional[Mapping[str, str]] = None, catch_exceptions: bool = True, color: bool = False, **extra: Any, ) -> Result: use_cli = _get_command(app) return super().invoke( use_cli, args=args, input=input, env=env, catch_exceptions=catch_exceptions, color=color, **extra, ) typer-0.4.0/typer/utils.py000066400000000000000000000012551411311634200155320ustar00rootroot00000000000000import inspect from typing import Any, Callable, Dict, get_type_hints import click from .models import ParamMeta def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: signature = inspect.signature(func) type_hints = get_type_hints(func) params = {} for param in signature.parameters.values(): annotation = param.annotation if param.name in type_hints: annotation = type_hints[param.name] params[param.name] = ParamMeta( name=param.name, default=param.default, annotation=annotation ) return params def _get_click_major() -> int: return int(click.__version__.split(".")[0])