pax_global_header00006660000000000000000000000064144241160530014512gustar00rootroot0000000000000052 comment=4c6098f9fe893cbe5536b1cb793ef7dd35bfd7c1 typer-0.9.0/000077500000000000000000000000001442411605300126635ustar00rootroot00000000000000typer-0.9.0/.coveragerc000066400000000000000000000004661442411605300150120ustar00rootroot00000000000000[run] source = typer tests docs_src omit = typer/_typing.py parallel = True context = '${CONTEXT}' [report] precision = 2 exclude_lines = pragma: no cover pragma: nocover pragma no cover raise NotImplementedError raise NotImplemented @overload if TYPE_CHECKING: typer-0.9.0/.github/000077500000000000000000000000001442411605300142235ustar00rootroot00000000000000typer-0.9.0/.github/DISCUSSION_TEMPLATE/000077500000000000000000000000001442411605300172015ustar00rootroot00000000000000typer-0.9.0/.github/DISCUSSION_TEMPLATE/questions.yml000066400000000000000000000122671442411605300217660ustar00rootroot00000000000000labels: [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 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 questions. 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 questions, I'll have to discard and 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 here. required: true - label: I used the GitHub search to find a similar question 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. * Review one Pull Request by downloading the code and following all the review process](https://typer.tiangolo.com/help-typer/#review-pull-requests). 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.9.0/.github/FUNDING.yml000066400000000000000000000000231442411605300160330ustar00rootroot00000000000000github: [tiangolo] typer-0.9.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001442411605300164065ustar00rootroot00000000000000typer-0.9.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000013661442411605300204040ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Security Contact about: Please report security vulnerabilities to security@tiangolo.com - name: Question or Problem about: Ask a question or ask about a problem in GitHub Discussions. url: https://github.com/tiangolo/typer/discussions/categories/questions - name: Feature Request about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already. url: https://github.com/tiangolo/typer/discussions/categories/questions - name: Show and tell about: Show what you built with Typer or to be used with Typer. url: https://github.com/tiangolo/typer/discussions/categories/show-and-tell typer-0.9.0/.github/ISSUE_TEMPLATE/privileged.yml000066400000000000000000000015651442411605300212720ustar00rootroot00000000000000name: Privileged description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. ๐Ÿ‘‡ body: - type: markdown attributes: value: | Thanks for your interest in Typer! ๐Ÿš€ If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/typer/discussions/categories/questions) instead. - type: checkboxes id: privileged attributes: label: Privileged issue description: Confirm that you are allowed to create an issue here. options: - label: I'm @tiangolo or he asked me directly to create an issue here. required: true - type: textarea id: content attributes: label: Issue Content description: Add the content of the issue here. typer-0.9.0/.github/actions/000077500000000000000000000000001442411605300156635ustar00rootroot00000000000000typer-0.9.0/.github/actions/comment-docs-preview-in-pr/000077500000000000000000000000001442411605300227555ustar00rootroot00000000000000typer-0.9.0/.github/actions/comment-docs-preview-in-pr/Dockerfile000066400000000000000000000001631442411605300247470ustar00rootroot00000000000000FROM python:3.7 RUN pip install httpx "pydantic==1.5.1" pygithub COPY ./app /app CMD ["python", "/app/main.py"] typer-0.9.0/.github/actions/comment-docs-preview-in-pr/action.yml000066400000000000000000000006121442411605300247540ustar00rootroot00000000000000name: Comment Docs Preview in PR description: Comment with the docs URL preview in the PR author: Sebastiรกn Ramรญrez inputs: token: description: Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }} required: true deploy_url: description: The deployment URL to comment in the PR required: true runs: using: docker image: Dockerfile typer-0.9.0/.github/actions/comment-docs-preview-in-pr/app/000077500000000000000000000000001442411605300235355ustar00rootroot00000000000000typer-0.9.0/.github/actions/comment-docs-preview-in-pr/app/main.py000066400000000000000000000040321442411605300250320ustar00rootroot00000000000000import logging import sys from pathlib import Path from typing import Optional import httpx from github import Github from github.PullRequest import PullRequest from pydantic import BaseModel, BaseSettings, SecretStr, ValidationError github_api = "https://api.github.com" class Settings(BaseSettings): github_repository: str github_event_path: Path github_event_name: Optional[str] = None input_token: SecretStr input_deploy_url: str class PartialGithubEventHeadCommit(BaseModel): id: str class PartialGithubEventWorkflowRun(BaseModel): head_commit: PartialGithubEventHeadCommit class PartialGithubEvent(BaseModel): workflow_run: PartialGithubEventWorkflowRun if __name__ == "__main__": logging.basicConfig(level=logging.INFO) settings = Settings() logging.info(f"Using config: {settings.json()}") g = Github(settings.input_token.get_secret_value()) repo = g.get_repo(settings.github_repository) try: event = PartialGithubEvent.parse_file(settings.github_event_path) except ValidationError as e: logging.error(f"Error parsing event file: {e.errors()}") sys.exit(0) use_pr: Optional[PullRequest] = None for pr in repo.get_pulls(): if pr.head.sha == event.workflow_run.head_commit.id: use_pr = pr break if not use_pr: logging.error(f"No PR found for hash: {event.workflow_run.head_commit.id}") sys.exit(0) github_headers = { "Authorization": f"token {settings.input_token.get_secret_value()}" } url = f"{github_api}/repos/{settings.github_repository}/issues/{use_pr.number}/comments" logging.info(f"Using comments URL: {url}") response = httpx.post( url, headers=github_headers, json={ "body": f"๐Ÿ“ Docs preview for commit {use_pr.head.sha} at: {settings.input_deploy_url}" }, ) if not (200 <= response.status_code <= 300): logging.error(f"Error posting comment: {response.text}") sys.exit(1) logging.info("Finished") typer-0.9.0/.github/dependabot.yml000066400000000000000000000004651442411605300170600ustar00rootroot00000000000000version: 2 updates: # GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" commit-message: prefix: โฌ† # Python - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" commit-message: prefix: โฌ† typer-0.9.0/.github/workflows/000077500000000000000000000000001442411605300162605ustar00rootroot00000000000000typer-0.9.0/.github/workflows/build-docs.yml000066400000000000000000000053371442411605300210400ustar00rootroot00000000000000name: Build Docs on: push: branches: - master pull_request: types: [opened, synchronize] workflow_dispatch: inputs: debug_enabled: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: false jobs: build-docs: runs-on: ubuntu-20.04 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.7" # 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: actions/cache@v3 id: cache with: path: ${{ env.pythonLocation }} key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v01 - name: Install Flit if: steps.cache.outputs.cache-hit != 'true' run: python3.7 -m pip install flit - name: Install docs extras if: steps.cache.outputs.cache-hit != 'true' run: python3.7 -m flit install --extras doc - name: Install Material for MkDocs Insiders if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' run: python3.7 -m pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git - uses: actions/cache@v3 with: key: mkdocs-cards-${{ github.ref }}-v1 path: .cache - name: Build Docs if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true run: python3.7 -m mkdocs build - name: Build Docs with Insiders if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false run: python3.7 -m mkdocs build --config-file mkdocs.insiders.yml - name: Zip docs run: bash ./scripts/zip-docs.sh - uses: actions/upload-artifact@v3 with: name: docs-zip path: ./site/docs.zip - name: Deploy to Netlify uses: nwtgck/actions-netlify@v2.0.0 with: publish-dir: './site' production-branch: master github-token: ${{ secrets.GITHUB_TOKEN }} enable-commit-comment: false env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} typer-0.9.0/.github/workflows/issue-manager.yml000066400000000000000000000011001442411605300215330ustar00rootroot00000000000000name: 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.4.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.9.0/.github/workflows/latest-changes.yml000066400000000000000000000021521442411605300217050ustar00rootroot00000000000000name: 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@v3 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.9.0/.github/workflows/preview-docs.yml000066400000000000000000000024271442411605300214170ustar00rootroot00000000000000name: Preview Docs on: workflow_run: workflows: - Build Docs types: - completed jobs: preview-docs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: Clean site run: | rm -rf ./site mkdir ./site - name: Download Artifact Docs uses: dawidd6/action-download-artifact@v2.26.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} workflow: build-docs.yml run_id: ${{ github.event.workflow_run.id }} name: docs-zip path: ./site/ - name: Unzip docs run: | cd ./site unzip docs.zip rm -f docs.zip - name: Deploy to Netlify id: netlify uses: nwtgck/actions-netlify@v2.0.0 with: publish-dir: './site' production-deploy: false github-token: ${{ secrets.GITHUB_TOKEN }} enable-commit-comment: false env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - name: Comment Deploy uses: ./.github/actions/comment-docs-preview-in-pr with: token: ${{ secrets.GITHUB_TOKEN }} deploy_url: "${{ steps.netlify.outputs.deploy-url }}" typer-0.9.0/.github/workflows/publish.yml000066400000000000000000000010671442411605300204550ustar00rootroot00000000000000name: Publish on: release: types: - created jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.7" - 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.9.0/.github/workflows/smokeshow.yml000066400000000000000000000016751442411605300210330ustar00rootroot00000000000000name: Smokeshow on: workflow_run: workflows: [Test] types: [completed] permissions: statuses: write jobs: smokeshow: if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - uses: actions/setup-python@v4 with: python-version: '3.9' - run: pip install smokeshow - uses: dawidd6/action-download-artifact@v2.26.0 with: workflow: test.yml commit: ${{ github.event.workflow_run.head_sha }} - run: smokeshow upload coverage-html env: SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100 SMOKESHOW_GITHUB_CONTEXT: coverage SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} typer-0.9.0/.github/workflows/test.yml000066400000000000000000000043321442411605300177640ustar00rootroot00000000000000name: Test on: push: branches: - master pull_request: types: [opened, synchronize] jobs: test: runs-on: ubuntu-20.04 strategy: matrix: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] click-7: [true, false] fail-fast: false steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install Flit run: pip install flit - name: Install Dependencies if: ${{ matrix.python-version != '3.6' }} run: python -m flit install --symlink - name: Install Dependencies if: ${{ matrix.python-version == '3.6' }} # This doesn't install the editable install, so coverage doesn't get subprocesses run: python -m pip install ".[test]" - 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 - run: mkdir coverage - name: Test run: bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-click-7-${{ matrix.click-7 }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-click-7-${{ matrix.click-7 }} - name: Store coverage files uses: actions/upload-artifact@v3 with: name: coverage path: coverage coverage-combine: needs: [test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.8' - name: Get coverage files uses: actions/download-artifact@v3 with: name: coverage path: coverage - run: pip install coverage[toml] - run: ls -la coverage - run: coverage combine coverage - run: coverage report - run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML uses: actions/upload-artifact@v3 with: name: coverage-html path: htmlcov typer-0.9.0/.gitignore000066400000000000000000000001651442411605300146550ustar00rootroot00000000000000.vscode *.pyc __pycache__ env3.7 env3.6 env dist .mypy_cache .idea site .coverage htmlcov .pytest_cache coverage.xml typer-0.9.0/.pre-commit-config.yaml000066400000000000000000000027631442411605300171540ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks default_language_version: python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: check-added-large-files - id: check-toml - id: check-yaml args: - --unsafe - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade rev: v3.3.2 hooks: - id: pyupgrade args: - --py3-plus - --keep-runtime-typing # This file is more readable without yield from exclude: ^docs_src/progressbar/tutorial004\.py - repo: https://github.com/PyCQA/autoflake rev: v2.1.1 hooks: - id: autoflake args: - --recursive - --in-place - --remove-all-unused-imports - --remove-unused-variables - --expand-star-imports - --exclude - __init__.py - --remove-duplicate-keys - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort name: isort (python) - id: isort name: isort (cython) types: [cython] - id: isort name: isort (pyi) types: [pyi] - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black ci: autofix_commit_msg: ๐ŸŽจ [pre-commit.ci] Auto format from pre-commit.com hooks autoupdate_commit_msg: โฌ† [pre-commit.ci] pre-commit autoupdate typer-0.9.0/CONTRIBUTING.md000066400000000000000000000001751442411605300151170ustar00rootroot00000000000000Please read the [Development - Contributing](https://typer.tiangolo.com/contributing/) guidelines in the documentation site. typer-0.9.0/LICENSE000066400000000000000000000020761442411605300136750ustar00rootroot00000000000000The 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.9.0/README.md000066400000000000000000000271551442411605300141540ustar00rootroot00000000000000

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[all]" ---> 100% Successfully installed typer ```
**Note**: that will include Rich. Rich is the recommended library to *display* information on the terminal, it is optional, but when installed, it's deeply integrated into **Typer** to display beautiful output. ## Example ### The absolute minimum * Create a file `main.py` with: ```Python import typer def main(name: str): print(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 TEXT [default: None] [required] | โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // 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): print(f"Hello {name}") @app.command() def goodbye(name: str, formal: bool = False): if formal: print(f"Goodbye Ms. {name}. Have a good day.") else: print(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 Check the new help:
```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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ goodbye โ”‚ โ”‚ hello โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // When you create a package you get โœจ auto-completion โœจ for free, installed with --install-completion // You have 2 subcommands (the 2 functions): goodbye and hello ```
Now check the help for the `hello` command:
```console $ python main.py hello --help Usage: main.py hello [OPTIONS] NAME โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
And now check the help for the `goodbye` command:
```console $ python main.py goodbye --help Usage: main.py goodbye [OPTIONS] NAME โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --formal --no-formal [default: no-formal] โ”‚ โ”‚ --help Show this message โ”‚ โ”‚ and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // Automatic --formal and --no-formal for the bool option ๐ŸŽ‰ ```
Now you can try out the new command line application:
```console // 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: * rich: and Typer will show nicely formatted errors automatically. * 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 `rich` and `shellingham` with `pip install typer[all]`. ## License This project is licensed under the terms of the MIT license. typer-0.9.0/SECURITY.md000066400000000000000000000022101442411605300144470ustar00rootroot00000000000000# 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.9.0/docs/000077500000000000000000000000001442411605300136135ustar00rootroot00000000000000typer-0.9.0/docs/alternatives.md000066400000000000000000000114141442411605300166370ustar00rootroot00000000000000What 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.9.0/docs/contributing.md000066400000000000000000000135341442411605300166520ustar00rootroot00000000000000First, 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.9.0/docs/css/000077500000000000000000000000001442411605300144035ustar00rootroot00000000000000typer-0.9.0/docs/css/custom.css000066400000000000000000000007031442411605300164270ustar00rootroot00000000000000.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.9.0/docs/css/termynal.css000066400000000000000000000042461442411605300167560ustar00rootroot00000000000000/** * 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-size: 15px; /* 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; line-height: 1.2; } [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.9.0/docs/features.md000066400000000000000000000105221442411605300157530ustar00rootroot00000000000000## 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.9.0/docs/help-typer.md000066400000000000000000000274531442411605300162410ustar00rootroot00000000000000Are 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. ## Subscribe to the newsletter You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/newsletter/){.internal-link target=_blank} to stay updated about: * News about FastAPI and friends, including Typer ๐Ÿš€ * Guides ๐Ÿ“ * Features โœจ * Breaking changes ๐Ÿšจ * Tips and tricks โœ… ## 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". By 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). * Hear when I make announcements or release new tools. * Connect with me on **Linkedin**. * Hear when I make announcements or release new tools (although I use Twitter more often ๐Ÿคทโ€โ™‚). * Read what I write (or follow me) on **Dev.to** or **Medium**. * Read other ideas, articles, and read about tools I have created. * Follow me to read when I publish something new. ## Tweet about **Typer** Tweet about **Typer** and let me and others know why you like it. 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. ## Help others with questions in GitHub You can try and help others with their questions in: * GitHub Discussions * GitHub Issues In many cases you might already know the answer for those questions. ๐Ÿค“ Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. ๐Ÿค— The idea is for the **Typer** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other. --- Here's how to help others with questions (in discussions or issues): ### Understand the question * Check if you can understand what is the **purpose** and use case of the person asking. * Then check if the question (the vast majority are questions) is **clear**. * In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**. * If you can't understand the question, ask for more **details**. ### Reproduce the problem For most of the cases and most of the questions there's something related to the person's **original code**. In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**. * You can ask them to provide a minimal, reproducible, example, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better. * If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first. ### Suggest solutions * After being able to understand the question, you can give them a possible **answer**. * In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do. ### Ask to close If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! ๐Ÿฆธ * Now, if that solved their problem, you can ask them to: * In GitHub Discussions: mark the comment as the **answer**. * In GitHub Issues: **close** the issue**. ## 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 or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc. Then you can try and help them solve those questions. ## Ask Questions You can create a new question in the GitHub repository, for example to: * Ask a **question** or ask about a **problem**. * Suggest a new **feature**. **Note**: if you do it, then I'm going to ask you to also help others. ๐Ÿ˜‰ ## Review Pull Requests You can help me review pull requests from others. Again, please try your best to be kind. ๐Ÿค— --- Here's what to have in mind and how to review a pull request: ### Understand the problem * First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue. * There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that. ### Don't worry about style * Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually. * Also don't worry about style rules, there are already automatized tools checking that. And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes. ### Check the code * Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem. * Then **comment** saying that you did that, that's how I will know you really checked it. !!! info Unfortunately, I can't simply trust PRs that just have several approvals. Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. ๐Ÿ˜… So, it's really important that you actually read and run the code, and let me know in the comments that you did. ๐Ÿค“ * If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well ๐Ÿ™ˆ), so it's better if you can focus on the fundamental things. ### Tests * Help me check that the PR has **tests**. * Check that the tests **fail** before the PR. ๐Ÿšจ * Then check that the tests **pass** after the PR. โœ… * Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that. * Then also comment what you tried, that way I'll know that you checked it. ๐Ÿค“ ## Create a Pull Request You can [contribute](contributing.md){.internal-link target=_blank} to the source code with Pull Requests, for example: * To fix a typo you found on the documentation. * To propose new documentation sections. * To fix an existing issue/bug. * Make sure to add tests. * To add a new feature. * Make sure to add tests. * Make sure to add documentation if it's relevant. ## Help Maintain Typer Help me maintain **Typer**! ๐Ÿค“ There's a lot of work to do, and for most of it, **YOU** can do it. The main tasks that you can do right now are: * [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above). * [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above). Those two tasks are what **consume time the most**. That's the main work of maintaining Typer. If you can help me with that, **you are helping me maintain Typer** and making sure it keeps **advancing faster and better**. ๐Ÿš€ ## Join the chat Join the ๐Ÿ‘ฅ FastAPI and Friends Discord chat server ๐Ÿ‘ฅ and hang out with others in the community. There's a `#typer` channel. !!! tip For questions, ask them in GitHub Discussions, there's a much better chance you will receive help there. Use the chat only for other general conversations. ### Don't use the chat for questions Have in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers. In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat. ๐Ÿ˜… Conversations in the chat are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation. On the other side, there are thousands of users in the chat, so there's a high chance you'll find someone to talk to there, almost all the time. ๐Ÿ˜„ ## 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.9.0/docs/img/000077500000000000000000000000001442411605300143675ustar00rootroot00000000000000typer-0.9.0/docs/img/favicon.png000077500000000000000000000006671442411605300165360ustar00rootroot00000000000000‰PNG  IHDR$$แ˜˜sBIT|dˆ pHYs%%IR$๐tEXtSoftwarewww.inkscape.org›๎<4IDATX…ํืMJ1ฦ๑Ÿ"/QŠ7PะMTGโ%\๊RoQ๔ถ"ด[ฝ”Btัฦ†กRฤ™.ๆw13™7๒ๆษฅiง๔|†C์Uิ'0\๖ฑฏšข_†)j„ qฮข4วำ<ชP18ม ตขFcWดต`ทขŽฌ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IENDฎB`‚typer-0.9.0/docs/img/github-social-preview.png000066400000000000000000001363711442411605300213210ustar00rootroot00000000000000‰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ฦ ํ๕ื_7T—"„โๆอ›b๚๕์์nCัััชK€ล1B!๎น#>๛์3ฮp“‹/ส+Vp_ !„Xฟ~ฝผrๅŠ๊2ภVพ๚kFPŽ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//ฏภŸ!*WฎlVฎ\ูฌ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นm6ํ‡~ืฏ_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š4QL‡uPโ๓ฯ?ืบu๋ๆศสสR] เ:uRY๐–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…ฟฟฟศหหทn7o.\IIIา[F4ฮ™3G›4i’ห 7ม:X๗C๋b๔่ัฦ่ัฃ-ำฐ~อโล‹ตำงOห+V8K—.ญบฐต=z˜cฦŒ*ื๕ฺพ}ปผvํš(Yฒคฒ๎ฅzฟฎ]ปาฉ๛‡CDDD˜ๅส•ล‹!!!ฆรแลŠw๎7n7n/^'Ož”yyyชKฮทีซWหญ[ทส6mฺ(?}=55ีR#๏(บt้bDEE™O<๑„Yฟ~}๓a๔+Wฎˆmถiqqqrอš5๒ฬ™3–;oบ%>๘c}๒ไษ.ีตภ:q?€ผฮฮ;eำฆM+Wฎt๑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้๏๛wQฬ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Ÿฎ%%%ษล‹;YGJ)บ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~‰ถmZ:๐๐Eโ7ฐฬTเคค$Y˜mœ#GŽศ]ปv)mC…‡‡‹qใฦ)ฟ=Œฎ]ปbใORฝ1k"เ3e๓ๆอ›7oฆQ OŸ>สงฏ]ปV3 ฯf าำวผ+**ส๔๗๗Wrlบ_|ัPต+๕ๆฺo‹/Voœ0a‚+$$Duํฅ—^RVnฺดI3Mหœš,B๙<้ส•+ขs็ฮŽ๙๓็s€|*Yฒคh฿พฝา^ๅๅห—ลŽ;<๚"'!!Aู3ฃK—.^1โษ)RDผ๖–๙} 3\ถl™า—ง!!!โ๙็Ÿทฬw0๚๕๋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ใฅš5kfฎZต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ํ’7np็ๅ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ผ)rssT TRq?`M€เFŸนึตkWGffฆ๊Rภฃ*WฎlถmVi๐ฑ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†่“&MาMSy 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ึฌฉผsyๅ฿'ุ ไำˆ#Œ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ำ1V( nคp‡ร!ฆOŸ๎š6mšKำธE€ป๕๊ีหจPก‚ฒ@aำฆMr๊ิฉบizถ„๛3ืฦ–.]*=}NOซ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๋*Tp4o1y๒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๒ๆอ›ฟ๚฿'&&ZfIร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„‡‡›ฟ๙™ทnWฎ\‘iiiโ๒ๅห"==]ž:uJž8qBœ8qBž๙ฤตm6็c=ฦนtบu๋šK—.u8pภ๙ฬ3ฯ‡CuI– ฺตkgบuKu)ผ ผฦิฉS•ถ_วŽ๋๓ฃตT ฏผ๒ŠQผxqีฅ<Mำฤsฯ=g$%%ๅ7Žp๋Wิฏ_฿œ7ož+%%ล๙ึ[oน*WฎLP๚_!!!ขo฿พฦย… ]้้้y+Wฎt-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ทnxทnฤ_U0‰1F~~~ผz๕๊Tทn]C'88˜ปปปแ7[•,Y’ฺถmหถmซย&&&ฒs็ฮฑkืฎQZZKMMฅิิT–ššJทnb๖š>^ช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ํิqqq1ฦhึฌYN@ ฐฐ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… ไ๏๏ฯ+WฎL\ะ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๛๖ํ์๙๓Lonnnิพ}{>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ษ’%4n88p 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ญ–._พ\ไoฎWฏท๕ญฏ`?ฦ@{M๕\ฟ~ฝฐo฿>รพ  ูบถ ฟฟ?๏ฝปแณวŽSุ๕ŒGฺkญ>ใJ๊‹<ีธ—๎ข'?็v1 ๙็Lyพ๊ืฏ_ z†|y(yพrqqมฺo&$&&227D๙}[ชT)>|ธแœfggx=มรZภ๒ย… L€ึษห0OOOz๔ผrึัง๒ตjีโxƒ=8ฆƒYํีz๒ไ‰โw๙”นํทณ˜˜่iลฦŒขตท๙}๕ืBVV#"jฺด)๏฿ฟžเห? ่ƒŽu๊ิแฆึบ|๙2“งำ–๏vๅสถdษ!&&†%''cŒชVญJ:uโร‡—๙ใว‹,ฐxy๖๛๏ฟ ๛๖ํ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๓&หฬฬ4nว€ศื๓ไ๚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๎ืววว๐™ฑcวŠฆ>ณ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>ถmksi๕๊ี&๗ฑ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lgHHˆ$Šขล๓๔๑วจ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^qาo‘‘‘Šศ๛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)ฐdNั็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ชง‰๓งฃml: 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฿า>วŒฃ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ˆˆ7nXบz๕jžฯK’ค1๎Lถ6z฿–ํวT\Sฃ๗๕ฤ‰ว๘๐กูส๓.c,OZ๋คDฤวŒ#ššอคัh๒ใ–f PิRBœ?]โCะผy๓BีWGŒก8?*•Šฯš5หไ๓๐์ูณZy์•W^1{l๙๓ธeห–&?๗๗฿็้06l˜ษ๓y๗๎]<iญญ'อ็ๆๆฦอu.q4จ-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 Yt:ข]ฒ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ถmVจ๚ฦ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#`9าdบห๚ 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&๗syฆฎนฯํฺต‹ษ฿œ๖ั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/ศ>mav๊   ป์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ฟ๑ฺตkssศ‘#ฌ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ซUnฉแ'ฯ 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อทnJDDiii”œœฬ ๛บuซ””dH๋ฤ‰mJซq0ึธม๙เมZณf!อ๛๗—ฌ๎700ะ”w>•\JLL,ะqVญZ%๓๗{ฯโ=งOŸ"ฝงใโโ่ัฃGdฎCลลล…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“วŒ#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†฿ทnjHซปป;™:พ)๒rิ})ศ;ขฦgำ~ๅ๗•นัo๖tyE‡ีฐ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‘ษGrly^*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’D7[ฝ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๔@"ำ๗ฒ<สฃ7xรฆ‹––ฆ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ญ็L0#"ซำ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•่qVw„%ศ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ฑm6ล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ย„ ._™ฉ_ฟพด๋^วศฺืeAFฎZตชืข่าฅKย8ฺผ๖-lyRŠmถ๙ืงy๓ๆ๛ย๕ๅmU˜ฎ‡ย1ถ๎Š4ฉQฃ†,_พผv่ะ!ๅม”&L0 ถgวจ ƒต‘฿b|`ุดiSำ๒ญ}.&$$bืŽ์์lล๘สwA[s&&&FิทŸนอ>Fr‹dภE!๒ฯวฦVE ๋ืฏ/๋ืฏ/_z้%ะกCJ฿พ}พ{พปw+K—.ตํr ฆ๐w์p,ใ“๙† Fd^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ๅLrีช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่ะม?฿๘GyNž<ฉ—๕{๏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๊ไ/วๅส•“vZJ้น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 9าvน?ฐณ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๋๛๏ฟ?่ออถmL7eห–•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๖์้๕=ะus๔่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พVฎYณฦtœ๕๎๛w฿ฉ_ต๊+๋มฆK—.yŒ฿U!cccๅษ“'ร–ซgžy&เแ_||ผœ6mšvy”๙แ\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ุ๋ฐaCX™\ฑ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{๗๎ นํ6nh[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บ๏น}7olj้๎YกBน}๛๖ ็Oc=%ิƒMcy4.฿ฎQะ้_๚—'ุ๑9gฮœฐหทท:4ขcฯุj7!!!เšVฆLcL!ง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+Wฎt-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ืƒwyGWUUœ:uJy๒ษ'๕nบฉฌลฦฦF|,วลล‰ฤฤDy๏ฝ๗ส๛๎ปO ง}๛๖2++K:uช{อš5JNNŽข๋บจRฅŠlถญ์฿ฟฟผys)„๛๗ื}๚GถmV่ฃ\kึฌqedd(็ฯŸฑฑฑ">>^6iาDถo฿^๖ํWt}K”(!}๏ภ๕ ธ6oฌ?~\๑zฝขrๅส2))It่ะA๏฿ฟฟ^ตjีฐห‹kึฌั/^์š;wฎkสีซ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ใพ๐/99Y9‡ฎ๋bไศ‘oศ!z$แŸBdffš~พ็ž{lหแ€p_™ษ“'ป6mฺ๖ ฎišx๙็พ>,ขขขฤŸgยภกTUƒ๖๓}๚๔ัรฝธ=คฅฅ)๏ฟพฟn6n8Zw;ศฬ™3]๛๖ํS„ศ๏›๏ๅ—_Žx๛ต‹5kึไฺ P_‘?ะ5bฤwฯž=ฃถn4ผqใ†่ปท๗๊ฏBผ๐ย :7€3้บ.† โ๖uš'ฦGเpโฤ ฅOŸ>n_Ÿว:u’mถๅžฮ!ถlูข๗ทฟ๕฿ซฏพ๊ญXฑbฤo ๏พ๛๎ฐ}g@0€ภฏ„ื๋ำงOwI)Ennฎhืฎ]ิภ;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–›6mาB 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 +WฎEQ„ฏ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 เ`€€ƒ๖ศ›ๆเ;IENDฎB`‚typer-0.9.0/docs/img/github-social-preview.svg000066400000000000000000000075401442411605300213270ustar00rootroot00000000000000 image/svg+xml Typer Build great CLIs. Easy to code. Based on Python type hints. typer-0.9.0/docs/img/icon-black.svg000066400000000000000000000004341442411605300171130ustar00rootroot00000000000000 typer-0.9.0/docs/img/icon-white.svg000066400000000000000000000033171442411605300171620ustar00rootroot00000000000000 image/svg+xml typer-0.9.0/docs/img/logo-margin/000077500000000000000000000000001442411605300166025ustar00rootroot00000000000000typer-0.9.0/docs/img/logo-margin/logo-margin-vector.svg000066400000000000000000000151601442411605300230410ustar00rootroot00000000000000 image/svg+xml typer-0.9.0/docs/img/logo-margin/logo-margin.svg000066400000000000000000000057021442411605300215420ustar00rootroot00000000000000 image/svg+xml Typer typer-0.9.0/docs/img/pycharm-completion.png000066400000000000000000000331441442411605300207140ustar00rootroot00000000000000‰PNG  IHDRu_fฤrsBITแOเtEXtSoftwareShutterc‚ะ IDATxฺํwx็a ๑of{ม๎ข๗^H‚ฝSี(Šฒ(KVฑeY๎ฒ์ฤqqฮ%พ$Nต}IžหYŽํ$๎-๎–cษชVe{ˆBข๗Žถ—™๏XA@ษ๗๗$๒`ฑ˜๙vฐหy1ณณฃฌ฿ธY,ถฒ’ขšššฑ/nทธพŒŒŒ€ƒส*ภั—Jาฒ;๎YŸAะ—๓B†{ฯิt๘$ p 1&หžQœ›๊ฐ™ต†ฺฎ€\c‹y;[๙ \‹})bฮ‘~OqB.ปฌค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ๅฌ๙ฺฆคjCO~ต๎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—.‚S6‚  ฦ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คy8IT<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ฯออตูlcท{<Žวฟอใq\ำnๅไฯ๏O†lืา˜๚ƒฺ๋~๛ฏฯิ‡ฆฟ?/Qฎ9‹๑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!toPใด+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ฝ*าอขoaฎ8ฒใK,7uฉชc›๒E‚วโ7c-ฅjžโB&rอนuใMนฯ=นฏ?(ีาu๏x๙]‡็wŽํˆ ކ'N๊ท}3ฮผD`Ž-Q๚๋k_ฏฏ&Gัvlฟ=ฃ้๚ดIOสธไ…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๘rfKž?l>๎พrŠ๙,‚วŸ๘œโ‹_"/pไMˆ—~w&ท&เ๚ใ๓—‰}•โyฺ็vjŽX3qWๆฬุ์–ฤoป}ํผL๋บž8Dx/แ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า‹œทู*„‰†[SP<ฮj–เK›Cแ@_.\DฮaFI’็ฮฌ"ณแย{^ำฌ๖4ซ}MrๆzZš|)Rษณํ+ต๚cนeจลXIวŠUชาู๛ฟ‚ƒš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+wuำฒ ซ๎๋:๕๊‹{›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จแxpP3ฅ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์wYh8^๗า  ๔ๅ๕—๏-ส๔ZrVƒ๚ขถtN™˜rd ๊ทF๏\หดบxฃใwŸj7=tr฿qภ๙K\›ฟ๖ีfSๅmตe›๎ฒ GGsหlฃ7:?/ r‹>Ur้Sาuำ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หk0{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ูpw๚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ํN8a"ฌ้{†/Iซe๗ หb0ฺ\สmŸชi#_9ีจ™2wฎุ๑A‹Iั ๛~04ซฃใ†ฌ๗|ไๆ›ฌชiUzpm\ฏ{ู้ฏU…ฅZoŸพใ?๑๎j]฿~๒ๅWง๑ฆuฯึฏ|ฦi•ฑฎถ฿๑ัใู;๓,‡ฯ$ส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###ฌธZ3Vม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ฯ ?๋ษ/สหtT-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ฬณฤ็็wwgษฮK /—๔๎ )gดMEย‘้>ีPฦขqอvlล๗ ๔ที๖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ํ[-gFLฬฏ&๎๏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 yiTIENDฎB`‚typer-0.9.0/docs/img/vscode-completion.png000066400000000000000000000543411442411605300205360ustar00rootroot00000000000000‰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 ๖ฬจ„…‚ฐ ธ2B!๔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า๖`€Ng7\”๙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’ต'~’EqaQา5มŒ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=pOBŽ H๙ฅY)๑q๑๚^๕ฎฦI K†=B!๔œไN#‡ ฦ8pŒ ๔i*‰จ2โน’ฦA{VฬภšKJ$๒๑~๋dQrtัตี’›ำฟุ๗Tfล„'”“ใ์ผfV†((:๏ธ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ฐ&ฑืว 5utู_`9็ํmป—^™๖uš๓‡xบztkฎวๆenฎGาฎ c?ฺ(!2้๋š๋ส๙n๒ฌฺtฑ‚์ญยฺ๏šใ๙ฏiแ›I3+WC%ศหพ|ท์!ณwซA’ภ`0่$“อคํI. ฆ›hศึQPtIfG/๔‹๎้8;รmฎ_J\Tˆ˜3/$tM OfdˆG!„ษ ^Wฤ ้@›Dฎz๒‹ฅร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โ!–่]^kwcX;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็wาX6ฎŽ&สฦšfฌ2B!„า„6ฦ,Xไi a~๊ฉ)V!„Bั บอ๖&~Eล _ขo็๊lpณฒ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เจ๙OBk€B=ฃน“5b๖Vชฒ?S๛-/[ํน …l0็"นฐ7้ยรMBฬWTiR}ธ™‰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๋ดwฎk[%2อa๋์-ๅD?หฅk๒๎พ€Cฏว.฿๕๎ƒGZ]"โ';9ง‘1าe|๑‰ๅ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ด๐ณฏ$`utาFUEรๅ 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อเฬ–::“7ฎv*‹˜xฆBB‚ูช๘ะ)ืlฝŒฺฮRผr‚์ฏ.้™^ฏm‹t*>ธpตฐ˜+–yฟ๏u"=}pApล@ Y‘›&&ฑX พswcX;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:๔งŽ ษำ้ฑ‡mz]จ๚iWเึFP๗.฿'๛|ฦ๒kย†B“ ฎ<#งฎื๙งOE'-ุธoำE{“ ศแJงู}-CE^ฤrๅบศษฟอbA{]…เjาub€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!„ๆฮฃ7b2๏.๛อ„™๛SJžฬ้•:*SF้ซLu๑q™l[6ฮˆeวEษัEฯลื@rๅhย•‡šยะฤZ‹สช[๎[น นฟว !„Bƒž;@))๛็TQณ๖B๒ฤฎ้Q๊ิด˜>hT‹Nอ#„Nบ{Xา6ํำึื'ฅฎ€˜น‘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[œ•วJy!กaโ5‘ib6EEfDญเ๎ ห 6ฯื๙X|˜ฯŒˆ bคลm‹)3CCถญว๕–<›NG…œฮฌˆpฯ{Gzฟณj<œ!๊l่[8xš!8๒I๐ฐx}k„gัถฯ’๋๎NRu่ณภCเ๐แธe!„B่ ไNƒ‘ณ]i4BR[žw๑REณ๊I-Lั1ำŸ\jW8si‘ข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] ไ•๘pC!„^4;;ปlŽๅ๘Šฏงƒ‰ฎ(ๅโฺ๒K™—*ฅ~=๛’%sาำำ๐|๖ี?Ÿ†๛oอ๚?ญ€ nฤ!„z^7ฎตตต๓ํ๏๏”V“REF!„BQฑ/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!ฬ่ฉfฎoผ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ฤฯ_๘๙+CowV[rr.QฉดฉSงภิฉSจTjNฮฅม›ๆN๔Y{๊ณ๋๕ๅŸN^๓กวs}c•6หษ๛฿0 h6คE๗๘%ไ๋\ุC—;-Fฃ4ฆ]9คsaPฅๆ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๓ฆฆ_ฮุุุhnn8sฯ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?cpgฯž=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ก"ษnuu—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ฮ๓ฯฟค~FGGjsrrขขขถŸนŽฝ_ฬ!„^ ทo฿666:thkk+X[[oฺดi็ฮทn===‡s๋Vๅณธh๙๙๙๙๙๙OCOพz๛s๙ๅัืืททท๖<O;ไเมƒึิฬ™3333ฅR้ u๕‡๖2™๚ —ท=๚;47ท ^)0w"„BUVVภฐ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}๑วnnฎT*ตฌฌ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]]๖ร7oLOO722 \พt้mlZพ|™ปป{|jตj๕๊ี{๗๎ี~ำO#$ I’ฺทJฅ๒ท฿~kmm๕๖๖ ๛xอš๗šššLLŒทmz๊ิ้Ÿูหห+<gฮ๏ึพ}๛ ธธฤหหk๘ 'Ož€๚๚๚ฮR466vg๙ภ–;ว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๑๓/,[ถ์ˆ•/พ๘;wuื๘๑๗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ดnz๘๐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mJyไ‘ยž={IฃF:t่0mฺดM›6gff 4จธxหขE‹B&<๗ฬ3ฯ๎ูณป๋ตkย‘9}๚๔ผผ?…v๏ดiำŸ์‘ผผผG}4+ซอ}๗๗๐รŠๆˆ‹.บ0?๕”)Sใโโสสส’’'N|py๙ๅ—{๖์9n\๎wYQฑฝ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ฮœ9Cห–-๛ฮ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๘ตˆฮยM7TQฑฝ>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ฅ ‡IENDฎB`‚typer-0.9.0/docs/index.md000066400000000000000000000271551442411605300152560ustar00rootroot00000000000000

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[all]" ---> 100% Successfully installed typer ```
**Note**: that will include Rich. Rich is the recommended library to *display* information on the terminal, it is optional, but when installed, it's deeply integrated into **Typer** to display beautiful output. ## Example ### The absolute minimum * Create a file `main.py` with: ```Python import typer def main(name: str): print(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 TEXT [default: None] [required] | โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // 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): print(f"Hello {name}") @app.command() def goodbye(name: str, formal: bool = False): if formal: print(f"Goodbye Ms. {name}. Have a good day.") else: print(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 Check the new help:
```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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ goodbye โ”‚ โ”‚ hello โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // When you create a package you get โœจ auto-completion โœจ for free, installed with --install-completion // You have 2 subcommands (the 2 functions): goodbye and hello ```
Now check the help for the `hello` command:
```console $ python main.py hello --help Usage: main.py hello [OPTIONS] NAME โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
And now check the help for the `goodbye` command:
```console $ python main.py goodbye --help Usage: main.py goodbye [OPTIONS] NAME โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --formal --no-formal [default: no-formal] โ”‚ โ”‚ --help Show this message โ”‚ โ”‚ and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // Automatic --formal and --no-formal for the bool option ๐ŸŽ‰ ```
Now you can try out the new command line application:
```console // 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: * rich: and Typer will show nicely formatted errors automatically. * 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 `rich` and `shellingham` with `pip install typer[all]`. ## License This project is licensed under the terms of the MIT license. typer-0.9.0/docs/js/000077500000000000000000000000001442411605300142275ustar00rootroot00000000000000typer-0.9.0/docs/js/custom.js000066400000000000000000000074711442411605300161100ustar00rootroot00000000000000document.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.9.0/docs/js/termynal.js000066400000000000000000000225041442411605300164230ustar00rootroot00000000000000/** * 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.9.0/docs/overrides/000077500000000000000000000000001442411605300156155ustar00rootroot00000000000000typer-0.9.0/docs/overrides/main.html000066400000000000000000000016201442411605300174260ustar00rootroot00000000000000{% extends "base.html" %} {%- block scripts %} {{ super() }}
You can ask questions about Typer. Try:
How can I terminate a program?
How to launch applications?
How to add help to CLI argument?
{%- endblock %} typer-0.9.0/docs/release-notes.md000066400000000000000000000765041442411605300167170ustar00rootroot00000000000000## Latest Changes ## 0.9.0 ### Features * โœจ Add support for PEP-593 `Annotated` for specifying options and arguments. Initial PR [#584](https://github.com/tiangolo/typer/pull/584) by [@ryangalamb](https://github.com/ryangalamb). * New docs: [Optional CLI arguments](https://typer.tiangolo.com/tutorial/arguments/optional/#an-alternative-cli-argument-declaration). * It is no longer required to pass a default value of `...` to mark a *CLI Argument* or *CLI Option* as required. * It is now recommended to use `Annotated` for `typer.Option()` and `typer.Argument()`. * All the docs have been updated to recommend `Annotated`. ### Docs * ๐Ÿ“ Update docs examples for custom param types using `Annotated`, fix overloads for `typer.Argument`. PR [#594](https://github.com/tiangolo/typer/pull/594) by [@tiangolo](https://github.com/tiangolo). ### Internal * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#592](https://github.com/tiangolo/typer/pull/592) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). ## 0.8.0 ### Features * โœจ Add support for custom types and parsers. Initial PR [#583](https://github.com/tiangolo/typer/pull/583) by [@jpurviance](https://github.com/jpurviance). Based on original PR [#443](https://github.com/tiangolo/typer/pull/443) by [@paulo-raca](https://github.com/paulo-raca). * New docs: [CLI Parameter Types: Custom Types](https://typer.tiangolo.com/tutorial/parameter-types/custom-types/). ### Upgrades * โฌ† Upgrade Rich, support 13.x. PR [#524](https://github.com/tiangolo/typer/pull/524) by [@musicinmybrain](https://github.com/musicinmybrain). ### Docs * ๐Ÿ“ Tweak docs, Custom Types path, main page and READAME colors, broken links. PR [#588](https://github.com/tiangolo/typer/pull/588) by [@tiangolo](https://github.com/tiangolo). * โœ Fix spelling (shinny -> shiny). PR [#586](https://github.com/tiangolo/typer/pull/586) by [@runofthemill](https://github.com/runofthemill). * ๐Ÿ“ Update docs about helping Typer. PR [#547](https://github.com/tiangolo/typer/pull/547) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix typo in datetime docs. PR [#495](https://github.com/tiangolo/typer/pull/495) by [@huxuan](https://github.com/huxuan). * โœ๏ธ Add quotes to package name that includes brackets in docs. PR [#475](https://github.com/tiangolo/typer/pull/475) by [@gjolga](https://github.com/gjolga). ### Internal * โฌ† Bump dawidd6/action-download-artifact from 2.24.2 to 2.26.0. PR [#558](https://github.com/tiangolo/typer/pull/558) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#549](https://github.com/tiangolo/typer/pull/549) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ๐Ÿ”ง Add `exclude_lines` to coverage configuration. PR [#585](https://github.com/tiangolo/typer/pull/585) by [@dmontagu](https://github.com/dmontagu). * โฌ†๏ธ Upgrade analytics. PR [#557](https://github.com/tiangolo/typer/pull/557) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update new issue chooser to suggest GitHub Discussions. PR [#544](https://github.com/tiangolo/typer/pull/544) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add GitHub Discussion templates for questions. PR [#541](https://github.com/tiangolo/typer/pull/541) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update pre-commit, Python version, isort version. PR [#542](https://github.com/tiangolo/typer/pull/542) by [@tiangolo](https://github.com/tiangolo). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#512](https://github.com/tiangolo/typer/pull/512) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump nwtgck/actions-netlify from 1.2.4 to 2.0.0. PR [#513](https://github.com/tiangolo/typer/pull/513) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Refactor CI artifact upload/download for docs previews. PR [#516](https://github.com/tiangolo/typer/pull/516) by [@tiangolo](https://github.com/tiangolo). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#500](https://github.com/tiangolo/typer/pull/500) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump actions/cache from 2 to 3. PR [#496](https://github.com/tiangolo/typer/pull/496) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump dawidd6/action-download-artifact from 2.24.1 to 2.24.2. PR [#494](https://github.com/tiangolo/typer/pull/494) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump dawidd6/action-download-artifact from 2.9.0 to 2.24.1. PR [#491](https://github.com/tiangolo/typer/pull/491) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/setup-python from 2 to 4. PR [#492](https://github.com/tiangolo/typer/pull/492) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ทโ€โ™‚๏ธ Consistently use `sys.executable` to run subprocesses, needed by OpenSUSE. PR [#408](https://github.com/tiangolo/typer/pull/408) by [@theMarix](https://github.com/theMarix). * ๐Ÿ‘ทโ€โ™‚๏ธ Ensure the `PYTHONPATH` is set properly when testing the tutorial scripts. PR [#407](https://github.com/tiangolo/typer/pull/407) by [@theMarix](https://github.com/theMarix). ## 0.7.0 ### Features * โœจ Make `typer.run()` not add completion scripts by default, it only makes sense in installed apps. Also update docs for handling [autocompletion in CLI options](https://typer.tiangolo.com/tutorial/options-autocompletion/). PR [#488](https://github.com/tiangolo/typer/pull/488) by [@tiangolo](https://github.com/tiangolo). * โœจ Add support for Python 3.11, tests in CI and official marker. PR [#487](https://github.com/tiangolo/typer/pull/487) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add CI for Python 3.10. PR [#384](https://github.com/tiangolo/typer/pull/384) by [@tiangolo](https://github.com/tiangolo). ### Fixes * ๐ŸŽจ Fix type annotation of `typer.run()`. PR [#284](https://github.com/tiangolo/typer/pull/284) by [@yassu](https://github.com/yassu). * ๐ŸŽจ Fix type annotations for `get_group`. PR [#430](https://github.com/tiangolo/typer/pull/430) by [@tiangolo](https://github.com/tiangolo). ### Docs * ๐Ÿ“ Add note about how subcommands with function names using underscores are converted to dashes. PR [#403](https://github.com/tiangolo/typer/pull/403) by [@targhs](https://github.com/targhs). * ๐Ÿ“ Fix typo in docs at `docs/tutorial/commands/help.md`. PR [#466](https://github.com/tiangolo/typer/pull/466) by [@fepegar](https://github.com/fepegar). * โœ Fix link in docs to `datetime.strptime()`. PR [#464](https://github.com/tiangolo/typer/pull/464) by [@Kobu](https://github.com/Kobu). * โœ Update `first-steps.md`, clarify distinction between parameter and argument. PR [#176](https://github.com/tiangolo/typer/pull/176) by [@mccarthysean](https://github.com/mccarthysean). * โœ Fix broken plac link. PR [#275](https://github.com/tiangolo/typer/pull/275) by [@mgielda](https://github.com/mgielda). ### Internal * โœ… Add extra tests just for coverage because monkeypatching with strange imports confuses coverage. PR [#490](https://github.com/tiangolo/typer/pull/490) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Tweak pytest coverage. PR [#485](https://github.com/tiangolo/typer/pull/485) by [@tiangolo](https://github.com/tiangolo). * โž• Bring back pytest-cov because coverage can't detect pytest-xdist. PR [#484](https://github.com/tiangolo/typer/pull/484) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump actions/upload-artifact from 2 to 3. PR [#477](https://github.com/tiangolo/typer/pull/477) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump actions/checkout from 2 to 3. PR [#478](https://github.com/tiangolo/typer/pull/478) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#411](https://github.com/tiangolo/typer/pull/411) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump nwtgck/actions-netlify from 1.1.5 to 1.2.4. PR [#479](https://github.com/tiangolo/typer/pull/479) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump tiangolo/issue-manager from 0.2.0 to 0.4.0. PR [#481](https://github.com/tiangolo/typer/pull/481) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Move from pytest-cov to coverage and Codecov to Smokeshow. PR [#483](https://github.com/tiangolo/typer/pull/483) by [@tiangolo](https://github.com/tiangolo). * โž• Add extra Material for MkDocs deps for docs. PR [#482](https://github.com/tiangolo/typer/pull/482) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update Dependabot config. PR [#476](https://github.com/tiangolo/typer/pull/476) by [@tiangolo](https://github.com/tiangolo). ## 0.6.1 ### Fixes * ๐Ÿ› Fix setting `FORCE_TERMINAL` with colors 2. PR [#424](https://github.com/tiangolo/typer/pull/424) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ› Fix setting `FORCE_TERMINAL` with colors. PR [#423](https://github.com/tiangolo/typer/pull/423) by [@tiangolo](https://github.com/tiangolo). ## 0.6.0 This release adds deep integrations with [Rich](https://rich.readthedocs.io/en/stable/). โœจ `rich` is an optional dependency, you can install it directly or it will be included when you install with: ```console $ pip install "typer[all]" ``` If Rich is available, it will be used to show the content from `--help` options, validation errors, and even errors in your app (exception tracebacks). There are new options to group commands, *CLI arguments*, and *CLI options*, support for [Rich Console Markup](https://rich.readthedocs.io/en/stable/markup.html), and more! ๐ŸŽ‰ ### Features * โœจ Richify, add integrations with Rich everywhere. PR [#419](https://github.com/tiangolo/typer/pull/419) by [@tiangolo](https://github.com/tiangolo). * Recommend Rich as the main information displaying tool, new docs: [Printing and Colors](https://typer.tiangolo.com/tutorial/printing/). * For most use cases not using Rich, use plain `print()` instead of `typer.echo()` in the docs, to simplify the concepts and avoid confusions. New docs: [Printing and Colors - typer Echo](https://typer.tiangolo.com/tutorial/printing/#typer-echo). * Define help panels for *CLI arguments*, new docs: [CLI Arguments with Help - CLI Argument help panels](https://typer.tiangolo.com/tutorial/arguments/help/#cli-argument-help-panels). * Define help panels for *CLI options*, new docs: [CLI Options with Help - CLI Options help panels](https://typer.tiangolo.com/tutorial/options/help/#cli-options-help-panels). * New docs for deprecating commands: [Commands - Command Help - Deprecate a Command](https://typer.tiangolo.com/tutorial/commands/help/#deprecate-a-command). * Support for Rich Markdown in docstrings, *CLI parameters* `help`, and `epilog` with the new parameter `typer.Typer(rich_markup_mode="markdown")`, new docs: [Commands - Command Help - Rich Markdown and Markup](https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup). * Support for Rich Markup (different from Markdown) in docstrings, *CLI parameters* `help`, and `epilog` with the new parameter `typer.Typer(rich_markup_mode="rich")`, new docs: [Commands - Command Help - Rich Markdown and Markup](https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup). * Define help panels for *commands*, new docs: [Commands - Command Help - Help Panels](https://typer.tiangolo.com/tutorial/commands/help/#help-panels). * New docs for setting an `epilog`, with support for Rich Markdown and Console Markup, new docs: [Commands - Command Help - Epilog](https://typer.tiangolo.com/tutorial/commands/help/#epilog). * โœจ Refactor and document handling pretty exceptions. PR [#422](https://github.com/tiangolo/typer/pull/422) by [@tiangolo](https://github.com/tiangolo). * Add support for customizing pretty short errors, new docs: [Exceptions and Errors](https://typer.tiangolo.com/tutorial/exceptions/). * โœจ Allow configuring pretty errors when creating the Typer instance. PR [#416](https://github.com/tiangolo/typer/pull/416) by [@tiangolo](https://github.com/tiangolo). ### Docs * ๐Ÿ“ Add docs for using Rich with Typer. PR [#421](https://github.com/tiangolo/typer/pull/421) by [@tiangolo](https://github.com/tiangolo). * Add new docs: [Ask with Prompt - Prompt with Rich](https://typer.tiangolo.com/tutorial/prompt/#prompt-with-rich). * Add new docs to handle progress bars and spinners with Rich: [Progress Par](https://typer.tiangolo.com/tutorial/progressbar/). ### Internal * โฌ†๏ธ Upgrade codecov GitHub Action. PR [#420](https://github.com/tiangolo/typer/pull/420) by [@tiangolo](https://github.com/tiangolo). ## 0.5.0 ### Features * โœจ Add pretty error tracebacks for user errors and support for Rich. PR [#412](https://github.com/tiangolo/typer/pull/412) by [@tiangolo](https://github.com/tiangolo). ### Docs * โœ Fix typo, "ASCII codes" to "ANSI escape sequences". PR [#308](https://github.com/tiangolo/typer/pull/308) by [@septatrix](https://github.com/septatrix). ## 0.4.2 ### Fixes * ๐Ÿ› Fix type conversion for `List` and `Tuple` and their internal types. PR [#143](https://github.com/tiangolo/typer/pull/143) by [@hellowhistler](https://github.com/hellowhistler). * ๐Ÿ› Fix `context_settings` for a Typer app with a single command. PR [#210](https://github.com/tiangolo/typer/pull/210) by [@daddycocoaman](https://github.com/daddycocoaman). ### Docs * ๐Ÿ“ Clarify testing documentation about checking `stderr`. PR [#335](https://github.com/tiangolo/typer/pull/335) by [@cgabard](https://github.com/cgabard). * โœ Fix typo in docs for CLI Option autocompletion. PR [#288](https://github.com/tiangolo/typer/pull/288) by [@graue70](https://github.com/graue70). * ๐ŸŽจ Fix header format for "Standard Input" in `docs/tutorial/printing.md`. PR [#386](https://github.com/tiangolo/typer/pull/386) by [@briancohan](https://github.com/briancohan). * โœ Fix typo in `docs/tutorial/terminating.md`. PR [#382](https://github.com/tiangolo/typer/pull/382) by [@kianmeng](https://github.com/kianmeng). * โœ Fix syntax typo in `docs/tutorial/package.md`. PR [#333](https://github.com/tiangolo/typer/pull/333) by [@ryanstreur](https://github.com/ryanstreur). * โœ Fix typo, duplicated word in `docs/tutorial/options/required.md`.. PR [#316](https://github.com/tiangolo/typer/pull/316) by [@michaelriri](https://github.com/michaelriri). * โœ Fix minor typo in `index.md`. PR [#274](https://github.com/tiangolo/typer/pull/274) by [@RmStorm](https://github.com/RmStorm). * โœ Fix double "and" typo in first-steps tutorial. PR [#225](https://github.com/tiangolo/typer/pull/225) by [@softwarebloat](https://github.com/softwarebloat). * ๐ŸŽจ Fix format in docs explaining `datetime` parameter type. PR [#220](https://github.com/tiangolo/typer/pull/220) by [@DiegoPiloni](https://github.com/DiegoPiloni). ### Internal * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#404](https://github.com/tiangolo/typer/pull/404) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ๐Ÿ‘ท Fix Material for MkDocs install in CI. PR [#395](https://github.com/tiangolo/typer/pull/395) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add pre-commit CI config. PR [#394](https://github.com/tiangolo/typer/pull/394) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Clear MkDocs Insiders cache. PR [#393](https://github.com/tiangolo/typer/pull/393) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Add pre-commit config and formatting. PR [#392](https://github.com/tiangolo/typer/pull/392) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Disable installing MkDocs Insiders in forks. PR [#391](https://github.com/tiangolo/typer/pull/391) by [@tiangolo](https://github.com/tiangolo). * โฌ†๏ธ Upgrade Codecov GitHub Action. PR [#383](https://github.com/tiangolo/typer/pull/383) by [@tiangolo](https://github.com/tiangolo). ## 0.4.1 ### Fixes * ๐Ÿ› Fix import of `get_terminal_size` for Click 8.1.0 support and upgrade Black to fix CI. PR [#380](https://github.com/tiangolo/typer/pull/380) by [@tiangolo](https://github.com/tiangolo) based on original PR [#375](https://github.com/tiangolo/typer/pull/375) by [@madkinsz](https://github.com/madkinsz). ### Internal * ๐Ÿ“ Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#368](https://github.com/tiangolo/typer/pull/368) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ’š Only test on push when on master, avoid duplicate CI runs from PRs. PR [#358](https://github.com/tiangolo/typer/pull/358) by [@tiangolo](https://github.com/tiangolo). * โœจ Add support for previewing docs in PRs from forks and enable MkDocs Insiders. PR [#357](https://github.com/tiangolo/typer/pull/357) by [@tiangolo](https://github.com/tiangolo). * โฌ†๏ธ Upgrade MkDocs Material, MDX-Include, and MkDocs structure. PR [#356](https://github.com/tiangolo/typer/pull/356) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update publish GitHub action. PR [#325](https://github.com/tiangolo/typer/pull/325) by [@tiangolo](https://github.com/tiangolo). ## 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.9.0/docs/tutorial/000077500000000000000000000000001442411605300154565ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/app-dir.md000066400000000000000000000026451442411605300173430ustar00rootroot00000000000000You 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.9.0/docs/tutorial/arguments/000077500000000000000000000000001442411605300174635ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/arguments/default.md000066400000000000000000000057401442411605300214370ustar00rootroot00000000000000We 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/arguments/default/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 `default_factory` argument: === "Python 3.6+" ```Python hl_lines="7-8 11" {!> ../docs_src/arguments/default/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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()`. !!! tip The word "factory" in `default_factory` is just a fancy way of saying "function that will create the default value". 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.9.0/docs/tutorial/arguments/envvar.md000066400000000000000000000073301442411605300213110ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/arguments/envvar/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="6" {!> ../docs_src/arguments/envvar/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="7" {!> ../docs_src/arguments/envvar/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/arguments/help.md000066400000000000000000000326761442411605300207530ustar00rootroot00000000000000In 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/arguments/help/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5-8" {!> ../docs_src/arguments/help/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="4-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 3.6+" ```Python hl_lines="5" {!> ../docs_src/arguments/help/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="7" {!> ../docs_src/arguments/help/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="9" {!> ../docs_src/arguments/help/tutorial005_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/arguments/help/tutorial006_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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. ```
## *CLI Argument* help panels You might want to show the help information for *CLI arguments* in different panels when using the `--help` option. If you have installed Rich as described in the docs for [Printing and Colors](../printing.md){.internal-link target=_blank}, you can set the `rich_help_panel` parameter to the name of the panel where you want this *CLI argument* to be shown: === "Python 3.6+" ```Python hl_lines="8 12" {!> ../docs_src/arguments/help/tutorial007_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="7 10" {!> ../docs_src/arguments/help/tutorial007.py!} ``` Then, if you check the `--help` option, you will see a default panel named "`Arguments`" for the *CLI arguments* that don't have a custom `rich_help_panel`. And next you will see other panels for the *CLI arguments* that have a custom panel set in the `rich_help_panel` parameter:
```console $ python main.py --help Usage: main.py [OPTIONS] NAME [LASTNAME] [AGE] Say hi to NAME very gently, like Dirk. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT Who to greet [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Secondary Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ lastname [LASTNAME] The last name โ”‚ โ”‚ age [AGE] The user's age โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ 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. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
In this example we have a custom *CLI arguments* panel named "`Secondary Arguments`". ## Help with style using Rich In a future section you will see how to use custom markup in the `help` for *CLI arguments* when reading about [Commands - Command Help](../commands/help.md#rich-markdown-and-markup){.internal-link target=_blank}. If you are in a hurry you can jump there, but otherwise, it would be better to continue reading here and following the tutorial in order. ## 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/arguments/help/tutorial008_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="4" {!> ../docs_src/arguments/help/tutorial008.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.9.0/docs/tutorial/arguments/index.md000066400000000000000000000002551442411605300211160ustar00rootroot00000000000000In 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.9.0/docs/tutorial/arguments/optional.md000066400000000000000000000155031442411605300216360ustar00rootroot00000000000000We 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="5" {!> ../docs_src/arguments/optional/tutorial001_an.py!} ``` !!! info Typer added support for `Annotated` (and started recommending it) in version 0.9.0. If you have an older version, you would get errors when trying to use `Annotated`. Make sure you Upgrade the Typer version to at least 0.9.0 before using `Annotated`. Before, you had this function parameter: ```Python name: str ``` And now we wrap it with `Annotated`: ```Python name: Annotated[str] ``` Both of these versions mean the same thing, `Annotated` is part of standard Python and is there for this. But the second version using `Annotated` allows us to pass additional metadata that can be used by **Typer**: ```Python name: Annotated[str, typer.Argument()] ``` Now we are being explicit that `name` is a *CLI argument*. It's still a `str` and it's still required (it doesn't have a default value). 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 ```Python name: Annoated[str, typer.Argument()] ``` ...that works exactly the same as ```Python 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="7" {!../docs_src/arguments/optional/tutorial002_an.py!} ``` Now we have: ```Python name: Annotated[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*). !!! 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. ## Alternative (old) `typer.Argument()` as the default value **Typer** also supports another older alternative syntax for declaring *CLI arguments* with additional metadata. Instead of using `Annotated`, you can use `typer.Argument()` as the default value: ```Python hl_lines="4" {!> ../docs_src/arguments/optional/tutorial001.py!} ``` !!! tip Prefer to use the `Annotated` version if possible. Before, because `name` didn't have any default value it would be a **required parameter** for the Python function, in Python terms. When using `typer.Argument()` as the default value **Typer** does the same and makes it a **required** *CLI argument*. 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, `typer.Argument()` receives a first parameter `default` that serves the same purpose of defining that default value, or making it required. Not passing any value to the `default` argument is the same as marking it as required. But you can also explicitly mark it as *required* by passing `...` as the `default` argument, passed to `typer.Argument(default=...)`. ```Python name: str = typer.Argument(default=...) ``` !!! info If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis". ```Python hl_lines="4" {!> ../docs_src/arguments/optional/tutorial003.py!} ``` And the same way, you can make it optional by passing a different `default` value, for example `None`: ```Python hl_lines="6" {!> ../docs_src/arguments/optional/tutorial002.py!} ``` Because the first parameter passed to `typer.Argument(default=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`. The `default` argument is the first one, so it's possible that you see code that passes the value without explicitly using `default=`, like: ```Python name: str = typer.Argument(...) ``` ...or like: ```Python name: str = typer.Argument(None) ``` ...but again, try to use `Annotated` if possible, that way your code in terms of Python will mean the same thing as with **Typer** and you won't have to remember any of these details. typer-0.9.0/docs/tutorial/arguments/other-uses.md000066400000000000000000000002431442411605300221020ustar00rootroot00000000000000`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.9.0/docs/tutorial/commands/000077500000000000000000000000001442411605300172575ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/commands/arguments.md000066400000000000000000000020741442411605300216110ustar00rootroot00000000000000The 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.9.0/docs/tutorial/commands/callback.md000066400000000000000000000113451442411605300213410ustar00rootroot00000000000000When 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.9.0/docs/tutorial/commands/context.md000066400000000000000000000067351442411605300213000ustar00rootroot00000000000000When 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.9.0/docs/tutorial/commands/help.md000066400000000000000000001023711442411605300205350ustar00rootroot00000000000000The 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 3.6+" ```Python hl_lines="4 9-11 22 26-30 43 47-51 60-62" {!> ../docs_src/commands/help/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="3 8-10 20 23-27 39 42-46 55-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." ```
## Deprecate a Command There could be cases where you have a command in your app that you need to deprecate, so that your users stop using it, even while it's still supported for a while. You can mark it with the parameter `deprecated=True`: ```Python hl_lines="14" {!../docs_src/commands/help/tutorial003.py!} ``` And when you show the `--help` option you will see it's marked as "`deprecated`":
```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 Create a user. โ”‚ โ”‚ delete Delete a user. (deprecated) โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
And if you check the `--help` for the deprecated command (in this example, the command `delete`), it also shows it as deprecated:
```console $ python main.py delete --help Usage: main.py delete [OPTIONS] USERNAME (deprecated) Delete a user. This is deprecated and will stop being supported soon. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
## Rich Markdown and Markup If you have **Rich** installed as described in [Printing and Colors](../printing.md){.internal-link target=_blank}, you can configure your app to enable markup text with the parameter `rich_markup_mode`. Then you can use more formatting in the docstrings and the `help` parameter for *CLI arguments* and *CLI options*. You will see more about it below. ๐Ÿ‘‡ !!! info By default, `rich_markup_mode` is `None`, which disables any rich text formatting. ### Rich Markup If you set `rich_markup_mode="rich"` when creating the `typer.Typer()` app, you will be able to use Rich Console Markup in the docstring, and even in the help for the *CLI arguments* and options: === "Python 3.6+" ```Python hl_lines="4 10 14-16 21 24 27" {!> ../docs_src/commands/help/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="3 9 13-15 20 22 24" {!> ../docs_src/commands/help/tutorial004.py!} ``` With that, you can use Rich Console Markup to format the text in the docstring for the command `create`, make the word "`create`" bold and green, and even use an emoji. You can also use markup in the help for the `username` CLI Argument. And the same as before, the help text overwritten for the command `delete` can also use Rich Markup, the same in the CLI Argument and CLI Option. If you run the program and check the help, you will see that **Typer** uses **Rich** internally to format the help. Check the help for the `create` command:
```console $ python main.py create --help Usage: main.py create [OPTIONS] USERNAME Create a new shiny user. โœจ This requires a username. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT The username to be created โ”‚ โ”‚ [default: None] โ”‚ โ”‚ [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
And check the help for the `delete` command:
```console $ python main.py delete --help Usage: main.py delete [OPTIONS] USERNAME Delete a user with USERNAME. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT The username to be deleted โ”‚ โ”‚ [default: None] โ”‚ โ”‚ [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --force --no-force Force the deletion ๐Ÿ’ฅ โ”‚ โ”‚ [default: no-force] โ”‚ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
### Rich Markdown If you set `rich_markup_mode="markdown"` when creating the `typer.Typer()` app, you will be able to use Markdown in the docstring: === "Python 3.6+" ```Python hl_lines="4 9 12-20 25 27-28" {!> ../docs_src/commands/help/tutorial005_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="3 7 9-17 22 24-25" {!> ../docs_src/commands/help/tutorial005.py!} ``` With that, you can use Markdown to format the text in the docstring for the command `create`, make the word "`create`" bold, show a list of items, and even use an emoji. And the same as before, the help text overwritten for the command `delete` can also use Markdown. Check the help for the `create` command:
```console $ python main.py create --help Usage: main.py create [OPTIONS] USERNAME Create a new shiny user. โœจ โ€ข Create a username โ€ข Show that the username is created โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Learn more at the Typer docs website โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT The username to be created โ”‚ โ”‚ [default: None] โ”‚ โ”‚ [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
And the same for the `delete` command:
```console $ python main.py delete --help Usage: main.py delete [OPTIONS] USERNAME Delete a user with USERNAME. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT The username to be deleted โ”‚ โ”‚ [default: None] โ”‚ โ”‚ [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --force --no-force Force the deletion ๐Ÿ’ฅ โ”‚ โ”‚ [default: no-force] โ”‚ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
!!! info Notice that in Markdown you cannot define colors. For colors you might prefer to use Rich markup. ## Help Panels If you have many commands or CLI parameters, you might want to show their documentation in different panels when using the `--help` option. If you installed Rich as described in [Printing and Colors](../printing.md){.internal-link target=_blank}, you can configure the panel to use for each command or CLI parameter. ### Help Panels for Commands To set the panel for a command you can pass the argument `rich_help_panel` with the name of the panel you want to use: === "Python 3.6+" ```Python hl_lines="22 30 38 46" {!> ../docs_src/commands/help/tutorial006.py!} ``` Commands without a panel will be shown in the default panel `Commands`, and the rest will be shown in the next panels:
```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 Create a new user. โœจ โ”‚ โ”‚ delete Delete a user. ๐Ÿ”ฅ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Utils and Configs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ config Configure the system. ๐Ÿ”ง โ”‚ โ”‚ sync Synchronize the system or something fancy like that. โ™ป โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Help and Others โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ help Get help with the system. โ“ โ”‚ โ”‚ report Report an issue. ๐Ÿ› โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
### Help Panels for CLI Parameters The same way, you can configure the panels for *CLI arguments* and *CLI options* with `rich_help_panel`. And of course, in the same application you can also set the `rich_help_panel` for commands. === "Python 3.6+" ```Python hl_lines="15 21 27 37" {!> ../docs_src/commands/help/tutorial007_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="12 16 21 30" {!> ../docs_src/commands/help/tutorial007.py!} ``` Then if you run the application you will see all the *CLI parameters* in their respective panels. * First the ***CLI arguments*** that don't have a panel name set in a **default** one named "`Arguments`". * Next the ***CLI arguments*** with a **custom panel**. In this example named "`Secondary Arguments`". * After that, the ***CLI options*** that don't have a panel in a **default** one named "`Options`". * And finally, the ***CLI options*** with a **custom panel** set. In this example named "`Additional Data`". You can check the `--help` option for the command `create`:
```console $ python main.py create --help Usage: main.py create [OPTIONS] USERNAME [LASTNAME] Create a new user. โœจ โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT The username to create [default: None] โ”‚ โ”‚ [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Secondary Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ lastname [LASTNAME] The last name of the new user โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --force --no-force Force the creation of the user โ”‚ โ”‚ [default: no-force] โ”‚ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Additional Data โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --age INTEGER The age of the new user โ”‚ โ”‚ [default: None] โ”‚ โ”‚ --favorite-color TEXT The favorite color of the new โ”‚ โ”‚ user โ”‚ โ”‚ [default: None] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
And of course, the `rich_help_panel` can be used in the same way for commands in the same application. And those panels will be shown when you use the main `--help` option.
```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 Create a new user. โœจ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Utils and Configs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ config Configure the system. ๐Ÿ”ง โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
You can see the custom panel for the commands for "`Utils and Configs`". ## Epilog If you need, you can also add an epilog section to the help of your commands: ```Python hl_lines="6" {!../docs_src/commands/help/tutorial008.py!} ``` And when you check the `--help` option it will look like:
```console $ python main.py --help Usage: main.py [OPTIONS] USERNAME Create a new user. โœจ โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * username TEXT [default: None] [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. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ Made with โค in Venus ```
typer-0.9.0/docs/tutorial/commands/index.md000066400000000000000000000162331442411605300207150ustar00rootroot00000000000000We 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 almost 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. ```
## CLI application completion There's a little detail that is worth noting here. To get shell/tab completion, it's necessary to build a package that you and your users can install and **call directly**. So instead of running a Python script like:
```console $ python main.py โœจ Some magic here โœจ ```
...It would be called like:
```console $ magic-app โœจ Some magic here โœจ ```
Having a standalone program like that allows setting up shell/tab completion. The first step to be able to create an installable package like that is to use an explicit `typer.Typer()` app. Later you can learn all the process to create a standalone CLI application and [Build a Package](../package.md){.internal-link target=_blank}. But for now, it's just good to know that you are on that path. ๐Ÿ˜Ž ## 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.9.0/docs/tutorial/commands/name.md000066400000000000000000000025441442411605300205260ustar00rootroot00000000000000By 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 ```
Note that any underscores in the function name will be replaced with dashes. So if your function is something like: ```Python def create_user(username: str): ... ``` Then the command name will be `create-user`. typer-0.9.0/docs/tutorial/commands/one-or-multiple.md000066400000000000000000000065761442411605300226470ustar00rootroot00000000000000You 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.9.0/docs/tutorial/commands/options.md000066400000000000000000000043651442411605300213040ustar00rootroot00000000000000Commands can also have their own *CLI options*. In fact, each command can have different *CLI arguments* and *CLI options*: === "Python 3.6+" ```Python hl_lines="8 14-17 27-29 38" {!> ../docs_src/commands/options/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/exceptions.md000066400000000000000000000601241442411605300201640ustar00rootroot00000000000000# Exceptions and Errors When your code has errors and you run it, it will show the error and an exception. Typer does some tricks to help you detect those errors quickly. ## Example Broken App Let's take this example broken app: ```Python hl_lines="5" {!../docs_src/exceptions/tutorial001.py!} ``` This code is broken because you can't sum a string and a number (`name + 3`). ## Exceptions with Rich If you have **Rich** installed (for example if you installed `"typer[all]"`), **Typer** will use it to automatically show you nicely printed errors. It will **omit** all the parts of the traceback (the chain of things that called your function) that come from the internal parts in Typer and Click. So, the error you see will be **much clearer** and simpler, to help you detect the problem in your code quickly:
```console $ python main.py โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Traceback (most recent call last) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ /home/user/code/superapp/main.py:5 in main โ”‚ โ”‚ โ”‚ โ”‚ 2 โ”‚ โ”‚ 3 โ”‚ โ”‚ 4 def main(name: str = "morty"): โ”‚ โ”‚ โฑ 5 โ”‚ print(name + 3) โ”‚ โ”‚ 6 โ”‚ โ”‚ 7 โ”‚ โ”‚ 8 if __name__ == "__main__": โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€ locals โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ name = 'morty' โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ TypeError: can only concatenate str (not "int") to str ```
## Exceptions without Rich If you don't have Rich installed, Typer will still do some tricks to show you the information **as clearly as possible**:
```console $ python main.py Traceback (most recent call last): File "main.py", line 12, in typer.run(main) File "main.py", line 8, in main print(name + 3) TypeError: can only concatenate str (not "int") to str ```
## Disable Local Variables for Security If your Typer application handles **delicate information**, for example a **password**, a **key**, a **token**, then it could be problematic if the automatic errors show the value in those local variables. This would be relevant in particular if your CLI application is being run on some CI (continuous integration) system that is recording the logs. The default errors above, when using Rich, show a section with: ```Python name = 'morty' ``` In this case, `name` is a local variable, it comes from a parameter passed to the function. But if it was something like a password, would would have liked to hide it. In that case, you can create the `typer.Typer()` application explicitly and set the parameter `pretty_exceptions_show_locals=False`: ```Python hl_lines="3" {!../docs_src/exceptions/tutorial002.py!} ``` And now when you run it, you will see the error without the local variables:
```console $ python main.py supersecret โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Traceback (most recent call last) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ /home/user/code/superapp/main.py:8 in main โ”‚ โ”‚ โ”‚ โ”‚ 5 โ”‚ โ”‚ 6 @app.command() โ”‚ โ”‚ 7 def main(password: str): โ”‚ โ”‚ โฑ 8 โ”‚ print(password + 3) โ”‚ โ”‚ 9 โ”‚ โ”‚ 10 โ”‚ โ”‚ 11 if __name__ == "__main__": โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ TypeError: can only concatenate str (not "int") to str ```
Note that you passed the password `supersecret`, but it's not shown anywhere in the error message. Being able to see the values of local variables is normally very **helpful** to diagnose, **debug**, and fix problems, but if you are dealing with delicate information, now you know how to secure it. ๐Ÿ”’ ## Disable Short Output If you want to show the full exception, including the parts in Typer and Click, you can use the parameter `pretty_exceptions_short=False`: ```Python hl_lines="3" {!../docs_src/exceptions/tutorial003.py!} ``` Now when you run it, you will see the whole output:
```console $ python main.py โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Traceback (most recent call last) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ /home/user/code/superapp/main.py:12 in <module> โ”‚ โ”‚ โ”‚ โ”‚ 9 โ”‚ โ”‚ 10 โ”‚ โ”‚ 11 if __name__ == "__main__": โ”‚ โ”‚ โฑ 12 โ”‚ app() โ”‚ โ”‚ 13 โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ locals โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ __annotations__ = {} โ”‚ โ”‚ โ”‚ โ”‚ __builtins__ = <module 'builtins' (built-in)> โ”‚ โ”‚ โ”‚ โ”‚ __cached__ = None โ”‚ โ”‚ โ”‚ โ”‚ __doc__ = None โ”‚ โ”‚ โ”‚ โ”‚ __file__ = 'main.py' โ”‚ โ”‚ โ”‚ โ”‚ __loader__ = <_frozen_importlib_external.SourceFileLoadโ€ฆ โ”‚ โ”‚ โ”‚ โ”‚ object at 0x7f047db1c050> โ”‚ โ”‚ โ”‚ โ”‚ __name__ = '__main__' โ”‚ โ”‚ โ”‚ โ”‚ __package__ = None โ”‚ โ”‚ โ”‚ โ”‚ __spec__ = None โ”‚ โ”‚ โ”‚ โ”‚ app = <typer.main.Typer object at 0x7f047db51d90> โ”‚ โ”‚ โ”‚ โ”‚ main = <function main at 0x7f047db56830> โ”‚ โ”‚ โ”‚ โ”‚ typer = <module 'typer' from โ”‚ โ”‚ โ”‚ โ”‚ '/home/user/code/superapp/env/lib/python3.โ€ฆ โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/typer/ma โ”‚ โ”‚ in.py:328 in __call__ โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/typer/ma โ”‚ โ”‚ in.py:311 in __call__ โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/click/co โ”‚ โ”‚ re.py:1130 in __call__ โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/typer/co โ”‚ โ”‚ re.py:723 in main โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/typer/co โ”‚ โ”‚ re.py:216 in _main โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/click/co โ”‚ โ”‚ re.py:1404 in invoke โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/click/co โ”‚ โ”‚ re.py:760 in invoke โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/env/lib/python3.7/site-packages/typer/ma โ”‚ โ”‚ in.py:683 in wrapper โ”‚ โ”‚ โ”‚ โ”‚ /home/user/code/superapp/main.py:8 in main โ”‚ โ”‚ โ”‚ โ”‚ 5 โ”‚ โ”‚ 6 @app.command() โ”‚ โ”‚ 7 def main(name: str = "morty"): โ”‚ โ”‚ โฑ 8 โ”‚ print(name + 3) โ”‚ โ”‚ 9 โ”‚ โ”‚ 10 โ”‚ โ”‚ 11 if __name__ == "__main__": โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€ locals โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ name = 'morty' โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ TypeError: can only concatenate str (not "int") to str ```
## Disable Pretty Exceptions You can also entirely disable pretty exceptions with the parameter `pretty_exceptions_enable=False`: ```Python hl_lines="3" {!../docs_src/exceptions/tutorial004.py!} ``` And now you will see the full standard exception as with any other Python program:
```console $ python main.py Traceback (most recent call last): File "main.py", line 12, in app() File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 328, in __call__ raise e File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 311, in __call__ return get_command(self)(*args, **kwargs) File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1130, in __call__ return self.main(*args, **kwargs) File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 723, in main **extra, File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 216, in _main rv = self.invoke(ctx) File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1404, in invoke return ctx.invoke(self.callback, **ctx.params) File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 760, in invoke return __callback(*args, **kwargs) File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 683, in wrapper return callback(**use_params) # type: ignore File "main.py", line 8, in main print(name + 3) TypeError: can only concatenate str (not "int") to str ```
You could also achieve the same with the environment variable `_TYPER_STANDARD_TRACEBACK=1`. This will work for any other Typer program too, in case you need to debug a problem in a Typer program made by someone else:
```console export _TYPER_STANDARD_TRACEBACK=1 $ python main.py Traceback (most recent call last): File "main.py", line 12, in app() File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 328, in __call__ raise e File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 311, in __call__ return get_command(self)(*args, **kwargs) File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1130, in __call__ return self.main(*args, **kwargs) File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 723, in main **extra, File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 216, in _main rv = self.invoke(ctx) File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1404, in invoke return ctx.invoke(self.callback, **ctx.params) File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 760, in invoke return __callback(*args, **kwargs) File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 683, in wrapper return callback(**use_params) # type: ignore File "main.py", line 8, in main print(name + 3) TypeError: can only concatenate str (not "int") to str ```
typer-0.9.0/docs/tutorial/first-steps.md000066400000000000000000000615111442411605300202670ustar00rootroot00000000000000## The simplest example The simplest **Typer** file could look like this: ```Python {!../docs_src/first_steps/tutorial001.py!} ``` 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 Try 'main.py --help' for help. โ•ญโ”€ Error โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ Missing argument 'NAME'. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ typer on ๎‚  richify [ยป!?] via ๐Ÿ v3.7.5 (env3.7) โฏ python main.py Usage: main.py [OPTIONS] NAME LASTNAME Try 'main.py --help' for help. โ•ญโ”€ Error โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ Missing argument 'NAME'. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // 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 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 TEXT [default: None] [required] โ”‚ โ”‚ * lastname TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --formal --no-formal [default: no-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. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
!!! 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 TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --lastname TEXT โ”‚ โ”‚ --formal --no-formal [default: no-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. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
!!! 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 TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --lastname TEXT โ”‚ โ”‚ --formal --no-formal [default: no-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. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
!!! 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. *Parameter* refers to the variable name in a function *declaration*. Like: ``` def bring_person(name: str, lastname: str = ""): pass ``` *Argument* 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.9.0/docs/tutorial/index.md000066400000000000000000000044771442411605300171230ustar00rootroot00000000000000## Python types If you need a refresher 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 shellingham rich ```
...that also includes `rich` and `shellingham`. typer-0.9.0/docs/tutorial/launch.md000066400000000000000000000017161442411605300172570ustar00rootroot00000000000000You 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.9.0/docs/tutorial/multiple-values/000077500000000000000000000000001442411605300206065ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/multiple-values/arguments-with-multiple-values.md000066400000000000000000000041341442411605300272360ustar00rootroot00000000000000*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 3.6+" ```Python hl_lines="8-10" {!> ../docs_src/multiple_values/arguments_with_multiple_values/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/multiple-values/index.md000066400000000000000000000002051442411605300222340ustar00rootroot00000000000000There are several ways to declare multiple values for *CLI options* and *CLI arguments*. We'll see them in the next short sections. typer-0.9.0/docs/tutorial/multiple-values/multiple-options.md000066400000000000000000000033001442411605300244500ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="1 7" {!> ../docs_src/multiple_values/multiple_options/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="7" {!> ../docs_src/multiple_values/multiple_options/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/multiple-values/options-with-multiple-values.md000066400000000000000000000050211442411605300267200ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="1 7" {!> ../docs_src/multiple_values/options_with_multiple_values/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/options-autocompletion.md000066400000000000000000000347231442411605300225440ustar00rootroot00000000000000As 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 program: === "Python 3.6+" ```Python {!> ../docs_src/options_autocompletion/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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](./options/callback-and-context.md){.internal-link target=_blank}: === "Python 3.6+" ```Python hl_lines="5-6 15" {!> ../docs_src/options_autocompletion/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="4-5 14" {!> ../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 3.6+" ```Python hl_lines="7-12" {!> ../docs_src/options_autocompletion/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="6-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 3.6+" ```Python hl_lines="4-8 11-17" {!> ../docs_src/options_autocompletion/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="3-7 10-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 3.6+" ```Python hl_lines="11-14" {!> ../docs_src/options_autocompletion/tutorial005_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="10-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 3.6+" ```Python hl_lines="9-14" {!> ../docs_src/options_autocompletion/tutorial006_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="8-11" {!> ../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 3.6+" ```Python hl_lines="13-14 16" {!> ../docs_src/options_autocompletion/tutorial007_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 been 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 a **Rich** `Console(stderr=True)`. Using `stderr=True` tells **Rich** that the output should be shown in "standard error". === "Python 3.6+" ```Python hl_lines="13 16-17" {!> ../docs_src/options_autocompletion/tutorial008_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="12 15-16" {!> ../docs_src/options_autocompletion/tutorial008.py!} ``` !!! info If you can't install and use Rich, you can also use `print(lastname, file=sys.stderr)` or `typer.echo("some text", err=True)` instead. 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 3.6+" ```Python hl_lines="16" {!> ../docs_src/options_autocompletion/tutorial009_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="15" {!> ../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.9.0/docs/tutorial/options/000077500000000000000000000000001442411605300171515ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/options/callback-and-context.md000066400000000000000000000175511442411605300234620ustar00rootroot00000000000000In 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 3.6+" ```Python hl_lines="5-8 11" {!> ../docs_src/options/callback/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="4-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 3.6+" ```Python hl_lines="6" {!> ../docs_src/options/callback/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5-7" {!> ../docs_src/options/callback/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="4-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 3.6+" ```Python hl_lines="5 8" {!> ../docs_src/options/callback/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/options/help.md000066400000000000000000000171531442411605300204320ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="7-8" {!> ../docs_src/options/help/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="6-7" {!> ../docs_src/options/help/tutorial001.py!} ``` The same way as with `typer.Argument()`, we can put `typer.Option()` inside of `Annotated`. We can then pass the `help` keyword parameter: ```Python lastname: Annotated[str, typer.Option(help="this option does this and that")] = "" ``` ...to create the help for that *CLI option*. The same way as with `typer.Argument()`, **Typer** also supports the old style using the function parameter default value: ```Python lastname: str = typer.Option(default="", help="this option does this and that") ``` 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 ๐ŸŽ‰ ```
## *CLI Options* help panels The same as with *CLI arguments*, you can put the help for some *CLI options* in different panels to be shown with the `--help` option. If you have installed Rich as described in the docs for [Printing and Colors](../printing.md){.internal-link target=_blank}, you can set the `rich_help_panel` parameter to the name of the panel you want for each *CLI option*: === "Python 3.6+" ```Python hl_lines="11 17" {!> ../docs_src/options/help/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="8 11" {!> ../docs_src/options/help/tutorial002.py!} ``` Now, when you check the `--help` option, you will see a default panel named "`Options`" for the *CLI options* that don't have a custom `rich_help_panel`. And below you will see other panels for the *CLI options* that have a custom panel set in the `rich_help_panel` parameter:
```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 TEXT [default: None] [required] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --lastname TEXT Last name of person to greet. โ”‚ โ”‚ --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. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Customization and Utils โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --formal --no-formal Say hi formally. โ”‚ โ”‚ [default: no-formal] โ”‚ โ”‚ --debug --no-debug Enable debugging. โ”‚ โ”‚ [default: no-debug] โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ```
Here we have a custom *CLI options* panel named "`Customization and Utils`". ## Help with style using Rich In a future section you will see how to use custom markup in the `help` for *CLI options* when reading about [Commands - Command Help](../commands/help.md#rich-markdown-and-markup){.internal-link target=_blank}. If you are in a hurry you can jump there, but otherwise, it would be better to continue reading here and following the tutorial in order. ## Hide default from help You can tell Typer to not show the default value in the help text with `show_default=False`: === "Python 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/help/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="4" {!> ../docs_src/options/help/tutorial003.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.9.0/docs/tutorial/options/index.md000066400000000000000000000003121442411605300205760ustar00rootroot00000000000000In 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.9.0/docs/tutorial/options/name.md000066400000000000000000000235641442411605300204250ustar00rootroot00000000000000By 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: Annotated[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 following positional argument passed to `typer.Option()`: === "Python 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/name/tutorial001_an.py!} ``` Here you are passing the string `"--name"` as the first positional argument to `typer.Option()`. === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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()`, as the first argument is `...` to mark it as required. !!! 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()`. 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. YOu can pass *positional* arguments to `typer.Option()` 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/name/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/name/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/name/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="6-7" {!> ../docs_src/options/name/tutorial005_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/options/password.md000066400000000000000000000033321442411605300213360ustar00rootroot00000000000000Apart from having a prompt, you can make a *CLI option* have a `confirmation_prompt=True`: === "Python 3.6+" ```Python hl_lines="7" {!> ../docs_src/options/password/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="8" {!> ../docs_src/options/password/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="6-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.9.0/docs/tutorial/options/prompt.md000066400000000000000000000050231442411605300210140ustar00rootroot00000000000000It's also possible to, instead of just showing an error, ask for the missing value with `prompt=True`: === "Python 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/prompt/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="7" {!> ../docs_src/options/prompt/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="6" {!> ../docs_src/options/prompt/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/options/required.md000066400000000000000000000044711442411605300213210ustar00rootroot00000000000000We 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, you can put `typer.Option()` inside of `Annotated` and leave the parameter without a default value. Let's make `--lastname` a required *CLI option*: === "Python 3.6+" ```Python hl_lines="5" {!> ../docs_src/options/required/tutorial001_an.py!} ``` The same way as with `typer.Argument()`, the old style of using the function parameter default value is also supported, in that case you would just not pass anything to the `default` parameter. === "Python 3.6+ non-Annotated" ```Python hl_lines="4" {!> ../docs_src/options/required/tutorial001.py!} ``` Or you can explictily pass `...` to `typer.Option(default=...)`: === "Python 3.6+ non-Annotated" ```Python hl_lines="4" {!> ../docs_src/options/required/tutorial002.py!} ``` !!! info If you hadn't seen that `...` before: it is 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. !!! tip Again, prefer to use the `Annotated` version if possible. That way your code will mean the same in standard Python and in **Typer**. 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.9.0/docs/tutorial/options/version.md000066400000000000000000000100701442411605300211560ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="9-12 17-19" {!> ../docs_src/options/version/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="15-17 22-24" {!> ../docs_src/options/version/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="23-26" {!> ../docs_src/options/version/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/package.md000066400000000000000000000504471442411605300174050ustar00rootroot00000000000000When 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.9.0/docs/tutorial/parameter-types/000077500000000000000000000000001442411605300206005ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/parameter-types/bool.md000066400000000000000000000115741442411605300220650ustar00rootroot00000000000000We 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/bool/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="7" {!> ../docs_src/parameter_types/bool/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/bool/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/bool/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/parameter-types/custom-types.md000066400000000000000000000032361442411605300236020ustar00rootroot00000000000000You can easily use your own custom types in your **Typer** applications. The way to do it is by providing a way to parse input into your own types. There are two ways to achieve this: * Adding a type `parser` * Expanding Click's custom types ## Type Parser `typer.Argument` and `typer.Option` can create custom parameter types with a `parser` callable. === "Python 3.6+" ```Python hl_lines="13-14 18-19" {!> ../docs_src/parameter_types/custom_types/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="12-13 17-18" {!> ../docs_src/parameter_types/custom_types/tutorial001.py!} ``` The function (or callable) that you pass to the parameter `parser` will receive the input value as a string and should return the parsed value with your own custom type. ## Click Custom Type If you already have a Click Custom Type, you can use it in `typer.Argument()` and `typer.Option()` with the `click_type` parameter. === "Python 3.6+" ```Python hl_lines="14-18 22-25" {!> ../docs_src/parameter_types/custom_types/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="13-17 21-22" {!> ../docs_src/parameter_types/custom_types/tutorial002.py!} ``` typer-0.9.0/docs/tutorial/parameter-types/datetime.md000066400000000000000000000051451442411605300227230ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="11" {!> ../docs_src/parameter_types/datetime/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/parameter-types/enum.md000066400000000000000000000037531442411605300220760ustar00rootroot00000000000000To 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 3.6+" ```Python hl_lines="15" {!> ../docs_src/parameter_types/enum/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/parameter-types/file.md000066400000000000000000000207051442411605300220450ustar00rootroot00000000000000Apart 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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/file/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5-6" {!> ../docs_src/parameter_types/file/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/file/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/file/tutorial004_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/file/tutorial005_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/parameter-types/index.md000066400000000000000000000042171442411605300222350ustar00rootroot00000000000000You 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.9.0/docs/tutorial/parameter-types/number.md000066400000000000000000000075611442411605300224230ustar00rootroot00000000000000You can define numeric validations with `max` and `min` values for `int` and `float` *CLI parameters*: === "Python 3.6+" ```Python hl_lines="6-8" {!> ../docs_src/parameter_types/number/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="5-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 3.6+" ```Python hl_lines="6-8" {!> ../docs_src/parameter_types/number/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="5-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 3.6+" ```Python hl_lines="5" {!> ../docs_src/parameter_types/number/tutorial003_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/parameter-types/path.md000066400000000000000000000074251442411605300220660ustar00rootroot00000000000000You 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 3.6+" ```Python hl_lines="1 8" {!> ../docs_src/parameter_types/path/tutorial001_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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 3.6+" ```Python hl_lines="11-16" {!> ../docs_src/parameter_types/path/tutorial002_an.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```Python hl_lines="9-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.9.0/docs/tutorial/parameter-types/uuid.md000066400000000000000000000033131442411605300220700ustar00rootroot00000000000000!!! 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.9.0/docs/tutorial/printing.md000066400000000000000000000241731442411605300176410ustar00rootroot00000000000000You can use the normal `print()` to show information on the screen: ```Python hl_lines="5" {!../docs_src/first_steps/tutorial001.py!} ``` It will show the output normally:
```console $ python main.py Hello World ```
## Use Rich You can also display beautiful and more complex information using Rich. ### Install Rich First, you need to install it:
```console // Rich comes with typer[all] $ pip install "typer[all]" ---> 100% Successfully installed typer rich // Alternatively, you can install Rich independently $ pip install rich ---> 100% Successfully installed rich ```
### Use Rich `print` For the simplest cases, you can just import `print` from `rich` and use it instead of the standard `print`: ```Python hl_lines="2 15" {!../docs_src/printing/tutorial001.py!} ``` Just with that, **Rich** will be able to print your data with nice colors and structure:
```console $ python main.py Here's the data { 'name': 'Rick', 'age': 42, 'items': [ {'name': 'Portal Gun'}, {'name': 'Plumbus'} ], 'active': True, 'affiliation': None } ```
### Rich Markup Rich also supports a custom markup syntax to set colors and styles, for example: ```Python hl_lines="6" {!../docs_src/printing/tutorial002.py!} ```
```console $ python main.py Alert! Portal gun shooting! ๐Ÿ’ฅ ```
In this example you can see how to use font styles, colors, and even emojis. To learn more check out the Rich docs. ### Rich Tables The way Rich works internally is that it uses a `Console` object to display the information. When you call Rich's `print`, it automatically creates this object and uses it. But for advanced use cases, you could create a `Console` yourself. ```Python hl_lines="2-3 5 9-12" {!../docs_src/printing/tutorial003.py!} ``` In this example, we create a `Console`, and a `Table`. And then we can add some rows to the table, and print it. If you run it, you will see a nicely formatted table:
```console $ python main.py โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ”ƒ Name โ”ƒ Item โ”ƒ โ”กโ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ โ”‚ Rick โ”‚ Portal Gun โ”‚ โ”‚ Morty โ”‚ Plumbus โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ```
Rich has many other features, as an example, you can check the docs for: * Prompt * Markdown * Panel * ...and more. ### Typer and Rich If you are wondering what tool should be used for what, **Typer** is useful for structuring the command line application, with options, arguments, subcommands, data validation, etc. In general, **Typer** tends to be the entry point to your program, taking the first input from the user. **Rich** is useful for the parts that need to *display* information. Showing beautiful content on the screen. The best results for your command line application would be achieved combining both **Typer** and **Rich**. ## "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" creating a Rich `Console` with `stderr=True`. !!! tip `stderr` is short for "standard error". Using `stderr=True` tells **Rich** that the output should be shown in "standard error". ```Python hl_lines="4 8" {!../docs_src/printing/tutorial004.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 Echo !!! warning In most of the cases, for displaying advanced information, it is recommended to use Rich. You can probably skip the rest of this section. ๐ŸŽ‰๐Ÿ˜Ž **Typer** also has a small utility `typer.echo()` to print information on the screen, it comes directly from Click. But normally you shouldn't need it. For the simplest cases, you can use the standard Python `print()`. And for the cases where you want to display data more beautifully, or more advanced content, you should use **Rich** instead. ### Why `typer.echo` `typer.echo()` (which is actually just `click.echo()`) applies some checks to try and convert binary data to strings, and other similar things. But in most of the cases you wouldn't need it, as in modern Python strings (`str`) already support and use Unicode, and you would rarely deal with pure `bytes` that you want to print on the screen. If you have some `bytes` objects, you would probably want to decode them intentionally and directly before trying to print them. And if you want to print data with colors and other features, you are much better off with the more advanced tools in **Rich**. !!! info `typer.echo()` comes directly from Click, you can read more about it in Click's docs. ### Color !!! note "Technical Details" The way color works in terminals is by using some codes (ANSI escape sequences) as part of the text. So, a colored text is still just a `str`. !!! tip Again, you are much better off using Rich for this. ๐Ÿ˜Ž 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/tutorial005.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 !!! tip In case you didn't see above, you are much better off using Rich for this. ๐Ÿ˜Ž 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/tutorial006.py!} ``` Check it:
python main.py Camila Welcome here Camila
typer-0.9.0/docs/tutorial/progressbar.md000066400000000000000000000141631442411605300203360ustar00rootroot00000000000000If you are executing an operation that can take some time, you can inform it to the user. ๐Ÿค“ ## Progress Bar You can use Rich's Progress Display to show a progress bar, for example: ```Python hl_lines="4 9" {!../docs_src/progressbar/tutorial001.py!} ``` You put the thing that you want to iterate over inside of Rich's `track()`, and then iterate over that. Check it:
```console $ python main.py ---> 100% Processed 100 things. ```
...actually, it will look a lot prettier. โœจ But I can't show you the animation here in the docs. ๐Ÿ˜… The colors and information will look something like this:
```console $ python main.py Processing... โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•ธโ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 74% 0:00:01 ```
## Spinner When you don't know how long the operation will take, you can use a spinner instead. Rich allows you to display many things in complex and advanced ways. For example, this will show two spinners: ```Python hl_lines="4 8-15" {!../docs_src/progressbar/tutorial002.py!} ``` I can't show you the beautiful animation here in the docs. ๐Ÿ˜… But at some point in time it will look like this (imagine it's spinning). ๐Ÿค“
```console $ python main.py โ น Processing... โ น Preparing... ```
You can learn more about it in the Rich docs for Progress Display. ## Typer `progressbar` If you can, you should use **Rich** as explained above, it has more features, it's more advanced, and can display information more beautifully. โœจ !!! tip If you can use Rich, use the information above, the Rich docs, and skip the rest of this page. ๐Ÿ˜Ž But if you can't use Rich, Typer (actually Click) comes with a simple utility to show progress bars. !!! info `typer.progressbar()` comes directly from Click, you can read more about it in Click's docs. ### Use `typer.progressbar` !!! tip Remember, you are much better off using Rich for this. ๐Ÿ˜Ž You can 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. ```Python hl_lines="8" {!../docs_src/progressbar/tutorial003.py!} ``` 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` !!! tip Remember, you are much better off using Rich for this. ๐Ÿ˜Ž 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/tutorial004.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` !!! tip Remember, you are much better off using Rich for this. ๐Ÿ˜Ž You can also set a `label`: ```Python hl_lines="8" {!../docs_src/progressbar/tutorial005.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/tutorial006.py!} ``` Check it:
python main.py Processed 100 things in batches.
typer-0.9.0/docs/tutorial/prompt.md000066400000000000000000000035651442411605300173320ustar00rootroot00000000000000When 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! ```
## Prompt with Rich If you installed Rich as described in [Printing and Colors](printing.md){.internal-link target=_blank}, you can use Rich to prompt the user for input: ```Python hl_lines="2 6" {!../docs_src/prompt/tutorial004.py!} ``` And when you run it, it will look like:
```console $ python main.py # Enter your name ๐Ÿ˜Ž:$ Morty Hello Morty ```
typer-0.9.0/docs/tutorial/subcommands/000077500000000000000000000000001442411605300177715ustar00rootroot00000000000000typer-0.9.0/docs/tutorial/subcommands/add-typer.md000066400000000000000000000073231442411605300222110ustar00rootroot00000000000000We'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.9.0/docs/tutorial/subcommands/callback-override.md000066400000000000000000000056501442411605300236720ustar00rootroot00000000000000When 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.9.0/docs/tutorial/subcommands/index.md000066400000000000000000000022611442411605300214230ustar00rootroot00000000000000You 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.9.0/docs/tutorial/subcommands/name-and-help.md000066400000000000000000000303451442411605300227260ustar00rootroot00000000000000When 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.9.0/docs/tutorial/subcommands/nested-subcommands.md000066400000000000000000000136751442411605300241220ustar00rootroot00000000000000We'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.9.0/docs/tutorial/subcommands/single-file.md000066400000000000000000000052511442411605300225140ustar00rootroot00000000000000In 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.9.0/docs/tutorial/terminating.md000066400000000000000000000057051442411605300203300ustar00rootroot00000000000000There 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 raising 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.9.0/docs/tutorial/testing.md000066400000000000000000000143371442411605300174650ustar00rootroot00000000000000Testing **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" independently from "standard output" if your `CliRunner` instance is created with the `mix_stderr=False` argument. !!! 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 3.6+" ```Python hl_lines="8" {!> ../docs_src/testing/app02_an/main.py!} ``` === "Python 3.6+ non-Annotated" !!! tip Prefer to use the `Annotated` version if possible. ```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.9.0/docs/tutorial/using-click.md000066400000000000000000000141251442411605300202130ustar00rootroot00000000000000!!! 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.9.0/docs/typer-cli.md000066400000000000000000000227471442411605300160610ustar00rootroot00000000000000# 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.9.0/docs_src/000077500000000000000000000000001442411605300144625ustar00rootroot00000000000000typer-0.9.0/docs_src/app_dir/000077500000000000000000000000001442411605300161005ustar00rootroot00000000000000typer-0.9.0/docs_src/app_dir/tutorial001.py000066400000000000000000000004661442411605300205440ustar00rootroot00000000000000from 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(): print("Config file doesn't exist yet") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/000077500000000000000000000000001442411605300164675ustar00rootroot00000000000000typer-0.9.0/docs_src/arguments/default/000077500000000000000000000000001442411605300201135ustar00rootroot00000000000000typer-0.9.0/docs_src/arguments/default/tutorial001.py000066400000000000000000000002201442411605300225430ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("Wade Wilson")): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/default/tutorial001_an.py000066400000000000000000000003051442411605300232250ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument()] = "Wade Wilson"): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/default/tutorial002.py000066400000000000000000000003741442411605300225560ustar00rootroot00000000000000import random import typer def get_name(): return random.choice(["Deadpool", "Rick", "Morty", "Hiro"]) def main(name: str = typer.Argument(default_factory=get_name)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/default/tutorial002_an.py000066400000000000000000000004561442411605300232350ustar00rootroot00000000000000import random import typer from typing_extensions import Annotated def get_name(): return random.choice(["Deadpool", "Rick", "Morty", "Hiro"]) def main(name: Annotated[str, typer.Argument(default_factory=get_name)]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/envvar/000077500000000000000000000000001442411605300177705ustar00rootroot00000000000000typer-0.9.0/docs_src/arguments/envvar/tutorial001.py000066400000000000000000000002451442411605300224270ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar="AWESOME_NAME")): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/envvar/tutorial001_an.py000066400000000000000000000003301442411605300231000ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument(envvar="AWESOME_NAME")] = "World"): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/envvar/tutorial002.py000066400000000000000000000002631442411605300224300ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar=["AWESOME_NAME", "GOD_NAME"])): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/envvar/tutorial002_an.py000066400000000000000000000003541442411605300231070ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: Annotated[str, typer.Argument(envvar=["AWESOME_NAME", "GOD_NAME"])] = "World" ): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/envvar/tutorial003.py000066400000000000000000000002701442411605300224270ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar="AWESOME_NAME", show_envvar=False)): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/envvar/tutorial003_an.py000066400000000000000000000003771442411605300231150ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: Annotated[ str, typer.Argument(envvar="AWESOME_NAME", show_envvar=False) ] = "World" ): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/000077500000000000000000000000001442411605300174175ustar00rootroot00000000000000typer-0.9.0/docs_src/arguments/help/tutorial001.py000066400000000000000000000002541442411605300220560ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(..., help="The name of the user to greet")): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial001_an.py000066400000000000000000000003311442411605300225300ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument(help="The name of the user to greet")]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial002.py000066400000000000000000000003471442411605300220620ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(..., help="The name of the user to greet")): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial002_an.py000066400000000000000000000004241442411605300225340ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument(help="The name of the user to greet")]): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial003.py000066400000000000000000000003321442411605300220550ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", help="Who to greet")): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial003_an.py000066400000000000000000000004151442411605300225350ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument(help="Who to greet")] = "World"): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial004.py000066400000000000000000000003561442411605300220640ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", help="Who to greet", show_default=False)): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial004_an.py000066400000000000000000000004651442411605300225430ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: Annotated[ str, typer.Argument(help="Who to greet", show_default=False) ] = "World" ): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial005.py000066400000000000000000000003471442411605300220650ustar00rootroot00000000000000import typer def main( name: str = typer.Argument( "Wade Wilson", help="Who to greet", show_default="Deadpoolio the amazing's name" ) ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial005_an.py000066400000000000000000000004711442411605300225410ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: Annotated[ str, typer.Argument( help="Who to greet", show_default="Deadpoolio the amazing's name" ), ] = "Wade Wilson" ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial006.py000066400000000000000000000002441442411605300220620ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", metavar="โœจusernameโœจ")): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial006_an.py000066400000000000000000000003271442411605300225420ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument(metavar="โœจusernameโœจ")] = "World"): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial007.py000066400000000000000000000006771442411605300220750ustar00rootroot00000000000000import typer def main( name: str = typer.Argument(..., help="Who to greet"), lastname: str = typer.Argument( "", help="The last name", rich_help_panel="Secondary Arguments" ), age: str = typer.Argument( "", help="The user's age", rich_help_panel="Secondary Arguments" ), ): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial007_an.py000066400000000000000000000010131442411605300225340ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: Annotated[str, typer.Argument(help="Who to greet")], lastname: Annotated[ str, typer.Argument(help="The last name", rich_help_panel="Secondary Arguments") ] = "", age: Annotated[ str, typer.Argument(help="The user's age", rich_help_panel="Secondary Arguments"), ] = "", ): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial008.py000066400000000000000000000003221442411605300220610ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", hidden=True)): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/help/tutorial008_an.py000066400000000000000000000004051442411605300225410ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument(hidden=True)] = "World"): """ Say hi to NAME very gently, like Dirk. """ print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/optional/000077500000000000000000000000001442411605300203145ustar00rootroot00000000000000typer-0.9.0/docs_src/arguments/optional/tutorial001.py000066400000000000000000000002031442411605300227450ustar00rootroot00000000000000import typer def main(name: str = typer.Argument()): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/optional/tutorial001_an.py000066400000000000000000000002651442411605300234330ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument()]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/optional/tutorial002.py000066400000000000000000000003671442411605300227610ustar00rootroot00000000000000from typing import Optional import typer def main(name: Optional[str] = typer.Argument(default=None)): if name is None: print("Hello World!") else: print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/optional/tutorial002_an.py000066400000000000000000000004441442411605300234330ustar00rootroot00000000000000from typing import Optional import typer from typing_extensions import Annotated def main(name: Annotated[Optional[str], typer.Argument()] = None): if name is None: print("Hello World!") else: print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/arguments/optional/tutorial003.py000066400000000000000000000002161442411605300227530ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(default=...)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/commands/000077500000000000000000000000001442411605300162635ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/arguments/000077500000000000000000000000001442411605300202705ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/arguments/tutorial001.py000066400000000000000000000003611442411605300227260ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): print(f"Creating user: {username}") @app.command() def delete(username: str): print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/callback/000077500000000000000000000000001442411605300200175ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/callback/tutorial001.py000066400000000000000000000013211442411605300224520ustar00rootroot00000000000000import typer app = typer.Typer() state = {"verbose": False} @app.command() def create(username: str): if state["verbose"]: print("About to create a user") print(f"Creating user: {username}") if state["verbose"]: print("Just created a user") @app.command() def delete(username: str): if state["verbose"]: print("About to delete a user") print(f"Deleting user: {username}") if state["verbose"]: print("Just deleted a user") @app.callback() def main(verbose: bool = False): """ Manage users in the awesome CLI app. """ if verbose: print("Will write verbose output") state["verbose"] = True if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/callback/tutorial002.py000066400000000000000000000003301442411605300224520ustar00rootroot00000000000000import typer def callback(): print("Running a command") app = typer.Typer(callback=callback) @app.command() def create(name: str): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/callback/tutorial003.py000066400000000000000000000004601442411605300224570ustar00rootroot00000000000000import typer def callback(): print("Running a command") app = typer.Typer(callback=callback) @app.callback() def new_callback(): print("Override callback, running a command") @app.command() def create(name: str): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/callback/tutorial004.py000066400000000000000000000004731442411605300224640ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/context/000077500000000000000000000000001442411605300177475ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/context/tutorial001.py000066400000000000000000000006331442411605300224070ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): print(f"Creating user: {username}") @app.command() def delete(username: str): print(f"Deleting user: {username}") @app.callback() def main(ctx: typer.Context): """ Manage users in the awesome CLI app. """ print(f"About to execute command: {ctx.invoked_subcommand}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/context/tutorial002.py000066400000000000000000000006061442411605300224100ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): print(f"Creating user: {username}") @app.command() def delete(username: str): print(f"Deleting user: {username}") @app.callback(invoke_without_command=True) def main(): """ Manage users in the awesome CLI app. """ print("Initializing database") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/context/tutorial003.py000066400000000000000000000007031442411605300224070ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): print(f"Creating user: {username}") @app.command() def delete(username: str): print(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: print("Initializing database") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/context/tutorial004.py000066400000000000000000000004251442411605300224110ustar00rootroot00000000000000import 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: print(f"Got extra arg: {extra_arg}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/000077500000000000000000000000001442411605300172135ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/help/tutorial001.py000066400000000000000000000022541442411605300216540ustar00rootroot00000000000000import typer app = typer.Typer(help="Awesome CLI user manager.") @app.command() def create(username: str): """ Create a new user with USERNAME. """ print(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: print(f"Deleting user: {username}") else: print("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: print("Deleting all users") else: print("Operation cancelled") @app.command() def init(): """ Initialize the users database. """ print("Initializing user database") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial001_an.py000066400000000000000000000024241442411605300223310ustar00rootroot00000000000000import typer from typing_extensions import Annotated app = typer.Typer(help="Awesome CLI user manager.") @app.command() def create(username: str): """ Create a new user with USERNAME. """ print(f"Creating user: {username}") @app.command() def delete( username: str, force: Annotated[ 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: print(f"Deleting user: {username}") else: print("Operation cancelled") @app.command() def delete_all( force: Annotated[ 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: print("Deleting all users") else: print("Operation cancelled") @app.command() def init(): """ Initialize the users database. """ print("Initializing user database") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial002.py000066400000000000000000000006671442411605300216630ustar00rootroot00000000000000import typer app = typer.Typer() @app.command(help="Create a new user with USERNAME.") def create(username: str): """ Some internal utility function to create. """ print(f"Creating user: {username}") @app.command(help="Delete a user with USERNAME.") def delete(username: str): """ Some internal utility function to delete. """ print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial003.py000066400000000000000000000006021442411605300216510ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): """ Create a user. """ print(f"Creating user: {username}") @app.command(deprecated=True) def delete(username: str): """ Delete a user. This is deprecated and will stop being supported soon. """ print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial004.py000066400000000000000000000014721442411605300216600ustar00rootroot00000000000000import typer app = typer.Typer(rich_markup_mode="rich") @app.command() def create( username: str = typer.Argument( ..., help="The username to be [green]created[/green]" ) ): """ [bold green]Create[/bold green] a new [italic]shiny[/italic] user. :sparkles: This requires a [underline]username[/underline]. """ print(f"Creating user: {username}") @app.command(help="[bold red]Delete[/bold red] a user with [italic]USERNAME[/italic].") def delete( username: str = typer.Argument(..., help="The username to be [red]deleted[/red]"), force: bool = typer.Option( False, help="Force the [bold red]deletion[/bold red] :boom:" ), ): """ Some internal utility function to delete. """ print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial004_an.py000066400000000000000000000016061442411605300223350ustar00rootroot00000000000000import typer from typing_extensions import Annotated app = typer.Typer(rich_markup_mode="rich") @app.command() def create( username: Annotated[ str, typer.Argument(help="The username to be [green]created[/green]") ] ): """ [bold green]Create[/bold green] a new [italic]shinny[/italic] user. :sparkles: This requires a [underline]username[/underline]. """ print(f"Creating user: {username}") @app.command(help="[bold red]Delete[/bold red] a user with [italic]USERNAME[/italic].") def delete( username: Annotated[ str, typer.Argument(help="The username to be [red]deleted[/red]") ], force: Annotated[ bool, typer.Option(help="Force the [bold red]deletion[/bold red] :boom:") ] = False, ): """ Some internal utility function to delete. """ print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial005.py000066400000000000000000000014221442411605300216540ustar00rootroot00000000000000import typer app = typer.Typer(rich_markup_mode="markdown") @app.command() def create(username: str = typer.Argument(..., help="The username to be **created**")): """ **Create** a new *shiny* user. :sparkles: * Create a username * Show that the username is created --- Learn more at the [Typer docs website](https://typer.tiangolo.com) """ print(f"Creating user: {username}") @app.command(help="**Delete** a user with *USERNAME*.") def delete( username: str = typer.Argument(..., help="The username to be **deleted**"), force: bool = typer.Option(False, help="Force the **deletion** :boom:"), ): """ Some internal utility function to delete. """ print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial005_an.py000066400000000000000000000015261442411605300223370ustar00rootroot00000000000000import typer from typing_extensions import Annotated app = typer.Typer(rich_markup_mode="markdown") @app.command() def create( username: Annotated[str, typer.Argument(help="The username to be **created**")] ): """ **Create** a new *shinny* user. :sparkles: * Create a username * Show that the username is created --- Learn more at the [Typer docs website](https://typer.tiangolo.com) """ print(f"Creating user: {username}") @app.command(help="**Delete** a user with *USERNAME*.") def delete( username: Annotated[str, typer.Argument(help="The username to be **deleted**")], force: Annotated[bool, typer.Option(help="Force the **deletion** :boom:")] = False, ): """ Some internal utility function to delete. """ print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial006.py000066400000000000000000000021671442411605300216640ustar00rootroot00000000000000import typer app = typer.Typer(rich_markup_mode="rich") @app.command() def create(username: str): """ [green]Create[/green] a new user. :sparkles: """ print(f"Creating user: {username}") @app.command() def delete(username: str): """ [red]Delete[/red] a user. :fire: """ print(f"Deleting user: {username}") @app.command(rich_help_panel="Utils and Configs") def config(configuration: str): """ [blue]Configure[/blue] the system. :wrench: """ print(f"Configuring the system with: {configuration}") @app.command(rich_help_panel="Utils and Configs") def sync(): """ [blue]Synchronize[/blue] the system or something fancy like that. :recycle: """ print("Syncing the system") @app.command(rich_help_panel="Help and Others") def help(): """ Get [yellow]help[/yellow] with the system. :question: """ print("Opening help portal...") @app.command(rich_help_panel="Help and Others") def report(): """ [yellow]Report[/yellow] an issue. :bug: """ print("Please open a new issue online, not a direct message") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial007.py000066400000000000000000000020241442411605300216550ustar00rootroot00000000000000from typing import Union import typer app = typer.Typer(rich_markup_mode="rich") @app.command() def create( username: str = typer.Argument(..., help="The username to create"), lastname: str = typer.Argument( "", help="The last name of the new user", rich_help_panel="Secondary Arguments" ), force: bool = typer.Option(False, help="Force the creation of the user"), age: Union[int, None] = typer.Option( None, help="The age of the new user", rich_help_panel="Additional Data" ), favorite_color: Union[str, None] = typer.Option( None, help="The favorite color of the new user", rich_help_panel="Additional Data", ), ): """ [green]Create[/green] a new user. :sparkles: """ print(f"Creating user: {username}") @app.command(rich_help_panel="Utils and Configs") def config(configuration: str): """ [blue]Configure[/blue] the system. :wrench: """ print(f"Configuring the system with: {configuration}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial007_an.py000066400000000000000000000022601442411605300223350ustar00rootroot00000000000000from typing import Union import typer from typing_extensions import Annotated app = typer.Typer(rich_markup_mode="rich") @app.command() def create( username: Annotated[str, typer.Argument(help="The username to create")], lastname: Annotated[ str, typer.Argument( help="The last name of the new user", rich_help_panel="Secondary Arguments" ), ] = "", force: Annotated[bool, typer.Option(help="Force the creation of the user")] = False, age: Annotated[ Union[int, None], typer.Option(help="The age of the new user", rich_help_panel="Additional Data"), ] = None, favorite_color: Annotated[ Union[str, None], typer.Option( help="The favorite color of the new user", rich_help_panel="Additional Data", ), ] = None, ): """ [green]Create[/green] a new user. :sparkles: """ print(f"Creating user: {username}") @app.command(rich_help_panel="Utils and Configs") def config(configuration: str): """ [blue]Configure[/blue] the system. :wrench: """ print(f"Configuring the system with: {configuration}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/help/tutorial008.py000066400000000000000000000004451442411605300216630ustar00rootroot00000000000000import typer app = typer.Typer(rich_markup_mode="rich") @app.command(epilog="Made with :heart: in [blue]Venus[/blue]") def create(username: str): """ [green]Create[/green] a new user. :sparkles: """ print(f"Creating user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/index/000077500000000000000000000000001442411605300173725ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/index/tutorial001.py000066400000000000000000000002121442411605300220230ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/index/tutorial002.py000066400000000000000000000003271442411605300220330ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): print("Creating user: Hiro Hamada") @app.command() def delete(): print("Deleting user: Hiro Hamada") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/name/000077500000000000000000000000001442411605300172035ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/name/tutorial001.py000066400000000000000000000004231442411605300216400ustar00rootroot00000000000000import typer app = typer.Typer() @app.command("create") def cli_create_user(username: str): print(f"Creating user: {username}") @app.command("delete") def cli_delete_user(username: str): print(f"Deleting user: {username}") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/one_or_multiple/000077500000000000000000000000001442411605300214575ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/one_or_multiple/tutorial001.py000066400000000000000000000002731442411605300241170ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): print("Creating user: Hiro Hamada") @app.callback() def callback(): pass if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/one_or_multiple/tutorial002.py000066400000000000000000000004371442411605300241220ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): print("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.9.0/docs_src/commands/options/000077500000000000000000000000001442411605300177565ustar00rootroot00000000000000typer-0.9.0/docs_src/commands/options/tutorial001.py000066400000000000000000000012651442411605300224200ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(username: str): print(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: print(f"Deleting user: {username}") else: print("Operation cancelled") @app.command() def delete_all( force: bool = typer.Option(..., prompt="Are you sure you want to delete ALL users?") ): if force: print("Deleting all users") else: print("Operation cancelled") @app.command() def init(): print("Initializing user database") if __name__ == "__main__": app() typer-0.9.0/docs_src/commands/options/tutorial001_an.py000066400000000000000000000014031442411605300230700ustar00rootroot00000000000000import typer from typing_extensions import Annotated app = typer.Typer() @app.command() def create(username: str): print(f"Creating user: {username}") @app.command() def delete( username: str, force: Annotated[ bool, typer.Option(prompt="Are you sure you want to delete the user?") ], ): if force: print(f"Deleting user: {username}") else: print("Operation cancelled") @app.command() def delete_all( force: Annotated[ bool, typer.Option(prompt="Are you sure you want to delete ALL users?") ] ): if force: print("Deleting all users") else: print("Operation cancelled") @app.command() def init(): print("Initializing user database") if __name__ == "__main__": app() typer-0.9.0/docs_src/exceptions/000077500000000000000000000000001442411605300166435ustar00rootroot00000000000000typer-0.9.0/docs_src/exceptions/tutorial001.py000066400000000000000000000001631442411605300213010ustar00rootroot00000000000000import typer def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/exceptions/tutorial002.py000066400000000000000000000002561442411605300213050ustar00rootroot00000000000000import typer app = typer.Typer(pretty_exceptions_show_locals=False) @app.command() def main(password: str): print(password + 3) if __name__ == "__main__": app() typer-0.9.0/docs_src/exceptions/tutorial003.py000066400000000000000000000002521442411605300213020ustar00rootroot00000000000000import typer app = typer.Typer(pretty_exceptions_short=False) @app.command() def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": app() typer-0.9.0/docs_src/exceptions/tutorial004.py000066400000000000000000000002531442411605300213040ustar00rootroot00000000000000import typer app = typer.Typer(pretty_exceptions_enable=False) @app.command() def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": app() typer-0.9.0/docs_src/first_steps/000077500000000000000000000000001442411605300170275ustar00rootroot00000000000000typer-0.9.0/docs_src/first_steps/tutorial001.py000066400000000000000000000001451442411605300214650ustar00rootroot00000000000000import typer def main(): print("Hello World") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/first_steps/tutorial002.py000066400000000000000000000001601442411605300214630ustar00rootroot00000000000000import typer def main(name: str): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/first_steps/tutorial003.py000066400000000000000000000002121442411605300214620ustar00rootroot00000000000000import typer def main(name: str, lastname: str): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/first_steps/tutorial004.py000066400000000000000000000003571442411605300214750ustar00rootroot00000000000000import typer def main(name: str, lastname: str, formal: bool = False): if formal: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/first_steps/tutorial005.py000066400000000000000000000003641442411605300214740ustar00rootroot00000000000000import typer def main(name: str, lastname: str = "", formal: bool = False): if formal: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/first_steps/tutorial006.py000066400000000000000000000005461442411605300214770ustar00rootroot00000000000000import 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: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/launch/000077500000000000000000000000001442411605300157345ustar00rootroot00000000000000typer-0.9.0/docs_src/launch/tutorial001.py000066400000000000000000000002351442411605300203720ustar00rootroot00000000000000import typer def main(): print("Opening Typer's docs") typer.launch("https://typer.tiangolo.com") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/launch/tutorial002.py000066400000000000000000000010171442411605300203720ustar00rootroot00000000000000from 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) print("Opening config directory") typer.launch(config_file_str, locate=True) if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/000077500000000000000000000000001442411605300176745ustar00rootroot00000000000000typer-0.9.0/docs_src/multiple_values/arguments_with_multiple_values/000077500000000000000000000000001442411605300262265ustar00rootroot00000000000000typer-0.9.0/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py000066400000000000000000000004461442411605300306700ustar00rootroot00000000000000from pathlib import Path from typing import List import typer def main(files: List[Path], celebration: str): for path in files: if path.is_file(): print(f"This file exists: {path.name}") print(celebration) if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py000066400000000000000000000004431442411605300306660ustar00rootroot00000000000000from 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: print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/arguments_with_multiple_values/tutorial002_an.py000066400000000000000000000005261442411605300313460ustar00rootroot00000000000000from typing import Tuple import typer from typing_extensions import Annotated def main( names: Annotated[ Tuple[str, str, str], typer.Argument(help="Select 3 characters to play with") ] = ("Harry", "Hermione", "Ron") ): for name in names: print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/multiple_options/000077500000000000000000000000001442411605300233025ustar00rootroot00000000000000typer-0.9.0/docs_src/multiple_values/multiple_options/tutorial001.py000066400000000000000000000004471442411605300257450ustar00rootroot00000000000000from typing import List, Optional import typer def main(user: Optional[List[str]] = typer.Option(None)): if not user: print("No provided users") raise typer.Abort() for u in user: print(f"Processing user: {u}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/multiple_options/tutorial001_an.py000066400000000000000000000005341442411605300264200ustar00rootroot00000000000000from typing import List, Optional import typer from typing_extensions import Annotated def main(user: Annotated[Optional[List[str]], typer.Option()] = None): if not user: print("No provided users") raise typer.Abort() for u in user: print(f"Processing user: {u}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/multiple_options/tutorial002.py000066400000000000000000000002621442411605300257410ustar00rootroot00000000000000from typing import List import typer def main(number: List[float] = typer.Option([])): print(f"The sum is {sum(number)}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/multiple_options/tutorial002_an.py000066400000000000000000000003471442411605300264230ustar00rootroot00000000000000from typing import List import typer from typing_extensions import Annotated def main(number: Annotated[List[float], typer.Option()] = []): print(f"The sum is {sum(number)}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/options_with_multiple_values/000077500000000000000000000000001442411605300257145ustar00rootroot00000000000000typer-0.9.0/docs_src/multiple_values/options_with_multiple_values/tutorial001.py000066400000000000000000000006231442411605300303530ustar00rootroot00000000000000from 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: print("No user provided") raise typer.Abort() print(f"The username {username} has {coins} coins") if is_wizard: print("And this user is a wizard!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/multiple_values/options_with_multiple_values/tutorial001_an.py000066400000000000000000000007101442411605300310260ustar00rootroot00000000000000from typing import Tuple import typer from typing_extensions import Annotated def main(user: Annotated[Tuple[str, int, bool], typer.Option()] = (None, None, None)): username, coins, is_wizard = user if not username: print("No user provided") raise typer.Abort() print(f"The username {username} has {coins} coins") if is_wizard: print("And this user is a wizard!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/000077500000000000000000000000001442411605300161555ustar00rootroot00000000000000typer-0.9.0/docs_src/options/callback/000077500000000000000000000000001442411605300177115ustar00rootroot00000000000000typer-0.9.0/docs_src/options/callback/tutorial001.py000066400000000000000000000004431442411605300223500ustar00rootroot00000000000000import 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)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial001_an.py000066400000000000000000000005201442411605300230220ustar00rootroot00000000000000import typer from typing_extensions import Annotated def name_callback(value: str): if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: Annotated[str, typer.Option(callback=name_callback)]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial002.py000066400000000000000000000005001442411605300223430ustar00rootroot00000000000000import typer def name_callback(value: str): print("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: str = typer.Option(..., callback=name_callback)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial002_an.py000066400000000000000000000005551442411605300230330ustar00rootroot00000000000000import typer from typing_extensions import Annotated def name_callback(value: str): print("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: Annotated[str, typer.Option(callback=name_callback)]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial003.py000066400000000000000000000006011442411605300223460ustar00rootroot00000000000000import typer def name_callback(ctx: typer.Context, value: str): if ctx.resilient_parsing: return print("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: str = typer.Option(..., callback=name_callback)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial003_an.py000066400000000000000000000006561442411605300230360ustar00rootroot00000000000000import typer from typing_extensions import Annotated def name_callback(ctx: typer.Context, value: str): if ctx.resilient_parsing: return print("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: Annotated[str, typer.Option(callback=name_callback)]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial004.py000066400000000000000000000006551442411605300223600ustar00rootroot00000000000000import typer def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str): if ctx.resilient_parsing: return print(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)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/callback/tutorial004_an.py000066400000000000000000000007321442411605300230320ustar00rootroot00000000000000import typer from typing_extensions import Annotated def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str): if ctx.resilient_parsing: return print(f"Validating param: {param.name}") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: Annotated[str, typer.Option(callback=name_callback)]): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/help/000077500000000000000000000000001442411605300171055ustar00rootroot00000000000000typer-0.9.0/docs_src/options/help/tutorial001.py000066400000000000000000000007201442411605300215420ustar00rootroot00000000000000import 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: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/help/tutorial001_an.py000066400000000000000000000010161442411605300222170ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: str, lastname: Annotated[str, typer.Option(help="Last name of person to greet.")] = "", formal: Annotated[bool, typer.Option(help="Say hi formally.")] = False, ): """ Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. """ if formal: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/help/tutorial002.py000066400000000000000000000012031442411605300215400ustar00rootroot00000000000000import 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.", rich_help_panel="Customization and Utils" ), debug: bool = typer.Option( False, help="Enable debugging.", rich_help_panel="Customization and Utils" ), ): """ Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. """ if formal: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/help/tutorial002_an.py000066400000000000000000000014121442411605300222200ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: str, lastname: Annotated[str, typer.Option(help="Last name of person to greet.")] = "", formal: Annotated[ bool, typer.Option( help="Say hi formally.", rich_help_panel="Customization and Utils" ), ] = False, debug: Annotated[ bool, typer.Option( help="Enable debugging.", rich_help_panel="Customization and Utils" ), ] = False, ): """ Say hi to NAME, optionally with a --lastname. If --formal is used, say hi very formally. """ if formal: print(f"Good day Ms. {name} {lastname}.") else: print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/help/tutorial003.py000066400000000000000000000002521442411605300215440ustar00rootroot00000000000000import typer def main(fullname: str = typer.Option("Wade Wilson", show_default=False)): print(f"Hello {fullname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/help/tutorial003_an.py000066400000000000000000000003351442411605300222240ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(fullname: Annotated[str, typer.Option(show_default=False)] = "Wade Wilson"): print(f"Hello {fullname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/000077500000000000000000000000001442411605300170755ustar00rootroot00000000000000typer-0.9.0/docs_src/options/name/tutorial001.py000066400000000000000000000002301442411605300215260ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--name")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial001_an.py000066400000000000000000000003051442411605300222070ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(user_name: Annotated[str, typer.Option("--name")]): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial002.py000066400000000000000000000002361442411605300215350ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--name", "-n")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial002_an.py000066400000000000000000000003131442411605300222070ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(user_name: Annotated[str, typer.Option("--name", "-n")]): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial003.py000066400000000000000000000002241442411605300215330ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "-n")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial003_an.py000066400000000000000000000003011442411605300222050ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(user_name: Annotated[str, typer.Option("-n")]): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial004.py000066400000000000000000000002431442411605300215350ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--user-name", "-n")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial004_an.py000066400000000000000000000003201442411605300222070ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(user_name: Annotated[str, typer.Option("--user-name", "-n")]): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial005.py000066400000000000000000000004311442411605300215350ustar00rootroot00000000000000import typer def main( name: str = typer.Option(..., "--name", "-n"), formal: bool = typer.Option(False, "--formal", "-f"), ): if formal: print(f"Good day Ms. {name}.") else: print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/name/tutorial005_an.py000066400000000000000000000005211442411605300222130ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: Annotated[str, typer.Option("--name", "-n")], formal: Annotated[bool, typer.Option("--formal", "-f")] = False, ): if formal: print(f"Good day Ms. {name}.") else: print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/password/000077500000000000000000000000001442411605300200175ustar00rootroot00000000000000typer-0.9.0/docs_src/options/password/tutorial001.py000066400000000000000000000003241442411605300224540ustar00rootroot00000000000000import typer def main( name: str, email: str = typer.Option(..., prompt=True, confirmation_prompt=True) ): print(f"Hello {name}, your email is {email}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/password/tutorial001_an.py000066400000000000000000000004061442411605300231330ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: str, email: Annotated[str, typer.Option(prompt=True, confirmation_prompt=True)], ): print(f"Hello {name}, your email is {email}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/password/tutorial002.py000066400000000000000000000005251442411605300224600ustar00rootroot00000000000000import typer def main( name: str, password: str = typer.Option( ..., prompt=True, confirmation_prompt=True, hide_input=True ), ): print(f"Hello {name}. Doing something very secure with password.") print(f"...just kidding, here it is, very insecure: {password}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/password/tutorial002_an.py000066400000000000000000000006021442411605300231320ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: str, password: Annotated[ str, typer.Option(prompt=True, confirmation_prompt=True, hide_input=True) ], ): print(f"Hello {name}. Doing something very secure with password.") print(f"...just kidding, here it is, very insecure: {password}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/prompt/000077500000000000000000000000001442411605300174765ustar00rootroot00000000000000typer-0.9.0/docs_src/options/prompt/tutorial001.py000066400000000000000000000002531442411605300221340ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option(..., prompt=True)): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/prompt/tutorial001_an.py000066400000000000000000000003301442411605300226060ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: str, lastname: Annotated[str, typer.Option(prompt=True)]): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/prompt/tutorial002.py000066400000000000000000000003141442411605300221330ustar00rootroot00000000000000import typer def main( name: str, lastname: str = typer.Option(..., prompt="Please tell me your last name") ): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/prompt/tutorial002_an.py000066400000000000000000000003761442411605300226210ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( name: str, lastname: Annotated[str, typer.Option(prompt="Please tell me your last name")], ): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/prompt/tutorial003.py000066400000000000000000000003061442411605300221350ustar00rootroot00000000000000import typer def main(project_name: str = typer.Option(..., prompt=True, confirmation_prompt=True)): print(f"Deleting project {project_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/prompt/tutorial003_an.py000066400000000000000000000003711442411605300226150ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( project_name: Annotated[str, typer.Option(prompt=True, confirmation_prompt=True)] ): print(f"Deleting project {project_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/required/000077500000000000000000000000001442411605300177755ustar00rootroot00000000000000typer-0.9.0/docs_src/options/required/tutorial001.py000066400000000000000000000002331442411605300224310ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option()): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/required/tutorial001_an.py000066400000000000000000000003151442411605300231100ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: str, lastname: Annotated[str, typer.Option()]): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/required/tutorial002.py000066400000000000000000000002461442411605300224360ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option(default=...)): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/version/000077500000000000000000000000001442411605300176425ustar00rootroot00000000000000typer-0.9.0/docs_src/options/version/tutorial001.py000066400000000000000000000006561442411605300223070ustar00rootroot00000000000000from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool): if value: print(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 ), ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/version/tutorial001_an.py000066400000000000000000000007561442411605300227660ustar00rootroot00000000000000from typing import Optional import typer from typing_extensions import Annotated __version__ = "0.1.0" def version_callback(value: bool): if value: print(f"Awesome CLI Version: {__version__}") raise typer.Exit() def main( name: Annotated[str, typer.Option()] = "World", version: Annotated[ Optional[bool], typer.Option("--version", callback=version_callback) ] = None, ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/version/tutorial002.py000066400000000000000000000010661442411605300223040ustar00rootroot00000000000000from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool): if value: print(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 ), ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/version/tutorial002_an.py000066400000000000000000000011561442411605300227620ustar00rootroot00000000000000from typing import Optional import typer from typing_extensions import Annotated __version__ = "0.1.0" def version_callback(value: bool): if value: print(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: Annotated[str, typer.Option(callback=name_callback)], version: Annotated[ Optional[bool], typer.Option("--version", callback=version_callback) ] = None, ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/version/tutorial003.py000066400000000000000000000011251442411605300223010ustar00rootroot00000000000000from typing import Optional import typer __version__ = "0.1.0" def version_callback(value: bool): if value: print(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 ), ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options/version/tutorial003_an.py000066400000000000000000000012261442411605300227610ustar00rootroot00000000000000from typing import Optional import typer from typing_extensions import Annotated __version__ = "0.1.0" def version_callback(value: bool): if value: print(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: Annotated[str, typer.Option(callback=name_callback)], version: Annotated[ Optional[bool], typer.Option("--version", callback=version_callback, is_eager=True), ] = None, ): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/options_autocompletion/000077500000000000000000000000001442411605300212775ustar00rootroot00000000000000typer-0.9.0/docs_src/options_autocompletion/tutorial001.py000066400000000000000000000003011442411605300237270ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str = typer.Option("World", help="The name to say hi to.")): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial001_an.py000066400000000000000000000003641442411605300244160ustar00rootroot00000000000000import typer from typing_extensions import Annotated app = typer.Typer() @app.command() def main(name: Annotated[str, typer.Option(help="The name to say hi to.")] = "World"): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial002.py000066400000000000000000000004701442411605300237370ustar00rootroot00000000000000import typer def complete_name(): return ["Camila", "Carlos", "Sebastian"] app = typer.Typer() @app.command() def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial002_an.py000066400000000000000000000005541442411605300244200ustar00rootroot00000000000000import typer from typing_extensions import Annotated def complete_name(): return ["Camila", "Carlos", "Sebastian"] app = typer.Typer() @app.command() def main( name: Annotated[ str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) ] = "World", ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial003.py000066400000000000000000000007361442411605300237450ustar00rootroot00000000000000import 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 app = typer.Typer() @app.command() def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial003_an.py000066400000000000000000000010221442411605300244100ustar00rootroot00000000000000import typer from typing_extensions import Annotated 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 app = typer.Typer() @app.command() def main( name: Annotated[ str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) ] = "World", ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial004.py000066400000000000000000000012301442411605300237340ustar00rootroot00000000000000import 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 app = typer.Typer() @app.command() def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial004_an.py000066400000000000000000000013141442411605300244150ustar00rootroot00000000000000import typer from typing_extensions import Annotated 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 app = typer.Typer() @app.command() def main( name: Annotated[ str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) ] = "World", ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial005.py000066400000000000000000000010631442411605300237410ustar00rootroot00000000000000import 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) app = typer.Typer() @app.command() def main( name: str = typer.Option( "World", help="The name to say hi to.", autocompletion=complete_name ) ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial005_an.py000066400000000000000000000011471442411605300244220ustar00rootroot00000000000000import typer from typing_extensions import Annotated 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) app = typer.Typer() @app.command() def main( name: Annotated[ str, typer.Option(help="The name to say hi to.", autocompletion=complete_name) ] = "World", ): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial006.py000066400000000000000000000004061442411605300237420ustar00rootroot00000000000000from typing import List import typer app = typer.Typer() @app.command() def main(name: List[str] = typer.Option(["World"], help="The name to say hi to.")): for each_name in name: print(f"Hello {each_name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial006_an.py000066400000000000000000000004771442411605300244300ustar00rootroot00000000000000from typing import List import typer from typing_extensions import Annotated app = typer.Typer() @app.command() def main( name: Annotated[List[str], typer.Option(help="The name to say hi to.")] = ["World"] ): for each_name in name: print(f"Hello {each_name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial007.py000066400000000000000000000012731442411605300237460ustar00rootroot00000000000000from 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) app = typer.Typer() @app.command() def main( name: List[str] = typer.Option( ["World"], help="The name to say hi to.", autocompletion=complete_name ) ): for n in name: print(f"Hello {n}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial007_an.py000066400000000000000000000013701442411605300244220ustar00rootroot00000000000000from typing import List import typer from typing_extensions import Annotated 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) app = typer.Typer() @app.command() def main( name: Annotated[ List[str], typer.Option(help="The name to say hi to.", autocompletion=complete_name), ] = ["World"], ): for n in name: print(f"Hello {n}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial008.py000066400000000000000000000013371442411605300237500ustar00rootroot00000000000000from typing import List import typer from rich.console import Console valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] err_console = Console(stderr=True) def complete_name(args: List[str], incomplete: str): err_console.print(f"{args}") for name, help_text in valid_completion_items: if name.startswith(incomplete): yield (name, help_text) app = typer.Typer() @app.command() def main( name: List[str] = typer.Option( ["World"], help="The name to say hi to.", autocompletion=complete_name ) ): for n in name: print(f"Hello {n}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial008_an.py000066400000000000000000000014341442411605300244240ustar00rootroot00000000000000from typing import List import typer from rich.console import Console from typing_extensions import Annotated valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] err_console = Console(stderr=True) def complete_name(args: List[str], incomplete: str): err_console.print(f"{args}") for name, help_text in valid_completion_items: if name.startswith(incomplete): yield (name, help_text) app = typer.Typer() @app.command() def main( name: Annotated[ List[str], typer.Option(help="The name to say hi to.", autocompletion=complete_name), ] = ["World"], ): for n in name: print(f"Hello {n}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial009.py000066400000000000000000000014621442411605300237500ustar00rootroot00000000000000from typing import List import typer from rich.console import Console valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] err_console = Console(stderr=True) def complete_name(ctx: typer.Context, args: List[str], incomplete: str): err_console.print(f"{args}") 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) app = typer.Typer() @app.command() def main( name: List[str] = typer.Option( ["World"], help="The name to say hi to.", autocompletion=complete_name ) ): for n in name: print(f"Hello {n}") if __name__ == "__main__": app() typer-0.9.0/docs_src/options_autocompletion/tutorial009_an.py000066400000000000000000000015571442411605300244330ustar00rootroot00000000000000from typing import List import typer from rich.console import Console from typing_extensions import Annotated valid_completion_items = [ ("Camila", "The reader of books."), ("Carlos", "The writer of scripts."), ("Sebastian", "The type hints guy."), ] err_console = Console(stderr=True) def complete_name(ctx: typer.Context, args: List[str], incomplete: str): err_console.print(f"{args}") 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) app = typer.Typer() @app.command() def main( name: Annotated[ List[str], typer.Option(help="The name to say hi to.", autocompletion=complete_name), ] = ["World"], ): for n in name: print(f"Hello {n}") if __name__ == "__main__": app() typer-0.9.0/docs_src/parameter_types/000077500000000000000000000000001442411605300176665ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/bool/000077500000000000000000000000001442411605300206215ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/bool/__init__.py000066400000000000000000000000001442411605300227200ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/bool/tutorial001.py000066400000000000000000000003201442411605300232520ustar00rootroot00000000000000import typer def main(force: bool = typer.Option(False, "--force")): if force: print("Forcing operation") else: print("Not forcing") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial001_an.py000066400000000000000000000004031442411605300237320ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(force: Annotated[bool, typer.Option("--force")] = False): if force: print("Forcing operation") else: print("Not forcing") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial002.py000066400000000000000000000005031442411605300232560ustar00rootroot00000000000000from typing import Optional import typer def main(accept: Optional[bool] = typer.Option(None, "--accept/--reject")): if accept is None: print("I don't know what you want yet") elif accept: print("Accepting!") else: print("Rejecting!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial002_an.py000066400000000000000000000005661442411605300237450ustar00rootroot00000000000000from typing import Optional import typer from typing_extensions import Annotated def main(accept: Annotated[Optional[bool], typer.Option("--accept/--reject")] = None): if accept is None: print("I don't know what you want yet") elif accept: print("Accepting!") else: print("Rejecting!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial003.py000066400000000000000000000003441442411605300232620ustar00rootroot00000000000000import typer def main(force: bool = typer.Option(False, "--force/--no-force", "-f/-F")): if force: print("Forcing operation") else: print("Not forcing") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial003_an.py000066400000000000000000000004271442411605300237420ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(force: Annotated[bool, typer.Option("--force/--no-force", "-f/-F")] = False): if force: print("Forcing operation") else: print("Not forcing") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial004.py000066400000000000000000000003411442411605300232600ustar00rootroot00000000000000import typer def main(in_prod: bool = typer.Option(True, " /--demo", " /-d")): if in_prod: print("Running in production") else: print("Running demo") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/bool/tutorial004_an.py000066400000000000000000000004241442411605300237400ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(in_prod: Annotated[bool, typer.Option(" /--demo", " /-d")] = True): if in_prod: print("Running in production") else: print("Running demo") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/custom_types/000077500000000000000000000000001442411605300224245ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/custom_types/__init__.py000066400000000000000000000000001442411605300245230ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/custom_types/tutorial001.py000066400000000000000000000010361442411605300250620ustar00rootroot00000000000000import typer class CustomClass: def __init__(self, value: str): self.value = value def __str__(self): return f"" def parse_custom_class(value: str): return CustomClass(value * 2) def main( custom_arg: CustomClass = typer.Argument(parser=parse_custom_class), custom_opt: CustomClass = typer.Option("Y", parser=parse_custom_class), ): print(f"custom_arg is {custom_arg}") print(f"--custom-opt is {custom_opt}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/custom_types/tutorial001_an.py000066400000000000000000000011351442411605300255400ustar00rootroot00000000000000import typer from typing_extensions import Annotated class CustomClass: def __init__(self, value: str): self.value = value def __str__(self): return f"" def parse_custom_class(value: str): return CustomClass(value * 2) def main( custom_arg: Annotated[CustomClass, typer.Argument(parser=parse_custom_class)], custom_opt: Annotated[CustomClass, typer.Option(parser=parse_custom_class)] = "Foo", ): print(f"custom_arg is {custom_arg}") print(f"--custom-opt is {custom_opt}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/custom_types/tutorial002.py000066400000000000000000000012061442411605300250620ustar00rootroot00000000000000import click import typer class CustomClass: def __init__(self, value: str): self.value = value def __repr__(self): return f"" class CustomClassParser(click.ParamType): name = "CustomClass" def convert(self, value, param, ctx): return CustomClass(value * 3) def main( custom_arg: CustomClass = typer.Argument(click_type=CustomClassParser()), custom_opt: CustomClass = typer.Option("Foo", click_type=CustomClassParser()), ): print(f"custom_arg is {custom_arg}") print(f"--custom-opt is {custom_opt}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/custom_types/tutorial002_an.py000066400000000000000000000013211442411605300255360ustar00rootroot00000000000000import click import typer from typing_extensions import Annotated class CustomClass: def __init__(self, value: str): self.value = value def __repr__(self): return f"" class CustomClassParser(click.ParamType): name = "CustomClass" def convert(self, value, param, ctx): return CustomClass(value * 3) def main( custom_arg: Annotated[CustomClass, typer.Argument(click_type=CustomClassParser())], custom_opt: Annotated[ CustomClass, typer.Option(click_type=CustomClassParser()) ] = "Foo", ): print(f"custom_arg is {custom_arg}") print(f"--custom-opt is {custom_opt}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/datetime/000077500000000000000000000000001442411605300214625ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/datetime/__init__.py000066400000000000000000000000001442411605300235610ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/datetime/tutorial001.py000066400000000000000000000003231442411605300241160ustar00rootroot00000000000000from datetime import datetime import typer def main(birth: datetime): print(f"Interesting day to be born: {birth}") print(f"Birth hour: {birth.hour}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/datetime/tutorial002.py000066400000000000000000000004451442411605300241240ustar00rootroot00000000000000from 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"] ) ): print(f"Launch will be at: {launch_date}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/datetime/tutorial002_an.py000066400000000000000000000005611442411605300246010ustar00rootroot00000000000000from datetime import datetime import typer from typing_extensions import Annotated def main( launch_date: Annotated[ datetime, typer.Argument( formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%m/%d/%Y"] ), ] ): print(f"Launch will be at: {launch_date}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/enum/000077500000000000000000000000001442411605300206325ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/enum/__init__.py000066400000000000000000000000001442411605300227310ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/enum/tutorial001.py000066400000000000000000000004531442411605300232720ustar00rootroot00000000000000from enum import Enum import typer class NeuralNetwork(str, Enum): simple = "simple" conv = "conv" lstm = "lstm" def main(network: NeuralNetwork = NeuralNetwork.simple): print(f"Training neural network of type: {network.value}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/enum/tutorial002.py000066400000000000000000000005251442411605300232730ustar00rootroot00000000000000from 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) ): print(f"Training neural network of type: {network.value}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/enum/tutorial002_an.py000066400000000000000000000006261442411605300237530ustar00rootroot00000000000000from enum import Enum import typer from typing_extensions import Annotated class NeuralNetwork(str, Enum): simple = "simple" conv = "conv" lstm = "lstm" def main( network: Annotated[ NeuralNetwork, typer.Option(case_sensitive=False) ] = NeuralNetwork.simple ): print(f"Training neural network of type: {network.value}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/000077500000000000000000000000001442411605300206055ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/file/__init__.py000066400000000000000000000000001442411605300227040ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/file/tutorial001.py000066400000000000000000000002641442411605300232450ustar00rootroot00000000000000import typer def main(config: typer.FileText = typer.Option(...)): for line in config: print(f"Config line: {line}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial001_an.py000066400000000000000000000003431442411605300237210ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(config: Annotated[typer.FileText, typer.Option()]): for line in config: print(f"Config line: {line}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial002.py000066400000000000000000000003121442411605300232400ustar00rootroot00000000000000import typer def main(config: typer.FileTextWrite = typer.Option(...)): config.write("Some config written by the app") print("Config written") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial002_an.py000066400000000000000000000003711442411605300237230ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(config: Annotated[typer.FileTextWrite, typer.Option()]): config.write("Some config written by the app") print("Config written") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial003.py000066400000000000000000000005011442411605300232410ustar00rootroot00000000000000import 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) print(f"Processed bytes total: {processed_total}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial003_an.py000066400000000000000000000005601442411605300237240ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(file: Annotated[typer.FileBinaryRead, typer.Option()]): processed_total = 0 for bytes_chunk in file: # Process the bytes in bytes_chunk processed_total += len(bytes_chunk) print(f"Processed bytes total: {processed_total}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial004.py000066400000000000000000000010431442411605300232440ustar00rootroot00000000000000import 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) print("Binary file written") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial004_an.py000066400000000000000000000011221442411605300237200ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(file: Annotated[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) print("Binary file written") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial005.py000066400000000000000000000003151442411605300232460ustar00rootroot00000000000000import typer def main(config: typer.FileText = typer.Option(..., mode="a")): config.write("This is a single line\n") print("Config line written") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/file/tutorial005_an.py000066400000000000000000000003721442411605300237270ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(config: Annotated[typer.FileText, typer.Option(mode="a")]): config.write("This is a single line\n") print("Config line written") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/index/000077500000000000000000000000001442411605300207755ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/index/__init__.py000066400000000000000000000000001442411605300230740ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/index/tutorial001.py000066400000000000000000000006121442411605300234320ustar00rootroot00000000000000import typer def main(name: str, age: int = 20, height_meters: float = 1.89, female: bool = True): print(f"NAME is {name}, of type: {type(name)}") print(f"--age is {age}, of type: {type(age)}") print(f"--height-meters is {height_meters}, of type: {type(height_meters)}") print(f"--female is {female}, of type: {type(female)}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/number/000077500000000000000000000000001442411605300211565ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/number/__init__.py000066400000000000000000000000001442411605300232550ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/number/tutorial001.py000066400000000000000000000004561442411605300236210ustar00rootroot00000000000000import 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), ): print(f"ID is {id}") print(f"--age is {age}") print(f"--score is {score}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/number/tutorial001_an.py000066400000000000000000000005611442411605300242740ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( id: Annotated[int, typer.Argument(min=0, max=1000)], age: Annotated[int, typer.Option(min=18)] = 20, score: Annotated[float, typer.Option(max=100)] = 0, ): print(f"ID is {id}") print(f"--age is {age}") print(f"--score is {score}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/number/tutorial002.py000066400000000000000000000005171442411605300236200ustar00rootroot00000000000000import 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), ): print(f"ID is {id}") print(f"--rank is {rank}") print(f"--score is {score}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/number/tutorial002_an.py000066400000000000000000000006221442411605300242730ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( id: Annotated[int, typer.Argument(min=0, max=1000)], rank: Annotated[int, typer.Option(max=10, clamp=True)] = 0, score: Annotated[float, typer.Option(min=0, max=100, clamp=True)] = 0, ): print(f"ID is {id}") print(f"--rank is {rank}") print(f"--score is {score}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/number/tutorial003.py000066400000000000000000000002621442411605300236160ustar00rootroot00000000000000import typer def main(verbose: int = typer.Option(0, "--verbose", "-v", count=True)): print(f"Verbose level is {verbose}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/number/tutorial003_an.py000066400000000000000000000003451442411605300242760ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(verbose: Annotated[int, typer.Option("--verbose", "-v", count=True)] = 0): print(f"Verbose level is {verbose}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/path/000077500000000000000000000000001442411605300206225ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/path/__init__.py000066400000000000000000000000001442411605300227210ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/path/tutorial001.py000066400000000000000000000010221442411605300232530ustar00rootroot00000000000000from pathlib import Path from typing import Optional import typer def main(config: Optional[Path] = typer.Option(None)): if config is None: print("No config file") raise typer.Abort() if config.is_file(): text = config.read_text() print(f"Config file contents: {text}") elif config.is_dir(): print("Config is a directory, will use all its config files") elif not config.exists(): print("The config doesn't exist") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/path/tutorial001_an.py000066400000000000000000000011071442411605300237350ustar00rootroot00000000000000from pathlib import Path from typing import Optional import typer from typing_extensions import Annotated def main(config: Annotated[Optional[Path], typer.Option()] = None): if config is None: print("No config file") raise typer.Abort() if config.is_file(): text = config.read_text() print(f"Config file contents: {text}") elif config.is_dir(): print("Config is a directory, will use all its config files") elif not config.exists(): print("The config doesn't exist") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/path/tutorial002.py000066400000000000000000000005631442411605300232650ustar00rootroot00000000000000from 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() print(f"Config file contents: {text}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/path/tutorial002_an.py000066400000000000000000000007131442411605300237400ustar00rootroot00000000000000from pathlib import Path import typer from typing_extensions import Annotated def main( config: Annotated[ Path, typer.Option( exists=True, file_okay=True, dir_okay=False, writable=False, readable=True, resolve_path=True, ), ] ): text = config.read_text() print(f"Config file contents: {text}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/parameter_types/uuid/000077500000000000000000000000001442411605300206345ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/uuid/__init__.py000066400000000000000000000000001442411605300227330ustar00rootroot00000000000000typer-0.9.0/docs_src/parameter_types/uuid/tutorial001.py000066400000000000000000000003041442411605300232670ustar00rootroot00000000000000from uuid import UUID import typer def main(user_id: UUID): print(f"USER_ID is {user_id}") print(f"UUID version is: {user_id.version}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/printing/000077500000000000000000000000001442411605300163145ustar00rootroot00000000000000typer-0.9.0/docs_src/printing/tutorial001.py000066400000000000000000000004501442411605300207510ustar00rootroot00000000000000import typer from rich import print data = { "name": "Rick", "age": 42, "items": [{"name": "Portal Gun"}, {"name": "Plumbus"}], "active": True, "affiliation": None, } def main(): print("Here's the data") print(data) if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/printing/tutorial002.py000066400000000000000000000002671442411605300207600ustar00rootroot00000000000000import typer from rich import print def main(): print("[bold red]Alert![/bold red] [green]Portal gun[/green] shooting! :boom:") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/printing/tutorial003.py000066400000000000000000000004501442411605300207530ustar00rootroot00000000000000import typer from rich.console import Console from rich.table import Table console = Console() def main(): table = Table("Name", "Item") table.add_row("Rick", "Portal Gun") table.add_row("Morty", "Plumbus") console.print(table) if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/printing/tutorial004.py000066400000000000000000000003261442411605300207560ustar00rootroot00000000000000import typer from rich.console import Console err_console = Console(stderr=True) def main(): err_console.print("Here is something written to standard error") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/printing/tutorial005.py000066400000000000000000000005551442411605300207630ustar00rootroot00000000000000import 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.9.0/docs_src/printing/tutorial006.py000066400000000000000000000002261442411605300207570ustar00rootroot00000000000000import typer def main(name: str): typer.secho(f"Welcome here {name}", fg=typer.colors.MAGENTA) if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/progressbar/000077500000000000000000000000001442411605300170135ustar00rootroot00000000000000typer-0.9.0/docs_src/progressbar/tutorial001.py000066400000000000000000000004731442411605300214550ustar00rootroot00000000000000import time import typer from rich.progress import track def main(): total = 0 for value in track(range(100), description="Processing..."): # Fake processing time time.sleep(0.01) total += 1 print(f"Processed {total} things.") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/progressbar/tutorial002.py000066400000000000000000000007341442411605300214560ustar00rootroot00000000000000import time import typer from rich.progress import Progress, SpinnerColumn, TextColumn def main(): with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True, ) as progress: progress.add_task(description="Processing...", total=None) progress.add_task(description="Preparing...", total=None) time.sleep(5) print("Done!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/progressbar/tutorial003.py000066400000000000000000000004711442411605300214550ustar00rootroot00000000000000import 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 print(f"Processed {total} things.") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/progressbar/tutorial004.py000066400000000000000000000007071442411605300214600ustar00rootroot00000000000000import 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 print(f"Processed {total} user IDs.") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/progressbar/tutorial005.py000066400000000000000000000005151442411605300214560ustar00rootroot00000000000000import 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 print(f"Processed {total} things.") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/progressbar/tutorial006.py000066400000000000000000000005201442411605300214530ustar00rootroot00000000000000import 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) print(f"Processed {total} things in batches.") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/prompt/000077500000000000000000000000001442411605300160035ustar00rootroot00000000000000typer-0.9.0/docs_src/prompt/tutorial001.py000066400000000000000000000002421442411605300204370ustar00rootroot00000000000000import typer def main(): person_name = typer.prompt("What's your name?") print(f"Hello {person_name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/prompt/tutorial002.py000066400000000000000000000003651442411605300204460ustar00rootroot00000000000000import typer def main(): delete = typer.confirm("Are you sure you want to delete it?") if not delete: print("Not deleting") raise typer.Abort() print("Deleting it!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/prompt/tutorial003.py000066400000000000000000000002641442411605300204450ustar00rootroot00000000000000import typer def main(): delete = typer.confirm("Are you sure you want to delete it?", abort=True) print("Deleting it!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/prompt/tutorial004.py000066400000000000000000000003011442411605300204360ustar00rootroot00000000000000import typer from rich.prompt import Prompt def main(): name = Prompt.ask("Enter your name :sunglasses:") print(f"Hey there {name}!") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/subcommands/000077500000000000000000000000001442411605300167755ustar00rootroot00000000000000typer-0.9.0/docs_src/subcommands/callback_override/000077500000000000000000000000001442411605300224305ustar00rootroot00000000000000typer-0.9.0/docs_src/subcommands/callback_override/tutorial001.py000066400000000000000000000004601442411605300250660ustar00rootroot00000000000000import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app, name="users") @users_app.callback() def users_callback(): print("Running a users command") @users_app.command() def create(name: str): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/callback_override/tutorial002.py000066400000000000000000000004621442411605300250710ustar00rootroot00000000000000import typer app = typer.Typer() def users_callback(): print("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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/callback_override/tutorial003.py000066400000000000000000000006311442411605300250700ustar00rootroot00000000000000import typer app = typer.Typer() def default_callback(): print("Running a users command") users_app = typer.Typer(callback=default_callback) app.add_typer(users_app, name="users") @users_app.callback() def user_callback(): print("Callback override, running users command") @users_app.command() def create(name: str): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/callback_override/tutorial004.py000066400000000000000000000010251442411605300250670ustar00rootroot00000000000000import typer app = typer.Typer() def default_callback(): print("Running a users command") users_app = typer.Typer(callback=default_callback) def callback_for_add_typer(): print("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(): print("Callback override, running users command") @users_app.command() def create(name: str): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/000077500000000000000000000000001442411605300207255ustar00rootroot00000000000000typer-0.9.0/docs_src/subcommands/name_help/tutorial001.py000066400000000000000000000003761442411605300233710ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial002.py000066400000000000000000000004411442411605300233630ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial003.py000066400000000000000000000004321442411605300233640ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial004.py000066400000000000000000000005641442411605300233730ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial005.py000066400000000000000000000007311442411605300233700ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial006.py000066400000000000000000000010021442411605300233610ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial007.py000066400000000000000000000010641442411605300233720ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/name_help/tutorial008.py000066400000000000000000000012041442411605300233670ustar00rootroot00000000000000import 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): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial001/000077500000000000000000000000001442411605300210615ustar00rootroot00000000000000typer-0.9.0/docs_src/subcommands/tutorial001/items.py000066400000000000000000000004521442411605300225550ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(item: str): print(f"Creating item: {item}") @app.command() def delete(item: str): print(f"Deleting item: {item}") @app.command() def sell(item: str): print(f"Selling item: {item}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial001/main.py000066400000000000000000000002611442411605300223560ustar00rootroot00000000000000import 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.9.0/docs_src/subcommands/tutorial001/users.py000066400000000000000000000003651442411605300226000ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(user_name: str): print(f"Creating user: {user_name}") @app.command() def delete(user_name: str): print(f"Deleting user: {user_name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial002/000077500000000000000000000000001442411605300210625ustar00rootroot00000000000000typer-0.9.0/docs_src/subcommands/tutorial002/main.py000066400000000000000000000012721442411605300223620ustar00rootroot00000000000000import 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): print(f"Creating item: {item}") @items_app.command("delete") def items_delete(item: str): print(f"Deleting item: {item}") @items_app.command("sell") def items_sell(item: str): print(f"Selling item: {item}") @users_app.command("create") def users_create(user_name: str): print(f"Creating user: {user_name}") @users_app.command("delete") def users_delete(user_name: str): print(f"Deleting user: {user_name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial003/000077500000000000000000000000001442411605300210635ustar00rootroot00000000000000typer-0.9.0/docs_src/subcommands/tutorial003/items.py000066400000000000000000000004521442411605300225570ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(item: str): print(f"Creating item: {item}") @app.command() def delete(item: str): print(f"Deleting item: {item}") @app.command() def sell(item: str): print(f"Selling item: {item}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial003/lands.py000066400000000000000000000002641442411605300225400ustar00rootroot00000000000000import 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.9.0/docs_src/subcommands/tutorial003/main.py000066400000000000000000000003451442411605300223630ustar00rootroot00000000000000import 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.9.0/docs_src/subcommands/tutorial003/reigns.py000066400000000000000000000003511442411605300227230ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def conquer(name: str): print(f"Conquering reign: {name}") @app.command() def destroy(name: str): print(f"Destroying reign: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial003/towns.py000066400000000000000000000003351442411605300226100ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def found(name: str): print(f"Founding town: {name}") @app.command() def burn(name: str): print(f"Burning town: {name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/subcommands/tutorial003/users.py000066400000000000000000000003651442411605300226020ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(user_name: str): print(f"Creating user: {user_name}") @app.command() def delete(user_name: str): print(f"Deleting user: {user_name}") if __name__ == "__main__": app() typer-0.9.0/docs_src/terminating/000077500000000000000000000000001442411605300170035ustar00rootroot00000000000000typer-0.9.0/docs_src/terminating/tutorial001.py000066400000000000000000000011261442411605300214410ustar00rootroot00000000000000import typer existing_usernames = ["rick", "morty"] def maybe_create_user(username: str): if username in existing_usernames: print("The user already exists") raise typer.Exit() else: print(f"User created: {username}") def send_new_user_notification(username: str): # Somehow send a notification here for the new user, maybe an email print(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.9.0/docs_src/terminating/tutorial002.py000066400000000000000000000003531442411605300214430ustar00rootroot00000000000000import typer def main(username: str): if username == "root": print("The root user is reserved") raise typer.Exit(code=1) print(f"New user created: {username}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/terminating/tutorial003.py000066400000000000000000000003461442411605300214460ustar00rootroot00000000000000import typer def main(username: str): if username == "root": print("The root user is reserved") raise typer.Abort() print(f"New user created: {username}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/testing/000077500000000000000000000000001442411605300161375ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app01/000077500000000000000000000000001442411605300170605ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app01/__init__.py000066400000000000000000000000001442411605300211570ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app01/main.py000066400000000000000000000004001442411605300203500ustar00rootroot00000000000000from typing import Optional import typer app = typer.Typer() @app.command() def main(name: str, city: Optional[str] = None): print(f"Hello {name}") if city: print(f"Let's have a coffee in {city}") if __name__ == "__main__": app() typer-0.9.0/docs_src/testing/app01/test_main.py000066400000000000000000000004531442411605300214170ustar00rootroot00000000000000from 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.9.0/docs_src/testing/app02/000077500000000000000000000000001442411605300170615ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app02/__init__.py000066400000000000000000000000001442411605300211600ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app02/main.py000066400000000000000000000003171442411605300203600ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str, email: str = typer.Option(..., prompt=True)): print(f"Hello {name}, your email is: {email}") if __name__ == "__main__": app() typer-0.9.0/docs_src/testing/app02/test_main.py000066400000000000000000000004341442411605300214170ustar00rootroot00000000000000from 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.9.0/docs_src/testing/app02_an/000077500000000000000000000000001442411605300175375ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app02_an/__init__.py000066400000000000000000000000001442411605300216360ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app02_an/main.py000066400000000000000000000003741442411605300210410ustar00rootroot00000000000000import typer from typing_extensions import Annotated app = typer.Typer() @app.command() def main(name: str, email: Annotated[str, typer.Option(prompt=True)]): print(f"Hello {name}, your email is: {email}") if __name__ == "__main__": app() typer-0.9.0/docs_src/testing/app02_an/test_main.py000066400000000000000000000004341442411605300220750ustar00rootroot00000000000000from 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.9.0/docs_src/testing/app03/000077500000000000000000000000001442411605300170625ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app03/__init__.py000066400000000000000000000000001442411605300211610ustar00rootroot00000000000000typer-0.9.0/docs_src/testing/app03/main.py000066400000000000000000000001721442411605300203600ustar00rootroot00000000000000import typer def main(name: str = "World"): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.9.0/docs_src/testing/app03/test_main.py000066400000000000000000000004341442411605300214200ustar00rootroot00000000000000import 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.9.0/docs_src/using_click/000077500000000000000000000000001442411605300167545ustar00rootroot00000000000000typer-0.9.0/docs_src/using_click/tutorial001.py000066400000000000000000000005631442411605300214160ustar00rootroot00000000000000import 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.9.0/docs_src/using_click/tutorial002.py000066400000000000000000000004371442411605300214170ustar00rootroot00000000000000import 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.9.0/docs_src/using_click/tutorial003.py000066400000000000000000000011621442411605300214140ustar00rootroot00000000000000import click import typer app = typer.Typer() @app.command() def top(): """ Top level command, form Typer """ print("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.9.0/docs_src/using_click/tutorial004.py000066400000000000000000000007771442411605300214300ustar00rootroot00000000000000import 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 """ print("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.9.0/mkdocs.insiders.yml000066400000000000000000000000631442411605300165040ustar00rootroot00000000000000INHERIT: mkdocs.yml plugins: - search - social typer-0.9.0/mkdocs.yml000066400000000000000000000126301442411605300146700ustar00rootroot00000000000000site_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 custom_dir: docs/overrides 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: "" 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 - 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 Option autocompletion: tutorial/options-autocompletion.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 - Custom Types: tutorial/parameter-types/custom-types.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 - tutorial/exceptions.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 - admonition - codehilite - extra - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' - pymdownx.tabbed: alternate_style: true - mdx_include: base_path: docs # Enable while writing docs to simplify highlighting # - pymdownx.highlight: # linenums: true extra: analytics: provider: google property: G-T78C5GNRXK 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.9.0/mypy.ini000066400000000000000000000001401442411605300143550ustar00rootroot00000000000000[mypy] ignore_missing_imports = True [mypy-typer.*] disallow_untyped_defs = True strict = True typer-0.9.0/pyproject.toml000066400000000000000000000056721442411605300156110ustar00rootroot00000000000000[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", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "License :: OSI Approved :: MIT License" ] requires = [ "click >= 7.1.1, <9.0.0", "typing-extensions >= 3.7.4.3", ] 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,<8.0.0", "pytest-cov >=2.10.0,<5.0.0", "coverage >=6.2,<7.0", "pytest-xdist >=1.32.0,<4.0.0", "pytest-sugar >=0.9.4,<0.10.0", "mypy ==0.910", "black >=22.3.0,<23.0.0", "isort >=5.0.6,<6.0.0", "rich >=10.11.0,<14.0.0", ] doc = [ "mkdocs >=1.1.2,<2.0.0", "mkdocs-material >=8.1.4,<9.0.0", "mdx-include >=1.4.1,<2.0.0", "pillow >=9.3.0,<10.0.0", "cairosvg >=2.5.2,<3.0.0", ] dev = [ "autoflake >=1.3.1,<2.0.0", "flake8 >=3.8.3,<4.0.0", "pre-commit >=2.17.0,<3.0.0", ] all = [ "colorama >=0.4.3,<0.5.0", "shellingham >=1.3.0,<2.0.0", "rich >=10.11.0,<14.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", ] [tool.pytest.ini_options] addopts = [ "--strict-config", "--strict-markers", ] xfail_strict = true junit_family = "xunit2" filterwarnings = [ "error", # TODO: until I refactor completion to use the new shell_complete "ignore:'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:typer", 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette', # For pytest-xdist 'ignore::DeprecationWarning:xdist', ] typer-0.9.0/scripts/000077500000000000000000000000001442411605300143525ustar00rootroot00000000000000typer-0.9.0/scripts/build-docs.sh000077500000000000000000000001141442411605300167320ustar00rootroot00000000000000#!/usr/bin/env bash python -m mkdocs build cp ./docs/index.md ./README.md typer-0.9.0/scripts/clean.sh000077500000000000000000000001421442411605300157700ustar00rootroot00000000000000#!/bin/sh -e if [ -d 'dist' ] ; then rm -r dist fi if [ -d 'site' ] ; then rm -r site fi typer-0.9.0/scripts/docs-live.sh000077500000000000000000000001041442411605300165710ustar00rootroot00000000000000#!/usr/bin/env bash set -e mkdocs serve --dev-addr 127.0.0.1:8008 typer-0.9.0/scripts/format-imports.sh000077500000000000000000000002471442411605300176770ustar00rootroot00000000000000#!/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.9.0/scripts/format.sh000077500000000000000000000003151442411605300162000ustar00rootroot00000000000000#!/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.9.0/scripts/get-pwsh-activate.sh000066400000000000000000000001601442411605300202370ustar00rootroot00000000000000curl https://raw.githubusercontent.com/python/cpython/main/Lib/venv/scripts/common/Activate.ps1 -o Activate.ps1 typer-0.9.0/scripts/lint.sh000077500000000000000000000001721442411605300156570ustar00rootroot00000000000000#!/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.9.0/scripts/netlify-docs.sh000077500000000000000000000005231442411605300173110ustar00rootroot00000000000000#!/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.9.0/scripts/publish.sh000077500000000000000000000000521442411605300163540ustar00rootroot00000000000000#!/usr/bin/env bash set -e flit publish typer-0.9.0/scripts/test-cov-html.sh000077500000000000000000000001201442411605300174100ustar00rootroot00000000000000#!/usr/bin/env bash set -e set -x bash scripts/test.sh --cov-report=html ${@} typer-0.9.0/scripts/test-files.sh000077500000000000000000000005361442411605300167740ustar00rootroot00000000000000#!/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.9.0/scripts/test.sh000077500000000000000000000007521442411605300156740ustar00rootroot00000000000000#!/usr/bin/env bash set -e set -x # For tests, a large terminal width export TERMINAL_WIDTH=3000 # Force disable terminal for tests inside of pytest, takes precedence over GITHUB_ACTIONS env var export _TYPER_FORCE_DISABLE_TERMINAL=1 bash ./scripts/test-files.sh # It seems xdist-pytest ensures modified sys.path to import relative modules in examples keeps working pytest --cov-config=.coveragerc --cov --cov-report=term-missing -o console_output_style=progress --numprocesses=auto ${@} typer-0.9.0/scripts/zip-docs.sh000066400000000000000000000001631442411605300164360ustar00rootroot00000000000000#! /usr/bin/env bash set -x set -e cd ./site if [ -f docs.zip ]; then rm -rf docs.zip fi zip -r docs.zip ./ typer-0.9.0/tests/000077500000000000000000000000001442411605300140255ustar00rootroot00000000000000typer-0.9.0/tests/__init__.py000066400000000000000000000000001442411605300161240ustar00rootroot00000000000000typer-0.9.0/tests/assets/000077500000000000000000000000001442411605300153275ustar00rootroot00000000000000typer-0.9.0/tests/assets/__init__.py000066400000000000000000000000001442411605300174260ustar00rootroot00000000000000typer-0.9.0/tests/assets/compat_click7_8.py000066400000000000000000000012431442411605300206470ustar00rootroot00000000000000from 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. """ print(f"Hello {name} {lastname}, it seems you have {age}, {nickname}") if __name__ == "__main__": app() typer-0.9.0/tests/assets/completion_no_types.py000066400000000000000000000010361442411605300217720ustar00rootroot00000000000000import 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)): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/tests/assets/completion_no_types_order.py000066400000000000000000000010361442411605300231650ustar00rootroot00000000000000import 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)): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.9.0/tests/assets/prog_name.py000066400000000000000000000002401442411605300176440ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(i: int): # pragma: no cover pass if __name__ == "__main__": app(prog_name="custom-name") typer-0.9.0/tests/assets/type_error_no_rich.py000066400000000000000000000002351442411605300215740ustar00rootroot00000000000000import typer import typer.main typer.main.rich = None def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": typer.run(main) typer-0.9.0/tests/assets/type_error_no_rich_short_disable.py000066400000000000000000000003251442411605300244760ustar00rootroot00000000000000import typer import typer.main typer.main.rich = None app = typer.Typer(pretty_exceptions_short=False) @app.command() def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": app() typer-0.9.0/tests/assets/type_error_normal_traceback.py000066400000000000000000000004621442411605300234440ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str = "morty"): print(name) broken_app = typer.Typer() @broken_app.command() def broken(name: str = "morty"): print(name + 3) if __name__ == "__main__": app(standalone_mode=False) typer.main.get_command(broken_app)() typer-0.9.0/tests/test_ambiguous_params.py000066400000000000000000000154351442411605300210040ustar00rootroot00000000000000import pytest import typer from typer.testing import CliRunner from typer.utils import ( AnnotatedParamWithDefaultValueError, DefaultFactoryAndDefaultValueError, MixedAnnotatedAndDefaultStyleError, MultipleTyperAnnotationsError, _split_annotation_from_typer_annotations, ) from typing_extensions import Annotated runner = CliRunner() def test_split_annotations_from_typer_annotations_simple(): # Simple sanity check that this utility works. If this isn't working on a given # python version, then no other tests for Annotated will work. given = Annotated[str, typer.Argument()] base, typer_annotations = _split_annotation_from_typer_annotations(given) assert base is str # No equality check on the param types. Checking the length is sufficient. assert len(typer_annotations) == 1 def test_forbid_default_value_in_annotated_argument(): app = typer.Typer() # This test case only works with `typer.Argument`. `typer.Option` uses positionals # for param_decls too. @app.command() def cmd(my_param: Annotated[str, typer.Argument("foo")]): ... # pragma: no cover with pytest.raises(AnnotatedParamWithDefaultValueError) as excinfo: runner.invoke(app) assert vars(excinfo.value) == dict( param_type=typer.models.ArgumentInfo, argument_name="my_param", ) def test_allow_options_to_have_names(): app = typer.Typer() @app.command() def cmd(my_param: Annotated[str, typer.Option("--some-opt")]): print(my_param) result = runner.invoke(app, ["--some-opt", "hello"]) assert result.exit_code == 0, result.output assert "hello" in result.output @pytest.mark.parametrize( ["param", "param_info_type"], [ (typer.Argument, typer.models.ArgumentInfo), (typer.Option, typer.models.OptionInfo), ], ) def test_forbid_annotated_param_and_default_param(param, param_info_type): app = typer.Typer() @app.command() def cmd(my_param: Annotated[str, param()] = param("foo")): ... # pragma: no cover with pytest.raises(MixedAnnotatedAndDefaultStyleError) as excinfo: runner.invoke(app) assert vars(excinfo.value) == dict( argument_name="my_param", annotated_param_type=param_info_type, default_param_type=param_info_type, ) def test_forbid_multiple_typer_params_in_annotated(): app = typer.Typer() @app.command() def cmd(my_param: Annotated[str, typer.Argument(), typer.Argument()]): ... # pragma: no cover with pytest.raises(MultipleTyperAnnotationsError) as excinfo: runner.invoke(app) assert vars(excinfo.value) == dict(argument_name="my_param") def test_allow_multiple_non_typer_params_in_annotated(): app = typer.Typer() @app.command() def cmd(my_param: Annotated[str, "someval", typer.Argument(), 4] = "hello"): print(my_param) result = runner.invoke(app) # Should behave like normal assert result.exit_code == 0, result.output assert "hello" in result.output @pytest.mark.parametrize( ["param", "param_info_type"], [ (typer.Argument, typer.models.ArgumentInfo), (typer.Option, typer.models.OptionInfo), ], ) def test_forbid_default_factory_and_default_value_in_annotated(param, param_info_type): def make_string(): return "foo" # pragma: no cover app = typer.Typer() @app.command() def cmd(my_param: Annotated[str, param(default_factory=make_string)] = "hello"): ... # pragma: no cover with pytest.raises(DefaultFactoryAndDefaultValueError) as excinfo: runner.invoke(app) assert vars(excinfo.value) == dict( argument_name="my_param", param_type=param_info_type, ) @pytest.mark.parametrize( "param", [ typer.Argument, typer.Option, ], ) def test_allow_default_factory_with_default_param(param): def make_string(): return "foo" app = typer.Typer() @app.command() def cmd(my_param: str = param(default_factory=make_string)): print(my_param) result = runner.invoke(app) assert result.exit_code == 0, result.output assert "foo" in result.output @pytest.mark.parametrize( ["param", "param_info_type"], [ (typer.Argument, typer.models.ArgumentInfo), (typer.Option, typer.models.OptionInfo), ], ) def test_forbid_default_and_default_factory_with_default_param(param, param_info_type): def make_string(): return "foo" # pragma: no cover app = typer.Typer() @app.command() def cmd(my_param: str = param("hi", default_factory=make_string)): ... # pragma: no cover with pytest.raises(DefaultFactoryAndDefaultValueError) as excinfo: runner.invoke(app) assert vars(excinfo.value) == dict( argument_name="my_param", param_type=param_info_type, ) @pytest.mark.parametrize( ["error", "message"], [ ( AnnotatedParamWithDefaultValueError( argument_name="my_argument", param_type=typer.models.ArgumentInfo, ), "`Argument` default value cannot be set in `Annotated` for 'my_argument'. Set the default value with `=` instead.", ), ( MixedAnnotatedAndDefaultStyleError( argument_name="my_argument", annotated_param_type=typer.models.OptionInfo, default_param_type=typer.models.ArgumentInfo, ), "Cannot specify `Option` in `Annotated` and `Argument` as a default value together for 'my_argument'", ), ( MixedAnnotatedAndDefaultStyleError( argument_name="my_argument", annotated_param_type=typer.models.OptionInfo, default_param_type=typer.models.OptionInfo, ), "Cannot specify `Option` in `Annotated` and default value together for 'my_argument'", ), ( MixedAnnotatedAndDefaultStyleError( argument_name="my_argument", annotated_param_type=typer.models.ArgumentInfo, default_param_type=typer.models.ArgumentInfo, ), "Cannot specify `Argument` in `Annotated` and default value together for 'my_argument'", ), ( MultipleTyperAnnotationsError( argument_name="my_argument", ), "Cannot specify multiple `Annotated` Typer arguments for 'my_argument'", ), ( DefaultFactoryAndDefaultValueError( argument_name="my_argument", param_type=typer.models.OptionInfo, ), "Cannot specify `default_factory` and a default value together for `Option`", ), ], ) def test_error_rendering(error, message): assert str(error) == message typer-0.9.0/tests/test_annotated.py000066400000000000000000000030741442411605300174170ustar00rootroot00000000000000import typer from typer.testing import CliRunner from typing_extensions import Annotated runner = CliRunner() def test_annotated_argument_with_default(): app = typer.Typer() @app.command() def cmd(val: Annotated[int, typer.Argument()] = 0): print(f"hello {val}") result = runner.invoke(app) assert result.exit_code == 0, result.output assert "hello 0" in result.output result = runner.invoke(app, ["42"]) assert result.exit_code == 0, result.output assert "hello 42" in result.output def test_annotated_argument_with_default_factory(): app = typer.Typer() def make_string(): return "I made it" @app.command() def cmd(val: Annotated[str, typer.Argument(default_factory=make_string)]): print(val) result = runner.invoke(app) assert result.exit_code == 0, result.output assert "I made it" in result.output result = runner.invoke(app, ["overridden"]) assert result.exit_code == 0, result.output assert "overridden" in result.output def test_annotated_option_with_argname_doesnt_mutate_multiple_calls(): app = typer.Typer() @app.command() def cmd(force: Annotated[bool, typer.Option("--force")] = False): if force: print("Forcing operation") else: print("Not forcing") result = runner.invoke(app) assert result.exit_code == 0, result.output assert "Not forcing" in result.output result = runner.invoke(app, ["--force"]) assert result.exit_code == 0, result.output assert "Forcing operation" in result.output typer-0.9.0/tests/test_compat/000077500000000000000000000000001442411605300163475ustar00rootroot00000000000000typer-0.9.0/tests/test_compat/__init__.py000066400000000000000000000000001442411605300204460ustar00rootroot00000000000000typer-0.9.0/tests/test_compat/test_option_get_help.py000066400000000000000000000031221442411605300231350ustar00rootroot00000000000000import os import subprocess import sys import typer.core 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_hidden_option_no_rich(): rich = typer.core.rich typer.core.rich = None 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 typer.core.rich = rich 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( [sys.executable, "-m", "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.9.0/tests/test_completion/000077500000000000000000000000001442411605300172355ustar00rootroot00000000000000typer-0.9.0/tests/test_completion/__init__.py000066400000000000000000000000001442411605300213340ustar00rootroot00000000000000typer-0.9.0/tests/test_completion/test_completion.py000066400000000000000000000123201442411605300230150ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from docs_src.commands.index 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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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.9.0/tests/test_completion/test_completion_complete.py000066400000000000000000000132261442411605300247130ustar00rootroot00000000000000import os import subprocess import sys from docs_src.commands.help import tutorial001 as mod def test_completion_complete_subcommand_bash(): result = subprocess.run( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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.9.0/tests/test_completion/test_completion_complete_no_help.py000066400000000000000000000042411442411605300264140ustar00rootroot00000000000000import os import subprocess import sys from docs_src.commands.index import tutorial002 as mod def test_completion_complete_subcommand_zsh(): result = subprocess.run( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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.9.0/tests/test_completion/test_completion_install.py000066400000000000000000000134671442411605300245600ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from unittest import mock import shellingham import typer from typer.testing import CliRunner from docs_src.commands.index import tutorial001 as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_completion_install_no_shell(): result = subprocess.run( [sys.executable, "-m", "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 ( "Option '--install-completion' requires an argument" in result.stderr or "--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( [ sys.executable, "-m", "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( [ sys.executable, "-m", "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( [ sys.executable, "-m", "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.9.0/tests/test_completion/test_completion_show.py000066400000000000000000000074631442411605300240710ustar00rootroot00000000000000import os import subprocess import sys from docs_src.commands.index import tutorial001 as mod def test_completion_show_no_shell(): result = subprocess.run( [sys.executable, "-m", "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 ( "Option '--show-completion' requires an argument" in result.stderr or "--show-completion option requires an argument" in result.stderr ) def test_completion_show_bash(): result = subprocess.run( [ sys.executable, "-m", "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( [ sys.executable, "-m", "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( [ sys.executable, "-m", "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( [ sys.executable, "-m", "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( [ sys.executable, "-m", "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.9.0/tests/test_exit_errors.py000066400000000000000000000014351442411605300200060ustar00rootroot00000000000000import errno import typer import typer.completion from typer.testing import CliRunner runner = CliRunner() def test_eoferror(): # Mainly for coverage/completeness app = typer.Typer() @app.command() def main(): raise EOFError() result = runner.invoke(app) assert result.exit_code == 1 def test_oserror(): # Mainly for coverage/completeness app = typer.Typer() @app.command() def main(): e = OSError() e.errno = errno.EPIPE raise e result = runner.invoke(app) assert result.exit_code == 1 def test_oserror_no_epipe(): # Mainly for coverage/completeness app = typer.Typer() @app.command() def main(): raise OSError() result = runner.invoke(app) assert result.exit_code == 1 typer-0.9.0/tests/test_others.py000066400000000000000000000176701442411605300167550ustar00rootroot00000000000000import os import subprocess import sys import typing from pathlib import Path 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 ParameterInfo, TyperInfo from typer.testing import CliRunner runner = CliRunner() 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_too_may_parsers(): def custom_parser(value: str) -> int: return int(value) # pragma: no cover class CustomClickParser(click.ParamType): name = "custom_parser" def convert( self, value: str, param: typing.Optional[click.Parameter], ctx: typing.Optional[click.Context], ) -> typing.Any: return int(value) # pragma: no cover expected_error = ( "Multiple custom type parsers provided. " "`parser` and `click_type` may not both be provided." ) with pytest.raises(ValueError, match=expected_error): ParameterInfo(parser=custom_parser, click_type=CustomClickParser()) def test_valid_parser_permutations(): def custom_parser(value: str) -> int: return int(value) # pragma: no cover class CustomClickParser(click.ParamType): name = "custom_parser" def convert( self, value: str, param: typing.Optional[click.Parameter], ctx: typing.Optional[click.Context], ) -> typing.Any: return int(value) # pragma: no cover ParameterInfo() ParameterInfo(parser=custom_parser) ParameterInfo(click_type=CustomClickParser()) def test_install_invalid_shell(): app = typer.Typer() @app.command() def main(): print("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): print(f"info name is: {ctx.info_name}") print(f"value is: {value}") @app.command() def main(name: str = typer.Option(..., callback=name_callback)): print("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): print(f"info name is: {ctx.info_name}") print(f"param name is: {param.name}") print(f"value is: {value}") @app.command() def main(name: str = typer.Option(..., callback=name_callback)): print("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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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): print(f"arg1: {type(arg1)} {arg1}") print(f"arg2: {type(arg2)} {arg2}") print(f"arg3: {type(arg3)} {arg3}") print(f"arg4: {type(arg4)} {arg4}") print(f"arg5: {type(arg5)} {arg5}") result = runner.invoke(app, ["Hello", "2", "invalid"]) # TODO: when deprecating Click 7, remove second option assert ( "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.stdout or "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 ) def test_context_settings_inheritance_single_command(): app = typer.Typer(context_settings=dict(help_option_names=["-h", "--help"])) @app.command() def main(name: str): pass # pragma: nocover result = runner.invoke(app, ["main", "-h"]) assert "Show this message and exit." in result.stdout typer-0.9.0/tests/test_prog_name.py000066400000000000000000000006321442411605300174060ustar00rootroot00000000000000import subprocess import sys from pathlib import Path def test_custom_prog_name(): file_path = Path(__file__).parent / "assets/prog_name.py" result = subprocess.run( [sys.executable, "-m", "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.9.0/tests/test_rich_utils.py000066400000000000000000000015211442411605300176020ustar00rootroot00000000000000import typer import typer.completion from typer.testing import CliRunner runner = CliRunner() def test_rich_utils_click_rewrapp(): app = typer.Typer(rich_markup_mode="markdown") @app.command() def main(): """ \b Some text Some unwrapped text """ print("Hello World") @app.command() def secondary(): """ \b Secondary text Some unwrapped text """ print("Hello Secondary World") result = runner.invoke(app, ["--help"]) assert "Some text" in result.stdout assert "Secondary text" in result.stdout assert "\b" not in result.stdout result = runner.invoke(app, ["main"]) assert "Hello World" in result.stdout result = runner.invoke(app, ["secondary"]) assert "Hello Secondary World" in result.stdout typer-0.9.0/tests/test_tracebacks.py000066400000000000000000000047741442411605300175540ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path def test_traceback_no_rich(): file_path = Path(__file__).parent / "assets/type_error_no_rich.py" result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "return get_command(self)(*args, **kwargs)" not in result.stderr assert "typer.run(main)" in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) def test_traceback_no_rich_short_disable(): file_path = Path(__file__).parent / "assets/type_error_no_rich_short_disable.py" result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "return get_command(self)(*args, **kwargs)" not in result.stderr assert "app()" in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) def test_unmodified_traceback(): file_path = Path(__file__).parent / "assets/type_error_normal_traceback.py" result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "morty" in result.stdout, "the call to the first app should work normally" assert "return callback(**use_params)" in result.stderr, ( "calling outside of Typer should show the normal traceback, " "even after the hook is installed" ) assert "typer.main.get_command(broken_app)()" in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) typer-0.9.0/tests/test_tutorial/000077500000000000000000000000001442411605300167275ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/__init__.py000066400000000000000000000000001442411605300210260ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/000077500000000000000000000000001442411605300217735ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/__init__.py000066400000000000000000000000001442411605300240720ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_default/000077500000000000000000000000001442411605300244565ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_default/__init__.py000066400000000000000000000000001442411605300265550ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_default/test_tutorial001.py000066400000000000000000000017241442411605300301570ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_default/test_tutorial001_an.py000066400000000000000000000017461442411605300306410ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.default import tutorial001_an 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, result.output 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_default/test_tutorial002.py000066400000000000000000000021211442411605300301500ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_default/test_tutorial002_an.py000066400000000000000000000021241442411605300306310ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.default import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/000077500000000000000000000000001442411605300243335ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/__init__.py000066400000000000000000000000001442411605300264320ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py000066400000000000000000000031661442411605300300360ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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" in result.output assert "default: World" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None 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" in result.output assert "default: World" in result.output typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001_an.py000066400000000000000000000031711442411605300305100ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.envvar import tutorial001_an 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" in result.output assert "default: World" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None 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" in result.output assert "default: World" in result.output typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002.py000066400000000000000000000023421442411605300300320ustar00rootroot00000000000000import subprocess import sys 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" 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_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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002_an.py000066400000000000000000000023451442411605300305130ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.envvar import tutorial002_an 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" 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_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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003.py000066400000000000000000000023671442411605300300420ustar00rootroot00000000000000import subprocess import sys 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" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003_an.py000066400000000000000000000023721442411605300305140ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.envvar import tutorial003_an 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" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/000077500000000000000000000000001442411605300237625ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_help/__init__.py000066400000000000000000000000001442411605300260610ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial001.py000066400000000000000000000024771442411605300274710ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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_help_no_rich(): rich = typer.core.rich typer.core.rich = None 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 typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial001_an.py000066400000000000000000000025021442411605300301340ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.help import tutorial001_an 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_help_no_rich(): rich = typer.core.rich typer.core.rich = None 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 typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial002.py000066400000000000000000000017341442411605300274650ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial002_an.py000066400000000000000000000017371442411605300301460ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial003.py000066400000000000000000000017231442411605300274640ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial003_an.py000066400000000000000000000017261442411605300301450ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial003_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial004.py000066400000000000000000000017271442411605300274710ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial004_an.py000066400000000000000000000017321442411605300301430ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial004_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial005.py000066400000000000000000000016051442411605300274650ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial005_an.py000066400000000000000000000016101442411605300301370ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial005_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial006.py000066400000000000000000000015651442411605300274730ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial006_an.py000066400000000000000000000015701442411605300301450ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.help import tutorial006_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial007.py000066400000000000000000000015531442411605300274710ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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 "Say hi to NAME very gently, like Dirk." in result.output assert "Arguments" in result.output assert "Secondary Arguments" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial007_an.py000066400000000000000000000015561442411605300301520ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.help import tutorial007_an 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 very gently, like Dirk." in result.output assert "Arguments" in result.output assert "Secondary Arguments" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial008.py000066400000000000000000000024531442411605300274720ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.help import tutorial008 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_help_no_rich(): rich = typer.core.rich typer.core.rich = None 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 typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_help/test_tutorial008_an.py000066400000000000000000000024561442411605300301530ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.help import tutorial008_an 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_help_no_rich(): rich = typer.core.rich typer.core.rich = None 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 typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/000077500000000000000000000000001442411605300246575ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/__init__.py000066400000000000000000000000001442411605300267560ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py000066400000000000000000000022441442411605300303560ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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 "Missing argument 'NAME'." in result.output def test_call_no_arg_standalone(): # Mainly for coverage result = runner.invoke(app, standalone_mode=False) assert result.exit_code != 0 def test_call_no_arg_no_rich(): # Mainly for coverage rich = typer.core.rich typer.core.rich = None result = runner.invoke(app) assert result.exit_code != 0 assert "Error: Missing argument 'NAME'" in result.stdout typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial001_an.py000066400000000000000000000022471442411605300310370ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.optional import tutorial001_an 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 "Missing argument 'NAME'." in result.output def test_call_no_arg_standalone(): # Mainly for coverage result = runner.invoke(app, standalone_mode=False) assert result.exit_code != 0 def test_call_no_arg_no_rich(): # Mainly for coverage rich = typer.core.rich typer.core.rich = None result = runner.invoke(app) assert result.exit_code != 0 assert "Error: Missing argument 'NAME'" in result.stdout typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial002.py000066400000000000000000000015631442411605300303620ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial002_an.py000066400000000000000000000015661442411605300310430ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.arguments.optional import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_arguments/test_optional/test_tutorial003.py000066400000000000000000000022441442411605300303600ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.arguments.optional import tutorial003 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 "Missing argument 'NAME'." in result.output def test_call_no_arg_standalone(): # Mainly for coverage result = runner.invoke(app, standalone_mode=False) assert result.exit_code != 0 def test_call_no_arg_no_rich(): # Mainly for coverage rich = typer.core.rich typer.core.rich = None result = runner.invoke(app) assert result.exit_code != 0 assert "Error: Missing argument 'NAME'" in result.stdout typer.core.rich = rich 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/000077500000000000000000000000001442411605300215675ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/__init__.py000066400000000000000000000000001442411605300236660ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_arguments/000077500000000000000000000000001442411605300246335ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_arguments/__init__.py000066400000000000000000000000001442411605300267320ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_arguments/test_tutorial001.py000066400000000000000000000020661442411605300303340ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_callback/000077500000000000000000000000001442411605300243625ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_callback/__init__.py000066400000000000000000000000001442411605300264610ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py000066400000000000000000000044351442411605300300650ustar00rootroot00000000000000import subprocess import sys import typer.core 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" in result.output assert "--no-verbose" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Manage users in the awesome CLI app." in result.output assert "--verbose" in result.output assert "--no-verbose" in result.output typer.core.rich = rich 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 ( "No such option: --verbose" in result.output or "no such option: --verbose" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_callback/test_tutorial002.py000066400000000000000000000011601442411605300300560ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_callback/test_tutorial003.py000066400000000000000000000013451442411605300300640ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_callback/test_tutorial004.py000066400000000000000000000015401442411605300300620ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_context/000077500000000000000000000000001442411605300243125ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_context/__init__.py000066400000000000000000000000001442411605300264110ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_context/test_tutorial001.py000066400000000000000000000015401442411605300300070ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_context/test_tutorial002.py000066400000000000000000000017261442411605300300160ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_context/test_tutorial003.py000066400000000000000000000017361442411605300300200ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_context/test_tutorial004.py000066400000000000000000000013551442411605300300160ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/000077500000000000000000000000001442411605300235565ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_help/__init__.py000066400000000000000000000000001442411605300256550ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial001.py000066400000000000000000000101771442411605300272610ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "--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" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial001_an.py000066400000000000000000000102021442411605300277240ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial001_an 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" in result.output assert "--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" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial002.py000066400000000000000000000032171442411605300272570ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial003.py000066400000000000000000000021621442411605300272560ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial003 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 user." in result.output assert "delete" in result.output assert "(deprecated)" in result.output assert "Delete a user." in result.output def test_help_delete(): result = runner.invoke(app, ["delete", "--help"]) assert result.exit_code == 0 assert "(deprecated)" in result.output assert "Delete a user." in result.output def test_call(): # Mainly for coverage result = runner.invoke(app, ["create", "Camila"]) assert result.exit_code == 0 result = runner.invoke(app, ["delete", "Camila"]) assert result.exit_code == 0 def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial004.py000066400000000000000000000034571442411605300272670ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial004 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 shiny user. โœจ" 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 shiny user. โœจ" in result.output assert "The username to be created" 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 "The username to be deleted" in result.output assert "Force the deletion ๐Ÿ’ฅ" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial004_an.py000066400000000000000000000034641442411605300277430ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial004_an 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 shinny user. โœจ" 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 shinny user. โœจ" in result.output assert "The username to be created" 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 "The username to be deleted" in result.output assert "Force the deletion ๐Ÿ’ฅ" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial005.py000066400000000000000000000035621442411605300272650ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial005 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 shiny user. โœจ" 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 shiny user. โœจ" in result.output assert "The username to be created" in result.output assert "Learn more at the Typer docs website" 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 "The username to be deleted" in result.output assert "Force the deletion ๐Ÿ’ฅ" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial005_an.py000066400000000000000000000035671442411605300277500ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial005_an 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 shinny user. โœจ" 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 shinny user. โœจ" in result.output assert "The username to be created" in result.output assert "Learn more at the Typer docs website" 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 "The username to be deleted" in result.output assert "Force the deletion ๐Ÿ’ฅ" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial006.py000066400000000000000000000031121442411605300272550ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial006 as mod app = mod.app runner = CliRunner() def test_main_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "create" in result.output assert "Create a new user. โœจ" in result.output assert "delete" in result.output assert "Delete a user. ๐Ÿ”ฅ" in result.output assert "Utils and Configs" in result.output assert "config" in result.output assert "Configure the system. ๐Ÿ”ง" in result.output assert "Synchronize the system or something fancy like that. โ™ป" in result.output assert "Help and Others" in result.output assert "Get help with the system. โ“" in result.output assert "Report an issue. ๐Ÿ›" in result.output def test_call(): # Mainly for coverage result = runner.invoke(app, ["create", "Morty"]) assert result.exit_code == 0 result = runner.invoke(app, ["delete", "Morty"]) assert result.exit_code == 0 result = runner.invoke(app, ["config", "Morty"]) assert result.exit_code == 0 result = runner.invoke(app, ["sync"]) assert result.exit_code == 0 result = runner.invoke(app, ["help"]) assert result.exit_code == 0 result = runner.invoke(app, ["report"]) assert result.exit_code == 0 def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial007.py000066400000000000000000000032531442411605300272640ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial007 as mod app = mod.app runner = CliRunner() def test_main_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "create" in result.output assert "Create a new user. โœจ" in result.output assert "Utils and Configs" in result.output assert "config" in result.output assert "Configure the system. ๐Ÿ”ง" in result.output def test_create_help(): result = runner.invoke(app, ["create", "--help"]) assert result.exit_code == 0 assert "username" in result.output assert "The username to create" in result.output assert "Secondary Arguments" in result.output assert "lastname" in result.output assert "The last name of the new user" in result.output assert "--force" in result.output assert "--no-force" in result.output assert "Force the creation of the user" in result.output assert "Additional Data" in result.output assert "--age" in result.output assert "The age of the new user" in result.output assert "--favorite-color" in result.output assert "The favorite color of the new user" in result.output def test_call(): # Mainly for coverage result = runner.invoke(app, ["create", "Morty"]) assert result.exit_code == 0 result = runner.invoke(app, ["config", "Morty"]) assert result.exit_code == 0 def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial007_an.py000066400000000000000000000032561442411605300277450ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial007_an as mod app = mod.app runner = CliRunner() def test_main_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "create" in result.output assert "Create a new user. โœจ" in result.output assert "Utils and Configs" in result.output assert "config" in result.output assert "Configure the system. ๐Ÿ”ง" in result.output def test_create_help(): result = runner.invoke(app, ["create", "--help"]) assert result.exit_code == 0 assert "username" in result.output assert "The username to create" in result.output assert "Secondary Arguments" in result.output assert "lastname" in result.output assert "The last name of the new user" in result.output assert "--force" in result.output assert "--no-force" in result.output assert "Force the creation of the user" in result.output assert "Additional Data" in result.output assert "--age" in result.output assert "The age of the new user" in result.output assert "--favorite-color" in result.output assert "The favorite color of the new user" in result.output def test_call(): # Mainly for coverage result = runner.invoke(app, ["create", "Morty"]) assert result.exit_code == 0 result = runner.invoke(app, ["config", "Morty"]) assert result.exit_code == 0 def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_help/test_tutorial008.py000066400000000000000000000013471442411605300272670ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.help import tutorial008 as mod app = mod.app runner = CliRunner() def test_main_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "Create a new user. โœจ" in result.output assert "Made with โค in Venus" in result.output def test_call(): # Mainly for coverage result = runner.invoke(app, ["Morty"]) assert result.exit_code == 0 def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_index/000077500000000000000000000000001442411605300237355ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_index/__init__.py000066400000000000000000000000001442411605300260340ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_index/test_tutorial001.py000066400000000000000000000012671442411605300274400ustar00rootroot00000000000000import subprocess import sys 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 "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_index/test_tutorial002.py000066400000000000000000000017411442411605300274360ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_name/000077500000000000000000000000001442411605300235465ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_name/__init__.py000066400000000000000000000000001442411605300256450ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_name/test_tutorial001.py000066400000000000000000000016601442411605300272460ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_one_or_multiple/000077500000000000000000000000001442411605300260225ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_one_or_multiple/__init__.py000066400000000000000000000000001442411605300301210ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial001.py000066400000000000000000000013621442411605300315210ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial002.py000066400000000000000000000016021442411605300315170ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_options/000077500000000000000000000000001442411605300243215ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_options/__init__.py000066400000000000000000000000001442411605300264200ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_commands/test_options/test_tutorial001.py000066400000000000000000000057541442411605300300310ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_commands/test_options/test_tutorial001_an.py000066400000000000000000000057571442411605300305120ustar00rootroot00000000000000import subprocess import sys from typer.testing import CliRunner from docs_src.commands.options import tutorial001_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_exceptions/000077500000000000000000000000001442411605300221475ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_exceptions/__init__.py000066400000000000000000000000001442411605300242460ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_exceptions/test_tutorial001.py000066400000000000000000000036531442411605300256530ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from typer.testing import CliRunner from docs_src.exceptions import tutorial001 as mod runner = CliRunner() def test_traceback_rich(): file_path = Path(mod.__file__) result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "return get_command(self)(*args, **kwargs)" not in result.stderr assert "typer.run(main)" not in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) assert "name = 'morty'" in result.stderr def test_standard_traceback_env_var(): file_path = Path(mod.__file__) result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": "1"}, ) assert "return get_command(self)(*args, **kwargs)" in result.stderr assert "typer.run(main)" in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) assert "name = 'morty'" not in result.stderr def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_exceptions/test_tutorial002.py000066400000000000000000000036671442411605300256610ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from typer.testing import CliRunner from docs_src.exceptions import tutorial002 as mod runner = CliRunner() def test_traceback_rich(): file_path = Path(mod.__file__) result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path), "secret"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "return get_command(self)(*args, **kwargs)" not in result.stderr assert "app()" not in result.stderr assert "print(password + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) assert "name = 'morty'" not in result.stderr def test_standard_traceback_env_var(): file_path = Path(mod.__file__) result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path), "secret"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": "1"}, ) assert "return get_command(self)(*args, **kwargs)" in result.stderr assert "app()" in result.stderr assert "print(password + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) assert "name = 'morty'" not in result.stderr def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_exceptions/test_tutorial003.py000066400000000000000000000022601442411605300256460ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from typer.testing import CliRunner from docs_src.exceptions import tutorial003 as mod runner = CliRunner() def test_traceback_rich_pretty_short_disable(): file_path = Path(mod.__file__) result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "return get_command(self)(*args, **kwargs)" not in result.stderr assert "app()" in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) assert "name = 'morty'" in result.stderr def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_exceptions/test_tutorial004.py000066400000000000000000000021711442411605300256500ustar00rootroot00000000000000import os import subprocess import sys from pathlib import Path from typer.testing import CliRunner from docs_src.exceptions import tutorial004 as mod runner = CliRunner() def test_rich_pretty_exceptions_disable(): file_path = Path(mod.__file__) result = subprocess.run( [sys.executable, "-m", "coverage", "run", str(file_path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""}, ) assert "return get_command(self)(*args, **kwargs)" in result.stderr assert "app()" in result.stderr assert "print(name + 3)" in result.stderr # TODO: when deprecating Python 3.6, remove second option assert ( 'TypeError: can only concatenate str (not "int") to str' in result.stderr or "TypeError: must be str, not int" in result.stderr ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_first_steps/000077500000000000000000000000001442411605300223335ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_first_steps/__init__.py000066400000000000000000000000001442411605300244320ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_first_steps/test_tutorial001.py000066400000000000000000000010611442411605300260260ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_first_steps/test_tutorial002.py000066400000000000000000000013331442411605300260310ustar00rootroot00000000000000import subprocess import sys 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 "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_first_steps/test_tutorial003.py000066400000000000000000000014001442411605300260250ustar00rootroot00000000000000import subprocess import sys 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 "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_first_steps/test_tutorial004.py000066400000000000000000000030221442411605300260300ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "[required]" in result.output assert "LASTNAME" in result.output assert "[required]" in result.output assert "--formal" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_first_steps/test_tutorial005.py000066400000000000000000000030071442411605300260340ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "[required]" in result.output assert "--lastname" in result.output assert "TEXT" in result.output assert "--formal" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_first_steps/test_tutorial006.py000066400000000000000000000026131442411605300260370ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_multiple_values/000077500000000000000000000000001442411605300232005ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/__init__.py000066400000000000000000000000001442411605300252770ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/000077500000000000000000000000001442411605300325715ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/__init__.py000066400000000000000000000000001442411605300346700ustar00rootroot00000000000000test_tutorial001.py000066400000000000000000000013651442411605300362140ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_valuesimport subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout test_tutorial002.py000066400000000000000000000027501442411605300362140ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_valuesimport subprocess import sys 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 ( "Argument 'names' takes 3 values" in result.stdout or "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout test_tutorial002_an.py000066400000000000000000000027641442411605300366770ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_valuesimport subprocess import sys import typer from typer.testing import CliRunner from docs_src.multiple_values.arguments_with_multiple_values import ( tutorial002_an 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 ( "Argument 'names' takes 3 values" in result.stdout or "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_multiple_values/test_multiple_options/000077500000000000000000000000001442411605300276455ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_multiple_options/__init__.py000066400000000000000000000000001442411605300317440ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001.py000066400000000000000000000021461442411605300333450ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001_an.py000066400000000000000000000021511442411605300340170ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.multiple_values.multiple_options import tutorial001_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002.py000066400000000000000000000016571442411605300333540ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002_an.py000066400000000000000000000016621442411605300340260ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.multiple_values.multiple_options import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/000077500000000000000000000000001442411605300322575ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/__init__.py000066400000000000000000000000001442411605300343560ustar00rootroot00000000000000test_tutorial001.py000066400000000000000000000026721442411605300357040ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_valuesimport subprocess import sys 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 ( "Option '--user' requires 3 arguments" in result.output or "--user option requires 3 arguments" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout test_tutorial001_an.py000066400000000000000000000026751442411605300363650ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_multiple_values/test_options_with_multiple_valuesimport subprocess import sys import typer from typer.testing import CliRunner from docs_src.multiple_values.options_with_multiple_values import tutorial001_an 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 ( "Option '--user' requires 3 arguments" in result.output or "--user option requires 3 arguments" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/000077500000000000000000000000001442411605300214615ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/__init__.py000066400000000000000000000000001442411605300235600ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_callback/000077500000000000000000000000001442411605300242545ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_callback/__init__.py000066400000000000000000000000001442411605300263530ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_callback/test_tutorial001.py000066400000000000000000000014251442411605300277530ustar00rootroot00000000000000import subprocess import sys 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 "Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_callback/test_tutorial001_an.py000066400000000000000000000014301442411605300304250ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.callback import tutorial001_an 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 "Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_callback/test_tutorial003.py000066400000000000000000000024671442411605300277640ustar00rootroot00000000000000import os import subprocess import sys 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 "Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "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( [sys.executable, "-m", "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.9.0/tests/test_tutorial/test_options/test_callback/test_tutorial003_an.py000066400000000000000000000025001442411605300304260ustar00rootroot00000000000000import os import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.callback import tutorial003_an 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 "Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "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( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL003_AN.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial003_an.py --", "COMP_CWORD": "1", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "--name" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_callback/test_tutorial004.py000066400000000000000000000024761442411605300277650ustar00rootroot00000000000000import os import subprocess import sys 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 "Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "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( [sys.executable, "-m", "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.9.0/tests/test_tutorial/test_options/test_callback/test_tutorial004_an.py000066400000000000000000000025071442411605300304360ustar00rootroot00000000000000import os import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.callback import tutorial004_an 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 "Invalid value for '--name': Only Camila is allowed" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "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( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL004_AN.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial004_an.py --", "COMP_CWORD": "1", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "--name" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_help/000077500000000000000000000000001442411605300234505ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_help/__init__.py000066400000000000000000000000001442411605300255470ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_help/test_tutorial001.py000066400000000000000000000024661442411605300271550ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_help/test_tutorial001_an.py000066400000000000000000000024711442411605300276270ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.help import tutorial001_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_help/test_tutorial002.py000066400000000000000000000021171442411605300271470ustar00rootroot00000000000000import subprocess import sys 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, ["World"]) assert result.exit_code == 0 assert "Hello World" in result.output def test_formal(): result = runner.invoke(app, ["World", "--formal"]) assert result.exit_code == 0 assert "Good day Ms. World" in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname" in result.output assert "Customization and Utils" in result.output assert "--formal" in result.output assert "--no-formal" in result.output assert "--debug" in result.output assert "--no-debug" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_help/test_tutorial002_an.py000066400000000000000000000021221442411605300276210ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.help import tutorial002_an as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_call(): result = runner.invoke(app, ["World"]) assert result.exit_code == 0 assert "Hello World" in result.output def test_formal(): result = runner.invoke(app, ["World", "--formal"]) assert result.exit_code == 0 assert "Good day Ms. World" in result.output def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname" in result.output assert "Customization and Utils" in result.output assert "--formal" in result.output assert "--no-formal" in result.output assert "--debug" in result.output assert "--no-debug" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_help/test_tutorial003.py000066400000000000000000000014621442411605300271520ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.help import tutorial003 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" in result.output assert "TEXT" in result.output assert "[default: Wade Wilson]" not in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_help/test_tutorial003_an.py000066400000000000000000000014651442411605300276330ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.help import tutorial003_an 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" in result.output assert "TEXT" in result.output assert "[default: Wade Wilson]" not in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/000077500000000000000000000000001442411605300234405ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_name/__init__.py000066400000000000000000000000001442411605300255370ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial001.py000066400000000000000000000021621442411605300271360ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "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_call_no_args(): result = runner.invoke(app, ["--name"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Option '--name' requires an argument" in result.output or "--name option requires an argument" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial001_an.py000066400000000000000000000021651442411605300276170ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.name import tutorial001_an 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" in result.output assert "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_call_no_args(): result = runner.invoke(app, ["--name"]) assert result.exit_code != 0 # TODO: when deprecating Click 7, remove second option assert ( "Option '--name' requires an argument" in result.output or "--name option requires an argument" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial002.py000066400000000000000000000017621442411605300271440ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "--name" in result.output assert "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial002_an.py000066400000000000000000000017651442411605300276250ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.name import tutorial002_an 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" in result.output assert "--name" in result.output assert "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial003.py000066400000000000000000000015341442411605300271420ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial003_an.py000066400000000000000000000015371442411605300276230ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.name import tutorial003_an 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" in result.output assert "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial004.py000066400000000000000000000017671442411605300271530ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "--user-name" in result.output assert "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial004_an.py000066400000000000000000000017721442411605300276250ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.name import tutorial004_an 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" in result.output assert "--user-name" in result.output assert "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial005.py000066400000000000000000000025001442411605300271360ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "--name" in result.output assert "TEXT" in result.output assert "-f" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_name/test_tutorial005_an.py000066400000000000000000000025031442411605300276170ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.name import tutorial005_an 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" in result.output assert "--name" in result.output assert "TEXT" in result.output assert "-f" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_prompt/000077500000000000000000000000001442411605300240415ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_prompt/__init__.py000066400000000000000000000000001442411605300261400ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_prompt/test_tutorial001.py000066400000000000000000000021021442411605300275310ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_prompt/test_tutorial001_an.py000066400000000000000000000021051442411605300302120ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.prompt import tutorial001_an 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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_prompt/test_tutorial002.py000066400000000000000000000021271442411605300275410ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_prompt/test_tutorial002_an.py000066400000000000000000000021321442411605300302130ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.prompt import tutorial002_an 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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py000066400000000000000000000030011442411605300275320ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_prompt/test_tutorial003_an.py000066400000000000000000000030041442411605300302130ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.prompt import tutorial003_an 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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_required/000077500000000000000000000000001442411605300243405ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_required/__init__.py000066400000000000000000000000001442411605300264370ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_required/test_tutorial001.py000066400000000000000000000024671442411605300300460ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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 "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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname" in result.output assert "TEXT" in result.output assert "[required]" in result.output typer.core.rich = rich def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_required/test_tutorial001_an.py000066400000000000000000000024721442411605300305200ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.options.required import tutorial001_an 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 "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" in result.output assert "TEXT" in result.output assert "[required]" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--lastname" in result.output assert "TEXT" in result.output assert "[required]" in result.output typer.core.rich = rich def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options/test_version/000077500000000000000000000000001442411605300242055ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_version/__init__.py000066400000000000000000000000001442411605300263040ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options/test_version/test_tutorial003.py000066400000000000000000000027031442411605300277060ustar00rootroot00000000000000import os import subprocess import sys 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 "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( [sys.executable, "-m", "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( [sys.executable, "-m", "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.9.0/tests/test_tutorial/test_options/test_version/test_tutorial003_an.py000066400000000000000000000027141442411605300303660ustar00rootroot00000000000000import os import subprocess import sys import typer from typer.testing import CliRunner from docs_src.options.version import tutorial003_an 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 "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( [sys.executable, "-m", "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( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL003_AN.PY_COMPLETE": "complete_bash", "COMP_WORDS": "tutorial003_an.py --name Rick --v", "COMP_CWORD": "3", "_TYPER_COMPLETE_TESTING": "True", }, ) assert "--version" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/000077500000000000000000000000001442411605300246035ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options_autocompletion/__init__.py000066400000000000000000000000001442411605300267020ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py000066400000000000000000000021371442411605300303040ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial002 as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "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(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial002_an.py000066400000000000000000000021501442411605300307550ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial002_an as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL002_AN.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial002_an.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(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py000066400000000000000000000021521442411605300303020ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial003 as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "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(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py000066400000000000000000000021631442411605300307620ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial003_an as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL003_AN.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial003_an.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(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py000066400000000000000000000022571442411605300303110ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial004 as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "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_aux.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(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial004_an.py000066400000000000000000000022701442411605300307620ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial004_an as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL004_AN.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial004_an_aux.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(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 assert "Hello Camila" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py000066400000000000000000000024051442411605300303070ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial007 as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "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(mod.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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial007_an.py000066400000000000000000000024161442411605300307670ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial007_an as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL007_AN.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial007_an.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(mod.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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py000066400000000000000000000025551442411605300303160ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial008 as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "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(mod.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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial008_an.py000066400000000000000000000025661442411605300307760ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial008_an as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL008_AN.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial008_an.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(mod.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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py000066400000000000000000000026311442411605300303120ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial009 as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "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(mod.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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_options_autocompletion/test_tutorial009_an.py000066400000000000000000000026421442411605300307720ustar00rootroot00000000000000import os import subprocess import sys from typer.testing import CliRunner from docs_src.options_autocompletion import tutorial009_an as mod runner = CliRunner() def test_completion(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env={ **os.environ, "_TUTORIAL009_AN.PY_COMPLETE": "complete_zsh", "_TYPER_COMPLETE_ARGS": "tutorial009_an.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(mod.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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/000077500000000000000000000000001442411605300231725ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/__init__.py000066400000000000000000000000001442411605300252710ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/000077500000000000000000000000001442411605300251645ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/__init__.py000066400000000000000000000000001442411605300272630ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py000066400000000000000000000023061442411605300306620ustar00rootroot00000000000000import subprocess import sys 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 ( "No such option: --no-force" in result.output or "no such option: --no-force" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001_an.py000066400000000000000000000023111442411605300313340ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial001_an 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 ( "No such option: --no-force" in result.output or "no such option: --no-force" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py000066400000000000000000000033211442411605300306610ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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" in result.output assert "--reject" in result.output assert "--no-accept" not in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--accept" in result.output assert "--reject" in result.output assert "--no-accept" not in result.output typer.core.rich = rich 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 ( "No such option: --no-accept" in result.output or "no such option: --no-accept" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002_an.py000066400000000000000000000033241442411605300313420ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial002_an 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" in result.output assert "--reject" in result.output assert "--no-accept" not in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--accept" in result.output assert "--reject" in result.output assert "--no-accept" not in result.output typer.core.rich = rich 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 ( "No such option: --no-accept" in result.output or "no such option: --no-accept" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003.py000066400000000000000000000017311442411605300306650ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "--force" in result.output assert "-F" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003_an.py000066400000000000000000000017341442411605300313460ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial003_an 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" in result.output assert "--force" in result.output assert "-F" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004.py000066400000000000000000000020271442411605300306650ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004_an.py000066400000000000000000000020321442411605300313370ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.bool import tutorial004_an 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" in result.output assert "--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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_custom_types/000077500000000000000000000000001442411605300267675ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_custom_types/__init__.py000066400000000000000000000000001442411605300310660ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial001.py000066400000000000000000000017711442411605300324720ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.custom_types 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 def test_parse_custom_type(): result = runner.invoke(app, ["0", "--custom-opt", "1"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_parse_custom_type_with_default(): result = runner.invoke(app, ["0"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial001_an.py000066400000000000000000000020001442411605300331320ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.custom_types import tutorial001_an as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 def test_parse_custom_type(): result = runner.invoke(app, ["0", "--custom-opt", "1"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_parse_custom_type_with_default(): result = runner.invoke(app, ["0"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002.py000066400000000000000000000020031442411605300324600ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.custom_types 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 def test_parse_custom_type(): result = runner.invoke(app, ["0", "--custom-opt", "1"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_parse_custom_type_with_default(): result = runner.invoke(app, ["0"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002_an.py000066400000000000000000000020061442411605300331410ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.custom_types import tutorial002_an as mod runner = CliRunner() app = typer.Typer() app.command()(mod.main) def test_help(): result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 def test_parse_custom_type(): result = runner.invoke(app, ["0", "--custom-opt", "1"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_parse_custom_type_with_default(): result = runner.invoke(app, ["0"]) assert "custom_arg is " in result.output assert "custom-opt is " in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_datetime/000077500000000000000000000000001442411605300260255ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_datetime/__init__.py000066400000000000000000000000001442411605300301240ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py000066400000000000000000000026771442411605300315360ustar00rootroot00000000000000import subprocess import sys 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 assert ( "Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]':" in result.stdout ) # TODO: when deprecating Click 7, remove second option assert ( "'july-19-1989' does not match the formats" in result.output or "invalid datetime format: july-19-1989. (choose from" in result.output ) assert "%Y-%m-%d" in result.output assert "%Y-%m-%dT%H:%M:%S" in result.output assert "%Y-%m-%d %H:%M:%S" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002.py000066400000000000000000000014701442411605300315250ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002_an.py000066400000000000000000000014731442411605300322060ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.datetime import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_enum/000077500000000000000000000000001442411605300251755ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_enum/__init__.py000066400000000000000000000000001442411605300272740ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py000066400000000000000000000024411442411605300306730ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "[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 ( "Invalid value for '--network': 'capsule' is not one of" in result.output or "Invalid value for '--network': invalid choice: capsule. (choose from" in result.output ) assert "simple" in result.output assert "conv" in result.output assert "lstm" in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002.py000066400000000000000000000014571442411605300307020ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002_an.py000066400000000000000000000014621442411605300313540ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.enum import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/000077500000000000000000000000001442411605300251505ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/__init__.py000066400000000000000000000000001442411605300272470ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001.py000066400000000000000000000015421442411605300306470ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001_an.py000066400000000000000000000015451442411605300313300ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial001_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002.py000066400000000000000000000015741442411605300306550ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002_an.py000066400000000000000000000015771442411605300313360ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial002_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003.py000066400000000000000000000014531442411605300306520ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003_an.py000066400000000000000000000014561442411605300313330ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial003_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py000066400000000000000000000016361442411605300306560ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004_an.py000066400000000000000000000016411442411605300313300ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial004_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005.py000066400000000000000000000020701442411605300306500ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005_an.py000066400000000000000000000020731442411605300313310ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.file import tutorial005_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_index/000077500000000000000000000000001442411605300253405ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_index/__init__.py000066400000000000000000000000001442411605300274370ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py000066400000000000000000000027761442411605300310510ustar00rootroot00000000000000import subprocess import sys 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" in result.output assert "INTEGER" in result.output assert "--height-meters" in result.output assert "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 ( "Invalid value for '--age': '15.3' is not a valid integer" in result.output or "Invalid value for '--age': 15.3 is not a valid integer" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/000077500000000000000000000000001442411605300255215ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/__init__.py000066400000000000000000000000001442411605300276200ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py000066400000000000000000000054001442411605300312150ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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" in result.output assert "INTEGER RANGE" in result.output assert "--score" in result.output assert "FLOAT RANGE" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--age" in result.output assert "INTEGER RANGE" in result.output assert "--score" in result.output assert "FLOAT RANGE" in result.output typer.core.rich = rich 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 ( ( "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." in result.output ) or "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 ( "Invalid value for '--age': 15 is not in the range x>=18" in result.output or "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 ( "Invalid value for '--score': 100.5 is not in the range x<=100." in result.output or "Invalid value for '--score': 100.5 is bigger than the maximum valid value" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001_an.py000066400000000000000000000054031442411605300316760ustar00rootroot00000000000000import subprocess import sys import typer import typer.core from typer.testing import CliRunner from docs_src.parameter_types.number import tutorial001_an 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" in result.output assert "INTEGER RANGE" in result.output assert "--score" in result.output assert "FLOAT RANGE" in result.output def test_help_no_rich(): rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "--age" in result.output assert "INTEGER RANGE" in result.output assert "--score" in result.output assert "FLOAT RANGE" in result.output typer.core.rich = rich 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 ( ( "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000." in result.output ) or "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 ( "Invalid value for '--age': 15 is not in the range x>=18" in result.output or "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 ( "Invalid value for '--score': 100.5 is not in the range x<=100." in result.output or "Invalid value for '--score': 100.5 is bigger than the maximum valid value" 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py000066400000000000000000000021031442411605300312130ustar00rootroot00000000000000import subprocess import sys 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 ( "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000" in result.output or "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002_an.py000066400000000000000000000021061442411605300316740ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.number import tutorial002_an 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 ( "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000" in result.output or "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003.py000066400000000000000000000026041442411605300312220ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003_an.py000066400000000000000000000026071442411605300317030ustar00rootroot00000000000000import subprocess import sys import typer from typer.testing import CliRunner from docs_src.parameter_types.number import tutorial003_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_path/000077500000000000000000000000001442411605300251655ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_path/__init__.py000066400000000000000000000000001442411605300272640ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001.py000066400000000000000000000027271442411605300306720ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001_an.py000066400000000000000000000027321442411605300313440ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.path import tutorial001_an 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002.py000066400000000000000000000025011442411605300306610ustar00rootroot00000000000000import subprocess import sys 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 "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 "Invalid value for '--config': File './' is a directory." in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002_an.py000066400000000000000000000025041442411605300313420ustar00rootroot00000000000000import subprocess import sys from pathlib import Path import typer from typer.testing import CliRunner from docs_src.parameter_types.path import tutorial002_an 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 "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 "Invalid value for '--config': File './' is a directory." in result.output def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_parameter_types/test_uuid/000077500000000000000000000000001442411605300251775ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_uuid/__init__.py000066400000000000000000000000001442411605300272760ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py000066400000000000000000000021631442411605300306760ustar00rootroot00000000000000import subprocess import sys 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 ( "Invalid value for 'USER_ID': '7479706572-72756c6573' is not a valid UUID" in result.output or "Invalid value for 'USER_ID': 7479706572-72756c6573 is not a valid UUID value" in result.output ) def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_prompt/000077500000000000000000000000001442411605300213075ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_prompt/__init__.py000066400000000000000000000000001442411605300234060ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_prompt/test_tutorial001.py000066400000000000000000000012041442411605300250010ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_prompt/test_tutorial002.py000066400000000000000000000016301442411605300250050ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_prompt/test_tutorial003.py000066400000000000000000000015551442411605300250140ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/000077500000000000000000000000001442411605300223015ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_subcommands/__init__.py000066400000000000000000000000001442411605300244000ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_subcommands/test_callback_override/000077500000000000000000000000001442411605300267735ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_subcommands/test_callback_override/__init__.py000066400000000000000000000000001442411605300310720ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial001.py000066400000000000000000000012131442411605300324650ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial002.py000066400000000000000000000012131442411605300324660ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial003.py000066400000000000000000000014141442411605300324720ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial004.py000066400000000000000000000015621442411605300324770ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/000077500000000000000000000000001442411605300252705ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/__init__.py000066400000000000000000000000001442411605300273670ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial001.py000066400000000000000000000017331442411605300307710ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial002.py000066400000000000000000000017331442411605300307720ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial003.py000066400000000000000000000017331442411605300307730ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial004.py000066400000000000000000000017331442411605300307740ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial005.py000066400000000000000000000020051442411605300307660ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial006.py000066400000000000000000000017231442411605300307750ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial007.py000066400000000000000000000017641442411605300310030ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial008.py000066400000000000000000000020011442411605300307650ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_tutorial001.py000066400000000000000000000052461442411605300260050ustar00rootroot00000000000000import os import subprocess import sys 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 env = os.environ.copy() env["PYTHONPATH"] = ":".join(list(tutorial001.__path__)) for module in [mod, items, users]: result = subprocess.run( [sys.executable, "-m", "coverage", "run", module.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env=env, ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_tutorial002.py000066400000000000000000000042021442411605300257750ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_subcommands/test_tutorial003.py000066400000000000000000000130441442411605300260020ustar00rootroot00000000000000import os import subprocess import sys import pytest from typer.testing import CliRunner from docs_src.subcommands import tutorial003 from docs_src.subcommands.tutorial003 import items, users 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 # For coverage, becauses the monkeypatch above sometimes confuses coverage result = runner.invoke(items.app, ["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 # For coverage, becauses the monkeypatch above sometimes confuses coverage result = runner.invoke(items.app, ["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 # For coverage, becauses the monkeypatch above sometimes confuses coverage result = runner.invoke(items.app, ["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 # For coverage, becauses the monkeypatch above sometimes confuses coverage result = runner.invoke(users.app, ["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 # For coverage, becauses the monkeypatch above sometimes confuses coverage result = runner.invoke(users.app, ["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 env = os.environ.copy() env["PYTHONPATH"] = ":".join(list(tutorial003.__path__)) for module in [mod, items, lands, reigns, towns, users]: result = subprocess.run( [sys.executable, "-m", "coverage", "run", module.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", env=env, ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_terminating/000077500000000000000000000000001442411605300223075ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_terminating/__init__.py000066400000000000000000000000001442411605300244060ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_terminating/test_tutorial001.py000066400000000000000000000022201442411605300260000ustar00rootroot00000000000000import subprocess import sys 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_existing_no_standalone(): # Mainly for coverage result = runner.invoke(app, ["rick"], standalone_mode=False) 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_terminating/test_tutorial002.py000066400000000000000000000013641442411605300260110ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_terminating/test_tutorial003.py000066400000000000000000000023711442411605300260110ustar00rootroot00000000000000import subprocess import sys import typer import typer.core 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_root_no_standalone(): # Mainly for coverage result = runner.invoke(app, ["root"], standalone_mode=False) assert result.exit_code == 1 def test_root_no_rich(): # Mainly for coverage rich = typer.core.rich typer.core.rich = None result = runner.invoke(app, ["root"]) assert result.exit_code == 1 assert "The root user is reserved" in result.stdout assert "Aborted!" in result.stdout typer.core.rich = rich def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_testing/000077500000000000000000000000001442411605300214435ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_testing/__init__.py000066400000000000000000000000001442411605300235420ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_testing/test_app01.py000066400000000000000000000006471442411605300240040ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_testing/test_app02.py000066400000000000000000000006471442411605300240050ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_testing/test_app02_an.py000066400000000000000000000006601442411605300244560ustar00rootroot00000000000000import subprocess import sys from docs_src.testing.app02_an import main as mod from docs_src.testing.app02_an.test_main import test_app def test_app02_an(): test_app() def test_script(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_testing/test_app03.py000066400000000000000000000006471442411605300240060ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_using_click/000077500000000000000000000000001442411605300222605ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_using_click/__init__.py000066400000000000000000000000001442411605300243570ustar00rootroot00000000000000typer-0.9.0/tests/test_tutorial/test_using_click/test_tutorial003.py000066400000000000000000000015761442411605300257700ustar00rootroot00000000000000import subprocess import sys 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 "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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_tutorial/test_using_click/test_tutorial004.py000066400000000000000000000017131442411605300257620ustar00rootroot00000000000000import subprocess import sys 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( [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) assert "Usage" in result.stdout typer-0.9.0/tests/test_type_conversion.py000066400000000000000000000063641442411605300206750ustar00rootroot00000000000000from enum import Enum from pathlib import Path from typing import Any, List, Optional, Tuple import click import pytest import typer from typer.testing import CliRunner runner = CliRunner() def test_optional(): app = typer.Typer() @app.command() def opt(user: Optional[str] = None): if user: print(f"User: {user}") else: print("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): print(f"User: {user}") result = runner.invoke(app, ["Camila"]) assert result.exit_code == 0 assert "User: Camila" in result.output class SomeEnum(Enum): ONE = "one" TWO = "two" THREE = "three" @pytest.mark.parametrize( "type_annotation", [List[Path], List[SomeEnum], List[str]], ) def test_list_parameters_convert_to_lists(type_annotation): # Lists containing objects that are converted by Click (i.e. not Path or Enum) # should not be inadvertently converted to tuples expected_element_type = type_annotation.__args__[0] app = typer.Typer() @app.command() def list_conversion(container: type_annotation): assert isinstance(container, list) for element in container: assert isinstance(element, expected_element_type) result = runner.invoke(app, ["one", "two", "three"]) assert result.exit_code == 0 @pytest.mark.parametrize( "type_annotation", [ Tuple[str, str], Tuple[str, Path], Tuple[Path, Path], Tuple[str, SomeEnum], Tuple[SomeEnum, SomeEnum], ], ) def test_tuple_parameter_elements_are_converted_recursively(type_annotation): # Tuple elements that aren't converted by Click (i.e. Path or Enum) # should be recursively converted by Typer expected_element_types = type_annotation.__args__ app = typer.Typer() @app.command() def tuple_recursive_conversion(container: type_annotation): assert isinstance(container, tuple) for element, expected_type in zip(container, expected_element_types): assert isinstance(element, expected_type) result = runner.invoke(app, ["one", "two"]) assert result.exit_code == 0 def test_custom_parse(): app = typer.Typer() @app.command() def custom_parser( hex_value: int = typer.Argument(None, parser=lambda x: int(x, 0)) ): assert hex_value == 0x56 result = runner.invoke(app, ["0x56"]) assert result.exit_code == 0 def test_custom_click_type(): class BaseNumberParamType(click.ParamType): name = "base_integer" def convert( self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context], ) -> Any: return int(value, 0) app = typer.Typer() @app.command() def custom_click_type( hex_value: int = typer.Argument(None, click_type=BaseNumberParamType()) ): assert hex_value == 0x56 result = runner.invoke(app, ["0x56"]) assert result.exit_code == 0 typer-0.9.0/typer/000077500000000000000000000000001442411605300140265ustar00rootroot00000000000000typer-0.9.0/typer/__init__.py000066400000000000000000000031021442411605300161330ustar00rootroot00000000000000"""Typer, build great CLIs. Easy to code. Based on Python type hints.""" __version__ = "0.9.0" from shutil import get_terminal_size as get_terminal_size 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 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.9.0/typer/_compat_utils.py000066400000000000000000000001361442411605300172420ustar00rootroot00000000000000import click def _get_click_major() -> int: return int(click.__version__.split(".")[0]) typer-0.9.0/typer/_completion_click7.py000066400000000000000000000115351442411605300201510ustar00rootroot00000000000000import 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.9.0/typer/_completion_click8.py000066400000000000000000000150401442411605300201450ustar00rootroot00000000000000import 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.9.0/typer/_completion_shared.py000066400000000000000000000205351442411605300202430ustar00rootroot00000000000000import 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.9.0/typer/_typing.py000066400000000000000000000464371442411605300160670ustar00rootroot00000000000000# Copied from pydantic 1.9.2 (the latest version to support python 3.6.) # https://github.com/pydantic/pydantic/blob/v1.9.2/pydantic/typing.py # mypy: ignore-errors import sys from os import PathLike from typing import ( # type: ignore TYPE_CHECKING, AbstractSet, Any, ClassVar, Dict, Generator, Iterable, List, Mapping, NewType, Optional, Sequence, Set, Tuple, Type, Union, _eval_type, cast, get_type_hints, ) from typing_extensions import Annotated, Literal try: from typing import _TypingBase as typing_base # type: ignore except ImportError: from typing import _Final as typing_base # type: ignore try: from typing import GenericAlias as TypingGenericAlias # type: ignore except ImportError: # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on) TypingGenericAlias = () try: from types import UnionType as TypesUnionType # type: ignore except ImportError: # python < 3.10 does not have UnionType (str | int, byte | bool and so on) TypesUnionType = () if sys.version_info < (3, 7): if TYPE_CHECKING: class ForwardRef: def __init__(self, arg: Any): pass def _eval_type(self, globalns: Any, localns: Any) -> Any: pass else: from typing import _ForwardRef as ForwardRef else: from typing import ForwardRef if sys.version_info < (3, 7): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._eval_type(globalns, localns) elif sys.version_info < (3, 9): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._evaluate(globalns, localns) else: def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: # Even though it is the right signature for python 3.9, mypy complains with # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast... return cast(Any, type_)._evaluate(globalns, localns, set()) if sys.version_info < (3, 9): # Ensure we always get all the whole `Annotated` hint, not just the annotated type. # For 3.6 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`, # so it already returns the full annotation get_all_type_hints = get_type_hints else: def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> Any: return get_type_hints(obj, globalns, localns, include_extras=True) if sys.version_info < (3, 7): from typing import Callable as Callable AnyCallable = Callable[..., Any] NoArgAnyCallable = Callable[[], Any] else: from collections.abc import Callable as Callable from typing import Callable as TypingCallable AnyCallable = TypingCallable[..., Any] NoArgAnyCallable = TypingCallable[[], Any] # Annotated[...] is implemented by returning an instance of one of these classes, depending on # python/typing_extensions version. AnnotatedTypeNames = {"AnnotatedMeta", "_AnnotatedAlias"} if sys.version_info < (3, 8): def get_origin(t: Type[Any]) -> Optional[Type[Any]]: if type(t).__name__ in AnnotatedTypeNames: return cast( Type[Any], Annotated ) # mypy complains about _SpecialForm in py3.6 return getattr(t, "__origin__", None) else: from typing import get_origin as _typing_get_origin def get_origin(tp: Type[Any]) -> Optional[Type[Any]]: """ We can't directly use `typing.get_origin` since we need a fallback to support custom generic classes like `ConstrainedList` It should be useless once https://github.com/cython/cython/issues/3537 is solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged. """ if type(tp).__name__ in AnnotatedTypeNames: return cast(Type[Any], Annotated) # mypy complains about _SpecialForm return _typing_get_origin(tp) or getattr(tp, "__origin__", None) if sys.version_info < (3, 7): # noqa: C901 (ignore complexity) def get_args(t: Type[Any]) -> Tuple[Any, ...]: """Simplest get_args compatibility layer possible. The Python 3.6 typing module does not have `_GenericAlias` so this won't work for everything. In particular this will not support the `generics` module (we don't support generic models in python 3.6). """ if type(t).__name__ in AnnotatedTypeNames: return t.__args__ + t.__metadata__ return getattr(t, "__args__", ()) elif sys.version_info < (3, 8): # noqa: C901 from typing import _GenericAlias def get_args(t: Type[Any]) -> Tuple[Any, ...]: """Compatibility version of get_args for python 3.7. Mostly compatible with the python 3.8 `typing` module version and able to handle almost all use cases. """ if type(t).__name__ in AnnotatedTypeNames: return t.__args__ + t.__metadata__ if isinstance(t, _GenericAlias): res = t.__args__ if t.__origin__ is Callable and res and res[0] is not Ellipsis: res = (list(res[:-1]), res[-1]) return res return getattr(t, "__args__", ()) else: from typing import get_args as _typing_get_args def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]: """ In python 3.9, `typing.Dict`, `typing.List`, ... do have an empty `__args__` by default (instead of the generic ~T for example). In order to still support `Dict` for example and consider it as `Dict[Any, Any]`, we retrieve the `_nparams` value that tells us how many parameters it needs. """ if hasattr(tp, "_nparams"): return (Any,) * tp._nparams return () def get_args(tp: Type[Any]) -> Tuple[Any, ...]: """Get type arguments with all substitutions performed. For unions, basic simplifications used by Union constructor are performed. Examples:: get_args(Dict[str, int]) == (str, int) get_args(int) == () get_args(Union[int, Union[T, int], str][int]) == (int, str) get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) get_args(Callable[[], T][int]) == ([], int) """ if type(tp).__name__ in AnnotatedTypeNames: return tp.__args__ + tp.__metadata__ # the fallback is needed for the same reasons as `get_origin` (see above) return ( _typing_get_args(tp) or getattr(tp, "__args__", ()) or _generic_get_args(tp) ) if sys.version_info < (3, 9): def convert_generics(tp: Type[Any]) -> Type[Any]: """Python 3.9 and older only supports generics from `typing` module. They convert strings to ForwardRef automatically. Examples:: typing.List['Hero'] == typing.List[ForwardRef('Hero')] """ return tp else: from typing import _UnionGenericAlias # type: ignore from typing_extensions import _AnnotatedAlias def convert_generics(tp: Type[Any]) -> Type[Any]: """ Recursively searches for `str` type hints and replaces them with ForwardRef. Examples:: convert_generics(list['Hero']) == list[ForwardRef('Hero')] convert_generics(dict['Hero', 'Team']) == dict[ForwardRef('Hero'), ForwardRef('Team')] convert_generics(typing.Dict['Hero', 'Team']) == typing.Dict[ForwardRef('Hero'), ForwardRef('Team')] convert_generics(list[str | 'Hero'] | int) == list[str | ForwardRef('Hero')] | int """ origin = get_origin(tp) if not origin or not hasattr(tp, "__args__"): return tp args = get_args(tp) # typing.Annotated needs special treatment if origin is Annotated: return _AnnotatedAlias(convert_generics(args[0]), args[1:]) # recursively replace `str` instances inside of `GenericAlias` with `ForwardRef(arg)` converted = tuple( ForwardRef(arg) if isinstance(arg, str) and isinstance(tp, TypingGenericAlias) else convert_generics(arg) for arg in args ) if converted == args: return tp elif isinstance(tp, TypingGenericAlias): return TypingGenericAlias(origin, converted) elif isinstance(tp, TypesUnionType): # recreate types.UnionType (PEP604, Python >= 3.10) return _UnionGenericAlias(origin, converted) else: try: setattr(tp, "__args__", converted) except AttributeError: pass return tp if sys.version_info < (3, 10): def is_union(tp: Optional[Type[Any]]) -> bool: return tp is Union WithArgsTypes = (TypingGenericAlias,) else: import types import typing def is_union(tp: Optional[Type[Any]]) -> bool: return tp is Union or tp is types.UnionType # noqa: E721 WithArgsTypes = (typing._GenericAlias, types.GenericAlias, types.UnionType) if sys.version_info < (3, 9): StrPath = Union[str, PathLike] else: StrPath = Union[str, PathLike] # TODO: Once we switch to Cython 3 to handle generics properly # (https://github.com/cython/cython/issues/2753), use following lines instead # of the one above # # os.PathLike only becomes subscriptable from Python 3.9 onwards # StrPath = Union[str, PathLike[str]] if TYPE_CHECKING: # Only in Pydantic # from .fields import ModelField TupleGenerator = Generator[Tuple[str, Any], None, None] DictStrAny = Dict[str, Any] DictAny = Dict[Any, Any] SetStr = Set[str] ListStr = List[str] IntStr = Union[int, str] AbstractSetIntStr = AbstractSet[IntStr] DictIntStrAny = Dict[IntStr, Any] MappingIntStrAny = Mapping[IntStr, Any] CallableGenerator = Generator[AnyCallable, None, None] ReprArgs = Sequence[Tuple[Optional[str], Any]] AnyClassMethod = classmethod[Any] __all__ = ( "ForwardRef", "Callable", "AnyCallable", "NoArgAnyCallable", "NoneType", "is_none_type", "display_as_type", "resolve_annotations", "is_callable_type", "is_literal_type", "all_literal_values", "is_namedtuple", "is_typeddict", "is_new_type", "new_type_supertype", "is_classvar", "update_field_forward_refs", "update_model_forward_refs", "TupleGenerator", "DictStrAny", "DictAny", "SetStr", "ListStr", "IntStr", "AbstractSetIntStr", "DictIntStrAny", "CallableGenerator", "ReprArgs", "AnyClassMethod", "CallableGenerator", "WithArgsTypes", "get_args", "get_origin", "get_sub_types", "typing_base", "get_all_type_hints", "is_union", "StrPath", ) NoneType = None.__class__ NONE_TYPES: Tuple[Any, Any, Any] = (None, NoneType, Literal[None]) if sys.version_info < (3, 8): # Even though this implementation is slower, we need it for python 3.6/3.7: # In python 3.6/3.7 "Literal" is not a builtin type and uses a different # mechanism. # for this reason `Literal[None] is Literal[None]` evaluates to `False`, # breaking the faster implementation used for the other python versions. def is_none_type(type_: Any) -> bool: return type_ in NONE_TYPES elif sys.version_info[:2] == (3, 8): # We can use the fast implementation for 3.8 but there is a very weird bug # where it can fail for `Literal[None]`. # We just need to redefine a useless `Literal[None]` inside the function body to fix this def is_none_type(type_: Any) -> bool: Literal[None] # fix edge case for none_type in NONE_TYPES: if type_ is none_type: return True return False else: def is_none_type(type_: Any) -> bool: for none_type in NONE_TYPES: if type_ is none_type: return True return False def display_as_type(v: Type[Any]) -> str: if ( not isinstance(v, typing_base) and not isinstance(v, WithArgsTypes) and not isinstance(v, type) ): v = v.__class__ if is_union(get_origin(v)): return f'Union[{", ".join(map(display_as_type, get_args(v)))}]' if isinstance(v, WithArgsTypes): # Generic alias are constructs like `list[int]` return str(v).replace("typing.", "") try: return v.__name__ except AttributeError: # happens with typing objects return str(v).replace("typing.", "") def resolve_annotations( raw_annotations: Dict[str, Type[Any]], module_name: Optional[str] ) -> Dict[str, Type[Any]]: """ Partially taken from typing.get_type_hints. Resolve string or ForwardRef annotations into type objects if possible. """ base_globals: Optional[Dict[str, Any]] = None if module_name: try: module = sys.modules[module_name] except KeyError: # happens occasionally, see https://github.com/samuelcolvin/pydantic/issues/2363 pass else: base_globals = module.__dict__ annotations = {} for name, value in raw_annotations.items(): if isinstance(value, str): if (3, 10) > sys.version_info >= (3, 9, 8) or sys.version_info >= ( 3, 10, 1, ): value = ForwardRef(value, is_argument=False, is_class=True) elif sys.version_info >= (3, 7): value = ForwardRef(value, is_argument=False) else: value = ForwardRef(value) try: value = _eval_type(value, base_globals, None) except NameError: # this is ok, it can be fixed with update_forward_refs pass annotations[name] = value return annotations def is_callable_type(type_: Type[Any]) -> bool: return type_ is Callable or get_origin(type_) is Callable if sys.version_info >= (3, 7): def is_literal_type(type_: Type[Any]) -> bool: return Literal is not None and get_origin(type_) is Literal def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: return get_args(type_) else: def is_literal_type(type_: Type[Any]) -> bool: return ( Literal is not None and hasattr(type_, "__values__") and type_ == Literal[type_.__values__] ) def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: return type_.__values__ def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]: """ This method is used to retrieve all Literal values as Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]` """ if not is_literal_type(type_): return (type_,) values = literal_values(type_) return tuple(x for value in values for x in all_literal_values(value)) def is_namedtuple(type_: Type[Any]) -> bool: """ Check if a given class is a named tuple. It can be either a `typing.NamedTuple` or `collections.namedtuple` """ from .utils import lenient_issubclass return lenient_issubclass(type_, tuple) and hasattr(type_, "_fields") def is_typeddict(type_: Type[Any]) -> bool: """ Check if a given class is a typed dict (from `typing` or `typing_extensions`) In 3.10, there will be a public method (https://docs.python.org/3.10/library/typing.html#typing.is_typeddict) """ from .utils import lenient_issubclass return lenient_issubclass(type_, dict) and hasattr(type_, "__total__") test_type = NewType("test_type", str) def is_new_type(type_: Type[Any]) -> bool: """ Check whether type_ was created using typing.NewType """ return isinstance(type_, test_type.__class__) and hasattr(type_, "__supertype__") # type: ignore def new_type_supertype(type_: Type[Any]) -> Type[Any]: while hasattr(type_, "__supertype__"): type_ = type_.__supertype__ return type_ def _check_classvar(v: Optional[Type[Any]]) -> bool: if v is None: return False return v.__class__ == ClassVar.__class__ and ( sys.version_info < (3, 7) or getattr(v, "_name", None) == "ClassVar" ) def is_classvar(ann_type: Type[Any]) -> bool: if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)): return True # this is an ugly workaround for class vars that contain forward references and are therefore themselves # forward references, see #3679 if ann_type.__class__ == ForwardRef and ann_type.__forward_arg__.startswith( "ClassVar[" ): return True return False # Only in Pydantic # def update_field_forward_refs(field: "ModelField", globalns: Any, localns: Any) -> None: # """ # Try to update ForwardRefs on fields based on this ModelField, globalns and localns. # """ # if field.type_.__class__ == ForwardRef: # field.type_ = evaluate_forwardref(field.type_, globalns, localns or None) # field.prepare() # if field.sub_fields: # for sub_f in field.sub_fields: # update_field_forward_refs(sub_f, globalns=globalns, localns=localns) # if field.discriminator_key is not None: # field.prepare_discriminated_union_sub_fields() # Only in Pydantic # def update_model_forward_refs( # model: Type[Any], # fields: Iterable["ModelField"], # json_encoders: Dict[Union[Type[Any], str], AnyCallable], # localns: "DictStrAny", # exc_to_suppress: Tuple[Type[BaseException], ...] = (), # ) -> None: # """ # Try to update model fields ForwardRefs based on model and localns. # """ # if model.__module__ in sys.modules: # globalns = sys.modules[model.__module__].__dict__.copy() # else: # globalns = {} # globalns.setdefault(model.__name__, model) # for f in fields: # try: # update_field_forward_refs(f, globalns=globalns, localns=localns) # except exc_to_suppress: # pass # for key in set(json_encoders.keys()): # if isinstance(key, str): # fr: ForwardRef = ForwardRef(key) # elif isinstance(key, ForwardRef): # fr = key # else: # continue # try: # new_key = evaluate_forwardref(fr, globalns, localns or None) # except exc_to_suppress: # pragma: no cover # continue # json_encoders[new_key] = json_encoders.pop(key) def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]: """ Tries to get the class of a Type[T] annotation. Returns True if Type is used without brackets. Otherwise returns None. """ try: origin = get_origin(type_) if origin is None: # Python 3.6 origin = type_ if issubclass(origin, Type): # type: ignore if not get_args(type_) or not isinstance(get_args(type_)[0], type): return True return get_args(type_)[0] except (AttributeError, TypeError): pass return None def get_sub_types(tp: Any) -> List[Any]: """ Return all the types that are allowed by type `tp` `tp` can be a `Union` of allowed types or an `Annotated` type """ origin = get_origin(tp) if origin is Annotated: return get_sub_types(get_args(tp)[0]) elif is_union(origin): return [x for t in get_args(tp) for x in get_sub_types(t)] else: return [tp] typer-0.9.0/typer/colors.py000066400000000000000000000006561442411605300157100ustar00rootroot00000000000000# 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.9.0/typer/completion.py000066400000000000000000000115631442411605300165570ustar00rootroot00000000000000import os import sys from typing import Any, Dict, Tuple import click from ._compat_utils import _get_click_major from ._completion_shared import Shells, get_completion_script, install from .models import ParamMeta from .params import Option from .utils import 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.9.0/typer/core.py000066400000000000000000000636611442411605300153440ustar00rootroot00000000000000import errno import inspect import os import sys from gettext import gettext as _ from typing import ( TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, TextIO, Tuple, Union, cast, ) import click import click.core import click.formatting import click.parser import click.types import click.utils from typer.completion import completion_init from ._compat_utils import _get_click_major if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal try: import rich from . import rich_utils except ImportError: # pragma: nocover rich = None # type: ignore if TYPE_CHECKING: # pragma: no cover if _get_click_major() == 7: import click.shell_completion MarkupMode = Literal["markdown", "rich", None] # 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 def _get_default_string( obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context, show_default_is_str: bool, default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], ) -> str: # Extracted from click.core.Option.get_help_record() to be reused by # rich_utils avoiding RegEx hacks if show_default_is_str: default_string = f"({obj.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 isinstance(obj, TyperOption) and obj.is_bool_flag and obj.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 obj.default: if obj.opts: default_string = click.parser.split_opt(obj.opts[0])[1] else: default_string = str(default_value) else: default_string = click.parser.split_opt(obj.secondary_opts[0])[1] # Typer override end elif ( isinstance(obj, TyperOption) and obj.is_bool_flag and not obj.secondary_opts and not default_value ): default_string = "" else: default_string = str(default_value) return default_string def _extract_default_help_str( obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context ) -> Optional[Union[Any, Callable[[], Any]]]: # Extracted from click.core.Option.get_help_record() to be reused by # rich_utils avoiding RegEx hacks # 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: if _get_click_major() > 7: default_value = obj.get_default(ctx, call=False) else: if inspect.isfunction(obj.default): default_value = "(dynamic)" else: default_value = obj.default finally: ctx.resilient_parsing = resilient return default_value def _main( self: click.Command, *, args: Optional[Sequence[str]] = None, prog_name: Optional[str] = None, complete_var: Optional[str] = None, standalone_mode: bool = True, windows_expand_args: bool = True, **extra: Any, ) -> Any: # Typer override, duplicated from click.main() to handle custom rich exceptions # Verify that the environment is configured correctly, or reject # further execution to avoid a broken script. if args is None: args = sys.argv[1:] # Covered in Click tests if os.name == "nt" and windows_expand_args: # pragma: no cover args = click.utils._expand_args(args) else: args = list(args) if prog_name is None: if _get_click_major() > 7: prog_name = click.utils._detect_program_name() else: from click.utils import make_str prog_name = make_str( os.path.basename(sys.argv[0] if sys.argv else __file__) ) # Process shell completion requests and exit early. if _get_click_major() > 7: self._main_shell_completion(extra, prog_name, complete_var) else: completion_init() from click.core import _bashcomplete # type: ignore _bashcomplete(self, prog_name, complete_var) try: try: with self.make_context(prog_name, args, **extra) as ctx: rv = self.invoke(ctx) if not standalone_mode: return rv # it's not safe to `ctx.exit(rv)` here! # note that `rv` may actually contain data like "1" which # has obvious effects # more subtle case: `rv=[None, None]` can come out of # chained commands which all returned `None` -- so it's not # even always obvious that `rv` indicates success/failure # by its truthiness/falsiness ctx.exit() except (EOFError, KeyboardInterrupt): click.echo(file=sys.stderr) raise click.Abort() except click.ClickException as e: if not standalone_mode: raise # Typer override if rich: rich_utils.rich_format_error(e) else: e.show() # Typer override end sys.exit(e.exit_code) except OSError as e: if e.errno == errno.EPIPE: sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) sys.exit(1) else: raise except click.exceptions.Exit as e: if standalone_mode: sys.exit(e.exit_code) else: # in non-standalone mode, return the exit code # note that this is only reached if `self.invoke` above raises # an Exit explicitly -- thus bypassing the check there which # would return its result # the results of non-standalone execution may therefore be # somewhat ambiguous: if there are codepaths which lead to # `ctx.exit(1)` and to `return 1`, the caller won't be able to # tell the difference between the two return e.exit_code except click.Abort: if not standalone_mode: raise # Typer override if rich: rich_utils.rich_abort_error() else: click.echo(_("Aborted!"), file=sys.stderr) # Typer override end sys.exit(1) 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, # Rich settings rich_help_panel: Union[str, None] = None, ): self.help = help self.show_default = show_default self.show_choices = show_choices self.show_envvar = show_envvar self.hidden = hidden self.rich_help_panel = rich_help_panel 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_default_string( self, *, ctx: click.Context, show_default_is_str: bool, default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], ) -> str: return _get_default_string( self, ctx=ctx, show_default_is_str=show_default_is_str, default_value=default_value, ) def _extract_default_help_str( self, *, ctx: click.Context ) -> Optional[Union[Any, Callable[[], Any]]]: return _extract_default_help_str(self, ctx=ctx) 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}") # Typer override: # Extracted to _extract_default_help_str() to allow re-using it in rich_utils default_value = self._extract_default_help_str(ctx=ctx) # Typer override end 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) ): # Typer override: # Extracted to _get_default_string() to allow re-using it in rich_utils default_string = self._get_default_string( ctx=ctx, show_default_is_str=show_default_is_str, default_value=default_value, ) # Typer override end if default_string: extra.append(_("default: {default}").format(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, # Rich settings rich_help_panel: Union[str, None] = None, ): # 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 ) self.rich_help_panel = rich_help_panel def _get_default_string( self, *, ctx: click.Context, show_default_is_str: bool, default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], ) -> str: return _get_default_string( self, ctx=ctx, show_default_is_str=show_default_is_str, default_value=default_value, ) def _extract_default_help_str( self, *, ctx: click.Context ) -> Optional[Union[Any, Callable[[], Any]]]: return _extract_default_help_str(self, ctx=ctx) 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)) # Typer override: # Extracted to _extract_default() to allow re-using it in rich_utils default_value = self._extract_default_help_str(ctx=ctx) # Typer override end 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) ): # Typer override: # Extracted to _get_default_string() to allow re-using it in rich_utils default_string = self._get_default_string( ctx=ctx, show_default_is_str=show_default_is_str, default_value=default_value, ) # Typer override end 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 __init__( self, name: Optional[str], *, context_settings: Optional[Dict[str, Any]] = None, callback: Optional[Callable[..., Any]] = None, params: Optional[List[click.Parameter]] = None, help: Optional[str] = None, epilog: Optional[str] = None, short_help: Optional[str] = None, options_metavar: Optional[str] = "[OPTIONS]", add_help_option: bool = True, no_args_is_help: bool = False, hidden: bool = False, deprecated: bool = False, # Rich settings rich_markup_mode: MarkupMode = None, rich_help_panel: Union[str, None] = None, ) -> None: super().__init__( name=name, context_settings=context_settings, callback=callback, params=params, 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, ) self.rich_markup_mode: MarkupMode = rich_markup_mode self.rich_help_panel = rich_help_panel 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 ) def main( self, args: Optional[Sequence[str]] = None, prog_name: Optional[str] = None, complete_var: Optional[str] = None, standalone_mode: bool = True, windows_expand_args: bool = True, **extra: Any, ) -> Any: return _main( self, args=args, prog_name=prog_name, complete_var=complete_var, standalone_mode=standalone_mode, windows_expand_args=windows_expand_args, **extra, ) def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: if not rich: return super().format_help(ctx, formatter) return rich_utils.rich_format_help( obj=self, ctx=ctx, markup_mode=self.rich_markup_mode, ) class TyperGroup(click.core.Group): def __init__( self, *, name: Optional[str] = None, commands: Optional[ Union[Dict[str, click.Command], Sequence[click.Command]] ] = None, # Rich settings rich_markup_mode: MarkupMode = None, rich_help_panel: Union[str, None] = None, **attrs: Any, ) -> None: super().__init__(name=name, commands=commands, **attrs) self.rich_markup_mode: MarkupMode = rich_markup_mode self.rich_help_panel = rich_help_panel 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 ) def main( self, args: Optional[Sequence[str]] = None, prog_name: Optional[str] = None, complete_var: Optional[str] = None, standalone_mode: bool = True, windows_expand_args: bool = True, **extra: Any, ) -> Any: return _main( self, args=args, prog_name=prog_name, complete_var=complete_var, standalone_mode=standalone_mode, windows_expand_args=windows_expand_args, **extra, ) def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: if not rich: return super().format_help(ctx, formatter) return rich_utils.rich_format_help( obj=self, ctx=ctx, markup_mode=self.rich_markup_mode, ) typer-0.9.0/typer/main.py000066400000000000000000001146161442411605300153350ustar00rootroot00000000000000import inspect import os import sys import traceback from datetime import datetime from enum import Enum from functools import update_wrapper from pathlib import Path from traceback import FrameSummary, StackSummary from types import TracebackType 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 MarkupMode, TyperArgument, TyperCommand, TyperGroup, TyperOption from .models import ( AnyType, ArgumentInfo, CommandFunctionType, CommandInfo, Default, DefaultPlaceholder, DeveloperExceptionConfig, FileBinaryRead, FileBinaryWrite, FileText, FileTextWrite, NoneType, OptionInfo, ParameterInfo, ParamMeta, Required, TyperInfo, ) from .utils import get_params_from_function try: import rich from rich.console import Console from rich.traceback import Traceback console_stderr = Console(stderr=True) except ImportError: # pragma: nocover rich = None # type: ignore _original_except_hook = sys.excepthook _typer_developer_exception_attr_name = "__typer_developer_exception__" def except_hook( exc_type: Type[BaseException], exc_value: BaseException, tb: TracebackType ) -> None: exception_config: Union[DeveloperExceptionConfig, None] = getattr( exc_value, _typer_developer_exception_attr_name, None ) standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK") if ( standard_traceback or not exception_config or not exception_config.pretty_exceptions_enable ): _original_except_hook(exc_type, exc_value, tb) return typer_path = os.path.dirname(__file__) click_path = os.path.dirname(click.__file__) supress_internal_dir_names = [typer_path, click_path] exc = exc_value if rich: rich_tb = Traceback.from_exception( type(exc), exc, exc.__traceback__, show_locals=exception_config.pretty_exceptions_show_locals, suppress=supress_internal_dir_names, ) console_stderr.print(rich_tb) return tb_exc = traceback.TracebackException.from_exception(exc) stack: List[FrameSummary] = [] for frame in tb_exc.stack: if any( [frame.filename.startswith(path) for path in supress_internal_dir_names] ): if not exception_config.pretty_exceptions_short: # Hide the line for internal libraries, Typer and Click stack.append( traceback.FrameSummary( filename=frame.filename, lineno=frame.lineno, name=frame.name, line="", ) ) else: stack.append(frame) # Type ignore ref: https://github.com/python/typeshed/pull/8244 final_stack_summary = StackSummary.from_list(stack) # type: ignore tb_exc.stack = final_stack_summary for line in tb_exc.format(): print(line, file=sys.stderr) return 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[TyperGroup]] = 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, # Rich settings rich_markup_mode: MarkupMode = None, rich_help_panel: Union[str, None] = Default(None), pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, pretty_exceptions_short: bool = True, ): self._add_completion = add_completion self.rich_markup_mode: MarkupMode = rich_markup_mode self.rich_help_panel = rich_help_panel self.pretty_exceptions_enable = pretty_exceptions_enable self.pretty_exceptions_show_locals = pretty_exceptions_show_locals self.pretty_exceptions_short = pretty_exceptions_short 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[TyperGroup]] = 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), # Rich settings rich_help_panel: Union[str, None] = Default(None), ) -> 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, rich_help_panel=rich_help_panel, ) return f return decorator def command( self, name: Optional[str] = None, *, cls: Optional[Type[TyperCommand]] = 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, # Rich settings rich_help_panel: Union[str, None] = Default(None), ) -> 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, # Rich settings rich_help_panel=rich_help_panel, ) ) return f return decorator def add_typer( self, typer_instance: "Typer", *, name: Optional[str] = Default(None), cls: Optional[Type[TyperGroup]] = 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), # Rich settings rich_help_panel: Union[str, None] = Default(None), ) -> 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, rich_help_panel=rich_help_panel, ) ) def __call__(self, *args: Any, **kwargs: Any) -> Any: if sys.excepthook != except_hook: sys.excepthook = except_hook try: return get_command(self)(*args, **kwargs) except Exception as e: # Set a custom attribute to tell the hook to show nice exceptions for user # code. An alternative/first implementation was a custom exception with # raise custom_exc from e # but that means the last error shown is the custom exception, not the # actual error. This trick improves developer experience by showing the # actual error last. setattr( e, _typer_developer_exception_attr_name, DeveloperExceptionConfig( pretty_exceptions_enable=self.pretty_exceptions_enable, pretty_exceptions_show_locals=self.pretty_exceptions_show_locals, pretty_exceptions_short=self.pretty_exceptions_short, ), ) raise e def get_group(typer_instance: Typer) -> TyperGroup: group = get_group_from_info( TyperInfo(typer_instance), pretty_exceptions_short=typer_instance.pretty_exceptions_short, rich_markup_mode=typer_instance.rich_markup_mode, ) 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: 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 single_command = typer_instance.registered_commands[0] if not single_command.context_settings and not isinstance( typer_instance.info.context_settings, DefaultPlaceholder ): single_command.context_settings = typer_instance.info.context_settings click_command = get_command_from_info( single_command, pretty_exceptions_short=typer_instance.pretty_exceptions_short, rich_markup_mode=typer_instance.rich_markup_mode, ) 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, *, pretty_exceptions_short: bool, rich_markup_mode: MarkupMode, ) -> TyperGroup: 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, pretty_exceptions_short=pretty_exceptions_short, rich_markup_mode=rich_markup_mode, ) 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, pretty_exceptions_short=pretty_exceptions_short, rich_markup_mode=rich_markup_mode, ) 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 assert issubclass(cls, TyperGroup) group = cls( 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, pretty_exceptions_short=pretty_exceptions_short, ), params=params, 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, rich_markup_mode=rich_markup_mode, # Rich settings rich_help_panel=solved_info.rich_help_panel, ) 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, *, pretty_exceptions_short: bool, rich_markup_mode: MarkupMode, ) -> 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, pretty_exceptions_short=pretty_exceptions_short, ), 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, rich_markup_mode=rich_markup_mode, # Rich settings rich_help_panel=command_info.rich_help_panel, ) return command def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]: convertor: Optional[Callable[[Any], Any]] = None if lenient_issubclass(type_, Path): convertor = param_path_convertor if lenient_issubclass(type_, Enum): convertor = generate_enum_convertor(type_) return convertor 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], 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_list_convertor( convertor: Optional[Callable[[Any], Any]] ) -> Callable[[Sequence[Any]], List[Any]]: def internal_convertor(value: Sequence[Any]) -> List[Any]: return [convertor(v) if convertor else v for v in value] return internal_convertor def generate_tuple_convertor( types: Sequence[Any], ) -> Callable[[Tuple[Any, ...]], Tuple[Any, ...]]: convertors = [determine_type_convertor(type_) for type_ in types] def internal_convertor(param_args: Tuple[Any, ...]) -> Tuple[Any, ...]: return tuple( convertor(arg) if convertor else arg for (convertor, arg) in zip(convertors, param_args) ) 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, pretty_exceptions_short: bool, ) -> 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: _rich_traceback_guard = pretty_exceptions_short # noqa: F841 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 parameter_info.click_type is not None: return parameter_info.click_type elif parameter_info.parser is not None: return click.types.FuncParamType(parameter_info.parser) elif 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 is_tuple = 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) is_tuple = True if parameter_type is None: parameter_type = get_click_type( annotation=main_type, parameter_info=parameter_info ) convertor = determine_type_convertor(main_type) if is_list: convertor = generate_list_convertor(convertor) if is_tuple: convertor = generate_tuple_convertor(main_type.__args__) 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), # Rich settings rich_help_panel=parameter_info.rich_help_panel, ), 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), # Rich settings rich_help_panel=parameter_info.rich_help_panel, ), 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]) -> None: app = Typer(add_completion=False) app.command()(function) app() typer-0.9.0/typer/models.py000066400000000000000000000371551442411605300156760ustar00rootroot00000000000000import inspect import io from typing import ( TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Type, TypeVar, Union, ) import click from ._compat_utils import _get_click_major if TYPE_CHECKING: # pragma: no cover if _get_click_major() > 7: import click.shell_completion from .core import TyperCommand, TyperGroup from .main import Typer 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["TyperCommand"]] = 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, # Rich settings rich_help_panel: Union[str, None] = None, ): 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 # Rich settings self.rich_help_panel = rich_help_panel class TyperInfo: def __init__( self, typer_instance: Optional["Typer"] = Default(None), *, name: Optional[str] = Default(None), cls: Optional[Type["TyperGroup"]] = 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), # Rich settings rich_help_panel: Union[str, None] = Default(None), ): 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 self.rich_help_panel = rich_help_panel 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type parser: Optional[Callable[[str], Any]] = None, click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = None, ): # Check if user has provided multiple custom parsers if parser and click_type: raise ValueError( "Multiple custom type parsers provided. " "`parser` and `click_type` may not both be provided." ) 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 self.default_factory = default_factory # Custom type self.parser = parser self.click_type = click_type # 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 # Rich settings self.rich_help_panel = rich_help_panel 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type parser: Optional[Callable[[str], Any]] = None, click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = 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, default_factory=default_factory, # Custom type parser=parser, click_type=click_type, # 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, # Rich settings rich_help_panel=rich_help_panel, ) 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type parser: Optional[Callable[[str], Any]] = None, click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = 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, default_factory=default_factory, # Custom type parser=parser, click_type=click_type, # 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, # Rich settings rich_help_panel=rich_help_panel, ) 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 class DeveloperExceptionConfig: def __init__( self, *, pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, pretty_exceptions_short: bool = True, ) -> None: self.pretty_exceptions_enable = pretty_exceptions_enable self.pretty_exceptions_show_locals = pretty_exceptions_show_locals self.pretty_exceptions_short = pretty_exceptions_short typer-0.9.0/typer/params.py000066400000000000000000000321311442411605300156630ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union, overload import click from .models import ArgumentInfo, OptionInfo if TYPE_CHECKING: # pragma: no cover import click.shell_completion @overload 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type parser: Optional[Callable[[str], 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[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, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... @overload 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type parser: Optional[Callable[[str], Any]] = None, click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = 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, default_factory=default_factory, # Custom type parser=parser, click_type=click_type, # 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, # Rich settings rich_help_panel=rich_help_panel, ) @overload 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, # Custom type parser: Optional[Callable[[str], 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[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, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... @overload 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, # Custom type click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... 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, default_factory: Optional[Callable[[], Any]] = None, # Custom type parser: Optional[Callable[[str], Any]] = None, click_type: Optional[click.ParamType] = 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[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, # Rich settings rich_help_panel: Union[str, None] = 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, default_factory=default_factory, # Custom type parser=parser, click_type=click_type, # 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, # Rich settings rich_help_panel=rich_help_panel, ) typer-0.9.0/typer/py.typed000066400000000000000000000000001442411605300155130ustar00rootroot00000000000000typer-0.9.0/typer/rich_utils.py000066400000000000000000000561461442411605300165610ustar00rootroot00000000000000# Extracted and modified from https://github.com/ewels/rich-click import inspect import sys from collections import defaultdict from os import getenv from typing import Any, DefaultDict, Dict, Iterable, List, Optional, Union import click from rich import box from rich.align import Align from rich.columns import Columns from rich.console import Console, RenderableType, group from rich.emoji import Emoji from rich.highlighter import RegexHighlighter from rich.markdown import Markdown from rich.padding import Padding from rich.panel import Panel from rich.table import Table from rich.text import Text from rich.theme import Theme if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal # Default styles STYLE_OPTION = "bold cyan" STYLE_SWITCH = "bold green" STYLE_NEGATIVE_OPTION = "bold magenta" STYLE_NEGATIVE_SWITCH = "bold red" STYLE_METAVAR = "bold yellow" STYLE_METAVAR_SEPARATOR = "dim" STYLE_USAGE = "yellow" STYLE_USAGE_COMMAND = "bold" STYLE_DEPRECATED = "red" STYLE_DEPRECATED_COMMAND = "dim" STYLE_HELPTEXT_FIRST_LINE = "" STYLE_HELPTEXT = "dim" STYLE_OPTION_HELP = "" STYLE_OPTION_DEFAULT = "dim" STYLE_OPTION_ENVVAR = "dim yellow" STYLE_REQUIRED_SHORT = "red" STYLE_REQUIRED_LONG = "dim red" STYLE_OPTIONS_PANEL_BORDER = "dim" ALIGN_OPTIONS_PANEL: Literal["left", "center", "right"] = "left" STYLE_OPTIONS_TABLE_SHOW_LINES = False STYLE_OPTIONS_TABLE_LEADING = 0 STYLE_OPTIONS_TABLE_PAD_EDGE = False STYLE_OPTIONS_TABLE_PADDING = (0, 1) STYLE_OPTIONS_TABLE_BOX = "" STYLE_OPTIONS_TABLE_ROW_STYLES = None STYLE_OPTIONS_TABLE_BORDER_STYLE = None STYLE_COMMANDS_PANEL_BORDER = "dim" ALIGN_COMMANDS_PANEL: Literal["left", "center", "right"] = "left" STYLE_COMMANDS_TABLE_SHOW_LINES = False STYLE_COMMANDS_TABLE_LEADING = 0 STYLE_COMMANDS_TABLE_PAD_EDGE = False STYLE_COMMANDS_TABLE_PADDING = (0, 1) STYLE_COMMANDS_TABLE_BOX = "" STYLE_COMMANDS_TABLE_ROW_STYLES = None STYLE_COMMANDS_TABLE_BORDER_STYLE = None STYLE_ERRORS_PANEL_BORDER = "red" ALIGN_ERRORS_PANEL: Literal["left", "center", "right"] = "left" STYLE_ERRORS_SUGGESTION = "dim" STYLE_ABORTED = "red" _TERMINAL_WIDTH = getenv("TERMINAL_WIDTH") MAX_WIDTH = int(_TERMINAL_WIDTH) if _TERMINAL_WIDTH else None COLOR_SYSTEM: Optional[ Literal["auto", "standard", "256", "truecolor", "windows"] ] = "auto" # Set to None to disable colors _TYPER_FORCE_DISABLE_TERMINAL = getenv("_TYPER_FORCE_DISABLE_TERMINAL") FORCE_TERMINAL = ( True if getenv("GITHUB_ACTIONS") or getenv("FORCE_COLOR") or getenv("PY_COLORS") else None ) if _TYPER_FORCE_DISABLE_TERMINAL: FORCE_TERMINAL = False # Fixed strings DEPRECATED_STRING = "(deprecated) " DEFAULT_STRING = "[default: {}]" ENVVAR_STRING = "[env var: {}]" REQUIRED_SHORT_STRING = "*" REQUIRED_LONG_STRING = "[required]" RANGE_STRING = " [{}]" ARGUMENTS_PANEL_TITLE = "Arguments" OPTIONS_PANEL_TITLE = "Options" COMMANDS_PANEL_TITLE = "Commands" ERRORS_PANEL_TITLE = "Error" ABORTED_TEXT = "Aborted." MARKUP_MODE_MARKDOWN = "markdown" MARKUP_MODE_RICH = "rich" _RICH_HELP_PANEL_NAME = "rich_help_panel" MarkupMode = Literal["markdown", "rich", None] # Rich regex highlighter class OptionHighlighter(RegexHighlighter): """Highlights our special options.""" highlights = [ r"(^|\W)(?P\-\w+)(?![a-zA-Z0-9])", r"(^|\W)(?P