pax_global_header00006660000000000000000000000064147601347660014530gustar00rootroot0000000000000052 comment=5474f0be4ef3dbd845d9eb6541a563861c9e31e4 typer-0.15.2/000077500000000000000000000000001476013476600127605ustar00rootroot00000000000000typer-0.15.2/.github/000077500000000000000000000000001476013476600143205ustar00rootroot00000000000000typer-0.15.2/.github/DISCUSSION_TEMPLATE/000077500000000000000000000000001476013476600172765ustar00rootroot00000000000000typer-0.15.2/.github/DISCUSSION_TEMPLATE/questions.yml000066400000000000000000000122711476013476600220560ustar00rootroot00000000000000labels: [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 tutorials 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.15.2/.github/FUNDING.yml000066400000000000000000000000231476013476600161300ustar00rootroot00000000000000github: [tiangolo] typer-0.15.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476013476600165035ustar00rootroot00000000000000typer-0.15.2/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000013631476013476600204760ustar00rootroot00000000000000blank_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/fastapi/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/fastapi/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/fastapi/typer/discussions/categories/show-and-tell typer-0.15.2/.github/ISSUE_TEMPLATE/privileged.yml000066400000000000000000000015641476013476600213660ustar00rootroot00000000000000name: 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/fastapi/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.15.2/.github/dependabot.yml000066400000000000000000000004651476013476600171550ustar00rootroot00000000000000version: 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.15.2/.github/labeler.yml000066400000000000000000000007761476013476600164630ustar00rootroot00000000000000docs: - all: - changed-files: - any-glob-to-any-file: - docs/** - docs_src/** - all-globs-to-all-files: - '!typer/**' - '!pyproject.toml' internal: - all: - changed-files: - any-glob-to-any-file: - .github/** - scripts/** - .gitignore - .pre-commit-config.yaml - pdm_build.py - requirements*.txt - all-globs-to-all-files: - '!docs/**' - '!typer/**' - '!pyproject.toml' typer-0.15.2/.github/workflows/000077500000000000000000000000001476013476600163555ustar00rootroot00000000000000typer-0.15.2/.github/workflows/add-to-project.yml000066400000000000000000000005601476013476600217150ustar00rootroot00000000000000name: Add to Project on: pull_request_target: issues: types: - opened - reopened jobs: add-to-project: name: Add to project runs-on: ubuntu-latest steps: - uses: actions/add-to-project@v1.0.2 with: project-url: https://github.com/orgs/fastapi/projects/2 github-token: ${{ secrets.PROJECTS_TOKEN }} typer-0.15.2/.github/workflows/build-docs.yml000066400000000000000000000054741476013476600211370ustar00rootroot00000000000000name: Build Docs on: push: branches: - master pull_request: types: - opened - synchronize env: UV_SYSTEM_PYTHON: 1 jobs: changes: runs-on: ubuntu-latest # Required permissions permissions: pull-requests: read # Set job outputs to values from filter step outputs: docs: ${{ steps.filter.outputs.docs }} steps: - uses: actions/checkout@v4 # For pull requests it's not necessary to checkout the code but for the main branch it is - uses: dorny/paths-filter@v3 id: filter with: filters: | docs: - README.md - docs/** - docs_src/** - requirements-docs.txt - requirements-docs-insiders.txt - pyproject.toml - mkdocs.yml - mkdocs.insiders.yml - mkdocs.maybe-insiders.yml - mkdocs.no-insiders.yml - .github/workflows/build-docs.yml - .github/workflows/deploy-docs.yml - data/** build-docs: needs: - changes if: ${{ needs.changes.outputs.docs == 'true' }} runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Setup uv uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml - name: Install docs extras run: uv pip install -r requirements-docs.txt - name: Install Material for MkDocs Insiders if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) run: uv pip install -r requirements-docs-insiders.txt env: TOKEN: ${{ secrets.TYPER_MKDOCS_MATERIAL_INSIDERS }} - uses: actions/cache@v4 with: key: mkdocs-cards-${{ github.ref }}-v1 path: .cache - name: Verify README run: python ./scripts/docs.py verify-readme - name: Build Docs run: python ./scripts/docs.py build - uses: actions/upload-artifact@v4 with: name: docs-site path: ./site/** include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why docs-all-green: # This job does nothing and is only used for the branch protection if: always() needs: - build-docs runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} allowed-skips: build-docs typer-0.15.2/.github/workflows/deploy-docs.yml000066400000000000000000000047361476013476600213340ustar00rootroot00000000000000name: Deploy Docs on: workflow_run: workflows: - Build Docs types: - completed permissions: deployments: write issues: write pull-requests: write statuses: write env: UV_SYSTEM_PYTHON: 1 jobs: deploy-docs: runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Setup uv uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml - name: Install GitHub Actions dependencies run: uv pip install -r requirements-github-actions.txt - name: Deploy Docs Status Pending run: python ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} RUN_ID: ${{ github.run_id }} - name: Clean site run: | rm -rf ./site mkdir ./site - uses: actions/download-artifact@v4 with: path: ./site/ pattern: docs-site merge-multiple: true github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - name: Deploy to Cloudflare Pages # hashFiles returns an empty string if there are no files if: hashFiles('./site/*') id: deploy env: PROJECT_NAME: typertiangolo BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' && 'main' ) || ( github.event.workflow_run.head_sha ) }} uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }} - name: Comment Deploy run: python ./scripts/deploy_docs_status.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }} COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} RUN_ID: ${{ github.run_id }} IS_DONE: "true" typer-0.15.2/.github/workflows/issue-manager.yml000066400000000000000000000022511476013476600216400ustar00rootroot00000000000000name: Issue Manager on: schedule: - cron: "13 21 * * *" issue_comment: types: - created issues: types: - labeled pull_request_target: types: - labeled workflow_dispatch: permissions: issues: write pull-requests: write jobs: issue-manager: if: github.repository_owner == 'fastapi' runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: tiangolo/issue-manager@0.5.1 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "delay": 864000, "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." }, "waiting": { "delay": 2628000, "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." } } typer-0.15.2/.github/workflows/labeler.yml000066400000000000000000000014741476013476600205140ustar00rootroot00000000000000name: Labels on: pull_request_target: types: - opened - synchronize - reopened # For label-checker - labeled - unlabeled jobs: labeler: permissions: contents: read pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }} - run: echo "Done adding labels" # Run this after labeler applied labels check-labels: needs: - labeler permissions: pull-requests: read runs-on: ubuntu-latest steps: - uses: docker://agilepathway/pull-request-label-checker:latest with: one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal repo_token: ${{ secrets.GITHUB_TOKEN }} typer-0.15.2/.github/workflows/latest-changes.yml000066400000000000000000000024651476013476600220110ustar00rootroot00000000000000name: 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: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 with: # To allow latest-changes to commit to the main branch token: ${{ secrets.TYPER_LATEST_CHANGES }} # Allow debugging with tmate - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - uses: tiangolo/latest-changes@0.3.2 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/release-notes.md latest_changes_header: '## Latest Changes' end_regex: '^## ' debug_logs: true label_header_prefix: '### ' typer-0.15.2/.github/workflows/publish.yml000066400000000000000000000017021476013476600205460ustar00rootroot00000000000000name: Publish on: release: types: - created jobs: publish: runs-on: ubuntu-latest strategy: matrix: package: - typer-slim - typer - typer-cli permissions: id-token: write steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.10" # Issue ref: https://github.com/actions/setup-python/issues/436 # cache: "pip" # cache-dependency-path: pyproject.toml - name: Install build dependencies run: pip install build - name: Build distribution env: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} run: python -m build - name: Publish uses: pypa/gh-action-pypi-publish@v1.12.4 typer-0.15.2/.github/workflows/smokeshow.yml000066400000000000000000000026661476013476600211310ustar00rootroot00000000000000name: Smokeshow on: workflow_run: workflows: - Test types: - completed permissions: statuses: write env: UV_SYSTEM_PYTHON: 1 jobs: smokeshow: if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.9' - name: Setup uv uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml - run: uv pip install -r requirements-github-actions.txt - uses: actions/download-artifact@v4 with: name: coverage-html path: htmlcov github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - run: smokeshow upload htmlcov 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.15.2/.github/workflows/test-redistribute.yml000066400000000000000000000037161476013476600225710ustar00rootroot00000000000000name: Test Redistribute on: push: branches: - master pull_request: types: - opened - synchronize jobs: test-redistribute: runs-on: ubuntu-latest strategy: matrix: package: - typer-slim - typer - typer-cli steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.10" # Issue ref: https://github.com/actions/setup-python/issues/436 # cache: "pip" # cache-dependency-path: pyproject.toml - name: Install build dependencies run: pip install build - name: Build source distribution env: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} run: python -m build --sdist - name: Decompress source distribution run: | cd dist tar xvf typer*.tar.gz - name: Install test dependencies if: ${{ matrix.package != 'typer-cli' }} run: | cd dist/typer*/ pip install -r requirements-tests.txt env: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} - name: Run source distribution tests if: ${{ matrix.package != 'typer-cli' }} run: | cd dist/typer*/ bash scripts/test.sh - name: Build wheel distribution run: | cd dist pip wheel --no-deps typer*.tar.gz # https://github.com/marketplace/actions/alls-green#why test-redistribute-alls-green: # This job does nothing and is only used for the branch protection if: always() needs: - test-redistribute runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} typer-0.15.2/.github/workflows/test.yml000066400000000000000000000071561476013476600200700ustar00rootroot00000000000000name: Test on: push: branches: - master pull_request: types: - opened - synchronize schedule: # cron every week on monday - cron: "0 0 * * 1" env: UV_SYSTEM_PYTHON: 1 jobs: test: strategy: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] python-version: [ "3.13" ] include: - os: ubuntu-22.04 python-version: "3.7" - os: macos-latest python-version: "3.8" - os: windows-latest python-version: "3.9" - os: ubuntu-latest python-version: "3.10" - os: macos-latest python-version: "3.11" - os: windows-latest python-version: "3.12" fail-fast: false runs-on: ${{ matrix.os }} steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Setup uv uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml - name: Install Dependencies run: uv pip install -r requirements-tests.txt - name: Lint run: bash scripts/lint.sh - run: mkdir coverage - run: bash ./scripts/test-files.sh - name: Test run: bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - name: Store coverage files uses: actions/upload-artifact@v4 with: name: coverage-${{ runner.os }}-${{ matrix.python-version }} path: coverage include-hidden-files: true coverage-combine: needs: [test] runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.8' - name: Setup uv uses: astral-sh/setup-uv@v5 with: version: "0.4.15" enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml - name: Get coverage files uses: actions/download-artifact@v4 with: pattern: coverage-* path: coverage merge-multiple: true - name: Install Dependencies run: uv pip install -r requirements-tests.txt - 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@v4 with: name: coverage-html path: htmlcov include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why check: # This job does nothing and is only used for the branch protection if: always() needs: - coverage-combine runs-on: ubuntu-latest steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} typer-0.15.2/.gitignore000066400000000000000000000001661476013476600147530ustar00rootroot00000000000000.vscode *.pyc __pycache__ .venv* env dist .mypy_cache .idea site htmlcov .pytest_cache coverage.xml .coverage* .cache typer-0.15.2/.pre-commit-config.yaml000066400000000000000000000013351476013476600172430ustar00rootroot00000000000000# 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: v5.0.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/astral-sh/ruff-pre-commit rev: v0.9.7 hooks: - id: ruff args: - --fix - id: ruff-format 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.15.2/CITATION.cff000066400000000000000000000010671476013476600146560ustar00rootroot00000000000000# This CITATION.cff file was generated with cffinit. # Visit https://bit.ly/cffinit to generate yours today! cff-version: 1.2.0 title: Typer message: >- If you use this software, please cite it using the metadata from this file. type: software authors: - given-names: Sebastiรกn family-names: Ramรญrez email: tiangolo@gmail.com identifiers: repository-code: 'https://github.com/fastapi/typer' url: 'https://typer.tiangolo.com' abstract: >- Typer, build great CLIs. Easy to code. Based on Python type hints. keywords: - typer - click license: MIT typer-0.15.2/CONTRIBUTING.md000066400000000000000000000001751476013476600152140ustar00rootroot00000000000000Please read the [Development - Contributing](https://typer.tiangolo.com/contributing/) guidelines in the documentation site. typer-0.15.2/LICENSE000066400000000000000000000020761476013476600137720ustar00rootroot00000000000000The 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.15.2/README.md000066400000000000000000000331771476013476600142520ustar00rootroot00000000000000

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/fastapi/typer --- Typer is a library for building CLI applications that users will **love using** and developers will **love creating**. Based on Python type hints. It's also a command line tool to run scripts, automatically converting them to CLI applications. 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. * **Run scripts**: Typer includes a `typer` command/program that you can use to run scripts, automatically converting them to CLIs, even if they don't use Typer internally. ## FastAPI of CLIs **Typer** is FastAPI's little sibling, it's the FastAPI of CLIs. ## Installation Create and activate a virtual environment and then install **Typer**:
```console $ pip install typer ---> 100% Successfully installed typer rich shellingham ```
## Example ### The absolute minimum * Create a file `main.py` with: ```Python def main(name: str): print(f"Hello {name}") ``` This script doesn't even use Typer internally. But you can use the `typer` command to run it as a CLI application. ### Run it Run your application with the `typer` command:
```console // Run your application $ typer main.py run // You get a nice error, you are missing NAME Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME Try 'typer [PATH_OR_MODULE] run --help' for help. โ•ญโ”€ Error โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ Missing argument 'NAME'. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // You get a --help for free $ typer main.py run --help Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME Run the provided Typer app. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT [default: None] [required] | โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // Now pass the NAME argument $ typer main.py run Camila Hello Camila // It works! ๐ŸŽ‰ ```
This is the simplest use case, not even using Typer internally, but it can already be quite useful for simple scripts. **Note**: auto-completion works when you create a Python package and run it with `--install-completion` or when you use the `typer` command. ## Use Typer in your code Now let's start using Typer in your own code, update `main.py` with: ```Python import typer def main(name: str): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) ``` Now you could run it with Python directly:
```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**: you can also call this same script with the `typer` command, but you don't need to. ## 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**. 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 the `typer` command. For a more complete example including more features, see the Tutorial - User Guide. ## Dependencies **Typer** stands on the shoulders of a giant. Its only internal required dependency is Click. By default it also comes with extra standard dependencies: * rich: to show nicely formatted errors automatically. * shellingham: to 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`. ### `typer-slim` If you don't want the extra standard optional dependencies, install `typer-slim` instead. When you install with: ```bash pip install typer ``` ...it includes the same code and dependencies as: ```bash pip install "typer-slim[standard]" ``` The `standard` extra dependencies are `rich` and `shellingham`. **Note**: The `typer` command is only included in the `typer` package. ## License This project is licensed under the terms of the MIT license. typer-0.15.2/SECURITY.md000066400000000000000000000022101476013476600145440ustar00rootroot00000000000000# 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.15.2/data/000077500000000000000000000000001476013476600136715ustar00rootroot00000000000000typer-0.15.2/data/members.yml000066400000000000000000000001001476013476600160350ustar00rootroot00000000000000members: - login: tiangolo - login: svlandeg - login: patrick91 typer-0.15.2/docs/000077500000000000000000000000001476013476600137105ustar00rootroot00000000000000typer-0.15.2/docs/about/000077500000000000000000000000001476013476600150225ustar00rootroot00000000000000typer-0.15.2/docs/about/index.md000066400000000000000000000001021476013476600164440ustar00rootroot00000000000000# About About **Typer**, its design, inspiration, and more. ๐Ÿค“ typer-0.15.2/docs/alternatives.md000066400000000000000000000115051476013476600167350ustar00rootroot00000000000000# Alternatives, Inspiration and Comparisons What 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.15.2/docs/contributing.md000066400000000000000000000172521476013476600167500ustar00rootroot00000000000000# Development - Contributing First, 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 typer repository and you want to deep dive in the code, here are some guidelines to set up your environment. ### Virtual Environment Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `typer`. ### Install Requirements Using `pip` After activating the environment, install the required packages:
```console $ pip install -r requirements.txt ---> 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 cloned local Typer source code. And if you update that local Typer source code 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. /// note | "Technical Details" This only happens when you install using this included `requirements.txt` instead of running `pip install typer` directly. That is because inside the `requirements.txt` file, the local version of Typer is marked to be installed in "editable" mode, with the `-e` option. /// ### 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. ## 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. ## Completion To try and test the completion for different shells and check that they are working you can use a Docker container. There's a `Dockerfile` and a Docker Compose file `compose.yaml` at `./scripts/docker/`. It has installed `bash`, `zsh`, `fish`, and `pwsh` (PowerShell for Linux). It also has installed `nano` and `vim`, so that you can check the modified configuration files for the shells (for example `.bashrc`, `.zshrc`, etc). It also has `uv` installed, so you can install the dependencies and the project quickly. The Docker Compose file mounts the main directory as `/code` inside the container, so you can change things and try them out. Go to the `./scripts/docker/` directory: ```console $ cd scripts/docker/ ``` Then run an interactive session with `bash` inside the container: ```console $ docker compose run typer bash root@79c4b9b70cbe:/code# ``` Then inside the container, you can install `typer` with: ```console $ uv pip install -r requirements.txt ``` Then, you can start the shell you want to use, the one where you want to try out completion: * `bash` * `fish` * `pwsh` * `zsh` For example: ```console $ zsh ``` Then install `typer` completion: ```console $ typer --install-completion ``` /// info In `pwsh` you will probably get a warning of: ```plaintext Set-ExecutionPolicy: Operation is not supported on this platform. ``` this is because that configuration is only available in Windows (and needed there), not in PowerShell for Linux. /// For completion to take effect, you need to restart the shell. So, exit the current shell: ```console $ exit ``` and start a new shell (for the same shell you installed completion in) again. For example: ```console $ zsh ``` Now you could create a demo file on the same Typer directory in your editor, for example `demo.py`: ```python import typer app = typer.Typer() @app.command() def hello(): print("Hello") @app.command() def goodbye(): print("Goodbye") if __name__ == "__main__": app() ``` Because the directory is mounted as a volume, you will be able to access the file from inside the container. So, you can try running it with the `typer` command, that will use the installed shell completion: ```console $ typer demo.py ``` And you should see the completion working: ```console run -- Run the provided Typer app. utils -- Extra utility commands for Typer apps. ``` And the same for the commands in your `demo.py` file: ```console $ typer demo.py run hello goodbye ``` You can also check the configuration file using `nano` or `vim`, for example: ```bash nano ~/.zshrc ``` It will show some content like: ```bash fpath+=~/.zfunc; autoload -Uz compinit; compinit zstyle ':completion:*' menu select ``` If you exit from the container, you can start a new one, you will probably have to install the packages again and install completion again. Using this process, you can test all the shells, with their completions, being able to start from scratch quickly in a fresh container, and verifying that everything works as expected. ## Docs First, make sure you set up your environment as described above, that will install all the requirements. ### Docs live During local development, there is a script that builds the site and checks for any changes, live-reloading:
```console $ python ./scripts/docs.py live [INFO] Serving on http://127.0.0.1:8008 [INFO] Start watching changes [INFO] Start detecting changes ```
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. /// tip Alternatively, you can perform the same steps that script does manually. Go into the docs directory at `docs/`: ```console $ cd docs/ ``` Then run `mkdocs` in that directory: ```console $ mkdocs serve --dev-addr 8008 ``` /// #### Typer CLI (optional) The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly. But you can also use Typer CLI, and you will get autocompletion in your terminal for the commands after installing completion. If you install Typer CLI, you can install completion with:
```console $ typer --install-completion zsh completion installed in /home/user/.bashrc. Completion will take effect once you restart the terminal. ```
### Docs Structure The documentation uses MkDocs. And there are extra tools/scripts in place in `./scripts/docs.py`. /// tip You don't need to see the code in `./scripts/docs.py`, you just use it in the command line. /// 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 to make 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. typer-0.15.2/docs/css/000077500000000000000000000000001476013476600145005ustar00rootroot00000000000000typer-0.15.2/docs/css/custom.css000066400000000000000000000021231476013476600165220ustar00rootroot00000000000000.termynal-comment { color: #4a968f; font-style: italic; display: block; } .termy [data-termynal] { white-space: pre-wrap; } .termy .linenos { display: none; } 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โ†ช"; } .shadow { box-shadow: 5px 5px 10px #999; } .user-list { display: flex; flex-wrap: wrap; margin-bottom: 2rem; } .user-list-center { justify-content: space-evenly; } .user { margin: 1em; min-width: 7em; } .user .avatar-wrapper { width: 80px; height: 80px; margin: 10px auto; overflow: hidden; border-radius: 50%; position: relative; } .user .avatar-wrapper img { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .user .title { text-align: center; } .user .count { font-size: 80%; text-align: center; } typer-0.15.2/docs/css/termynal.css000066400000000000000000000043031476013476600170450ustar00rootroot00000000000000/** * 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; /* Custom line-height */ 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.15.2/docs/environment-variables.md000066400000000000000000000207741476013476600205560ustar00rootroot00000000000000# Environment Variables Before we jump into **Typer** code, let's cover a bit some of the **basics** that we'll need to understand how to work with Python (and programming) in general. Let's check a bit about **environment variables**. /// tip If you already know what "environment variables" are and how to use them, feel free to skip this. /// An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well). Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc. ## Create and Use Env Vars You can **create** and use environment variables in the **shell (terminal)**, without needing Python: //// tab | Linux, macOS, Windows Bash
```console // You could create an env var MY_NAME with $ export MY_NAME="Wade Wilson" // Then you could use it with other programs, like $ echo "Hello $MY_NAME" Hello Wade Wilson ```
//// //// tab | Windows PowerShell
```console // Create an env var MY_NAME $ $Env:MY_NAME = "Wade Wilson" // Use it with other programs, like $ echo "Hello $Env:MY_NAME" Hello Wade Wilson ```
//// ## Read env vars in Python You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**. For example you could have a file `main.py` with: ```Python hl_lines="3" import os name = os.getenv("MY_NAME", "World") print(f"Hello {name} from Python") ``` /// tip The second argument to `os.getenv()` is the default value to return. If not provided, it's `None` by default, here we provide `"World"` as the default value to use. /// Then you could call that Python program: //// tab | Linux, macOS, Windows Bash
```console // Here we don't set the env var yet $ python main.py // As we didn't set the env var, we get the default value Hello World from Python // But if we create an environment variable first $ export MY_NAME="Wade Wilson" // And then call the program again $ python main.py // Now it can read the environment variable Hello Wade Wilson from Python ```
//// //// tab | Windows PowerShell
```console // Here we don't set the env var yet $ python main.py // As we didn't set the env var, we get the default value Hello World from Python // But if we create an environment variable first $ $Env:MY_NAME = "Wade Wilson" // And then call the program again $ python main.py // Now it can read the environment variable Hello Wade Wilson from Python ```
//// As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**. You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration. To do that, create it right before the program itself, on the same line:
```console // Create an env var MY_NAME in line for this program call $ MY_NAME="Wade Wilson" python main.py // Now it can read the environment variable Hello Wade Wilson from Python // The env var no longer exists afterwards $ python main.py Hello World from Python ```
/// tip You can read more about it at The Twelve-Factor App: Config. /// ## Types and Validation These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS). That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code. You will learn more about using environment variables for your CLI applications later in the section about [CLI Arguments with Environment Variables](./tutorial/arguments/envvar.md){.internal-link target=_blank}. ## `PATH` Environment Variable There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run. The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows. For example, the `PATH` environment variable could look like this: //// tab | Linux, macOS ```plaintext /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin ``` This means that the system should look for programs in the directories: * `/usr/local/bin` * `/usr/bin` * `/bin` * `/usr/sbin` * `/sbin` //// //// tab | Windows ```plaintext C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 ``` This means that the system should look for programs in the directories: * `C:\Program Files\Python312\Scripts` * `C:\Program Files\Python312` * `C:\Windows\System32` //// When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable. For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list. If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**. ### Installing Python and Updating the `PATH` When you install Python, you might be asked if you want to update the `PATH` environment variable. //// tab | Linux, macOS Let's say you install Python and it ends up in a directory `/opt/custompython/bin`. If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable. It could look like this: ```plaintext /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin ``` This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. //// //// tab | Windows Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`. If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable. ```plaintext C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin ``` This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one. //// This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. So, if you type:
```console $ python ```
//// tab | Linux, macOS The system will **find** the `python` program in `/opt/custompython/bin` and run it. It would be roughly equivalent to typing:
```console $ /opt/custompython/bin/python ```
//// //// tab | Windows The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it. It would be roughly equivalent to typing:
```console $ C:\opt\custompython\bin\python ```
//// This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}. It will also be useful when you **create your own CLI programs** as, for them to be available for your users, they will need to be somewhere in the `PATH` environment variable. ## Conclusion With this you should have a basic understanding of what **environment variables** are and how to use them in Python. You can also read more about them in the Wikipedia for Environment Variable. In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them. For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md). typer-0.15.2/docs/features.md000066400000000000000000000105011476013476600160450ustar00rootroot00000000000000# Features ## 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 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 the `typer` command. **Typer** uses `shellingham` to auto-detect the current shell when installing completion. If you don't want to include `shellingham`, install `typer-slim`. **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` (if you installed `pip install typer-slim`) 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. 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.15.2/docs/help-typer.md000066400000000000000000000274601476013476600163340ustar00rootroot00000000000000# Help Typer - Get Help Are 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/fastapi/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/fastapi/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/fastapi/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 automated 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.15.2/docs/img/000077500000000000000000000000001476013476600144645ustar00rootroot00000000000000typer-0.15.2/docs/img/favicon.png000066400000000000000000000252401476013476600166220ustar00rootroot00000000000000‰PNG  IHDR๔xิ๚ pHYs ™ ™พ5๓ltEXtSoftwarewww.inkscape.org›๎< IDATxœํ{œ๕๑๗c&ŒSย0ต7ๅE9eW*[าูuwไื=D๙ษ๎hทSิvˆ–ˆ„ฝ%ŠvJ*ีฆฆคธC1„”™ใ ๆ| ’ร0ืต>kญ๏๋๙x|=0๋๚พ3+฿ท๋บึ๕ yž'เ–8๋ (8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8(:ภ!•$•—TARใ,aค‡F–q–ฐ€3$ต•ิ^R#I %5Pqภ๛$ญ“”.้kIK๚TRnธ„<ฯ zŽฺ’n‘ิER;Iๅ‚ž€(tPา'’”๔‚คฬ ' ชฤKบQROI$• bbTกคw%อ”๔/I~OเwHtณคฟHช๏็pิfIใ%MU๑Y_๘UB’n“4Fลง€ฟถJบ_า?ๆGh iขคซJœฤG’๚IZSšƒ”ๆ9!I%ญ‹?แาQา—’จx->-ง{ ฒคi’tบ€R{Mา’v๊ Oง4๔–คบง๚Bเป’ฎ‘ดแT^tช—ZHZ""ลน*~ˆPSyัฉ€Ž’>”TใT&ซ&้I—•๔%ฝะTลw๒œ~"ื>Iฟ“ดโd_X’pžคฅ’’K mปค’ึŸ่‹NV*JZฎโ@t๘ZRk๏ฟpผ3ใลโ@ดซ(iฑเXgฎ–๔vะ‰@ุt’๔‘ฟqtI๚BRณ0†มZ)ฉนคŸฃ/t‹?ฑๆbIฃฯ|,้’p&a๑ฉคv‡qไ€vb๑ Vต•ิๆ๐/โ๘ƒแฯโฏฌฌ,mฺดIปw๏ึ๛ตq€ภIUจPA*TPีชUUทn]UฌXั:Ri!้3้.$HสP๑nBQแเมƒJKKำโล‹๕ษ'ŸhํฺตสศศฐŽˆa)))jุฐกฺทoฏห/ฟ\ํฺตำgœa๋T์’TKRแ๐Iฏ˜F*|-ZดHณfอา๋ฏฟฎœœ๋H‡•+WN]ปvีwกฮ;ซlูฒึ‘Jโ:Iฏห๓Gเ \w3+–-[๘€ตkื=1%===๐9/๋ึญ z bJ8ึฮ@o๔๛l๋88 c.พ๘bฅฅฅ้าK/ตއ]rษ%๚๒ห/uูe—YGมqp€ฑณฮ:KmถUํฺตอ2ไๅๅiฯž=สฬฬิชUซtเภณ,(fอšiษ’%ชPก‚u@ีซWื[oฝฅŽ;๊๓ฯ?ทŽƒฃPŒ”+WNcวŽUฏ^ฝ”˜˜h็g………Zปvญ^uอŸ?_ห–-ณŽ„ชYณฆ^}๕UD”rๅสiม‚jบต~๘แ๋88— $&&๊๗฿Wฟ~"j๑—Š๏ฟเ‚ 4dศ}๖ูg๚โ‹/tำM7).ŽทJค›9sฆ~๓›฿Xว~ฅVญZš9sฆu …ฟี 6LํฺตณŽQ"อ›7ืนs•––ฆ‹.บศ:Žใ๊ซฏึ5ื\c8ฎ+ฎธBื^{ญu fgœq†๚๖ํkใ”ตnZห—/ื๗฿oว0rไH๋ภI5JกPศ:ก„Yใฦuๆ™gZว8-๑๑๑3fŒfฯžอƒd"ศ…^จ-ZXวNชI“&jึฌ™u Bณ*UชXG(ต[oฝU/ผ๐‚โใน‡4\๕ึ€ใ9(aถaร๋พธแ†4eส๋ินsg๋@‰๗ฟทŽ€C(aถeหฅฅฅYว๐Ejjชz๖์iรy\pu ฤ7nฬ}‚``๘๐แึ|3iา$ีซWฯ:†ณTตjU๋@‰UจPgUD €ท฿~[ณfอฒŽแ‹ *hฺดi4z#ๅส•ณŽœฒคค$๋ภฬืลฬ>ฺ;vTjjชu 'Qผx฿F €‘]ปv้ฎป๎ฒŽแ›qใฦ)%%ล: „(†^{ํ5๋_ฒŽแ‹ส•+k๒ไษึ1%D0v๗wkวŽึ1|quื้†nฐŽ( €ฑŸ~๚Iƒ ฒŽแ›I“&qW:D @˜5k–,X`รษษษ3fŒu ภIP"ฤ=๗ฃ={๖Xว๐Eฏ^ฝิฉS'๋€ DˆŒŒ 2ฤ:†/BกฆNสร> ‚Q"ศณฯ>ซwyว:†/๊ึญซ‡zศ:เ8(ฤ๓<๕้ำGูููึQ|1hะ ถฉ€Eˆ0›7oึ_๒๋พˆื๔้ำUถlY๋(€ฃP"ะSO=ฅ%K–Xว๐ลE]คZว…ŠŠŠิซW/ฒŽ๑€ง>๚H:tฐŽRjนนนjึฌYฬ_ ฦ‰UฉRE6lPตjีฌฃ”ฺž={Tฏ^=ํนำ: "FฉฉWฏ^สษษฑŽRj‰‰‰š: ญ_ฟ^kืฎตŽQ*›7oึชUซฌcภaG(55UyyyึQJญRฅJšiรƒ โูp'5z๔h8pภ:Fฉ•+W.f๎k€าขเค๖์ูฃW^yล:†/nปํ6ถ QPBณgฯถŽเ‹๊ีซ๋ส+ฏดŽๆ((‘ฅK—ชจจศ:†/ฺทooฬQP"YYYZปvญu _ดjีส:˜ฃ ฤพ{๋พเ“@ภ)ˆ…GKา™gžiฬQPbž็YG๐E\o{เoB”ุYgeม;w๎ดŽๆ((ฑฺตk[G๐Effฆu0G@‰TฌXQ5ฒŽแ‹eห–YGs”H›6mTฆL๋พ๘๔ำOญ#€9 Jไ–[nฑŽเ‹ฝ{๗๊w฿ตŽๆ(8ฉคค$xใึ1|๑ย /่เมƒึ1ภ'5hะ UฌXั:Fฉๅๅๅiธqึ1 "PpBษษษ๐Cใฐމ€_;vฌฮ9็๋ฅvเภ๕๎;fถ0ฟQ๐ณห/ฟ\ฝ{๗ถŽแ‹‡~X6lฐŽ‹IR๙๒ๅ๕์ณฯ* YG)ต•+W๊ษ'ŸดŽIาจQฃty็Yว(ต‚‚ฅฆฆ*??฿: D4 ิฎ];๕๋ืฯ:†/q}๙ๅ—ึ1 โQ—˜˜จiำฆฉL™2ึQJ-==]>๚จu ˆ ว1B7ถŽQjEEE๊ีซ—rrrฌฃ@T 8ฌYณfบ๏พ๛ฌc๘โ™gžั’%Kฌc@ิ 8*!!A3gฮTูฒeญฃ”Zffฆ็?[ว€จBpิƒ>จ&MšXว๐ล=๗ฃ={๖Xว€จBpPำฆM5t่P๋พ˜;wฎ^}๕U๋u(މื๔้ำ•`ฅิvํฺลNpš(Žน๛ีฒeK๋พธ๏พ๛ดm6๋•(iุฐกz่!๋พ๘เƒ4kึ,๋ต(Žˆ‹‹ำดiำtฦgXG)5v๚€าฃ8bภ€๊ะกƒu _<๘เƒฺธqฃu ˆjิญ[W#FŒฐŽแ‹eห–้ฉงžฒŽQใBกฆNชคค$๋(ฅVPP >}๚จฐฐะ: D= @Œ๋ทฏ:u๊dร#GŽิส•+ญc@L ฤฐ””9า:†/ึญ[งQฃFYว€˜AˆaำฆMS•*Uฌc”;€(1๊ฮ;๏T็ฮญc๘bาคIZบtฉu ˆ)€TซV-ํoณŽแ‹-[ถ่มดŽ1‡ƒ&MšคชUซZว๐E•••eb ฦrห-๚ใhรณgฯึkฏฝfb †œuึY?~ผu _์นSดŽ1‹C&Nœจ5jXว๐ลฝ๗ซํท[ว€˜Eˆ]ปvีวXว๐ลขE‹4gฮ๋ำ(1 rๅสš2eŠu _์฿ฟ_๚๕ณŽ1ฦฏ””๋พ:tจ6mฺdb ส]yๅ•ู๊ณงu _|๖ูgš]eส”ฑŽโ‹>}๚(;;:8‰Eyไ5nุ:†/fฮœฉท฿~:8‹%.พ๘b 4ศ:†/~๚้'๗ทu p ฤววk๚๔้*[ถฌu_๔๋ืO;v์ฐŽNฃD|Pอ›7ทŽแ‹7xC๓ๆอณŽฮฃDธF้ฐŽแ‹}๛๖ฉo฿พึ1ขDด2eสhๆฬ™JLLดŽโ‹!C†่‡~ฐŽ ข ๐C“ ภษp€˜•””คฮ;ซ[ทnบโŠ+Tปvm…B!฿็๑>^yyy …Bอ่€P(คsฮ9'ศ)ˆ9ฟํo]ฅ0|  aร†AO@L วฺI ย4hะ ๐9/ญZต z bJ๋ึญŸ#ะ›%)33Sตkืr bF(RFF†’““'๐3ตjีโ2%ิธqใภ)Lพ๚๋ร1 Q/\kfเ—$้๋ฏฟึ\๔4DฝีซW‡eอ ห€ฦซy๓ๆแ˜ €จีฒeหฐƒ9lป๕๔๏฿?\S• ถนยr @’๒๓๓ี Amผ9ำUฮ=๗\ญ[ทN๑๑๑a™/lgส–-ซ๛๏ฟ?\ำUxเฐ-RฯHRAAZตjฅ•+W†kJ"^๓ๆอตlู2•)S&ls†ํ €Tผปัฤ‰฿เ€hง‰'†u๑—ย\$ฉ}๛๖๊ทoธง "๕๋ืOํฺต ๛ผ!ฯ๓ ๆ"““ฃK.นD+Vฌ็ดD”ฆM›๊ำO?Uนrๅย=uQศ๓ผ}’*†{ๆu๋ึฉUซVสสส ๗ิ˜ซ\นฒ–/_ฎz๕๊YLฟ/NRถลฬ 6ิ‚ ”˜˜h1=f๔าK/Y-’”'ษ์Ÿเ—_~น^|๑ลฐ฿๘€•ธธ8=๓บ๊ชซ,cdลIฺa™เ๘ƒž{๎9%$$Xฦ p š={ถnผ๑F๋(;โ$ฅ[งธ๕ึ[ตhั"UชTษ: HJJา‚ t๓อ7[G‘คuq’ึYงค+ฎธBK–,Qร† ญฃเซF)--M;wถŽrุบ8IkญSึดiS-_พ\ทv›u|ัฃG}๙็บ๐ย ญฃi]ศ๓ผZ’ถJŠจว๓-\ธP ะฆM›ฌฃpสฮ=๗\M˜0Aื^{ญu”ฃy’jลIสT„\8าuื]ง5kึ่‘GQ•*UฌใP"UซVีฃ>ช5kึDโโ/Iซ%m“็y๒๔kP2’‘4Sาห’ ž จpคš*พ๔_S‘R@๙๑1@e(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8ˆ€ƒ(8่ฟ้๙ๆ ฺtdIENDฎB`‚typer-0.15.2/docs/img/github-social-preview.png000066400000000000000000001430571476013476600214150ustar00rootroot00000000000000‰PNG  IHDR€ร๓ีก pHYsรรวoจdtEXtSoftwarewww.inkscape.org›๎< IDATxœ์yx w๑๗ฬ9"b—อTฉ"จตจ *ถปE{ํญถึRKธ{W›Rีj๕VตtำVซJmA,Mb_ชJ,qW‚ศ"ษ93ฟ?z๓ีMIฮ9Ÿ9“็ใบ๒ซ=๓สษd2๓:ŸE3M3Oุ’ฎ:๏ก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 @ภฦœชืกๆ …๙›/P†Vฃ๏‹ย€?๛ํ5ฬใ_เS€ฐMฏ๘;าDฤ๑ฟ/CDjใ(J(กšC(-W>๐p #๘ Tั„๓@ัvๅทฐN /bไTpๅˆ฿‡!=เ5\`?€๐+K–,‘&Mšศ๒ๅหUG1MS>๚่#ฉ_ฟพฌ^ฝฺ‡ฃP €๐4ฏ= ๋_’๛๎ปO.\ธเญC@œ={Vบu๋&oฟถ7C @ุžๆ•ิQฃFษฌYณผ๑าเnท[FŽ)้้้2aยีqเ*Fย๒ž{๎9ส?~ใŸงฬœ9ำ/อ@BK๛๓ฯe๒ไษชcภM7nœฌ_ฟ^u  @XXrrฒ :Tu ธi†aศร?,iiiชฃ ฌk๔่ั’™™ฉ:ศษ“'Y €%Pย’–/_.฿~๛ญ๊P(๓ๆอ“={๖จŽ ˆฃ„%M™2Eu(4ร0Xว€r€ฐœ๘๘xIHHP<โซฏพ’ไไdี1a€ฐœ>๘@u๐ร0ไ“O>Q@FKqป๒ีW_ฉŽ๕ล_จŽ ฃ„ฅ์ูณGาาาTว๚แ‡ไ๔้ำชc(ข(a)ฌภŽLำ”ฤฤDี1Q€ฐ”จŽ^q๐เAีQ€ฐvส`WGU@EKษศศPผ‚๋U(a)ูููช#€WdeeฉŽ ˆข„ฅ†ก:xื7ชP6Fุ `c€€9UfN:ีญ:วHNNึๆฮซ๔Cสฝ{อ›77UfธQu๊ิQG@Pบtiy่ก‡b+โ„„ๅ`ซVญฬ|ะ//เฏ0ฐ1 @ภฦ(ฃlŒฐ1 @ภฦ(sช€ทdggห้ำงต‹/สล‹ๅาฅKr้า%-==]‚‚‚$ @J—.m:N)]บดH๙๒ๅ%,,ฬิuฦฬฐ @–•ŸŸ/็ฯŸืRSSๅ์ูณryํย… ๒ฟ2Gปx๑ขdddศล‹ต;W฿\น|๙ฒˆˆ+VL‚‚‚$((HŠ+&eส”นZ๘„„„HhhจYฉR%‰ˆˆ0kึฌi–(QBีทŒสฬฬ”ญ[ทjIIIฺ๛_ํุฑcZrrฒ–ššZ ืs:Rนre3""B"""ฬˆˆณFาฐaCณfอšฆฆi RNNŽdffj.\ŒŒ ษศศะ222$++๋๊uล4Mษฬฬผ๚ƒฟx๑ขธn‘ซื–RฅJ™‡Cส”)#บฎKูฒeฅt้าfppฐTจPA*Tจภ๕JPPย4MIMMี~๚้'9qโ„๖๓ฯ?หษ“'ต'Nh'Ož”ณgฯj:Ÿถ7šฆIลŠอฺตkK๚๕อ ˜76#""Lo…มอKKK“-[ถ่[ถlัตƒj†ax์๕].—?~\;~ธศoฮ—rๅสIใฦอ&Mš76›5kf–.]ฺcว†็\นฮ๓ฯr๊ิ)ํไษ“r๊ิฉซื›ณgฯj้้้’ŸŸ๏ณLRฉRฅ_•หUชT‘ชUซšRฑbEำ้คฎgqFaฉฉฉr๐เAๅรX"##อเเ`ี1 $//Oถlูข=lบต้p8Tว๘Cyyyrไศํ_r๘๐aํ่ัฃฺัฃGต\ี๑~ว4MIIIัRRRdใฦWถUชT1[ตjeFEE™;v4*Tจ 2f‘”‘‘!ห—/ื—,Yขoบีฃ…฿อHOO—ธธ8-..ฮ!๒หhม-Z˜ัััF๗๎อส•+S+šš*ะฎ|}Z;}๚ด์ุฑใWaฑbลคVญZfํฺตอz๕๊™u๋ึ5###ฅbลŠ”ฮธ.ๅ7{PงAƒfppฐจ~hJHHะนTแ๎ป๏V๖เ—šš*[ถlัทmฆmูฒE;t่veM,;sปฒm6mถmŽษ“';ฺดic๖๏฿฿่นณaี‘˜ๆิฉSฺ‡~จ๚้งzZZš๊87ฬ0 ูฒe‹ถeหวK/ฝไxเŒAƒUซVฅ )„3gฮh‰‰‰ฺ๖ํตmถiIIIEโZ๓[๙๙๙WG9.]บ๔๊ฟ—+WN"##อ6mฺ:t0###9฿๐+€E˜ฎ๋าฆMใ๋ฏฟVบีๅŽ;4—ห%8ญi็ฮส ภ:๘ฌ<อษษ‘M›6้๋ืฏืโใใต๗ฟสฟี\.ืี้ ๚ภ‡~ุ(Yฒค๊h~้๘๑ใฺฬ™3๕/พ๘B๗๗‘ฃ2w๎\ฝ๗ำ๛๔้cŒ=ฺจ^ฝ:ลฬ ศษษ‘ฤฤDmำฆM๚ฦตร‡๙kอ๕คงง_…:u๊T 7;t่`>๚่ฃฦmทฦ9 @€ขฎ]ปvๆื_ญ4Cvvถ์ฝ[kผน_=ค˜ฆ)ปvํRZžH๋ึญฝพฝ๛zllฌž˜˜hน๕ดฌไฤ‰ฺคI“oฟถc่ะก๎มƒS ฬฬL™6mšใฃ>๒๛โ๏ท\.—,\ธP_ผxฑ>pเ@ใฟ็.SฆŒ๊X–ณ~mํฺตฺฆM›๔;wjVœ๎ํ/ฮœ9ฃ}๖ูgฺ}๗gvmชใภ(9•*U’J•* wซ—-'Nœ“'OŠชEผˆ}๗†ฆiำTฝ%$$๘]˜œœฌฉžšุขE 3((ศ๋วy้ฅ—7.--M^yๅว๛๏ฟ๏x๚้ง<๐€ก๋Jปbห2MS/^ฌOž<ูq๖์Yีqผสๅrษ๙๓๕ฏพ๚Jืฟๅพ๛‹๔ หๅ’-[ถhซWฏึืฌYฃ8q‚Q~คišิฏ_฿ฏฎย{( •+W–qใฦษ}๗'ทzซ๊8๘3gฮศฒeหdฦŒ๒ใ?ชŽ9aaar๛ํท›ชwNHHะวŒใWรEm๚/n^jjช<๙ไ“Ž?P๕ีW 4เa?๘ฃ6aยว๖ํ•.๙RZZšŒ7ฮฑt้R}๚๔้๎J•*™๓"77Wึฎ]ซฏZตJ[ฟ~ฝž‘‘ก:’mUญZีdค)ฎเ#(ถฆiš<๓ฬ3r๘๐ay๒ษ'),*<<\† "฿ฝฬš5KŠ/ฎ:PไดkืN๙ร็ฮ;njiQ฿7n฿พ}Z๗๎SงNe$ฅ2๒k๚๔้Žฎ]ป:‹Z๙wญ 6h:urฦฦฦฺ๚=pน\ฒn:m๔่ัŽ;๎ธฃุฃ>๊Xฒd ๅŸ—1๚ืb เฅK—๖๘"ๆL‘ผI‡C,X ๚๕S7ศแpศˆ#คqใฦาญ[7นpแ‚๊H@‘ัฎ];cฮœ9J?พt้’์ฺตKkูฒฅ฿<ฐจ.#""ฬฺตk๛อ๛Uิน\.y๋ญท๔ 6hsๆฬqีอ Nœ8ก1ยa…ดVpแยyไ‘Gœร‡7ฦ๏ถำT๑ญ[ทjK–,ัฟ๖[ฟฺษู.(q- @ภCŠ/.“&M’GyD‚ƒƒฝrŒดด4ูทoŸlธQพ๘โ ๙แ‡ผrป˜>}:ๅŸŸjีช•,ZดHbbbฤn‹€VีฒeKณD‰r้า%ฅ9ฆฬสสีปR2๚ฯ?ํทO๋าฅ‹s๚๔้๎๎ปฉOx7oฌ1ยI๔kฆiสoฟญ=zT๓Ÿธ}ฑฎง/๔๏฿฿ฉ๚๏JQFˆkู็ฃ@ฑE‹ษ“O>้ต๒ODคB… าฎ];y๙็e฿พ}ฒz๕jiฺดฉืŽ็ฯ:v์(cวŽU…ะฉS'y๑วUวŠŒโล‹K‹-”?(ฤวว๛อ้ฮ;5ีฃ๓;t่ g†‚ษสส’ว{ฬ๑ๆ›o๊ช7เ๑•๙๓็๋  ปŽีซW๋}๚๔qฆงงซŽ`อQ\หonฐ+‹ŠŠ’^ฝz๙ธ;w–ญ[ทสห/ฟ,‡ร็วทฒฉSงชŽ˜0a‚”.]Zu  ศhืฎ๒‘Hปw๏ึ._พฌ:ฦ Q=}1 @ขขข”ฬPpฆiสดiำcวŽuุ}ฤ๛ซฏพ๊˜8qขํฟOOุปwฏึงO็นs็TG “ฐฐ0ี1`!€€ดhัBูฑ‡Œ?^พ๚k6N๘ŸศศHiึฌ™๊๐€้ฝป๊@‘ัถm[ๅ#._พฌผXปQ;v์Pz/ผysณdษ’*#ภC/^ฌ4ศ้/ๅ๗อ0MS^xแวoผมณ็M8pเ€v๗3ฦ่?aภฌ๐‡น{๗๎๒้งŸŠฆ๙ล3“WuํฺUux?Oภw๊ิฉcVฌXQ๙ƒ?L6 Cv๏ญ๔n‡g#๋ึญำ์ฬฯฯWลc\.—Œ;ึ1o<หN[QRR’6pเ@ึะCDFF*{kแB xภาฅK%;;[u น๏พ๛ไษ'ŸTCนš5kชŽชUซ–๊@‘าฎ];ๅ –4+))IหสสRš @์'..N9rครํvซŽRh.—K†๎Xผx1ฯœ…ฐ}๛vm๔่ัŽขฒN$<‡ @๐[\Œ8w๎œผ๐ย ชcˆˆศคI“$22Ru ฅX๋ย^ยรรUGŠ+€ปw๏ึฌ>โeวŽJKสส•+›u๊ิQณ‚็}๛ํท๚๘๑ใzqgร0d์ุฑŽ+V๐ผ้+Vฌะ_}๕Uฟ>'เ{LฦoqA<ไ๕ื_—„„ี1$ @}๗"ฝ)HQํศ้tชŽ)mถ5T_G๓๓๓e๛๖ํ–จzย๖ํ๓`gcŸ|๒‰>w๎\ฟ}V›0a‚cษ’%~›฿ŠfฮœฉวฦฦZ๚บ๋([ถฌTญZ•ฟ๘.ส€‡†!ƒ–\ีQคE‹2bฤี1~จ|๙๒–˜6”`้๛T @xคI“qqq~W๘ผ๓ฮ;๚G}d้฿_dšฆ<๑ฤฮ””ฟ;'เ{‘‘‘&kรใทธ0t๐เAy๙ๅ—Uว‘)SฆH5Tว๘!+”KV^0--MŽ;ฆ,_ฑbลคM›6lbsnท[q็ฉSง,๛ป๐[ซWฏึ'OžฬT /IKK“1cฦฐ ’>ศƒ๕P6eสูตk—๊RฒdI™5k–๊?ญผ\ฺปwฏv๑โEี1ะŽ;t•เอ›77K•*ฅ์๘๐ .ศฐaร.—Ku”ฟ๔ำO?icฦŒq†๒ห‡ญลววkŸ~๚)ฯ๑ธ.ึรแยx˜หๅ’AƒI~~พ๊(าตkW้฿ฟฟ๊?ำจQ#3,,L้รƒหๅ’mถYrไ“ฆาฐ!ปw๏ึz๋-K?ท]พ|Y†๊Pฝ3vQ๑๏‘ššช:,,22’ฟc้?$€ฟฺปwฏผ๚๊ซชcˆˆศ›oพษฎธ€›ขišt๊ิI๙รƒUืTฝฐฆhรท|๓Mว?hษB\ไ—๕ ๗๏฿oู|v“••%SงNeช5P‰%คVญZภ๏X๒ฆ ฐƒ—^zI๖๏฿ฏ:†ห๋ฏฟฎ:ภฯt๎Y๙(ณu๋ึYฎPpน\๒๗฿+หUฑbEณnบ<ุ1๙๙๙๒ฤO8nท๊(ฟ“ }๐ม๘cK=ฟeggหO<มบ †!ฏผ๒ -~‡ @๐g,๕ฐ›ญ[ทสฬ™3Uว‘ทzKJ—.ญ:ภOZbงูตkืZชT=๗๎ป๏ๆมฎ›6mš#--MuŒซfฬ˜แ๘๙็Ÿ-๕;๚W*Tจ u๋ึ5๏ธใ3**สŒŠŠ2›4ibึซWฯ ๗ซ฿ฏ 6hชื$…๕Pโฯ8U์๎น็ž“=zHญZต”ๆจVญšLž_fฬ˜แ๘๘ใญฟM4|††๐ฒœœ2dˆฤลล‰ฆฉ€nไศ‘ฒhั"‰Wšเ:u๊d่บฎtz_bbข–-%K–T–แZ*G4kึ์/ห ;ซRฅŠyห-ทH… ฬ๒ๅหหพฬrๅสI… ฬ๐๐p 3CBBDืฏ฿KฅงงKRR’v๐เAm๓ๆอฺฆM›๔์์l}'…๓ัG้ร† 3"""”>ไ?๓—หzฝ“ำ้”{๎นวธ๛ป๏พ๛ฆืB ’† š 64๛๕๋'.—หฝbล }๙บGmุฐA;t่VปvmJˆำ้ึ‰ลŸก|`ใฦ2gฮy์ฑว”ๆะu]ๆฯŸ/7–\ฅYึ"52wํฺฅ์ก7//O6nจวฤฤ(๙v๚๔iํไษ“ส‹๖ํ+T=zด1`ภผๅส•“-Z˜-Zด0๑H^^ž{ำฆMฺ๙๓›7oถ\ษsญผผ<™1c†ฺkฏ)งลวว[๊}า4Mบw๏n<๔ำฦญท๊ฑฤ้tJฯž=ž={‰‰‰ฺไษ“ปw๏ถฬ๗nšฆฬŸ?_Ÿ6mš๒QกPฏvํฺf@@€๊ฐ(Kู์ไ™gž‘ใวซŽ!u๊ิ‘gŸ}Vu ฏ๒—O๐qc๘yjYa7`ซฌธ}๛vฅ9ฺทoฯจ/ ่่hsแย…ฎ5kึธฌพูส—_~ฉงคค(9 รษ“'[j๓‰*Uช˜ .tฝ๓ฮ;nO–ฟีชU+sูฒeฎทzห]ถlYoๆฆ-YฒDWน9ฌƒ้ฟธF>’™™)ƒ ’ุุXๅSว/_|๑…์฿ฟ_io9}๚ด๊๐ 'NจŽi:u2งNช4C\\œn†๛ฏฆuz›ส๕ยรรอz๕๊๑`็‘‘‘ๆG}ไZบtฉย /8RSSUG๚|™;wฎ>qโDŸ๚Zนrฅ~เภK”๒""111ฦ๋ฏฟ๎๖ี๔xMำไพ๛๎3ZตjeŽ1ยฑm6ๅ๏ENNŽฌZตJฟ๏พ๛”`cGๅส•“388X‚ƒƒอ๒ๅห‹ˆH‰%$ @œNง”,Yา๙ฅ ฯสสา๒๓๓%''Grss%77Wrrrดดด49sๆŒคฅฅiyyy^ษJˆ๋ก|hบuฒ`ม๙ว?ก4G@@€ผ๛๎ปาบukqปํ7[`๛๖ํช#ภƒv๏ญ:PคีฉSวฌVญšy๘qeนฉฉฉฒw๏^ญqใฦJlT€ํท7U€Xิ๔๊ีหhีช•1pเ@็ฝ{-๗ๆ๖ูg๚SO=ๅ ๒้q฿yฬ’kง IDAT็หฬ"5j”๑์ณฯบUnTชTษ\ดh‘๋๑วw,_พ\๙{ฒdษ ภ(WฎœิจQรผํถฬ*UชHXX˜YฉR%344Tยรร%$$ฤ,Vฌ˜ว›žž.วืŽ;ฆ%''หัฃGตไไd-99Yหสส*๐๋Rโz(3fŒDGGK•*U”ๆhัข…Œ9Rfฮœฉ4‡7ฌZตJ๒๒๒„๕/์แ๋ฏฟV(๒:w๎lฮŸ?_iฒvํZฝqใฦส>ตสออ•๛๗+-Uป( “/ฟา๕๘ใ;Vฎ\ฉผไนVVV–|๛ํทz฿พ}}V๚lถMSน&่ตฦg<๙ไ“J?ษูณgปNง|๕ืJฯM›6i็ฯŸ—เเ`•1,-,,Lš6mj4hะภผ๒ช$Kนrๅค\นrfร† wm?{๖ฌ>|๘j!xไศํ่ัฃrโฤ ํz๏่บ.‘‘‘ญภŸข|,##C†.฿|๓๊(2eสYพ|น$''ซŽโQ้้้2o<9rค๊((ค-[ถศ–-[TวŠผฮ;๓็ฯW๚pปvํZํฉงžRvฝ{๗j๙๙๙JŽํt:ฅ]ปvŒ์QคD‰๒ฮ;๏ธyไm๚๕–(ฟฎXธpกO @ซŒ{ไ‘G”—W่บ.oผ๑†;55UKHHPv~ธ\.Yถl™>pเ@ฎืจ^ฝบ9|๘pฃUซVfญZตข •ะะP๓ฎป๎๚U^—ห%IIIฺฝ{ต={๖h{๖์ั’’’ฎ–‚ทr‹YชT)%™แ,qŠšeห–ษ็Ÿฎ:†ษฌYณTว๐ŠI“&ษนs็Tว@!ไ็็ห˜1cฤ4โ^ ฐต-Z˜ชผ฿ฟฟv๚๔ieื*ง6iาฤ๔ี๚f๘cNงSๆฮ๋jิจ‘ฅ(mบU๓ี๔ฃGjฑฑฑสŸ5jdพ๐ย –(ฎ(Vฌ˜ผ๓ฮ;ฎฐฐ0ฅ9TBดขศศH๓ก‡2ฅปงำ)‘‘‘fฟ~ŒiำฆนืฌYใ:t่P7฿|ใz้ฅ—ƒฆลuq9rคXaQ้{๎นG  :†ว>}Z๎ฝ๗^๑ึป๐พ1cฦศถmTว ฟj™๗ฯ<๗s†สŠ๑๑๑<ใ๘Cึ; 1#FŒvํฺ‰๊้$๚๕“E‹YbsO;v์˜๏—็Ÿ^๎ฝ๗^iบตDDDˆ7ืณ ‘2eสxํ๕}%''GRSSล[# rrrไฤ‰ฒ{๗nYบtฉlบี+วPxAAAาพ}{cลŠส./]บ$฿}๗ํำBl๛๖ํสFิดmึะ4๔Xอ๓ฯ?oฤฦฦ*/ลฎุดi“ๆอ๕ }5อ๘zžz๊)ฅลฺชQฃ†ูณgOใซฏพR๒žฉˆ€ตY ุ\JJŠ<๕ิS2|ีQไญท’ธธ8ฑฺ๋!‡’W^yลgว+]บดt์ุQ๚๖ํ+<๐€8Ÿป0ึฌY#Ÿ|๒‰ฌZตส๋TฐŽ=z˜+VฌPšaํฺตzttดOง`๎นSูuปvํgA+V4}๔QcฦŒส‹1‘7๊ฃG๖สงu)))ฺถm”–J๕๊ี3ปu๋f๙ัW 2DYxเภ-==]ส•+งโ๐,ฬฐ€ข๎w฿•ีซWซŽ!ีชU“ษ“'ซŽaYYY๒๕ื_Kฅaร†๒w฿ฉŽt]GŽ‘๖ํK—.]dม‚”~ว ำ€ืฎ]ซ๙zIU€่บ.w฿}ท฿”Eอˆ#*TPCD~)ฉณณณฝ๒ฺkึฌ๑๙๏o 6ฬฏFยqวf:u”ผi†aHbb"ฯ๙~‡ `>๚จ%F9RขขขTวฐQ:v์(๏ฝ๗ž๊(hใฦาขE ูฐaƒ๊(,,((H:t่ ดJIIั~๑GŸ5YYYr๘๐a%อCƒ LซL๘ฝ   y๘แ‡-Qะๆ็็{m๊็ชUซ”>3Kฯž=-๑>฿Œ=z(หผo฿>iK๘  `?๔“L˜0Au ัu]ๆฯŸ/ชฃุN^^ž :T–-[ฆ:สฏซ:†-†!=๔œ={VuqปางOIOOW€ŸˆŽŽ6‚‚‚”fˆ๕ู=ฌช้ฟ"ย๔_?`ฅัiXงoถmZ~~พง_๖ฆ๘๋:˜u๊ิ1CBB”;))‰€~‡ฐร0d่ะกr้า%ีQd๘๑Rฟ~}ี1l)##ร2k-.Xฐ@๖๏฿ฏ:?RขD ๅำ€๗๎ซ;wฮ'วฺฑc‡’๛ๅ2eสH“&Mฒ๘(j๚๖ํk‰๐๐แรšหๅ๒่k&$$(}^t:e‰๗๗fiš&wyง’์วื._พฌโะ,Œฐ˜C‡ษฤ‰Uว€€y๗wf็Z๓๛๏K^^ž๊2gฮี๘!ีำ€ รีซW{>ึๅrษฎ]ป”Œค‰ŠŠ2œNงŠCใ&ตlูาฌ\นฒ๒ฒ6//ฯใ๋Uzk]มีธqcณt้า*#Jฝz๕”œnท[ฺูฅฌ‹ฐ ื^{MTว;๏ผSFฅ:†-eff*฿855Uถo฿ฎ4ิฑcGๅำ€Wฎ\้๕๛ุ={๖h/^๔๖aP๛๖ํ•Jธ1บฎ+/ลฏุฟฟวJŸผผ<พ^Aดkืฮ/G]QงNeวf0€฿ข,ศ0 2dˆไๆๆชŽ"/ฟฒz๋ญชcุา”ะกCb~}_ @+LŽืฒฒฒผ~ ฏเ:(‹๊฿‡+<นC๖”ฏืฐaCฟ=จ^ฝบฒGŽก๐+€€E8p@ฆL™ข:†ษฌYณTวฐฅำงO+=~jjชาใ๐oชG<ๅๅๅษ๚๕๋ฝz/ฏไ^๙๖o7+UชไืลGQำขE ณdษ’ชcxtฤฝ{•Hทv›๊…RฑbEeฟวgฮœQuhEXุห/ฟ,ปvํRC๎น็0`€๊ถฃ๚Su+ฌAภu์ุัP]xฌ\นาkE^^ž์นSIโฏปžeลŠ“–-[*น:tศฃ#=๕Z(ส฿ำย(_พผฒ๕ดSSS•ธฌ…ฐ0—ห%ƒV^‰ˆผ๑ฦฆ:ภ"ฌ0 x๚๕บทvบฑc‡ฆj)Ž๖ํ[b:)nN“&M”ฮž=๋ฑ๘TO!ฝ๕ึ[M]๗๏วUMำคx๑โJŽ}๖์Y%ว`]l-Xž={d๚๔้2~xฅ9‚ƒƒeฦŒาฟฅ9ึัฝ{wsูฒeสŽŸ-›7oึขฃฃ=>JHี๔฿   KŒ$รอปใŽ;”Lำ”3gฮhUซV-t–ไไdOD*ฐ’%KสฆM›~#X เ^|๑E้ีซ—ิซWOiŽ~๚ษขE‹ไ›oพQš` ัััFฉRฅชvส๙e7เ่่hทง_7!!Aษรsห–-อ€€‡F!5jิศิ4MLSm˜’’"UซV-ิkdggห™3g”Hทoื|๐AžW (--M รE ภsธ~เ๒ๅห2x๐`qป=|sำfฯž-eห–U`ฃtฺcllฌ๎้ฟ—.]’ปw+)?:u๊ค|) ฆ|๙๒ช| 'ŠปŸYS]dขp\.—œ?^u B๘‰-[ถศ[oฝฅ:†Tฎ\Y&Mšค:ภ"๚๖ํซดฐ:ผlบีฃeึญ[5๋๏jš&:uขu๑c5jิPARRR ์ k็ฮc0€ซ(?๒ฯSŽ9ข:†Œ1BขขขTวX@ห–-อ*Uช(-ญVญZๅั{ฺ„„%๗ศ‘‘‘fฅJ•(ุ-ทข็็‰€ชงย3.]บค: กHNNŽ :T๙ฺ2บฎห๙๓%00Pi€zบฎK๏ฝ•a๚๖o=:]1>>^I๙แอLเ[ีซWW3๔ฤ๎ฏงOŸ๖@จๆญ]า๘' @ภฯlุฐAๆฮซ:†~๛ํสw&XC฿พ} MS7`่๔้ำฺŽ;<เโล‹๒ร?(๙f:w๎ฬ๚~.<<\uŒ๚บpแ#m //OuB๘กงŸ~ZŽ?ฎ:†L˜0A5jค:@ฑ[oฝีlิจ‘า‘Oห—/๗ศ}mBB‚๎rน<๑R7%,,L6lจ|๔ '88X๙ฯ๐๒ๅห….๏ฒฒฒ<Šyโ\`€€สฬฬ”ว{Lu q:2gฮq8ชฃSฝศ๒ๅห5ร(|Uำ;u๊คt%<ฃB… ช#Hnnnก_###รI #\‹๐S+Wฎ”ฯ?\u น๓ฮ;eศ!ชc๋ีซ— ์๘งOŸึv๎Y่-!!๕P`v)/^ผHmž8ุ เว&L˜ nท[u ™8qข/^\u €Bๅส•“่่hีฃ uo›žž.๔y๑(mฺดa?(Qข„ู๊๘‘c๖ภฯภต(?v๔่Q‰UC*Uช$=z๔P XŸ>}”Žb[ถlYกฆ๗wบ'ฆ฿ฌจจ(ร ล ฏXฑbสGrzขฬฯฯ๗@€•P~n้าฅช#ˆˆHฯž=UG(ึฑcG#88Xู๑ฯœ9Sจiภ‰‰‰LEกจœ…' @แภ๓๘`ภต(?ทcวีDDคiำฆช#s:าซW/ฟฌbMำคSงN€6a…ะ›ณYa‰ €kQ~.%%Eu๙e0ฝ{๗VZt๐นs็ไศ‘#>/๋ืฏoVฌX‘ะ&ฌPœyb]f]็1ั‚‚‚ธถธŠ+;เ็ฌฒFKฑbลTGX@ฃFฬฺตk+{ุ่ต @ืข\ใฦUG‘ฝ{๗ชŽฐˆพ}๛*G-_พ\ฟx๑โ ๗ฆiสฆM›|~_n6lุ€6โrน”ษืขฤ€ฎE๘นŽ;ชŽ ""๋ึญS`aaaานsge#ฒณณๅ›oพนแ๛๛๗kฉฉฉŒ๔‡บu๋fjš๒พ”™™ฉ:‚Gฆ—*UŠbฺฟ' 3๏ผ๓NFYูLFF†๊Rพ|๙Bฟ =pp เงnปํ6yโ‰'TวำงOหไษ“UวXŒฎ๋าฏ_?ฅ#.\๘—๗บ/^”;v๘|`ทnL]็Vnาำำ•ฯ้ -๔k”-[–โศฯišf‰ฌƒปภ้บ.๓็ฯทฤยพ?ธ\ธpAu €๏Wบศโล‹๕ฟฺ•๕ป๏พำ๓๓๓}”่๔่ัƒ้ฟ6”’’ข:‚„††บผ ๓D(Tฎ\9Qy`=\?4bฤiถญ๊ฒ|๙rYผxฑ๊‹ •ฮ;+VฌP๒กsZZšฌYณF๏ฝ๛Ÿ–m*ึ “ๆอ›3ยส†RRR” )๔kTชTI๙๙yฯ=๗ฯ?\u €ล 0@Y(๒ห4เ๋€7n๔yac0ืžNœ8ก:‚„„„บผ ๗D”Bซ^ฝบ๒"์‚;ภhš&sๆฬฑฤzO>๙ค%nrึึฆMฅ›lฺดI๛ณQYษษษฺ๑ใว}^^ฏ„๛๙็Ÿm1Pๅ๏์YYYส฿Kฐ @ภ ฟ๎ฺตซแp8|}X๘ˆสชš5kz์wฌAƒส ภu๋ึ๑ผ ยฐธูณgKhhจ๊๒ฺkฏษฮ;Uว๘)Mำ”ผv37ฒ๛/<&33ำS€=Yึฏ_฿T]X'&&j™™™J3€]PึณgO้ำง๊r๘๐ay๑ลUว๘น|ะ Tv/ฟRฯฮฮ–””อืe ำํmำฆMบํVCjีชๅฑื*Qข„ิซWO้9{๙๒e๙ไ“Oxfเb XTppฐฬ;Wu 1MS†.—.]Rเ็ส—//๛฿”‚หสส’/ฟR‹‹๓๙Hญ˜˜ฆฺุŠ+”ซPก‚TญZีฃ…]ซVญ”—ึ๏ฟ๎โ ธ9€€Eฝ๑ฦฎ:†ฬ›7Oึญ[ง:ภ& คt์‡~จค์ำงำm*//Oึฏ_ฏนชI“&†ฆy๖ิnูฒฅ๒๐ไษ“ฺตำ๗ร…ฐ ˜˜0`€๊’’’"ฯ>๛ฌ๊‰ŒŒ4›7oฎฌT8x๐ ถfอŸืจQรlธฑ๒"ฑz๕j=++Ku iาค‰วฯฑป๎บห(Vฌ˜ง_๖ฆฝ๒ส+Žs็ฮฉŽ~ฐ˜2eสศ;๏ผฃ:†ˆˆŒ9R.\ธ :ภfT๔๕tยฝ{3๚ฯฦ>๐CKะ7oฌ~ฺ†ˆ„††JำฆMฝz๓ึญ[7ฃL™2<ฤMษฯฯ—#F8&Ož์pปชใ€_ +^ผธผ๛๎ปb…ตUžy9rไˆ๊€"ขI“&ๆwaOุรž๖๏฿ฏMž<ูก:ว;v4ผ}$๗฿ฟฅ {ำ4ๅํท฿ึxเ็O?d‰2ึ๒๒๒TGเวิ7@๗๏[๊ึญซ:†์ูณG|๓Mี1EŒ]G–/_^:v์hห๏ญ(;qโ„6`ภวฅK—TGนชK—.>)š ไ๕ขฑ ต:8gฬ˜กฑ ;sๆŒถ`มฝฮ-ZS€ฒ(B7n,ใฦSC\.— 4H\.—๊(€"ฆWฏ^FHHˆ๊ืฃGฃX1žี=)##C้๑๚้'ํ๏ป#55ี2ฃอJ–,)mถ๕Iั\ฝzuณK—.–,ตssse๚๔้Žึญ[;,X ็็็ซŽT(ิfฮœฉวฤฤ8›6m๊?~ผcร† Zjjชœ={Vu<~ŠP$ @,X NงSuyๅ•Wd๗๎ชcŠ €€y่ก‡,Y*ปz”)S?ธใฤ‰>/เถnช๕่ัรy์ุ1ห”""={๖4}vผ1cฦšfฉทเWN:ฅ?ับuk็์ูณ๕๓็ฯซŽtCN:ฅ}๙็๚่ัฃอš5sv์ุั๙ส+ฏ8๖๎ซvm๎C‡Y๗ภา(Ež{๎9ฉ_ฟพ๊r่ะ!™4i’๊€"lเภn_–Vฝzuำ›2EฆiสW_}ฅทnฺ9jิ(ว๖ํWŽxฺฅK—ไล_t๔ํืiล2ฉ>-š4h`๚ริ๖“'Oj“&Mr4mฺดุc=ๆXนrฅnฅiฉฉฉฒdษฉงžruื]ฮๆอ›;ว็๘๒ห/๕”””๋|‡ฆP ๊‡EPร† ๅูgŸUC ร!C†Hnnฎ๊(€",$$Dz๗๎m|๒ษ'ถ๘pบw๏–%ๅ๏\.—,YฒD_ฒd‰^ญZ5ณGf‡Œfอš™žšY‘••%Ÿ~๚ฉ>oผฟ,dTฉ[ทฎูธqcŸอฯ<๓Œง๛ร๎ป๙๙๙ฒlู2}ูฒe่hำฆัฆMณYณffddควฮ—๋ษออ•}๛๖i{๖์ัพ{mฯž=Zrrrฯ) @E๘˜ำ้”๗{Oฌฐ.ะœ9sd๓ๆอชc ร† 3>๛์30,?ธ่บ4M“>}๚0๚ฯGŽ?ฎอš5K›5k–^บtiiึฌ™ูคIใŽ;๎0kืฎ-ๆ–ฑgฯž•อ›7๋ฑฑฑZ\\œž••ๅๅ๔…ใ๋ัWิซWฯ์ำงฑhั"ฟ*์sss%66V‘_v6ŽŒŒ4kีชeึฌYำฌYณฆTฉRล ‘เเเ./^ผ(ฉฉฉฺ๙๓็%--M๛๙็Ÿๅุฑcฺพไไษ“š'หRฆ(( @ภวž~๚iiฺดฉ๊r๊ิ)™0a‚๊ˆˆHญZตฬŽ;ฑฑฑ~U*V๓ๆออ๊ีซS*••%qqqZ\\œใสฟJDD„"+V4K–,)‡CŠ/.ููู’‘‘!็ฯŸื’’’ด .จŒSJ”(!}๚๔Qึ–?๓ฬ3ฦ๒ๅห๕์์lU -''Gถo฿ฎm฿พ ต2eสH‰%ฬโล‹‹ำ้”’%KJffฆ˜ฆ)นนนฺๅห—ๅาฅKโ๋‡9B @(บ๖ๅ_๚—๊""2bฤIOOW€ซ}๔Qฟ/๛๖ํ๋฿Cm&77WŽ9ข9rDDฤ6ลIฟ~ŒาฅK+;~xxธ๙ไ“Oบ_|๑Eว_ื)33S233-wฮคฆฆJFF†”-[Vu~ฦฏoฐข๋บฬŸ?_ฌฐศ๙ย… e้าฅชc๐+ญZต25jไทฃ็ฅG€๐ชโล‹หˆ#”ŸgC† 1š4iโทฟฏŒu เ#cวŽ•จจ(ี1ไ๙๓2v์Xี1๘Cร† S^lTLLŒาQY(z่!ฃbลŠส‹7]ืeฺดi๎โล‹ซŽRไฐ €‚ | F๒โ‹/ชŽ!@—ง< IDAT""ใฦ“3gฮจŽภ๊ฝปQญZ5ๅๅFA~[^ย?Xe๔฿u๋ึ5Ÿ}๖Y๋ol3ฌ  (/ำ4Mๆฮ+ฅJ•REึฏ_/ฑ๊)‡ร!ƒ ฒLมqฃ*Wฎlถnฺ/‹K๘ใFxxธฅฮณกC‡ํททT&ปc €‚ ผl๘๐แญ:†ไไไศะกCล4น?X[๒ๅหซŽqS๚๔้c๊:ทึ๐žะะP;vฌๅFiš&ณfอr๙๋ศ]๔ฟMmเฆp—xQตjีd๊ิฉชcˆˆศ„ $99Yu RPP๔๋ืฯoFjšฦ๔_xฤ‰V๙ตlูฒ๒๎ป๏บƒ‚‚TG)Nž<ฉ]บtIu ~†๐ข9sๆˆ฿ถm›็?Q€6x๐`ฃXฑbชc–-[š5jิ`๔ผฆmถๆฝ๗k้’น^ฝzๆฌYณNงSu3 ƒ€4 @ภK $๗s๊’——'ƒทr3F๘Sแแแf฿พ}-]x\1`ภฟศ (SงN๕‹นฮ;ำงOwk”ทQธY€€TชTIฆOŸฎ:†ˆˆL™2E~๘แี1ธiฃF2ฌ>šจ|๙๒Cฏy๚้งีซW๗›ฆ}๛๖5&L˜เ…ฅ?ฃpณ(/˜5k–Xa๑๒ƒZf BnV๕๊ีอž={Zบ\๋ำง :l*&&ฦ6l˜ฅศˆ#Œ‰'RzธY€€‡=๘เƒr๏ฝ๗ชŽ!†aศ!C$77Wu lิจQ†•wื}๐มฎœจ_ฟพ9sๆLฟN;lุ0ใๅ—_fM@/9t่ะg๏พรฃจ๖ฮ์ฆB „@H่ฝ—ะkDDEEDA@้rๅ^Aธ Hฝ Uz $!t %„Rvwๆภ์ovูฬ&ปูd๓~=ฯ<ืpggฮฬœ9sๆ3ง”ฮŒSrkSฅP@@}๕ืŽN=k…x่ะ!G' Hjืฎอ{๖์Y"ƒl-[ถไตkื.5]3ก๔(_พ<-_พ\๔๐๐ptRŠd๘๐แา๊ีซuพพพŽNŠำนqใำjตŽN”"ุะขE‹(00ะัษ ›7oาว์่dุฤ๘๑ใฅ’ุ ๊ีW_-‘I(<<}pัๅห—ัCภ‚ƒƒJJJข๎ปำำงOP่ะกC”ํฐ๏ฺตหa๛(Œ๑ใว—ˆV€;w–œe|6pผ5j๐M›6้š7o^f๒T… ่๛๏ฟ๛ํ7]XXX™9n[Cฌ € $$$8t;w๎คถmRjjชCำึษษษqXwํ„„:w๎œC๖ PXผYณf >-—ภ&z๗๎-๙็Ÿe6ึกCพs็N'Ÿ|"๚๙๙9:9ฅฮีซW™(–ˆ๏"P `ทowH0%55•FM={๖ค๔๔๔b฿?ฬ™3Iงำ๛~?๙ไLฅา่ัฃ| )q’@้ใ้้I๓็ฯ—/_.z{{;:9ๅ๊๊JcฦŒ‘Ž9ข}็w$///G'ฉิศออฅทoฃ (‚ € hตZ๊ำงํ฿ฟ฿๎๛สสสขํทำˆ#($$„–/_Ž@N)–˜˜HŸ~๚iฑ๎3..ŽโโโŠuŸถrๆฬ‡พ์>\Tกก๐"""๘๎ปuC‡EKR___š:uชx์ุ1ํฤ‰ล๒ๅห;:I%š ิชU+๎ˆษP:ฉgq๛๖mŠŠŠขš5kRHHู๚ๅเ้ำง๔เมบrๅ I๊‹ฮdฮœ9Fฏพ๚ช๗u์ุ1>|8‚ฦP*=}๚”ึฎ]๋ฐ่›—— <a‰‰‰‘๎นรNž|˜>|h‡”ุO๕๊ีi๊ิฉึฐqใฦxแ*a6lุ ผyำa/๛]ปv•jิจ|Q‚๘๘๘ะฐaรคaร†IทnbถmcถmN:ล5\Šทท7u้าEzๅ•Wค:pฦœ.>ๅPฎฎฎิทo_ฉo฿พDD”œœฬ8ภ<ศ>์ะย…กVซฉnบผiำฆผI“&ผE‹<44ิฉส™I“&‰Zญึa๛ uุพJฦ9ื8:เTิDT่šNƒ (>>†ษ(บu๋F;w๎,ส&8aคo(“rrrจ}๛๖๊ดด4‡ES6lุ kืฎSฝ˜[ซF.ใ^ๆฯŸ/*™8#==<(8p€>|˜]ฟ~ฎ๙ฆFผ]ปvผkืฎ<22|DEJLLd/^d ์าฅK์าฅKไศrCฮรรƒยรรyƒ x๚๕yƒ xƒ 8๒ ด€mูฒe‚#_โ๋ืฏฯหz๐ฏ4๑๗๗ง˜˜)&&†ˆˆ222่ฬ™3์นs,99™%''ณkืฎ1k[‹yyyQตjีxHHฏ[ท.ีฏ_Ÿ7mฺ”Wฎ\yฃPฉTTฏ^=^ฏ^=ƒ๋‘‘‘A๑๑๑์ฺตk,--าาาXjj*KKKฃ{๗๎ฑุd‚=///*WฎฏXฑ"๑jีชQpp0'ฯPHHทื8ฯJ %ึฝ{๗่ป๏พs่[๓›oพ‰ฑJ1???ŠŠŠโQQQก\บ>KKKฃ์์lสออeyyyD๔,˜ไํํอ===ฉ|๙๒ภห•+็๔Cั๘๙๙Q๛๖ํy๛๖ํMju:ฅงงSNNหหหฃœœาjต”ฟ๚>>>\bŒ‘ฏฏ/นปปS๙๒ๅy๙๒ๅษลลฅ๘  €kฺดiชฌฌ,‡ํฟRฅJผ_ฟ~:!wwwชVญฏVญZ?ก%_คVซ)00ศ๕Gพงเฐ™ิ,ูฐaƒฐ}๛v‡ึWว'กe”v@‰“––ฦfอšๅะฎฟ๔๊ซฏข๕”z@‰"Š"?^•‘‘แะtŒ3Ftwwwhl@(Qfฯžญ:x๐ รf%"ชXฑ" 6 ญภ) %ฦš5k„+V8ผŽ:fฬัรรรัษฐ ‡Wฎˆˆ>ฬ>๘c‡Ž๛GDTนre>bฤดง 8ัฃGูศ‘#ีZญึัIก>๘@rssst2l@ ์์l‡ํ{ำฆMยะกCีYYYKCพš5k๒—_~ญภฉ Pฦ้t:jูฒฅหุฑcU‡fœ๓bูovv6MŸ>]5n8UNNNฑ์ณ “&M’ิjตฃ“`S”q๑๑๑,##ƒ6o, 8PฑcG๕าฅK…ดด4ปฬฤ+ImูฒE่ิฉ“zๅส•%ฆ>ฺธqcึเt๐y Œ;y๒คA ๏๊ีซl๎นชนs็R:uxTTŒŒ”ฺดiร]]] ฝŸ‡า–-[„QHNNถKpฑฐc4gฮ‘ฑ•,›@ Œ3ส%$$ฐ„„ถt้RAญVSXXฏ[ท.ฏ[ท.ฏWฏฏPก๙๚๚Rนrๅธทท7ฉีjE‘233)%%…]ฟ~;wŽ๗฿์๔้ำLงำ็ก)+ตhัขx๚>3สธ'N(j๖ฆำ้่าฅK์าฅKNีLฮฆM›†ฎฟเดJฬ˜+P๎ปGทo฿vช€žตฦ'ก๕8-สฐ'N”้๚`HH7nZ€S+ำ>€ฒฮา๘ฮŽ1FŸน่ๆๆๆ่คุ€eXYพ๔าKRDDบ€ำC Œา้tt๙2ฌPกอ˜1Ctt:Š€Pข0V&฿A  @๙%ั๙๓็Ynnฎฃ“แ๓ๆอ+Tจเ่d กD๑๔๔tt์ยหหหัIxฮ‰'สdd:66VŠŽŽฦฤPf %Šฃ“`พพพŽNภsสโ๘4oัษ(”E‹QXX˜ฃ“๐œิิT–šš๊Tภ *ะ/ฟข๛๐รEฦœ๊ะl@(ัฆOŸNฑฑฑŽN€UฦŒC#GŽtt2L:~ธSEศบw๏.ํฺตKืฑcGŒ๗`€Pข ‚@๋ึญฃศศHG'@‘ะท฿~kM#ธ6แ,้๋ฏฟ๑Gฬ๔ Pมึl^๗๐๐ ;vPmฝi›zํตืhํฺตฟJดำงO—๊ ฝ๗{า_ฅ8p ไ่๔”BฉเๆๆFฟ๖Mš4‰0ถ”4...๔ๅ—_าชUซศลลล^ปA 'ฐ‰๏ฟ^|๏ฝ๗คภภ@G'ล*ๅส•ฃ‰'ŠวŽำNžœ† BnnnลตkNฯZ NG๑๑๑์์ูณ์๒ๅห,))‰%''SZZใv๑8•JE!!!ผ^ฝzผ~๚ผqใฦผu๋ึผ๏-ฐBวŽีZญึa๛Ÿ2eŠ‹€…€ ุƒŠ0พไ7h๏ฝt่ะ!JHH ไไdz๒ไ 9ช”nไ๋๋KแแแTงNŠˆˆ ฮ;Sๅส•‘‘žตฐp(ญVKw๏e)))”ššส233)##ƒ233Yff&ๅๅๅQNN=ปิj5yzz’‹‹ ๐€€ชRฅ  คชUซ"ุP {`„๎ๅถไธ&7P๊a`ฐNซ ภVะ๒Š@ฐัั p@‘ ๖‚V€E'สR("มžะ  hPŽ@‘!๖ฤ /ฏ……๒l@ฐ7‰0~€ตPv€อ ลA$Œa ZO€M!ลA@€‚q"า9:เ\„โ’R‹ €i€] ล A@€็I„เุ €เ:ย๘V๙DB™vคvt ฬ’่YK@1งภ๒'๛@ซhฐ+ม‘๒วปY€ฒ?(VBI ณ0๚ม@g“_ึ!๐ล @(I๒[ลˆ๔,hผ”ฤเBI…f@WK'† €Cภ‰!เฤ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ตฃE๓ๅ—_ ;v์ˆˆ\]]i๋ึญ:A0ื้งŸ„ŸYnุฐAWพ|y›ฆGE๊ำงZงำQ฿พ}ฅ &H6 ๅไไ‹‹ ฉี(ฦภ๙ฝ๓ฮ;ช„„FDTซV-พt้Rััiฐ$::Z•““รˆˆบt้"M:๕A(’]ปvฑ๙๓็ซ๒๗ฟ-6n˜;2M๖ถeห๖อ7฿่๓โล‹ลฺตk<฿ฟ๔าKชว3"ขˆˆiๆฬ™(ณP™zsทoพ}ปขVืื—๊ิฉร[ถlษํผBูฐaƒp๘qFDิดiSn.๘GDดy๓fถgฯFDHถ]ผx‘ํุฑƒๅk๓}@ู’——G‡f{๗๎๖๏฿ฯฎ_ฟN้้้์้ำงDDไใใCAAAผu๋ึ<22’0@๒๑๑)pป3gฮrsscŒๆอ›'ชTช‚~V u๋ึ gฮœั฿ร‡—๊ีซ็t•ำM›6 GŽaฏiY@@Ÿ4i*H’D?๔“••EDD/ผ๐‚ำ•)ฆ,\ธPธs็Nๅ cŒx@@5jิˆ7iา„ปบบGมŒซWฏฒญ[ท๊+ฅ]ปvudrœสาฅK…kืฎYผ/T*•+WŽWชT‰š5kฦ๋ืฏฯmQวqด]ปv ๙๏2DDAAAeข,„ฒํฯ?ิ็{•JEUซVตyพOKKฃธธ8}™ฎ]ป"ื๑กd*SภีซW ?๘ฃีžU*๕๊ีKš3gŽิดiำ๓ ัjตtyอูฌY3‹i;y๒คโu ๋ิฉS…E๓ๆอKฬ๙ๅโโโ„คค$""rww'Gดโ|๘1-^ผXXธpก*--อ์zOž<ก„„–ภ~๚้'z๏ฝ๗Tร† “ๆฬ™#VจPมไo๎ปGsๆฬัื„CCC๙็Ÿn“t/Xฐ@฿ฃFrสเึขE‹ *แ…ีงOํˆsN_~๙ฅ IฯNqร† yŸ>}P.—2 ,?๘GT6žญ’$ั'Ÿ|ขz๒ไ‰ีฟ๕๗๗ง#FH3gฮ}}}ํ:(๊ƒ๖3kึ,‹๕"S*WฎLcวŽ?๘เษำำำN)+ุูณgู๖ํ๕ycุฐaš ž]ฆz๕๊< ภึI,ำVฎ\)ปwˆžๅ™#F ~Vศ๓}ํฺตน———]๗AT2ส์ฏฟZศออ%"ข๐๐p๊฿ฟ?๒ฃ ”ฉเ‰' ๕ฒ*Š"mบUุนsงฐrๅJ๑ีW_-™/>>žๅ฿D–ƒz< ›7o๊฿^7ตผ๐PซีิคI‡`ฝ้ำง /^dDDญZตโล\พ|น0iา$ีใวญํ“'Ohษ’%ย๏ฟ.๒ห/บnบ=—ห[yyytแยถ(,,ฬ้๎ฮ๙s/w…U*ฮ์ส•+์ฃ>าป็อ›'"X๚”ฤŠนฝ%&&ฒยˆˆาำำ้?๙ว๖๏฿/;๙*iไy–1V&๒lqธ}๛6ณ6๘GD”ššJ3fฬPmธQุพ}ปฎRฅJvH]มึฎ]+|๑ล๚ฦฃFา*-็œNŸ>m๗w™ฒJ’$š0a‚ฃห Aƒค#F86Q@:Žฮ;Wฌ๏๐๖R< ๗฿__4i’ิฟG&ษi”™`NN]บtIŸฑฝผผจM›6f3๖7่๊ีซ,ฟี‘FฃกQฃFฉZดhมํั๏Zw๎1๚ตkืฮlš์๐0&/<๊ิฉร๙• ็้ำง”˜˜จฟŽ-Zด(ถผ๘๑c=zดj๚๕-uU*ตlู’wํฺ•7jิˆpบw๏ฅฅฅฑC‡ฑ-[ถ่ฟZ=kๅ—˜˜ศLํ๕;<ำh4๚ฟ›4iยsพ๔Wฎ\a=าํ๋๋Kตjี*ิ9์ุฑc‰ŽK(€ IDAT๘ โฌŠซ์๛’U*•Z๑—$ฦๅt5xอš5Mฎซัh(!!ฟ฿เ฿ฏ_ฟฮ ค:rไˆฮ~)Sไื/$$คฤฅSฺ—้มมม<,,์น๕ฒณณ้ย… ฯัOŸ>อFŽฉถm›C๎ yพๆ+VT[ใบžgถ•`_p~K†๘๘x–““ฃ^ืๅ้ำง,ฟnแ๋๋k—nฦึ@ี~สL๐ฬ™3,b "ขˆˆพ}๛v‹ฟ;w๎ฐ3f+Wฎิ#๒๒๒h๙๒ๅย‚ >๘vŸ>}xŸ>}=ภ+า๖๊ˆขHgฯžล—นR๎๔้ำL๖.ฎ๋๘่ั#ŠŠŠRหฟrนธธะฐaรคฉSงJL|ไศ‘$Šขธfอaส”)ยปw‘๙|nฏ`I๛zf/ฦว9`ภiลŠ/แyฦญpŠ3 ถ#ฟŽแแแv้Sา—33fฬ ๊Žvเภ6v์X•ผ%๖ัฃGู‰'๒~๑’ญ๕Y่ฦ๗ลคI“คwyวไ}กำ้hํฺตยป๏พซสฬฬิ๛๖ํูฉSงXqH0๎=`mพ(+u,G)ŽwEฐ^qๅ๛ฯ?\ดีpHถ€h?V‡WZๆๆ โ+Vฌ{๗๎mฐฎ|ฝาB~TญZ5›฿D—.]าOฬ@„siๅˆ/.Ož<กž={ชTฉย๗๎ซ[ฑb…จคu™Jฅขื^{MJLLิEGGK...dnf8{uM*+_ซ๐P.=ŒวK27.&”\’$‘|b!g-WŒฆมw๏ญ3–ฦz[ivํฺ5๖๐แC฿e%ฯk๎ ตZMฏฝ๖š๔๛๏ฟ?ืX@>a_q1nมgm@๛’ืaAภ๙-!ไ๙^ฅRQIšภžไวํ๏๏O5kึ,ว]TภxภIyKยาข8&มƒู9ศฏฃ‡‡ีฏ_฿๎ืqศ!ชcวŽ้๗ฦO:ฅ๋ะกƒี๛๖๑๑กธธ8q๎นข‡‡วsฝ{๗่๖ํ๚}ีฌY“๛๙๙:ํre%0†{ฝtภxIฮมx,ผฒpƒžžžžคt6๕J•*=7$ŠVซx˜1ฐ<#์G‚N้Xบuใ 64X/55ตุ€Eอ˜ฤพไ็744”cฅ’ก8&)‰ไ้ฆM›:ๅJŽRfบๅล\ ใค๕‰฿ฝ{7ใู*AAAŠ+ญ้้้้lิจ77Hฏ5๋ObMPE:pเบuซœœLฌbลŠ<88˜z๗๎-u้าEฟญโœD’$:t่บuซp๙๒eJOOg<88˜:w๎ฬ๛๖ํ+ๅ_ทห—/ณ๋ืฏ๋ฉS'ฎV›ฮ๚gฯže๙ใศฉีj๊ิฉ“222hใฦยฑcวXJJ =~˜๙๛๛๓ฅK—Ёfำy๚๔iถs็Nv้า%๖เม–M<44”z๖์)EEEq•Je๒๗–คฆฆามƒ…s็ฮฑห—/ำ๛๗™Fฃ!___ เmฺดแ=z๔เ5jิ(๐:$%%ฑ7nั‘#G๔ื1((ˆ๏3[โv์ุ‘ปนนYvนีซW [ทnี฿d•*Uขํท‹E ZญVำG}dฒKŒ='‰7˜D้ธx'Ožd{๖์a.\`ฉฉฉ์้ำงไแแAnnnฬรรรy›6mxซVญ •Wlษ8จไ๊๊jถฅฅ-hตZ:~8;~8ปt้ปu๋หสส"ตZM<<<œ"##ฅฮ;›ฝฏ r๖์Yถ{๗nvyv๗๎]–••Eไ๊๊JีชUใaaaผM›6ผu๋ึลลๅน๔ํทฯเ+ea๒ิ™3g ฦ/‹ˆˆเ๎๎๎…:žC‡ฑ์์lบw๏หศศะปงง'ํฺตหไ์๋๋Kญ[ทถ˜nQ้๘๑ใl็ฮ,11‘=x๐€ๅๅๅQๅส•yhh(EGGKญZตโฦฯL{หออฅC‡ฑ}๛๖ Wฏ^ฅ‡ฒ์์l๒๒๒ขฌY3ฝ{w^ฝzuซฎห… ุฮ;ู™3g ส๎}ูํ๊๊Z่tŸ={–ลลลฑ„„–––ฦ|}}ypp0EDD๐๛K๙yญจ/อดgฯแฏฟbw๎ก0wwwชXฑ"oบ5ๅ๖่PฦcQ5nุชฒฯฺz[พผผ<ƒ๒ๆ๖ํ,++‹ศ฿฿Ÿืฎ]›ขขขคจจจB็s{–๗w๏ฅ;w GŽaฉฉฉ”‘‘มผผผจrๅสผcวŽผo฿พRa[็ืปถlู"\พ|™=zฤxpp0๕่ัC๊ฝปญธ'y๔่ํฝ[๘๋ฏฟุํท้ัฃGฬหห‹*Vฌศถmหcbbค^xA๑๖ไu""รzcNNmผY๘๋ฏฟุญ[ท่ษ“'ฬ฿฿Ÿทhั‚8sโฏ[ทnLRทn]n๊Cง)แแแ\Kุ'Nดฬ๋นsก๒žผ๋}ํฺต)88˜?z๔H_๏๚๏kฐGฑ]ปv™^ฝz๕H>;pAด’’’ุ๚๕๋Ybb"ปs็sssฃ^x๗่ัƒ๗๋ืO*l]UงำััฃGูฎ]ป„ไไdบ>ำjตTนreFัััR‹-'ฒฒฒ ู๊u๊ิ1่•u์ุ1ถqใFแฺตk”ššสผฝฝฉz๕๊<::Z๊ัฃ‡อƒ ๒๗๙0N•*U2[_๒฿Ž9ขŸกž1F]ปvต:฿ผy“%&&๊nะ 7พ_?ฎฏฯxzzR๛๖ํ๕๛นvํฐa‹g)))LฅRQ•*Uxฏ^ฝxLLLกฏ=ัณ๚ลุž={„7nะ๛๗™$ITนre^ฟ~}๓\p– šไโล‹lร† ์๒ๅห์ฮ;ฬ‚‚‚xฯž=yllฌd\g5'11‘ผySw—.]ฬ>ใy6š\7;;›6n(}๚H๎๎๎Š๒Rƒ ค๛๗[uํํY=zTทo_‘1fq›|ม‚:K๕ SหŽ;ดี;ฺถm+บuKห9ืt๋ึMฟn5์Vผy๓ฆ๖ํท฿6Y?3ฮใ|๐๘๔้SE}๑ล๕๗\๙๒ๅน$IญVซ๙๓ฯu–ส>gฮปีaโโโ ๎‹#F(พ/z๕๊ep,X O็๐แร ๊€gฯžต*oK’ค‘—i>>>๖ํZฮนfลŠ:Kืฦ๒็Ÿคมธ๎1o<็\“˜˜จําฅ‹ลผYปvm้ย… VS^^žfษ’%บjีชxฯถkืN:ผขํoธัเ|lผYว9ื8p@ A‹๛ุ๊ฑฃt}›ๆ)๙{า%<<\OGGGไ๋ฎtั้tš&Mš่ำPฑbE“ๅuีชU๕๋t๋ึMโœkฎ_ฟฎํปทdฉซQฃ†dู้r}อดiำี/๚๗๏/ฆคคุๅž?sๆŒAพ๚๋ฏuœsอ… ดฏ]ƒ คฤฤDEวฃฟŽ•+Wๆ–ึํูณง~ฟ/ผ๐็Ysฮœ9:ณ้quuๅ๓็ฯทX>X]Vtํฺีไ3fำฆMบ๚๕๋+ฮ฿๛ŸC฿ฑŠk)ภร‡<={๖T\ษออีTฉREŸyธฅยื๘แผm6ลiฬ˜1๚›ฯลล…็ไไ˜]๗ํท฿ึฏ๋๊๊สsssอฎ๛้งŸค้ฦำคำ้4cวŽUjิจ‘๔๔้Sv๘๐แŠ+%JNงy๗wงซZตjRzzบF„ณ๔b››ซquuี~ิจQโฑcวด๒๋oผ˜ ~$$$XT zLSRั๒ห/ U‘jีช•dฉโlu mถEฎิO™2ลเz๖๎n/ ๙‹!GD|ฯž=6)์ฟ๛๎;ƒk๓แ‡šฝ๒๒๒4ฦnฅหนs็๚p๚๕ื_ Ž๓7ฐ๙ฝฎัh4J_ฤๅ‹‹‹ ฿ดiS/_ึ–q๒ลิ}*๙`Œ๑GYuผ/ฝ๔’Ay~๑โลB_ใSงNi s\Ÿ}๖™ษ๓v๛๖mญใŽา๒์ะกCvองซWฏึ๙๚๚Z}œืฏ_7›ฎฌฌ,อ+ฏผbUพ`Œ๑UซV)~แ_บtฉNAาาโ๏๏ฯSRR4๒J~บu ,#%Iา,Xฐ@WPเฯx้ีซ—d๊ƒ–#ใฏ?๘ฃโsผ{๗nญ๑qYZ?33S#‚ีyษ๏ฺตซภ|nฯ๒>77W๓ฮ;๏Xฝํ๗{OQน-Šขๆƒ>Pผบu๋Jนนน/q–>.e๙ํท฿,พlšZ"##%K๕ๅE^'๊าฅ‹”’’ขiผนโr๐๛๏ฟทK๐ใ?6ธ .Tผy…ศ0ภถx๑bƒ็๚ส•+ญJš5kฬ~X-ฬณ–1ฦkึ,ปฯ$k5ษVcbZSฮŒ7N๕ำO?้วว‡bccฅˆˆH\ซีา;wุนs็ุŽ;XRR๓๐๐ บuฺ๋๔ฒVqŒsxแย–››KDฯ&-ŠŒŒ”š4iยCBBศืื—‹ขศRSSi฿พ}lใฦB^^=หหcฦŒบu๋&Yบ&OžฌZฒd‰{yyQ฿พ}ฅจจ(^ฉR%๒๑๑แZญ–๎ฝหฮŸ?ฯv๎ษ.^ผศT*•ษก Zตjล๗์ูรˆH?หa็ฮ—;wฒ๛ฟำงeย„ RQฎ1cŒ&Ož,๔ำOB~wฑJ•*ั๐แรอฮ >XงNิฉฉฉ๚๓๓๓ฃ—^zI๊ะกเ๗๏฿g;v์`ฟป?{xvv6ลฦฦช“““ตๅส•+์ก˜ฤ9งฑcวช–.]jPvTฏ^๗ํ—ทhั‚pFร๎ฝKวŽcถm๔Sa๚๔้๊ขขข๘€คš5krI’(>>ž}๗ย•+WXzz:M›6Meอ Zญ–† ข๚๗฿๕็GฅRQ๗๎yงNค5j‡‡ฟs็ผyณฐm6ถทo฿ฮพ๘โ แใ?ถ8ำnq(lฝ-33“ฦฏ?วŒ1๚็…ฌSงN1Izvศ/ผ๐EFFJ7ๆีซW'___ฎีjYJJ ๏c›7o๒วฬออฅทzK• ณิอj์ุฑv)๏=zD=z๔P?~`˜˜ฉC‡<((ˆT*]ฝz•ึญ['ศวูๆ›o„จจ(ฏ_?ณืšsNร† Sญ]ปึ /ลฤฤH}๛๖ๅUซVๅนนน์๏ฟf?๐ƒp๏=บt้›4i’๊มƒ๚ํุฃ๛๏gŸ}&L›6อเ^jุฐ!8p TงNruuฅ„„Zฝzตp๑โEq๏฿ฟŸMš4IตpแBณyยxุœ*Uช๐จจ(uRRSซีิญ[7ฑcGฉrๅส”““CวŽckืฎไ๕วูณg #FŒPNฉย'Ožd๒ฑ=<<(22RVญZqใ๕GŽฉ(MOž<ก?PŸG๊ีซว'L˜ ฯWMš4แ“'O–RSSi๕๊ี๚๕บu๋ฦอMfเๅๅ๕„Uฦว~๘q6cฦ ็œชVญสฃฃฃyฝz๕ธฅคคะ๚๕๋yูy๓ๆM๖๗฿ ~๘กล๒ํนsฌs็ฮj๙$64`ภฉ]ปvผB… <--mถmฺดIศ/;ž๘เษ\wใZ๘‘้ผซh๗๎ฃ3f่๏ใ6mฺ๐ื_น๋c|ํ>|HƒVๅๅๅQ… ่•W^‘"""ธฟฟ?๐เถm[ทn?fำงO้•W^Q={VWะะ sๆฬfอšฅโV๋ึญy=คบu๋Rนrๅ๘๛๗ูž={ุบu๋๔ฯƒคค$๖ึ[oฉโโโl๚c<ศฝ{ูฬ™3UD”฿5œืฉS‡{{{ำํท้ื_5xKNNfซVญฦŽk6฿งฅฅั;wี3nบฅ๏2Nคฏฃซฎ^ฝส\]]ฉ[ทn<""Bช\น2=}๚”Ž=ส~๕WA>๏ฬ™3…W_}U2พF_]ทnพ ฐŸŸ=ฺl๚ `๐ฝ๛ชๅห—๋7๎ํํMัััRdd$ ิื๑SRRุนs็ุฮ;YBBsqq!{v็.Q,ŽeฤˆQ`%] oบฅ}ํตื ~ืจQ#ฉ ฎ7๒fฑีชUSšษธลหจQฃฬ~15^ท 8๒/™=z๔ฐ˜&yWฃc6๗ีไๆอ›Zs]\m๚ร๘ tร† อ6็ฯสสา๔่ัรdบ~๛ํ7ณืธWืธ.]บH๑๑๑&๗%๏๛ื_iๅ๙ชUซtข(š฿ƒ4ฦ_ ๊ŽฺซW/้ซฏพาฅฆฆxฮพ๘โ ƒใ .0?2DžฝผผธNงณ}i|พ[ดha๗ึฦ_ ำ]ำb"))ษdžูณgมฎW^yETาฅ๏๏ฟึ~๒ษ'viIaอานsgƒใดวืฒีซW๋† &8p@k๎ษ_’’’ดaaaiZทnู๛ุ่ฑcZyw‘ุุXQI‹ฝำงOk'Ožl๒w้Qฺญ๊ิฉฃ๘๚?z๔H#ฟญ้ษ#?WฉTf๏ใก{๒—มƒ‹>4นํ]ปv=wmถlูb1ฯอŸ?฿`?๕๋ื—>l๖7'Nœะส[ 2ฦl~฿หห๙โใใรWฌXaฒฃีj z•=kฅgi[ทn5(๓,ตโ4๎้˜Ÿฆ˜˜ั\ร}๛๖=w-ฬฝW็/๒:V๗๎็ฉƒห€ +Z*ฉ/8หR&€ 64(8_uq๒ไษ&—ื_]lีช•dป[ทn’นŠ•|‘?œcccgคำงOdุ%K–˜ฝ๙Œปx}๗wfืฝพAภcฺดifำดeหƒํ6o\สศศฐ˜๎ฝ{๗>ื\ืšJ‰’ลธผy๓›๑?x๐ภเ"INN6[เผ๑ฦฯ5~๕ืE%/ฉฉฉš *่Wฝzuษ\Hพ\ฟ~ าaฑณถูฟ|Cข‚วๆ#lัลืาาฝ{wƒ๛rัขEv๛๓็rฦ•เย.ฦ(ฬŽ5ูปwoqืซWO*๊8ŠลนH’ค)_พผม=2|๘p๑อ7฿ดj™>}บล๓nm>?~ธมu}๗wอn๐เม๚{"$$DQwฐ‚–””Ma๒ีgŸ}fP‘ฺฐaƒอ๎K—.œ“ฯ?ชmห?f?~ผXะu1่`อK„’ลธ+LญZต,ท”.“&M2('{๖์)Y‚ƒsฎ๙๓ฯ? ฮฏ‹‹‹ู็๕๋ื >๒„„‹้NKK3๙ ๛๋ฏฟฬฮxธ๗฿ฟภkฦ๙๓,ดŠc‰78ท+Vไๆ๊l|๐8pเ@ั8Xํ๊๊ส?๔SEcY[ฤ๙๘ใอ๏๒qืlYหฝ‚ X›Zพ–อu‰฿ปwฏม‡’ X๛๏ฟŸซ2ฦธน๔ย,—.]าสปพ๙๘๘X/›๓gZ๊ิฉcp฿|๓ู๓e|๋ญทฤ‚>F5สเ>ZณfM๋47o48ฟJวฺ63œˆLvU•™”Ž๑|yญ|จW^yล์ฝ omพ0U๗(Wฎ\ ำาา4๒๔UฉRล์9“$Ic<ฆฺ”)S |–ฯ˜1รเ6o์>Œว‰'z6kAu๓}๛๖ฎ ฦึ.:N#F ‘u๑Uฒศว‹/hธๅะกCeฬธqใฬ๎366๖น๗ณท฿~ปภ็›๑GŒกC‡šวถm าำฃGE]g๙gƒ๋?{๖l›๗Zญ๖น1 โๆอ›Zy#,,ฬb~18d)?ŸS"โ&L(๐Z ‘50!ฒ<ฉ.฿Jฯ™|ึฐฐฐ3ฌII[œ>h<ˆ5K@@2dˆจt?ใ‡ณ5;z\MhhจพฏPกฟy๓ฆขใ6ฎXูrFcฐ}??ฟว0ฬ_Œ[pZ3ัณฑb”Vึๅ_[<==ญ@Y>&‡‡ทe_ฟ~ฝA^ฑ48ฟ๑8Hึ บnํ’““ฃ1žก b[,ฦนโž$''วเa>cฦŒR๕ตษx๎ย.ึ|Qบศ'จ0€Eัเ%bย„ 6K‡||%%-K'ศ็8™i IDATิฺV‹๑˜LJฦ*ห_ŒวZู๊ณงTะห/็\๓๘๑cƒ๛ฺ–/ทo฿ึส'[ฉXฑขล:J—ณgฯผภ†‡‡+'สxR„}๛๖™L|’%Aฬถ1^Œ[ฏXš$>> 0าฟลy๛โล‹๗ตฃห%ใ @ฌY๊ีซ'Mš4IT๒ฎ(‹|Œป7฿|ำlหR๙>[ือ›7œkZ†oุฐมเทฆzEhตZผ%eนrๅxคj-อš53ธ'l=HTT”ม๖•NถpแBƒใถ๔‘FI๔ฌี˜’๒oๅส•๛07ฮUaใ–7ตญ™6mฺscหฝ๖&7~xซ'‘____ni"๙บ!!!Vๅ Suฅ*ไ-S-งŒ?0ฝ๘โ‹Š> ฟ฿เฐฅAฦฝ<<<ี}๓๒๒4*•J;s“v9ผม๙๗ฟmU:tจU Œ' ด8vฒ๑๘•Šฯnบeิ3๗Q233S#Ÿ์ฅnบŠ{chตZ|ข‚ZYณObฉ…ญ๑"oฉ^ณfM‹๙ฅฐ€=cWษ}b<ฮ่๚๕๋อๆฑ?๘รเธ•~ืjต๙ธฦcb๙‹ๅŽ๐Nเฬ™3, kฅงงำ๕๋ื™FฃQ4็z4๗๙ฌ๗Dว฿ลล…,%$_ืีีU๑บDๆว๋Zตj•pๅสบ3gฮๅำา[าบukƒ๕l9K\\œ Oื”)SDKcสํึดiSnn<‹ผผ<ส๛‚ู่๘‚K–,•L~เภƒ1ผ&Ož,4”\ร† ๕““C๒qŠ๊Ÿ‡’žฅ{แ๔้ำ๚qˆ์3nOพƒฒ์์l฿AAAผvํฺv_พขŒj‹ํ^ปvๅไไ่ฮ3ญด0>ฮยฒG’5g๎ผฆฆฆฒGธ^aศวมนrๅ {๘ฑล๕'Nœ(ไ›๊ๆๆF฿~๛ญฦaŒ)>็œsš={ถพš7ožมุ4฿~๛ญhnSc:t0ุฟฉc=}๚4“]๛๊ซฏJ:u*ิณ5<<œ{{{›\๗ฃ>า…้ใใC฿}๗โTทn]๎๊๊ช[>†ญ#ฅœนu๋ป{๗ฎ-“cา?๋‰ศ|9rํฺ5Xฆถ"IMœ8QŸŸยยย๘ฬ™3_kใบ‰ฉk๋ฏฟ ๒๚ะิฉSลZตj9ผ>ธo฿>ถo฿>}บ๚๕๋'ฝ๘โ‹ŠฦซŒˆˆ(๐^อg<ๆึ’%K•ฦใพYฎ0Œ๏ NG๋ืฏไหฺตk…oฟV9rคชjีช.๚ืฟyฝฎM›6?๙ษbj,7K้Yทn ฟ3gฮ_xแ“๋rฮษšฑLงๅล_”๚๔้ฃh^^^๚็์sDQคนs็Œถx๑bั;ƒ\@@ษ๋ฏZญ–RSSMะ๘8>๐CIIืีี•ไใI๓กอfŠZ76ฮ;ฦ๏รฦ–.]j0>ใ๙๓E???“๋ปwไใWๆ฿“JฯชVญส+WฎฌO›|;r ,๒ว›#"Zถl™(ฯ7–จีjชSงŽ~™™™6ฉ๓=]† ขธ OAe‘|?‹~๊ิ)ƒx…า๛ฤD๙จ8vก4?ผy“ษŸiฅํซ89$ ฦ™จaร†& ข(Rff&ป{๗.%%%1NG’$ัแร‡Yฟ~Tใฦc‹-ฒ˜“J๓ ๒ี+UชDoฟถโAภ๘ถฌ๐ษ๊๗๖๖ฆqใฦ)N—ฟฟฟโtO-ีซWOัq|๕ีW“ Lœ8ัชิ๙ชง'Š"Y3p๔ร‡)##ƒeff>Wุ9rฤ OVชTษ์v์3E>9Qห–-ํ#*|Pฤšํ™?wฦืg๑โลBddคิญ[ทb9ข2UžZฝฅdศๅไไะƒXVVษ'ส'ฏุ™K“๑๙_ฑb…ะตkWSไIZตjลใโโˆู่หฮษ“'อNฒkื.&Ÿคaโฤ‰Š^ฌ!ฟV!!!ผ|๙๒Š~ทw๏^v้า%oGŽ)™›8ร”jีช๑ฃG๊//W +==`๐๘† ๒aร†๙šฝ{—6nจ฿n๛๖ํy๗๎ญ:V๙฿๙89๙ณ•1FำงOท๙ณ5))‰mถMฟŸัฃGKึ—jตZญ]i6.gz๗๎อMฝ,ไไไฐฌฌ,JNNึtž}๘oฟฆ๓๐๐0๙[3ˆ๑ฤ 4เ–๒Irr2“ภ)jP>ษHAไ“y„††š\็?ฎ_ฟn0žน`ฆ)ีชUฃ๓็ฯ๋6—ฟŒp3ƒwž>}j๐|1w…%O— VOŽgอD ๗๏฿'๙dXํฺตใ–&(3&พ๘โ‹Rƒ งฏrๅส”QศT#ˆผผ=QQQ๘ƒAAไ๏lถ|~%฿งงง๋k้ciQ&้ืฏŸTฃFB•–๒ฐธ+Tจ@J๗!ุADดlู2กs็ฮRa๋ฮฬ้€ฦวŒ3$ใูbŒ=~˜–,Y"ฬž=[•_เ.^ผXhุฐ!๋ญทRUซVUbฌีjI>๓ ฅ›Oซีา๙๓็ESี?w๎“‰:tจU3—vถช๐ฅฆฆา๔้z้ฅ—$sญL1~[J—q!;|๘pEวššJ๏ ^Lๅ/ฟJศฟฆTฌX‘,‰ˆฮž=ห6lุ ์ูณ‡]ผx‘eff*ฺ››ฯZ,'?žžžค4Z๒™™‰ˆjีชeฏ]้?ไBCCนญf(U ็พพพ”อ222จ{๗๎๊ๆอ›๓˜˜ฉw๏ผiำฆ\ฅR™๚นร็ส•+E{ฬL๔l6ฯM›6 ›7ofงNb7n`œ+•นฏ   ^ฉR%สŸ๗ŸูjU7bbbx๏ฝฅ-Zpk^จ๓™ช› j4z๗w๕888˜4KฉตŠาฺโ็Ÿ6xษ~ใ7ฌ ดWภ *ฯ”Xทn ๚Ž;ึ์์„ึnW๚๏อ7฿ด้ฑjตZ๚๕ื_๕็ณC‡<,,L๑ตP๚l]พ|นA+Ÿ   พkื.ล'(''ว Eถฅ็„ฝI’dwiำฆMgู%"บt้›:uชฐy๓fˆ(++‹ฌพpแ‚NIo†ŒŒ Š‹‹6oฬฮœ9รไA ‚˜+oŒห๛{๗๎นผ_ถl™A@น\นrdอต–๗‰žฟึษษษ์ะกC๚u $™ ™b‚mซ๚เ“'O ๊Zญ[ทๆึฬึh์0W.ฟ3ฤฤฤ(GbbขA%<<ฆ๗‘ผฎhz๕๊๑™3gJ/ฟฒล๒ญVญZ฿฿_ขnฉเ์ูณU)))Œ่Y>\ดh‘ลYEภl0n}mN^^žA@ื\0๗็Ÿ6H_QŸ{ๆ๎๙5lบตโ๗ฤฤฤDƒ๚=?ึชU‹๛๘๘X๕๛&Mšp777}าR๙่ฃT๙=1ิju-ศŒทej–`Kไe’ฉ๛>..Nศฏ=koM™JDtํฺ5๒๙)?๖   ฎดพ๘๑c’SํฺตอฎkM๏ลข”IIIมoKHไวmnฆpSBBB สฐ'OžP๏ฝีM›6ๅัััRŸ>}xณfอ Uวw6N ๓ะ)WฎM:U  ัฃG๋kf๓ๆอFm๖ลC^ฐ[๓p‹7่&b้ท.\Pผฎ๑—Ls๋๎ฝเ€๚๔้cUแ*๏nฃVซฉI“&6)๖์ู#ศxึดฬ "บqใ†โk/ฯ'jตšซค@ปvํไ/iGeปw/๔}e้+GRR›8qข๊?(T%0,,Œ[๊ฦ"?5ฒkส8h‹ฎ|)J}KŒป๛๙๙‘น๎Rฎฎฎ4}๚tqาคI'๗ไษ“์ไษ“ช™3g’u์ุ‘๗๊ีKzๅ•W$ใ–ฌŽย9งำงO+~ ฐ$Iขฏพ๚J๘์ณฯT๒/๗ึจ[ทฎษt ‚@sๆฬ฿z๋-ƒ๓๖์Yv๖์Y6w๎\มหห‹"""x=ค!C†(nIีขE ฎRฉ๔Asเฏพ๚Jฟ(~๕ีW’ญปŠ]พ|ู  ฒ5y}ฯž=Nk[คฅฅดฒU๚ๅึ’;wlณ_ฟ~EnG๔์“฿‚ P฿พ}ญฺฎq7/ใ ํัฃG >ะๅูJd:๙็Ÿ้˜8qb‘ ๏5jๅ็E’ภฒฒฒ๔7jิˆ+๙ Yทn]พqใFฑ[ทn,?gffา7฿|#,Xฐภl€]งำั๙๓…/ฟRUPท}K๛6๕๏ถ.๏๓๒๒h๚kอ9งุ๔Zต>h|๊9ปo฿>&ึGGG[{/—นrษธ๎ุฑฃโ๔หืแแแ\iB%Œ[ธธธ๑q•JEพพพฯฯjิจม›5kฦปvํส?N™รฃ-Z๐๒๖นsLงำ=ืj4>>ž-\ธะ`XƒศศHลˆฌ{&ื=ฺทo/)้’M๔์ู.ฏฃ›zžI’D๛๖ํำo0<<๊!iไม“A ทo3๙zึไ-๙๑Y)ˆ(Št๖์ูBwฯ&zึภ QฃF๘๑ใŒศ|ฐ๚ศ‘#์งŸ~าŸ๋1cฦHฝ3ส๓އ‡‡ี=Hไฯ้็~ปcวƒด~๗wยw฿}W่!าl๕ิ้tƒฌษ/ฦ็฿R~ฑฆ๗ข๑บQQQ…สร๕๋ื7vD๎๛=zฌษjตšfอš%Ž?เนx๚๔iv๚๔iีœ9sศ›"""๔ฯ€€ฅ›w*Nฬษษ!๙๘qึ4#%">|ธ๔๛๏ซ๒[บu‹]พ|™™๚ชw๓ๆMv}฿…ฏ ฿Zณฎา€วฝ{๕k๏พรฃจ๖วŸูMB ! )‰คจ๑ชWi""–/ขคจ`CPGฝŠ_QโฐขbPC ฉน"(MA$$กD:ป;s~เ๎ofฒe6ูฮ}ฟž็<ัู)gฮœ๙œ+ฎบ๊ชฐ ื;v๘~'33SF๊evูฒe†ทK๖ถm|ŽทฺะขE ห=รยm% %P‹๖ย… •œœœ(๓ JBB‚HMM•๑๑๑">>^ฦวว oกชชชx๗w}ื6X^9~ธแ:Vๆ๐_!„ุฟฟแ๏H@ฉฌ!ฮๆแใมๆšโ์œ/ีชUฃGv๊‡ฤx;vL,Xฐ@Yฐ`s๘๐แฮGyD?~ผ‰žTฑs็NรyYYYเๅuโฤ q็wFๅๆๆN`TT”oซ7ฏ๋็i[ตj•ขยซภ 4H‹‰‰ร‡๗`?๊ฉช*rrr มฟฎ]ปสว{Lmืฎ]ะzะ?๑_9###dOฏH•๗๚^:BœํyuัEํ`RRRส …า็ูดดดฐฆษpปย"ฒฯsป"๗’ห&๓ะ3ซS ์ุฑระ7าฯวs5Gsvvถ&„๐ี7lุ`ฮ™3วก7N ี c๎มWั๙ส๛~UตjUฟๅ|EŸ{†ko%o Q€N๓ๆอร ‚…ณm!สŸทฒณณๅดiำ|ฏ_ฟ|๓อ7๚|0qโD5T๙b^$า๗กC‡ SตjีJv้าฅB๗VyN‰Tพ6*ษูp 'ฆฮHวHไวjฝz๕๒=s—.]๊๐ืเๆฬ1y๒dวฺตk•ฅK—†œjฤNlฌHa๋uไศร6ฝ„VไทVญZe่}v.qน\Bฟ๒‘ี•ผ๒๓๓ฤด‘ฌ”์ฺตห๗๏๚๕๋[ฎˆ qv‘ p‰p ็๔CฌำำำๅK/ฝ๑นผ เ๋…𐐠–-[ๆฑ2?ŸนU+œ7`yธฬ/L๚ึืสRY €”ทUทFbภ€ฺ€„ฆi๊ฆM›”oฟVษออuฌZตสฐ"๓าฅK•๛฿ŽH,|P^•๒ษ'Ÿ8๔ซi?๑ฤฺ๏†\]์๔้ำbลŠaฟ\TซVM๔้ำG๋ำงRช๙ฯ”oฟVYธpก#??฿pืญ[งฬœ9ำjข์์l๙{๏ !ส.’——ง|๑วพใ>|ธVYso ซฝpv๎i๘๛โ‹/๋wอ=ขฏฟ๚ ฿๎ป 7lุฐข›BŸ/B„ฌๆแฝvะ๗เKII)S๎s๐เAKรฒฬS]๔้ำGป๛๎ปฯ[9QQ-gฬต@uถYณf9๔CฟฦŽซŽ7.ไy+--5์ฃี‹Dyoพึ#FŒะ"ู[SJi˜ž#นyืฌYcชWV}0!!A„;ฏ~ธธ8q๕ีW—ู7ซำๆ๘Sูฯวs4ฏโผqใF฿B วร† ๓=ร.ฝ๔Rieaพ]ปvEt๒ฮZถl้w๎/๓s/)iฌ>๗๔๛’œœlนSƒวใ k๎๗pUtฏ` ˜ธ๖ฺke๏ฝCๆsบืฦะsุ|m๔ ฟq๖9้๗ธ๒ชศuฑฺQ‘@ฮลHว:u๊”{™ธธ8q๗k๗฿ฟRช›7o๖=sWฌXaxๆฎZตJ™5k–ฃขxNส=ฦ๏ ขL๓๛์ณ–*Ÿ}๖™C?ฒไ\–๗•}ญ;&๔eฉฟ•—ƒัฏิ-DdŸลลลพ๓vI฿ณ๑ฦoิ5žGj$รแˆ่mB๓uจa็‘””$๔ซพ๋หผ๐วoผtแ"๗๗ำาา,/œaut๙Yn๏:ณ jีชขkืฎ~ห’๒๖žฺฒe‹Rั:N0๚jธฑaz•pdffสฺตk๛ฎ~8fTT”x๕ื-ี๏ฬy'œwT๓"\าnc๎!~!=?๕็ฏYณf–f9z๔จaHzค†Fช| g๔b8ฝฐƒQEดiำFŽ5Jหฯฯ๗lูฒลc‡๓ŽuฎWรํ๘ูgŸพๅ•W,๕๓‹๘๋iศ[oฝeX่"’ €่[‹}ึ<‘ฎ~๛ก์ทO๙์ณฯ|ˆtฅDขงฏ…ฒgฯ%œŠจyถีแSััั†‡E กF๑๙็Ÿ๛ŽฃZตjขWฏ^–^RVฌXafffส`+๙้_บZดhQ้+$™']บtฉ!ˆi•นˆ~๘H|||D4ษสส’“&M2ˆ*๓„โop‡bณ{๗nE฿ฉo฿พšี@ฯ”)S"าูฌY3๙๚๋ฏ›ฯศ2่าK/5gฒe๒ไษ|ดSฆL keอp์ฝะ"œแ ๆแ๚yBYพ|นข๛?๓?Zฝz๕*|-ฬAC'หรฯฑZ~ฦŠ>๚ศ—๏233eงNสซ>‡๓l๕x<โ7ฐ”ฏอ/•๑:W-๎ณH฿‹X!nธแฟำj์ฝwฝ๏ฟ~KซJkš&๔‹™๒ฦjy_ูืฺO}ะ๒=Q\\lxูŽd/{!Œภ'Ož ๋EmาคI† ิk-RC๎ยyYทb๏ฝ†ž7‘œk}O.๏"[ทnU^}๕U฿yผ๗{5ซS<่๋—B„ท€EE†Ž ๔]๓ณ œ๕ํท฿ๆพ๋ฎป.ไc^คฒๆs —”าะำผ"‹8š๏yo gอš5สฌYณ|y็แ‡ถsู|์แl่…ษน.๕.นไiž‚็|พcถ ž++หาพy<Co#!‚็Mำ Cีญ’iทr‹แ7Š‹‹ล[oฝUก๒h็ฮสš5kžสช@„ตH8ฬ=ำำำ#ฒ๒ุนsง!จj๚p้๓นe{า2{๖l‡yŽ—๒L๕เŸ๓rปััั† ๔† ๓ย7t“๔ื[,R~๙ๅร฿แฌHWฝzuร1Z]\ยใ๑ˆกC‡๚ŽQQ1t่ะˆcbbขแ๏ีซWGไ๓sฌ–ฟ;bฤง>8:t่Pฟ$หEaaกaxd0ำฆMs่ƒ5ฎ™ฯ๙^๚;ฉh9=|‡นกขGeถQ๒fฦŒKY$=ญ”๗ๆžO‘พึUชT1CฬeI0cวŽu๊ƒทแL=`E\\œ๏<—–– ซuญ•+W*๚`}ถme‡Bั 5o–žyHOกRY ˜่€'Ož?๔“ข_๘ฃvํฺ–๐๚๙็Ÿ}ฎVญšๅRBœmะาดฉŒa‡ๆนU๕#บ‚9sๆŒaลuงำ)qฟฯฝs5O|ธ๖๏฿ox.UtŠ }),,ฟปข_๘#55UŒ7ฮr1_Cซ๏จ'OžcฦŒ๑๗ ข_ฟ~eฎMe—ฉๅตu๋ึr๗๚n#Dเีฺ#มก+Rำห]ุ6ธy๓ๆr/r๊ิ)‘““UXX่๛o๕๊ี“Vว†๚๋ฏ†่ป?งOŸฝz๕Šา?ุ"นˆีH}\\œกวาถm”mถ…,gฯžํ˜={vฅ ๗๐ท=}๋ฃ?RJ1bฤ็7฿|Sฆตฑ2B–ฃ—R–้PQ๚๙Cๅ)ฏ!C†8อ-ฎม‚"ลลล†VตŠL*nีๅ—_.อ๛4fฬ็ฺตkร~๘jš&xใ G๋๋+ IDATึญฃ฿+ซ]™-ณๆ@‡ฟž,Bœญ(ฬ›7ฯแMๅ9‡กT๖๐฿ฝ{๗ึ๗Šd๓ๆอส!C n+ €Xๅ็[:f}xืฎ]ส Aƒœ็bแ/}๋ฉgŸ]Vฟk~Nฮ›7Oฑr-† ๆิ็‘~๚iแฎ&H๋ึญ ร๕๑G%??ฟยyœ‡๓๒๒.ฬ`๖ฮ;๏ž}ูููrภ€~๏M}๋ต”R่{ฬฒy๓feิจQ†|h!„ุ่ฑฃ!๘8|‡น)ห—/7”3๚FฃHฉH9๓ใ?*4œท๛kๆู2฿#V๒๘๊ีซ•แร‡ถoeซฌ”๗:t0ท™3gFด็|TT”ะEปwฏbezŽฯ>๛ฬanภซ์๚เG}rฟŠŠŠDNNŽำ{}Nง˜6mZภ๒๗ฟ}/๓\nO=๕”Sฟš๖๘๑ใีpžณ๚๒(555ฌFาH๕สฌRฅJภฉmฬฯฝนs็Zชว2ฤฉ2x๐`-PฅH๖žŠไ0ี={๖ฎhฏ}sy๐ม๚!/ฟฒjuˆฑy!„˜?~ศ๗k!„4hS_ฮ?๓ช~xฒWณfอค>๕ห/ฟ(๚๙Bฯ—ึ@ิ_ญLุฌพc๓ฯ†บ~†ฟ5)ฅหŽi๊ิฉ!„๔ฆงŸ~Z ๕—หๅ๚๓ฯ=-Zดะ๔฿u:๒ป๏พs๛nห–- ฿๙๘ใ=>[TTไุ๊ฑฃแ๓Bูบuk-ุo\qลพ๏ดiำ&่goฟvี๛ูฤฤD์ณT๕๛ัฅKMำด€ŸŸ={ถ'&&Fš๗ีW_ xฬๅI‹/v๋ท๏p8ไ[oฝๅ๗7๖๏฿๏บ๓ฮ;U!„ŒŽŽ6์ื“O>๐ฺ๐ร†฿xใ7ย:†ฏฟฺ๐่่h๙ษ'ŸXฦ\ใว๗,\ธะoชSงŽoQQQr๛๖ํ๓แฉSง\๗฿ฟjพ.Bนjีช€฿๛๗฿ วpำM7อ[‘J‹-r+Šbุฯ„„๙มx‚ๅ?orน\ฎ๗{ฯ“‘‘กyฯฉSง~ถ^ฝzพ{GQYZZ‘c่฿ฟฟแ|ฯ™3ว๏ต้ฅ—<3gฮ๔ธ\.Kบuซปnบพํfeeiว๏g๛๕๋gุ‡ฑcว†,๋ยMร‡7ฦ๔้ำ#zฏ๋_2”ื=๔Pะcศหหsี๛ึnปํ6ฟ฿{ํตื<ฏฟบ็ฬ™3–๖g๗๎๎ิิT_žน๘โ‹5ซ฿๐ร=ๆชฬkใ๏X๕ฟ9cฦ หื๊ฯ?tีำฦ—&Nœ๐๛ว๕๘ใ๒Fzzบv่ะกˆSฏ^ฝ ฟัจQ#m!ฟ็rน\฿|๓Mภฒ๏’K.1<‡๏ฝ๗ ืg๚๔้žจจ(฿็ซWฏ.๓Ÿ[oฝeธ 6ิ‚•=kืฎu'''—ษ7ฝ{๗บ_ื]wแ8ฎบ๊*ํ๐แร–ฮญฆiฎผผ<๗ƒ>๔7233 ฟ์ผ–7]sอ5†฿(((๙ลลลฎ^xมSณfMร9kฺดiภsื›พฯŽ92่ฑ๕ีW๎ฺตk—น.๒‹ทผwป–Žjy๏rน\ฉฉฉ†}ศษษQญ>Wnทkผyž`ๅะc=f87ื]w]ภg”า๕ษ'Ÿxš฿า^xแ…ˆ>#ฬu๚:u๊ศฝ{๗ฬ;w๎t7kึฬŸFŒ๐ธKJJ\V๋ๆd.๓—.]ั{ฃK—.†ใXฑbEฤ๏=}:~ธK_ฮ้S๋ึญƒๆ้าK/๕ํ๕๊ีๅัฃG-wฤˆ†X\\l๙ปญ[ท๖nvvvภzmqqฑ๋ฏy%-ฝ ธ\.ื Aƒ ๛ีดiS-ุq5/ Bศ””i๕nทK๕๏฿?ขuˆ|Cศ!ฺ๛]๒Bถkื.่ปฅ9-Xฐภํo;/ฝ๔Rะ:ษ<`ธ6;vิTU ๘;ๆ:L๚๕ตmถYพว ƒ R<ฑ๋2dศ฿>9y์ุ1ห฿mฺดฉ/ฏu่ะ!`พ?pเ€แZ=๓ฬ3ฏฝ{ ื"œบ์ฬ™3 ๅใฺตkž๛ฟ3|๖ล_ด๔™4i’gฦŒ–฿ฑv์ุaจk5kึL ๔ผ~๐มรช/]’m€}๚๔1\ฐvํฺiTอiภ€๊wก๓ŸิฬH!ฮfฮœ2šตjี’oพ๙ฆว[๕x<ฎM›6นŸyๆ5>>๗@r8– v—หๅ๚k’r)„ šำำำ}@—.]‚t สbบu๋ฆ๔ำOพ›Tำ4ืฺตk ›Bš_ž*ฃRr๕ีW— ”^qลฺุฑcีiำฆyžyO๗๎ีฟๆ‰’qqqr์ุฑ–‚2Rž}กำvอš5aร_ญพm8yฯ=๗จห–-s› ฃใวปVฏ^ํž๒ศ#j^^ž_pเ็Ÿv;V5่ฅ;v๘ญด6jิH  Žd๚โ‹/ eZีชUe฿พ}ี &๎ๅ3fxUЇ fธ'E‘C‡Uw฿98qโ„๋ใ?๖่_์„ฒnบ2œŠณีดs็Nทนั)11QN:ีSXXh๘liiฉkัขE๎กC‡ช๕๊ีำ:u๊๐๙7kึฌ2มฺ=zจ?๘ฃ๏nท๋๛๏ฟw›ห๙่่h™››๔XOœ8แ๚kม#_jัข…ถt้RCพcว๗ฐaรT๏1šŸญ“'OzOฏ_ฟํt: ฟsัEiSงN๕์ูณงฬ>๖o๎/พ๘ย๓๐รซ 4ะ„๐s๔่Q—พฎ"Dx/โV’วใq5,—๚๕๋Wฆฮ6pเ@ตOŸ>j๗๎ีฌฌ,อ_ "===่ ฟoรuw:rย„ }™์๑x\ห—/w฿qวชท~d.oฆL™โ๗บTfy?{๖์2y6++K›={ถงคคฤ๐YUU]?ณ๛ฃ>๒๔้ำG๕ƒทถo฿๎6_๋๋ฏฟ^3บืฏ_๏พ็ž{|็ฦœgฟ๖ˆ–วw%%%๖ซ^ฝzฺผy๓ ;ปvํr;V57d๔์ูS ศออ5”ม๊ๆ๔ฤO^ึร pYI๚บŽำ้”'Nœˆ่๖%sู๎}ฌ\น2์ฺ๋ญ[7รถš5kฆ >\2eŠแyดdษ’2๎ิฉ“๏ปiii–งOŸ>ําwีจh~ษw8rไศ‘ชพกุ้ฑcฎ>๚ศcฮ๋)))2TXlง‘}ำฆM†|9mฺดˆึ๏v๏ํ6_ใ=zจฯ>๛ฌg๚๔้†๋c._ฅ๚๕๋—ษ;ัััR\ต’ฦo(๋ผP‡CŽ5Jี?ƒNŸ>ํš?พงUซVe™B5H:tศe~ŸจYณฆ3fŒ๊oŸ‹‹‹]฿}๗{ฬ˜1ชทNPืJ๚ว?ก้๊๗๓Oรณz๘๐แ๓ฝนล_ฬ[Ÿ}๖™แZ„๓ž๑ะC๙๎ญ˜˜y๚๔้€Ÿ]บtฉaŸbbbd๏ฝี๑ใว—ฉฟ๊๋ำํท๗]‡ว\]ผxฑ๛ฯ?,ณญ[ทบGญ๊๔ขฃฃๅขE‹ๆอห/ฟงๆอ›๑wฌ๓‘lฬสส*S…›5jคๅๅๅY*ฐ6lุP&ˆๆM5jิ(๓฿ฎฝ๖Zmูฒe– ๖7>ฌŽน%ำJ๏วมƒ๛ ฤคคคศŒŒ อ๚่ฃช>๚จ๏;•U)ูปwฏ\้ ”RRRไขE‹/ผ๐‚ก ๚ใ?^ร๘Ž!Xฒ`i฿พ}eZš๕LII‘้้้eฮก7Uฏ^]jQ๕Wูโl๋wffฆVฏ^=Mโ#ฟ๒Kท๗D![ถl๒แก๏1,mผ9ข•zUU]๚|T”˜˜(?๘เฟ๗ฤW_}ๅ7 njฺดฉแ<š+˜๑๑๑า_๋ฆวใqyิ๚ไp8drrฒฬฬฬิาาา4sV!ซUซด7๑ษ“'หดิห๏ๅIšฆนๅpSณfอๆลำงOป7n\ๆr:ฒQฃFZFF†f~กฺ๋ตซ6gฮรฑ`มฟว๎ฏท ข(2))Ifddh้้้šพล›bccๅฌYณยzุkšๆา๗ๆ๑ฆฏพ๚ชR{mxSiii™ “ฟ”žž๎๗Z>|ธLo/}–ššช™ƒMBœญh[้ฑU๔๖o{=ckืฎ-5jค%$$H๓g‚๕๚๑x<†ž๚TซV-Yฟ~}อ_o๗„„„€yอœ{๏=ฟex||ผฬศศะฬ=:t่ ™[ฬ—-[๒ท^{ํต€็งF2==]KII‘๒นม[อฝD4h๑^โ[ถl‰H9}๓อ7ซEEEAุ๋ฑc†^แ%7nฌ5mฺT3—ูwq‡๚ฮ;๏ฎ‹ฟ@]e–๗คก๒—ฏ5jค%&&– แMก‚[O>๙ค฿ํ'''หŒŒ M฿@"ฤู้งžzส๗EQdค{K)]ฑวœโlฝง~๚ZญZต๏ AƒิP=ธ'L˜`ธถ๓ฅvํฺ๙๒RFFFD๏={๖๎‹-Zœ“๚บฑ7๕๋ืฏ\=_ฬ๗M 4aยCพิ4อๅํ0!„ปwท๛kืฎ5œทP9ŠŠŠ\FtsJJJ’ฉฉฉšฟผwษ%—h[ทn šWฬฃl‚•ตๆN๏ฉ๒$Mำ\mฺด ๙พฮศ๏ร† ;๏vmพํิฉSGฎ]ปึ๐ž%ำาาด&Mš๘ญฟ]z้ฅZฐ^ย๚”——็6ื1๕ๅr๚๕ตz๕๊iๆF*o๊ึญ[ฤ๎Kทmx†„ Oๆ‘s}๔Qภ|?n8Cฺทo_ภs5z๔hร5 ็=#;;—ฟBt๔ำสำงOปnฝ๕ึ˜š5kส้ำง{4M3 ืฉฬJษž={ๆH๚'๛๗๏ฏz+œ;wถrข๐๊Af%8pภuใ7†tNHHฃGZธ›{—Jmฺดัึญ[็6ฟ€๔ํ7ไรฃจจศะา๊/UญZUZาn๚ไ“O<๚^ซVSƒ 4sฯ sz๖ูg-U>Cฅœœรy4W0;v์่7์ุฑร]ž๒ฺ่kฏีB\WญZeุ‡ิิT้kณ}๛๖ˆผ˜ !ไw฿0/ฎYณฦญฏ์JUชT‘ฯ=๗œวๅrนฦŒcธ7ฝ๘8pภๅ/ˆ*eggkๅญl›ห‚[nนๅœXบtฉ;---่ิฃG€๛ดgฯwถm-ัััrเภชฟึึHง๗{ฯN0:***dเ๕๘๑ใฎ[nนลRซ(Šผ๙ๆ›ี฿~๛-ฌ|1z๔h5Ppฮ›œNง>|ธz๊ิ)ืะกCห5็ร?๔Xm0ำSvvถถ~๚€ว๔ส+ฏสะp^ฤญฆ๗฿ฟๅt•*Uไํท฿ฎkล7ง%K–ธฝฬ้S\\œ|ๅ•W<วen๔<~x™ํVfy๏Mšฆนžyฟ@cจ<ึนs็ร็nทซgฯž!๏‰ธธ89eสชช†‘ 6ฌด๚เป๏พ๋ฑr„8c,ุห>u๏wผ๓%UU]๚œฝz๕Š่ฝ๑้งŸ๎‹๛๎ป๏œ๓ˆ๔ž*o'…P้—_~q‡ šภƒ%๓ปpฝz๕‚”๔= ฝ=๙๕ฏl่๒&‡ร!๛๕๋ง†๛›+Vฌp{ง '5mฺTณาpc5๒}จ๚๔๒ห/ฮ}ฐiฃ๔๏กz0๊๋ดแผg˜hV†ฐฏ]ปึํฏC€>้Gv์ปืจฑ+Xบ๊ชซ‚ึ{ค<นK„„หฯ† =E :|๘ฐโoตŸ@jิจ!๊ิฉ#[ดh!.ป์2™––&ห๓ป#GŽิZตj%_|๑E็ส•+ ‹คฆฆส;๏ผS0@kูฒฅโ์ไœ๚…E‚-๊QซV-หŸu:†ฯ5Œ6จุุX๑ล_จ๏พ๛ฎœ>}บร<iffฆ์ูณง8p oRํ+ฎธBz'ŒฎฬI‰4h /^์ูฒe‹’››ซ์ทOQUUคฆฆสŒŒ ัตkWอ;wiiฉXฑb…o฿ปt้tฟฎพ๚j฿bVW๔'99Yไๆๆz-Zคฬž=๑๗฿๛]ธnบโ’K.‘็?e‡ด๖ํKช{ผ๖j๛๖ํๅ”)S›6m2L€'บt้ข๕่ัC๖์ูS‹ŠŠ%%%โž{๎๑}จG!ฏRR’ศหห๓๘ใสชUซ๓สkBœ]`!*ชrŠŒ=zhทzซ๖ๅ—_:>๔Seๅส•สพ}๛สL๖[ตjUัฆM™-oปํ6ํบ๋ฎ“กVฎWฏžฐบ€O0ทzซaชชถheม&Mšศโโb๗7฿|ใศฯฯWึฎ]ซl฿พ0)ฎg๏ฆM›ส:ศป๏พ[ป๖ฺkCๆGMำ ๛™™๑๛๐ไษ“9Bl๕+ฏผRฎ_ฟ3~xวW_}UfA†–-[ส๎ปห๛๎ปO๓.\Tฟ~}฿โLีซW๗ปบ`rrฒ(..v/\ธะ‘ŸŸฏฌ^ฝZ๙ๅ—_๓ไ๙NงS\|๑ลฒ]ปv2''G๋ิฉSDฮeีชUล”)S*mี_ฺตk'๕Wฯ’%K”7*………Š~ีZ!„่ึญ[ภใkะ แ‡<๏ผ๓Žc๎นŽๅห—+ …8ปา}๋ึญe็ฮๅ Aƒด`ซฌGR฿พ}ตnบiฏฝ๖š๓๋ฏฟV6o\f‘’””‘ญu๎Y๖์ูS 5Y}\\œ๘๒ห/ี/พ๘Bพ๛ส๗฿๏0ŸซฆM›สฮ;หพ}๛jๅyNL˜0Aฝ๎บ๋ดI“&9๓๒๒U๋คฆฆŠ=zh๗฿ฟๆ]Q6==—ฏสฌRHฏ^ฝด[nนE›>}บ#77ืฑz๕jๅฬ™3†ฯDGG‹† สถmสvํฺษ.]บศPื/11ัPtำMฟ5jิฐ\ฮ8QซV-Qฟ~}ูฒeKyูe—IŠหVดo฿W,Xฐภ๑็Ÿ๚Ÿข(ขu๋ึฒ{๗๎Z฿พ}}uยF๙ฎKบu…ฟ@*ณผื๏฿ำO?ญ๕ํWN›6อ๑w฿)7n4ไ+!ฮ.|ะดiSy๙ๅ—ห๖ํหฎ]ปjๆUฃ‰ŠŠs็ฮUปu๋&งM›Vfaฉ&Mšศป๎บK๖๏฿_๓ฎาุฆM้])=++ซาสƒ~๚i:u’“&Mr|๙ๅ—สฏฟjุทš5kŠ๖ํkทzซผ๗{ต˜˜Kmูฒฅจ[ทฎ&„_|ฑๅ…*๓Oqื]w๙๒mคWxฏRฅŠaยoฟœไqธnรตhธqX‹]ศ)ฅ๋|๏„:uJ*งN้้้W๐ปP๖ุcม?X1|‡7๘'„ฯ<๓ŒZžเŸB่งกชSงŽ๐N5 2โ‚ไvปลฤ‰หฬลไฯ๎ป•N:E-^ผุ๗ภ4hv๕ีW๓ภpNฬ;ื๑๊ซฏ๚žฉ?ฐvuืQ.x{๗๎U๎น็งw๎ๅfอšษ &ะŽถmฆ 0ภืc๏ฒห.“#GŽ,๗ิ'๚`ถmm3๏pก`0.H›7oVฺดiUงNqื]wi;w–-[ถ”IIIRJ)Š‹‹•M›6)_}๕•2o<‡ห๕ณ๑๕ื_/,Xเนๆจ`?'OžgฮœชชŠ}๛๖)sๆฬqLž<ูW5iาDxชUซvž๗#ว#Ž;&„ขคคDYฑb…2n8วฝ{!ฮ]ถl™‡†t˜นnแ]”กจจHYผxฑ2aยgaaกโ์ะ฿u๋ึyผ ^†ซธธX$''๛&[9rค๖โ‹/ˆ"ศ–ซใ๏oใฦŠB>|XLŸ>1}๚tK฿ป๋ฎปด™3gชT–๛๎ปฯ๙้งŸ๚ํA_ปvm๑๙็Ÿซขนs็:z๗๎pUษ“'ซเฯŒ3>๚จ฿ผฃ(Š˜9sฆZเŸBฌ_ฟะฏ<+๚Ž!ภธ ๖oa๕๗nผน๐รี9sๆจท—ฝ˜+จ^๕๋ื—yyyžฌฌ,*ฌ€ ’~ˆฅ^LLŒ˜:uช๚ศ#ญWฎGๅ ”wชUซ&{๏=ตw๏ส;ๆํ"€ธ ?^ฝ๓ฮ;ตผผ image/svg+xml Typer Build great CLIs. Easy to code. Based on Python type hints. typer-0.15.2/docs/img/icon-square.svg000066400000000000000000000055271476013476600174440ustar00rootroot00000000000000 image/svg+xml typer-0.15.2/docs/img/icon.svg000066400000000000000000000055761476013476600161520ustar00rootroot00000000000000 image/svg+xml typer-0.15.2/docs/img/logo-margin/000077500000000000000000000000001476013476600166775ustar00rootroot00000000000000typer-0.15.2/docs/img/logo-margin/logo-margin-vector.svg000066400000000000000000000140511476013476600231340ustar00rootroot00000000000000 image/svg+xml typer-0.15.2/docs/img/logo-margin/logo-margin-white-vector.svg000066400000000000000000000140661476013476600242600ustar00rootroot00000000000000 image/svg+xml typer-0.15.2/docs/img/logo-margin/logo-margin-white.svg000066400000000000000000000067041476013476600227600ustar00rootroot00000000000000 image/svg+xml Typer typer-0.15.2/docs/img/logo-margin/logo-margin.svg000066400000000000000000000066671476013476600216520ustar00rootroot00000000000000 image/svg+xml Typer typer-0.15.2/docs/img/pycharm-completion.png000066400000000000000000000331441476013476600210110ustar00rootroot00000000000000‰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.15.2/docs/img/vscode-completion.png000066400000000000000000000543411476013476600206330ustar00rootroot00000000000000‰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.15.2/docs/index.md000066400000000000000000000335431476013476600153510ustar00rootroot00000000000000

Typer 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/fastapi/typer --- Typer is a library for building CLI applications that users will **love using** and developers will **love creating**. Based on Python type hints. It's also a command line tool to run scripts, automatically converting them to CLI applications. 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. * **Run scripts**: Typer includes a `typer` command/program that you can use to run scripts, automatically converting them to CLIs, even if they don't use Typer internally. ## FastAPI of CLIs **Typer** is FastAPI's little sibling, it's the FastAPI of CLIs. ## Installation Create and activate a virtual environment and then install **Typer**:
```console $ pip install typer ---> 100% Successfully installed typer rich shellingham ```
## Example ### The absolute minimum * Create a file `main.py` with: ```Python def main(name: str): print(f"Hello {name}") ``` This script doesn't even use Typer internally. But you can use the `typer` command to run it as a CLI application. ### Run it Run your application with the `typer` command:
```console // Run your application $ typer main.py run // You get a nice error, you are missing NAME Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME Try 'typer [PATH_OR_MODULE] run --help' for help. โ•ญโ”€ Error โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ Missing argument 'NAME'. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // You get a --help for free $ typer main.py run --help Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME Run the provided Typer app. โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ * name TEXT [default: None] [required] | โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --help Show this message and exit. โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ // Now pass the NAME argument $ typer main.py run Camila Hello Camila // It works! ๐ŸŽ‰ ```
This is the simplest use case, not even using Typer internally, but it can already be quite useful for simple scripts. **Note**: auto-completion works when you create a Python package and run it with `--install-completion` or when you use the `typer` command. ## Use Typer in your code Now let's start using Typer in your own code, update `main.py` with: ```Python import typer def main(name: str): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) ``` Now you could run it with Python directly:
```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**: you can also call this same script with the `typer` command, but you don't need to. ## 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**. 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 the `typer` command. For a more complete example including more features, see the Tutorial - User Guide. ## Dependencies **Typer** stands on the shoulders of a giant. Its only internal required dependency is Click. By default it also comes with extra standard dependencies: * rich: to show nicely formatted errors automatically. * shellingham: to 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`. ### `typer-slim` If you don't want the extra standard optional dependencies, install `typer-slim` instead. When you install with: ```bash pip install typer ``` ...it includes the same code and dependencies as: ```bash pip install "typer-slim[standard]" ``` The `standard` extra dependencies are `rich` and `shellingham`. **Note**: The `typer` command is only included in the `typer` package. ## License This project is licensed under the terms of the MIT license. typer-0.15.2/docs/js/000077500000000000000000000000001476013476600143245ustar00rootroot00000000000000typer-0.15.2/docs/js/custom.js000066400000000000000000000105211476013476600161730ustar00rootroot00000000000000function setupTermynal() { document.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 code`) .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(); } async function main() { setupTermynal() } document$.subscribe(() => { main() }) typer-0.15.2/docs/js/termynal.js000066400000000000000000000225031476013476600165170ustar00rootroot00000000000000/** * 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.15.2/docs/management-tasks.md000066400000000000000000000204601476013476600174730ustar00rootroot00000000000000# Repository Management Tasks These are the tasks that can be performed to manage the Typer repository by [team members](./management.md#team){.internal-link target=_blank}. /// tip This section is useful only to a handful of people, team members with permissions to manage the repository. You can probably skip it. ๐Ÿ˜‰ /// ...so, you are a [team member of Typer](./management.md#team){.internal-link target=_blank}? Wow, you are so cool! ๐Ÿ˜Ž You can help with everything on [Help Typer - Get Help](./help-typer.md){.internal-link target=_blank} the same ways as external contributors. But additionally, there are some tasks that only you (as part of the team) can perform. Here are the general instructions for the tasks you can perform. Thanks a lot for your help. ๐Ÿ™‡ ## Be Nice First of all, be nice. ๐Ÿ˜Š You probably are super nice if you were added to the team, but it's worth mentioning it. ๐Ÿค“ ### When Things are Difficult When things are great, everything is easier, so that doesn't need much instructions. But when things are difficult, here are some guidelines. Try to find the good side. In general, if people are not being unfriendly, try to thank their effort and interest, even if you disagree with the main subject (discussion, PR), just thank them for being interested in the project, or for having dedicated some time to try to do something. It's difficult to convey emotion in text, use emojis to help. ๐Ÿ˜… In discussions and PRs, in many cases, people bring their frustration and show it without filter, in many cases exaggerating, complaining, being entitled, etc. That's really not nice, and when it happens, it lowers our priority to solve their problems. But still, try to breath, and be gentle with your answers. Try to avoid using bitter sarcasm or potentially passive-aggressive comments. If something is wrong, it's better to be direct (try to be gentle) than sarcastic. Try to be as specific and objective as possible, avoid generalizations. For conversations that are more difficult, for example to reject a PR, you can ask me (@tiangolo) to handle it directly. ## Edit PR Titles * Edit the PR title to start with an emoji from gitmoji. * Use the emoji character, not the GitHub code. So, use `๐Ÿ›` instead of `:bug:`. This is so that it shows up correctly outside of GitHub, for example in the release notes. * Start the title with a verb. For example `Add`, `Refactor`, `Fix`, etc. This way the title will say the action that the PR does. Like `Add support for teleporting`, instead of `Teleporting wasn't working, so this PR fixes it`. * Edit the text of the PR title to start in "imperative", like giving an order. So, instead of `Adding support for teleporting` use `Add support for teleporting`. * Try to make the title descriptive about what it achieves. If it's a feature, try to describe it, for example `Add support for teleporting` instead of `Create TeleportAdapter class`. * Do not finish the title with a period (`.`). Once the PR is merged, a GitHub Action (latest-changes) will use the PR title to update the latest changes automatically. So, having a nice PR title will not only look nice in GitHub, but also in the release notes. ๐Ÿ“ ## Add Labels to PRs The same GitHub Action latest-changes uses one label in the PR to decide the section in the release notes to put this PR in. Make sure you use a supported label from the latest-changes list of labels: * `breaking`: Breaking Changes * Existing code will break if they update the version without changing their code. This rarely happens, so this label is not frequently used. * `security`: Security Fixes * This is for security fixes, like vulnerabilities. It would almost never be used. * `feature`: Features * New features, adding support for things that didn't exist before. * `bug`: Fixes * Something that was supported didn't work, and this fixes it. There are many PRs that claim to be bug fixes because the user is doing something in an unexpected way that is not supported, but they considered it what should be supported by default. Many of these are actually features or refactors. But in some cases there's an actual bug. * `refactor`: Refactors * This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc. * `upgrade`: Upgrades * This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`. * `docs`: Docs * Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations. * You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`. * `internal`: Internal * Use this for changes that only affect how the repo is managed. For example upgrades to internal dependencies, changes in GitHub Actions or scripts, etc. /// tip Some tools like Dependabot, will add some labels, like `dependencies`, but have in mind that this label is not used by the `latest-changes` GitHub Action, so it won't be used in the release notes. Please make sure one of the labels above is added. /// ## Review PRs If a PR doesn't explain what it does or why, ask for more information. A PR should have a specific use case that it is solving. * If the PR is for a feature, it should have docs. * Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use. * The docs should include a source example file, not write Python directly in Markdown. * If the source example(s) file can have different syntax for Python 3.8, 3.9, 3.10, there should be different versions of the file, and they should be shown in tabs in the docs. * There should be tests testing the source example. * Before the PR is applied, the new tests should fail. * After applying the PR, the new tests should pass. * Coverage should stay at 100%. * If you see the PR makes sense, or we discussed it and considered it should be accepted, you can add commits on top of the PR to tweak it, to add docs, tests, format, refactor, remove extra files, etc. * Feel free to comment in the PR to ask for more information, to suggest changes, etc. * Once you think the PR is ready, move it in the internal GitHub project for me to review it. ## Dependabot PRs Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others. * If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. ๐Ÿ˜ฑ Let me check it first. There's a good chance that some additional tweaks or updates are needed. * If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. ๐Ÿ˜Ž ## Mark GitHub Discussions Answers When a question in GitHub Discussions has been answered, mark the answer by clicking "Mark as answer". Many of the current Discussion Questions were migrated from old issues. Many have the label `answered`, that means they were answered when they were issues, but now in GitHub Discussions, it's not known what is the actual response from the messages. You can filter discussions by `Questions` that are `Unanswered`. typer-0.15.2/docs/management.md000066400000000000000000000034631476013476600163540ustar00rootroot00000000000000# Repository Management Here's a short description of how the Typer repository is managed and maintained. ## Owner I, @tiangolo, am the creator and owner of the Typer repository. ๐Ÿค“ I normally give the final review to each PR before merging them. I make the final decisions on the project, I'm the BDFL. ๐Ÿ˜… ## Team There's a team of people that help manage and maintain the project. ๐Ÿ˜Ž They have different levels of permissions and [specific instructions](./management-tasks.md){.internal-link target=_blank}. Some of the tasks they can perform include: * Adding labels to PRs. * Editing PR titles. * Adding commits on top of PRs to tweak them. * Mark answers in GitHub Discussions questions, etc. * Merge some specific types of PRs. Joining the team is by invitation only, and I could update or remove permissions, instructions, or membership. ### Team Members This is the current list of team members. ๐Ÿ˜Ž
{% for user in members["members"] %} {% endfor %}
Additional to them, there's a large community of people helping each other and getting involved in the projects in different ways. ## External Contributions External contributions are very welcome and appreciated, including answering questions, submitting PRs, etc. ๐Ÿ™‡โ€โ™‚๏ธ There are many ways to [help maintain Typer](./help-typer.md){.internal-link target=_blank}. typer-0.15.2/docs/overrides/000077500000000000000000000000001476013476600157125ustar00rootroot00000000000000typer-0.15.2/docs/overrides/main.html000066400000000000000000000000321476013476600175170ustar00rootroot00000000000000{% extends "base.html" %} typer-0.15.2/docs/release-notes.md000066400000000000000000002177221476013476600170130ustar00rootroot00000000000000# Release Notes ## Latest Changes ## 0.15.2 ### Features * โœจ Allow custom styles for commands in help output. PR [#1103](https://github.com/fastapi/typer/pull/1103) by [@TheTechromancer](https://github.com/TheTechromancer). * โœจ Avoid the unnecessary import of `typing_extensions` in newer Python versions. PR [#1048](https://github.com/fastapi/typer/pull/1048) by [@horta](https://github.com/horta). ### Fixes * ๐Ÿ› Fix shell completions for the fish shell. PR [#1069](https://github.com/fastapi/typer/pull/1069) by [@goraje](https://github.com/goraje). ### Refactors * ๐Ÿšš Rename test to corner-cases to make it more explicit. PR [#1083](https://github.com/fastapi/typer/pull/1083) by [@tiangolo](https://github.com/tiangolo). ### Docs * โœ๏ธ Fix small typos in the tutorial documentation. PR [#1137](https://github.com/fastapi/typer/pull/1137) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“ Update optional CLI argument section in tutorial with `Annotated`. PR [#983](https://github.com/fastapi/typer/pull/983) by [@gkeuccsr](https://github.com/gkeuccsr). * ๐Ÿ“ Clarify the need for `mix_stderr` when accessing the output of `stderr` in tests. PR [#1045](https://github.com/fastapi/typer/pull/1045) by [@mrchrisadams](https://github.com/mrchrisadams). ### Internal * ๐Ÿ”ง Add support for Python 3.13, tests in CI and add PyPI trove classifier. PR [#1091](https://github.com/fastapi/typer/pull/1091) by [@edgarrmondragon](https://github.com/edgarrmondragon). * โฌ† Bump ruff from 0.9.6 to 0.9.7. PR [#1161](https://github.com/fastapi/typer/pull/1161) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1162](https://github.com/fastapi/typer/pull/1162) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.9.5 to 0.9.6. PR [#1153](https://github.com/fastapi/typer/pull/1153) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1151](https://github.com/fastapi/typer/pull/1151) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.9.4 to 0.9.5. PR [#1146](https://github.com/fastapi/typer/pull/1146) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1142](https://github.com/fastapi/typer/pull/1142) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.9.3 to 0.9.4. PR [#1139](https://github.com/fastapi/typer/pull/1139) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1135](https://github.com/fastapi/typer/pull/1135) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.9.1 to 0.9.3. PR [#1136](https://github.com/fastapi/typer/pull/1136) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1130](https://github.com/fastapi/typer/pull/1130) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.8.6 to 0.9.1. PR [#1118](https://github.com/fastapi/typer/pull/1118) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4. PR [#1132](https://github.com/fastapi/typer/pull/1132) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-material from 9.5.49 to 9.5.50. PR [#1129](https://github.com/fastapi/typer/pull/1129) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ’š Fix test matrix for Python 3.7. PR [#1116](https://github.com/fastapi/typer/pull/1116) by [@svlandeg](https://github.com/svlandeg). * โฌ† Bump ruff from 0.8.4 to 0.8.6. PR [#1107](https://github.com/fastapi/typer/pull/1107) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1109](https://github.com/fastapi/typer/pull/1109) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump pillow from 11.0.0 to 11.1.0. PR [#1104](https://github.com/fastapi/typer/pull/1104) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1102](https://github.com/fastapi/typer/pull/1102) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.8.3 to 0.8.4. PR [#1097](https://github.com/fastapi/typer/pull/1097) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump astral-sh/setup-uv from 4 to 5. PR [#1098](https://github.com/fastapi/typer/pull/1098) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump markdown-include-variants from 0.0.3 to 0.0.4. PR [#1100](https://github.com/fastapi/typer/pull/1100) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump ruff from 0.8.2 to 0.8.3. PR [#1090](https://github.com/fastapi/typer/pull/1090) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1093](https://github.com/fastapi/typer/pull/1093) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump mkdocs-material from 9.5.48 to 9.5.49. PR [#1092](https://github.com/fastapi/typer/pull/1092) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3. PR [#1088](https://github.com/fastapi/typer/pull/1088) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1087](https://github.com/fastapi/typer/pull/1087) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.8.1 to 0.8.2. PR [#1084](https://github.com/fastapi/typer/pull/1084) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-material from 9.5.47 to 9.5.48. PR [#1086](https://github.com/fastapi/typer/pull/1086) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.15.1 ### Features * ๐Ÿ—‘๏ธ Deprecate `shell_complete` and continue to use `autocompletion` for CLI parameters. PR [#974](https://github.com/fastapi/typer/pull/974) by [@svlandeg](https://github.com/svlandeg). ### Docs * โœ๏ธ Fix a few typos in the source and documentation. PR [#1028](https://github.com/fastapi/typer/pull/1028) by [@kkirsche](https://github.com/kkirsche). * ๐Ÿ“ Fix minor inconsistencies and typos in tutorial. PR [#1067](https://github.com/fastapi/typer/pull/1067) by [@tvoirand](https://github.com/tvoirand). * โœ๏ธ Fix a few small typos in the documentation. PR [#1077](https://github.com/fastapi/typer/pull/1077) by [@svlandeg](https://github.com/svlandeg). ### Internal * ๐Ÿ”ง Update build-docs filter patterns. PR [#1080](https://github.com/fastapi/typer/pull/1080) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update deploy docs preview script. PR [#1079](https://github.com/fastapi/typer/pull/1079) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update members. PR [#1078](https://github.com/fastapi/typer/pull/1078) by [@tiangolo](https://github.com/tiangolo). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1071](https://github.com/fastapi/typer/pull/1071) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Update httpx requirement from <0.28.0,>=0.27.0 to >=0.27.0,<0.29.0. PR [#1065](https://github.com/fastapi/typer/pull/1065) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.15.0 ### Features * โœจ Add support for extending typer apps without passing a name, add commands to the top level. PR [#1037](https://github.com/fastapi/typer/pull/1037) by [@patrick91](https://github.com/patrick91). * New docs: [One File Per Command](https://typer.tiangolo.com/tutorial/one-file-per-command/). ### Internal * โฌ† Bump mkdocs-material from 9.5.46 to 9.5.47. PR [#1070](https://github.com/fastapi/typer/pull/1070) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump ruff from 0.8.0 to 0.8.1. PR [#1066](https://github.com/fastapi/typer/pull/1066) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.14.0 ### Breaking Changes * ๐Ÿ”ฅ Remove auto naming of groups added via `add_typer` based on the group's callback function name. PR [#1052](https://github.com/fastapi/typer/pull/1052) by [@patrick91](https://github.com/patrick91). Before, it was supported to infer the name of a command group from the callback function name in the sub-app, so, in this code: ```python import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app) @users_app.callback() def users(): # <-- This was the inferred command group name """ Manage users in the app. """ @users_app.command() def create(name: str): print(f"Creating user: {name}") ``` ...the command group would be named `users`, based on the name of the function `def users()`. Now you need to set it explicitly: ```python import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app, name="users") # <-- Explicitly set the command group name @users_app.callback() def users(): """ Manage users in the app. """ @users_app.command() def create(name: str): print(f"Creating user: {name}") ``` Updated docs [SubCommand Name and Help](https://typer.tiangolo.com/tutorial/subcommands/name-and-help/). **Note**: this change will enable important features in the next release. ๐Ÿคฉ ### Internal * โฌ† Bump pypa/gh-action-pypi-publish from 1.10.3 to 1.12.2. PR [#1043](https://github.com/fastapi/typer/pull/1043) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-material from 9.5.44 to 9.5.46. PR [#1062](https://github.com/fastapi/typer/pull/1062) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump ruff from 0.7.4 to 0.8.0. PR [#1059](https://github.com/fastapi/typer/pull/1059) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump astral-sh/setup-uv from 3 to 4. PR [#1061](https://github.com/fastapi/typer/pull/1061) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1053](https://github.com/fastapi/typer/pull/1053) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). ## 0.13.1 ### Features * โœจ Remove Rich tags when showing completion text. PR [#877](https://github.com/fastapi/typer/pull/877) by [@svlandeg](https://github.com/svlandeg). * โœจ Render Rich markup as HTML in Markdown docs. PR [#847](https://github.com/fastapi/typer/pull/847) by [@svlandeg](https://github.com/svlandeg). * โœจ Support cp850 encoding for auto-completion in PowerShell. PR [#808](https://github.com/fastapi/typer/pull/808) by [@svlandeg](https://github.com/svlandeg). * โœจ Allow gettext translation of help message. PR [#886](https://github.com/fastapi/typer/pull/886) by [@svlandeg](https://github.com/svlandeg). ### Refactors * ๐Ÿ› Fix printing HTML from Rich output. PR [#1055](https://github.com/fastapi/typer/pull/1055) by [@tiangolo](https://github.com/tiangolo). ### Docs * ๐Ÿ“ Update markdown includes to use the new simpler format. PR [#1054](https://github.com/fastapi/typer/pull/1054) by [@tiangolo](https://github.com/tiangolo). ### Internal * โฌ† Bump ruff from 0.7.3 to 0.7.4. PR [#1051](https://github.com/fastapi/typer/pull/1051) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1047](https://github.com/fastapi/typer/pull/1047) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.7.2 to 0.7.3. PR [#1046](https://github.com/fastapi/typer/pull/1046) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#1044](https://github.com/fastapi/typer/pull/1044) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Update pytest-cov requirement from <6.0.0,>=2.10.0 to >=2.10.0,<7.0.0. PR [#1033](https://github.com/fastapi/typer/pull/1033) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.13.0 ### Features * โœจ Handle `KeyboardInterrupt` separately from other exceptions. PR [#1039](https://github.com/fastapi/typer/pull/1039) by [@patrick91](https://github.com/patrick91). * โœจ Update `launch` to not print anything when opening urls. PR [#1035](https://github.com/fastapi/typer/pull/1035) by [@patrick91](https://github.com/patrick91). * โœจ Show help items in order of definition. PR [#944](https://github.com/fastapi/typer/pull/944) by [@svlandeg](https://github.com/svlandeg). ### Fixes * ๐Ÿ› Fix equality check for custom classes. PR [#979](https://github.com/fastapi/typer/pull/979) by [@AryazE](https://github.com/AryazE). * ๐Ÿ› Allow colon in zsh autocomplete values and descriptions. PR [#988](https://github.com/fastapi/typer/pull/988) by [@snapbug](https://github.com/snapbug). ### Refactors * ๐Ÿ—‘๏ธ Deprecate support for `is_flag` and `flag_value` parameters. PR [#987](https://github.com/fastapi/typer/pull/987) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ”ฅ Remove unused functionality from `_typing.py` file. PR [#805](https://github.com/fastapi/typer/pull/805) by [@ivantodorovich](https://github.com/ivantodorovich). * โœ๏ธ Fix typo in function name `_make_rich_text`. PR [#959](https://github.com/fastapi/typer/pull/959) by [@svlandeg](https://github.com/svlandeg). ### Internal * โœ… Only run completion installation tests when the env var `_TYPER_RUN_INSTALL_COMPLETION_TESTS` is set. PR [#995](https://github.com/fastapi/typer/pull/995) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“ Update the docstring of the `_make_rich_text` method. PR [#972](https://github.com/fastapi/typer/pull/972) by [@svlandeg](https://github.com/svlandeg). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1040](https://github.com/fastapi/typer/pull/1040) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump mkdocs-material from 9.5.42 to 9.5.44. PR [#1042](https://github.com/fastapi/typer/pull/1042) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump ruff from 0.7.1 to 0.7.2. PR [#1038](https://github.com/fastapi/typer/pull/1038) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-macros-plugin from 1.3.6 to 1.3.7. PR [#1031](https://github.com/fastapi/typer/pull/1031) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1032](https://github.com/fastapi/typer/pull/1032) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.7.0 to 0.7.1. PR [#1029](https://github.com/fastapi/typer/pull/1029) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pillow from 10.4.0 to 11.0.0. PR [#1023](https://github.com/fastapi/typer/pull/1023) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-material from 9.5.35 to 9.5.42. PR [#1027](https://github.com/fastapi/typer/pull/1027) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump ruff from 0.6.5 to 0.7.0. PR [#1026](https://github.com/fastapi/typer/pull/1026) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-macros-plugin from 1.2.0 to 1.3.6. PR [#1025](https://github.com/fastapi/typer/pull/1025) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Update pre-commit requirement from <4.0.0,>=2.17.0 to >=2.17.0,<5.0.0. PR [#1012](https://github.com/fastapi/typer/pull/1012) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pypa/gh-action-pypi-publish from 1.10.1 to 1.10.3. PR [#1009](https://github.com/fastapi/typer/pull/1009) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#1001](https://github.com/fastapi/typer/pull/1001) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ๐Ÿ‘ท Update Deploy docs CI to use uv. PR [#1021](https://github.com/fastapi/typer/pull/1021) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Fix smokeshow, checkout files on CI. PR [#1020](https://github.com/fastapi/typer/pull/1020) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Use uv in CI. PR [#1019](https://github.com/fastapi/typer/pull/1019) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update `labeler.yml`. PR [#1014](https://github.com/fastapi/typer/pull/1014) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update worfkow deploy-docs-notify URL. PR [#1011](https://github.com/fastapi/typer/pull/1011) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade Cloudflare GitHub Action. PR [#1010](https://github.com/fastapi/typer/pull/1010) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump mkdocs-macros-plugin from 1.0.5 to 1.2.0. PR [#992](https://github.com/fastapi/typer/pull/992) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump ruff from 0.6.4 to 0.6.5. PR [#991](https://github.com/fastapi/typer/pull/991) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-material from 9.5.34 to 9.5.35. PR [#996](https://github.com/fastapi/typer/pull/996) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#993](https://github.com/fastapi/typer/pull/993) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#982](https://github.com/fastapi/typer/pull/982) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#980](https://github.com/fastapi/typer/pull/980) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Update `issue-manager.yml`. PR [#978](https://github.com/fastapi/typer/pull/978) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump ruff from 0.6.3 to 0.6.4. PR [#975](https://github.com/fastapi/typer/pull/975) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump mkdocs-material from 9.5.33 to 9.5.34. PR [#963](https://github.com/fastapi/typer/pull/963) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.1. PR [#973](https://github.com/fastapi/typer/pull/973) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#966](https://github.com/fastapi/typer/pull/966) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ๐Ÿ’š Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#967](https://github.com/fastapi/typer/pull/967) by [@svlandeg](https://github.com/svlandeg). * โฌ† Bump ruff from 0.6.1 to 0.6.3. PR [#961](https://github.com/fastapi/typer/pull/961) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#689](https://github.com/fastapi/typer/pull/689) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * โฌ† Bump ruff from 0.2.0 to 0.6.1. PR [#938](https://github.com/fastapi/typer/pull/938) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Update `latest-changes` GitHub Action. PR [#955](https://github.com/fastapi/typer/pull/955) by [@tiangolo](https://github.com/tiangolo). ## 0.12.5 ### Features * ๐Ÿ’„ Unify the width of the Rich console for help and errors. PR [#788](https://github.com/fastapi/typer/pull/788) by [@racinmat](https://github.com/racinmat). * ๐Ÿšธ Improve assertion error message if a group is not a valid subclass. PR [#425](https://github.com/fastapi/typer/pull/425) by [@chrisburr](https://github.com/chrisburr). ### Fixes * ๐Ÿ› Ensure `rich_markup_mode=None` disables Rich formatting. PR [#859](https://github.com/fastapi/typer/pull/859) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ› Fix sourcing of completion path for Git Bash. PR [#801](https://github.com/fastapi/typer/pull/801) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ› Fix PowerShell completion with incomplete word. PR [#360](https://github.com/fastapi/typer/pull/360) by [@patricksurry](https://github.com/patricksurry). ### Refactors * ๐Ÿ”ฅ Remove Python 3.6 specific code paths. PR [#850](https://github.com/fastapi/typer/pull/850) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ”ฅ Clean up redundant code. PR [#858](https://github.com/fastapi/typer/pull/858) by [@svlandeg](https://github.com/svlandeg). ### Docs * โ™ป๏ธ Use F-strings in Click examples in docs. PR [#891](https://github.com/fastapi/typer/pull/891) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“Add missing `main.py` in tutorial on CLI option names. PR [#868](https://github.com/fastapi/typer/pull/868) by [@fsramalho](https://github.com/fsramalho). * ๐Ÿ“ Fix broken link. PR [#835](https://github.com/fastapi/typer/pull/835) by [@OhioDschungel6](https://github.com/OhioDschungel6). * ๐Ÿ“ Update package docs with the latest versions of Typer and Poetry. PR [#781](https://github.com/fastapi/typer/pull/781) by [@kinuax](https://github.com/kinuax). * ๐Ÿ“ Update the Progress Bar tutorial with correct output. PR [#199](https://github.com/fastapi/typer/pull/199) by [@n1ckdm](https://github.com/n1ckdm). * ๐Ÿ“ Add docs and scripts to test completion in different shells. PR [#953](https://github.com/fastapi/typer/pull/953) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix a typo in `docs/virtual-environments.md`. PR [#952](https://github.com/fastapi/typer/pull/952) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix typo in `docs/contributing.md`. PR [#947](https://github.com/fastapi/typer/pull/947) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Add docs for virtual environments, environment variables, and update contributing. PR [#946](https://github.com/fastapi/typer/pull/946) by [@tiangolo](https://github.com/tiangolo). ### Internal * ๐Ÿ”จ Pre-install dependencies in Docker so that testing in Docker is faster. PR [#954](https://github.com/fastapi/typer/pull/954) by [@tiangolo](https://github.com/tiangolo). * โœ… Add `needs_bash` test fixture. PR [#888](https://github.com/fastapi/typer/pull/888) by [@svlandeg](https://github.com/svlandeg). * โฌ† Bump mkdocs-material from 9.5.18 to 9.5.33. PR [#945](https://github.com/fastapi/typer/pull/945) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pillow from 10.3.0 to 10.4.0. PR [#939](https://github.com/fastapi/typer/pull/939) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Fix issue-manager. PR [#948](https://github.com/fastapi/typer/pull/948) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ™ˆ Remove extra line in .gitignore. PR [#936](https://github.com/fastapi/typer/pull/936) by [@tiangolo](https://github.com/tiangolo). * โฌ† Update pytest-cov requirement from <5.0.0,>=2.10.0 to >=2.10.0,<6.0.0. PR [#844](https://github.com/fastapi/typer/pull/844) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.9.0. PR [#865](https://github.com/fastapi/typer/pull/865) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Update pytest requirement from <8.0.0,>=4.4.0 to >=4.4.0,<9.0.0. PR [#915](https://github.com/fastapi/typer/pull/915) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Update pytest-sugar requirement from <0.10.0,>=0.9.4 to >=0.9.4,<1.1.0. PR [#841](https://github.com/fastapi/typer/pull/841) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.12.4 ### Features * โœจ Add support for Python 3.12, tests in CI and official marker. PR [#807](https://github.com/tiangolo/typer/pull/807) by [@ivantodorovich](https://github.com/ivantodorovich). ### Fixes * ๐Ÿ› Fix support for `UnionType` (e.g. `str | None`) with Python 3.11. PR [#548](https://github.com/fastapi/typer/pull/548) by [@jonaslb](https://github.com/jonaslb). * ๐Ÿ› Fix `zsh` autocompletion installation. PR [#237](https://github.com/fastapi/typer/pull/237) by [@alexjurkiewicz](https://github.com/alexjurkiewicz). * ๐Ÿ› Fix usage of `Annotated` with future annotations in Python 3.7+. PR [#814](https://github.com/fastapi/typer/pull/814) by [@ivantodorovich](https://github.com/ivantodorovich). * ๐Ÿ› Fix `shell_complete` not working for Arguments. PR [#737](https://github.com/fastapi/typer/pull/737) by [@bckohan](https://github.com/bckohan). ### Docs * ๐Ÿ“ Update docs links, from tiangolo to new fastapi org. PR [#919](https://github.com/fastapi/typer/pull/919) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Add docs for team and repo management. PR [#917](https://github.com/tiangolo/typer/pull/917) by [@tiangolo](https://github.com/tiangolo). ### Internal * ๐Ÿ”ง Add URLs to `pyproject.toml`, show up in PyPI. PR [#931](https://github.com/fastapi/typer/pull/931) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Do not sync labels as it overrides manually added labels. PR [#930](https://github.com/fastapi/typer/pull/930) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update labeler GitHub Action to add only one label. PR [#927](https://github.com/fastapi/typer/pull/927) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update labeler GitHub Actions permissions and dependencies. PR [#926](https://github.com/fastapi/typer/pull/926) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add GitHub Action label-checker. PR [#925](https://github.com/fastapi/typer/pull/925) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add GitHub Action labeler. PR [#924](https://github.com/fastapi/typer/pull/924) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add GitHub Action add-to-project. PR [#922](https://github.com/fastapi/typer/pull/922) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update docs.py script to enable dirty reload conditionally. PR [#918](https://github.com/tiangolo/typer/pull/918) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update MkDocs previews. PR [#916](https://github.com/tiangolo/typer/pull/916) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade build docs configs. PR [#914](https://github.com/tiangolo/typer/pull/914) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update MkDocs to have titles in Markdown files instead of config. PR [#913](https://github.com/tiangolo/typer/pull/913) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add alls-green for test-redistribute. PR [#911](https://github.com/tiangolo/typer/pull/911) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update docs-previews to handle no docs changes. PR [#912](https://github.com/tiangolo/typer/pull/912) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท๐Ÿป Show docs deployment status and preview URLs in comment. PR [#910](https://github.com/tiangolo/typer/pull/910) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Enable auto dark mode from system. PR [#908](https://github.com/tiangolo/typer/pull/908) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ’„ Add dark mode logo. PR [#907](https://github.com/tiangolo/typer/pull/907) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update tabs and admonitions with new syntax and new MkDocs features. PR [#906](https://github.com/tiangolo/typer/pull/906) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Enable MkDocs Material features. PR [#905](https://github.com/tiangolo/typer/pull/905) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Enable dark mode for docs. PR [#904](https://github.com/tiangolo/typer/pull/904) by [@tiangolo](https://github.com/tiangolo). * โž– Do not install jieba for MkDocs Material as there are no chinese translations. PR [#903](https://github.com/tiangolo/typer/pull/903) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ™ˆ Add MkDocs Material cache to gitignore. PR [#902](https://github.com/tiangolo/typer/pull/902) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update lint script. PR [#901](https://github.com/tiangolo/typer/pull/901) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Update MkDocs configs and docs build setup. PR [#900](https://github.com/tiangolo/typer/pull/900) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump actions/cache from 3 to 4. PR [#839](https://github.com/tiangolo/typer/pull/839) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿฑ Update Typer icon and logo. PR [#899](https://github.com/tiangolo/typer/pull/899) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update issue-manager.yml GitHub Action permissions. PR [#897](https://github.com/tiangolo/typer/pull/897) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Refactor GitHub Action to comment docs deployment URLs and update token, preparing for GitHub org. PR [#896](https://github.com/tiangolo/typer/pull/896) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”จ Update docs Termynal scripts to not include line nums for local dev. PR [#882](https://github.com/tiangolo/typer/pull/882) by [@tiangolo](https://github.com/tiangolo). * โฌ† Bump black from 23.3.0 to 24.3.0. PR [#837](https://github.com/tiangolo/typer/pull/837) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pillow from 10.1.0 to 10.3.0. PR [#836](https://github.com/tiangolo/typer/pull/836) by [@dependabot[bot]](https://github.com/apps/dependabot). * โœ… Add CI configs to run tests on Windows and MacOS. PR [#824](https://github.com/tiangolo/typer/pull/824) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ‘ท Update GitHub Actions to upload and download artifacts. PR [#829](https://github.com/tiangolo/typer/pull/829) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Tweak CI for test-redistribute, add needed env vars for slim. PR [#827](https://github.com/tiangolo/typer/pull/827) by [@tiangolo](https://github.com/tiangolo). * โœ… Generalize test suite to run on Windows. PR [#810](https://github.com/tiangolo/typer/pull/810) by [@svlandeg](https://github.com/svlandeg). * โœ… Add `__init__.py` files to fix test suite. PR [#809](https://github.com/tiangolo/typer/pull/809) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ”ง Update MkDocs Material, enable plugins. PR [#813](https://github.com/tiangolo/typer/pull/813) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Tweak development scripts and configs after migration to PDM, Ruff, etc.. PR [#797](https://github.com/tiangolo/typer/pull/797) by [@tiangolo](https://github.com/tiangolo). ## 0.12.3 ### Fixes * ๐Ÿ› Fix Rich formatting with no commands. PR [#796](https://github.com/tiangolo/typer/pull/796) by [@svlandeg](https://github.com/svlandeg). ## 0.12.2 ### Features * โœจ Improve column help display, ensure commands column width is the same on all panels. PR [#567](https://github.com/tiangolo/typer/pull/567) by [@ssbarnea](https://github.com/ssbarnea). ### Fixes * ๐Ÿ› Add support for an argument of type `Optional[Tuple]` and default value `None`. PR [#757](https://github.com/tiangolo/typer/pull/757) by [@Asthestarsfalll](https://github.com/Asthestarsfalll). ### Docs * ๐Ÿ”ง Fix typo in Github template. PR [#793](https://github.com/tiangolo/typer/pull/793) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“ Fix typos in documentation. PR [#761](https://github.com/tiangolo/typer/pull/761) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“ Update console output with Click 8 messages. PR [#789](https://github.com/tiangolo/typer/pull/789) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“ย Remove references to a .rst README generated by poetry new. PR [#632](https://github.com/tiangolo/typer/pull/632) by [@jonasmmiguel](https://github.com/jonasmmiguel). ## 0.12.1 Now you don't need to install `typer[all]`. When you install `typer` it comes with the default optional dependencies and the `typer` command. If you don't want the extra optional dependencies (`rich` and `shellingham`), you can install `typer-slim` instead. You can also install `typer-slim[standard]`, which includes the default optional dependencies, but not the `typer` command. Now the package `typer-cli` doesn't add anything on top of what `typer` has, it only depends on `typer`, and is there only for backwards compatibility, so that projects that depend on `typer-cli` can get the latest features of the `typer` command while they upgrade their dependencies to require `typer` directly. ### Features * โœจ Add support for `typer ./someprogram.py utils docs --title`. PR [#782](https://github.com/tiangolo/typer/pull/782) by [@tiangolo](https://github.com/tiangolo). ### Fixes * ๐Ÿ› Fix broken installation when upgrading from `typer <0.12.0` to `typer >=0.12.0`, make `typer` independent of `typer-slim`, include `typer` command in `typer` package. PR [#791](https://github.com/tiangolo/typer/pull/791) by [@tiangolo](https://github.com/tiangolo). This fixes a problem that would break the `typer` installation directory when upgrading from `typer <0.12.0` to `typer >=0.12.0`, see issue [#790](https://github.com/tiangolo/typer/issues/790). By installing the latest version (`0.12.1`) it fixes it, for any previous version, even if the installation directory was already broken by the previous upgrade. ### Internal * ๐Ÿ‘ท Add cron to run test once a week on monday. PR [#783](https://github.com/tiangolo/typer/pull/783) by [@estebanx64](https://github.com/estebanx64). ## 0.12.0 In version `0.12.0`, the `typer` package depends on `typer-slim[standard]` which includes the default dependencies (instead of `typer[all]`) and `typer-cli` (that provides the `typer` command). If you don't want the extra optional dependencies (`rich` and `shellingham`), you can install `typer-slim` instead. You can also install `typer-slim[standard]`, which includes the default optional dependencies, but not the `typer` command. In version `0.12.0` the `typer-cli` package only provides the `typer` command, but the code is still in the main code, so even without installing `typer-cli`, it can be called with `python -m typer`. This approach of having `typer` depend on `typer-slim[standard]` instead of including the whole code and dependencies itself caused an issue when upgrading from `typer <0.12.0` to `typer >=0.12.0`, see issue [#790](https://github.com/tiangolo/typer/issues/790). This is fixed in version `0.12.1`. ### Features * โœจ Add `typer-slim` package without extras, make `typer` include `typer-slim[default]` and integrate Typer CLI (`typer` command) into Typer. PR [#780](https://github.com/tiangolo/typer/pull/780) by [@tiangolo](https://github.com/tiangolo). ### Internal * ๐Ÿ”ง Temporarily disable social plugin while a MkDocs issue is handled. PR [#779](https://github.com/tiangolo/typer/pull/779) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Fix install MkDocs Insiders only when available. PR [#778](https://github.com/tiangolo/typer/pull/778) by [@tiangolo](https://github.com/tiangolo). ## 0.11.1 ### Fixes * ๐Ÿ”ง Explicitly include testing files in sdist for redistributors (e.g. OpenSUSE) and add CI to test redistribution. PR [#773](https://github.com/tiangolo/typer/pull/773) by [@tiangolo](https://github.com/tiangolo). ### Internal * ๐Ÿ‘ท Do not use the cache for dependencies when publishing to PyPI. PR [#774](https://github.com/tiangolo/typer/pull/774) by [@tiangolo](https://github.com/tiangolo). ## 0.11.0 ### Breaking Changes * ๐Ÿ”ง Refactor package manager, move from Flit to PDM, remove private pip extras for `test`, `doc`, `dev`. PR [#764](https://github.com/tiangolo/typer/pull/764) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove support for Click 7, require Click 8+. PR [#760](https://github.com/tiangolo/typer/pull/760) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ฅ Remove support for Python 3.6. PR [#758](https://github.com/tiangolo/typer/pull/758) by [@tiangolo](https://github.com/tiangolo). ### Refactors * ๐Ÿ”ง Migrate from Black, isort, flake8, autoflake, pyupgrade to Ruff. PR [#763](https://github.com/tiangolo/typer/pull/763) by [@tiangolo](https://github.com/tiangolo). ### Internal * โฌ†๏ธ Upgrade coverage and configs. PR [#769](https://github.com/tiangolo/typer/pull/769) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ”ง Upgrade mypy and config. PR [#768](https://github.com/tiangolo/typer/pull/768) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade Smokeshow GitHub action. PR [#767](https://github.com/tiangolo/typer/pull/767) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade latest-changes GitHub Action. PR [#766](https://github.com/tiangolo/typer/pull/766) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade issue-manager GitHub Action. PR [#765](https://github.com/tiangolo/typer/pull/765) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Add alls-green to CI. PR [#759](https://github.com/tiangolo/typer/pull/759) by [@tiangolo](https://github.com/tiangolo). ## 0.10.0 ### Fixes * ๐Ÿ› Fix default value of `None` for CLI Parameters when the type is `list | None` and the default value is `None`. PR [#664](https://github.com/tiangolo/typer/pull/664) by [@theowisear](https://github.com/theowisear). ## 0.9.4 ### Features * โœจ Improve support for CLI translations using gettext. PR [#417](https://github.com/tiangolo/typer/pull/417) by [@mjodmj](https://github.com/mjodmj). ## 0.9.3 ### Fixes * ๐Ÿ› Fix evaluating stringified annotations in Python 3.10 (also `from __future__ import annotations`). PR [#721](https://github.com/tiangolo/typer/pull/721) by [@heckad](https://github.com/heckad). ## 0.9.2 ### Fixes * ๐Ÿ› Fix display of default value for Enum parameters inside of a list, include docs and tests. PR [#473](https://github.com/tiangolo/typer/pull/473) by [@asieira](https://github.com/asieira). * ๐Ÿ› Update type annotations for `show_default` parameter and update docs for setting a "Custom default string". PR [#501](https://github.com/tiangolo/typer/pull/501) by [@plannigan](https://github.com/plannigan). ### Docs * ๐Ÿ“ Add docs and test for `no_args_is_help` feature. PR [#751](https://github.com/tiangolo/typer/pull/751) by [@svlandeg](https://github.com/svlandeg). ## 0.9.1 ### Fixes * ๐Ÿ› Add missing `default_factory` in `Argument` overloads. PR [#750](https://github.com/tiangolo/typer/pull/750) by [@m9810223](https://github.com/m9810223). * ๐Ÿ› Fix preserving case in enum values. PR [#571](https://github.com/tiangolo/typer/pull/571) by [@avaldebe](https://github.com/avaldebe). ### Docs * ๐Ÿ“ Remove obsolete references to `--install-completion` for `typer.run()` scripts. PR [#595](https://github.com/tiangolo/typer/pull/595) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Update docs example for a Typer/Click group to make new subcommands explicit. PR [#755](https://github.com/tiangolo/typer/pull/755) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“ Update docs for building a package, file structure example. PR [#683](https://github.com/tiangolo/typer/pull/683) by [@davidbgk](https://github.com/davidbgk). * ๐Ÿ“ Update link in docs to the newest stable version of click. PR [#675](https://github.com/tiangolo/typer/pull/675) by [@javier171188](https://github.com/javier171188). * ๐Ÿ”ง Add `CITATION.cff` file for academic citations. PR [#681](https://github.com/tiangolo/typer/pull/681) by [@tiangolo](https://github.com/tiangolo). * โœ Fix typo in `docs/tutorial/exceptions.md`. PR [#702](https://github.com/tiangolo/typer/pull/702) by [@menzenski](https://github.com/menzenski). * โœ Fix typo in `docs/tutorial/options/name.md`. PR [#725](https://github.com/tiangolo/typer/pull/725) by [@bwagner](https://github.com/bwagner). * โœ Fix typo in `docs/tutorial/arguments/optional.md`. PR [#602](https://github.com/tiangolo/typer/pull/602) by [@tadasgedgaudas](https://github.com/tadasgedgaudas). ### Internal * โฌ† [pre-commit.ci] pre-commit autoupdate. PR [#606](https://github.com/tiangolo/typer/pull/606) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ๐Ÿ‘ท Install MkDocs Material Insiders only when secrets are available, for Dependabot. PR [#685](https://github.com/tiangolo/typer/pull/685) by [@tiangolo](https://github.com/tiangolo). * โš’๏ธ Update build-docs.yml, do not zip docs. PR [#645](https://github.com/tiangolo/typer/pull/645) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Deploy docs to Cloudflare. PR [#644](https://github.com/tiangolo/typer/pull/644) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade CI for docs. PR [#642](https://github.com/tiangolo/typer/pull/642) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update token for latest changes. PR [#635](https://github.com/tiangolo/typer/pull/635) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update CI workflow dispatch for latest changes. PR [#643](https://github.com/tiangolo/typer/pull/643) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update token for Material for MkDocs Insiders. PR [#636](https://github.com/tiangolo/typer/pull/636) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ› Fix internal type annotations and bump mypy version. PR [#638](https://github.com/tiangolo/typer/pull/638) by [@paulo-raca](https://github.com/paulo-raca). * ๐Ÿ’ก Add comments to document overload definitions in code. PR [#752](https://github.com/tiangolo/typer/pull/752) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ”ฅ Remove Jina QA Bot as it has been discontinued. PR [#749](https://github.com/tiangolo/typer/pull/749) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Update build docs CI cache paths. PR [#707](https://github.com/tiangolo/typer/pull/707) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ‘ท Upgrade latest-changes GitHub Action. PR [#691](https://github.com/tiangolo/typer/pull/691) by [@tiangolo](https://github.com/tiangolo). ## 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.15.2/docs/resources/000077500000000000000000000000001476013476600157225ustar00rootroot00000000000000typer-0.15.2/docs/resources/index.md000066400000000000000000000001511476013476600173500ustar00rootroot00000000000000# Resources Additional resources, how to **help** and get help, how to **contribute**, and more. โœˆ๏ธ typer-0.15.2/docs/tutorial/000077500000000000000000000000001476013476600155535ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/app-dir.md000066400000000000000000000026541476013476600174400ustar00rootroot00000000000000# CLI Application Directory You can get the application directory where you can, for example, save configuration files with `typer.get_app_dir()`: {* docs_src/app_dir/tutorial001.py hl[9] *} 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.15.2/docs/tutorial/arguments/000077500000000000000000000000001476013476600175605ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/arguments/default.md000066400000000000000000000042531476013476600215320ustar00rootroot00000000000000# CLI Arguments with Default We 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`: {* docs_src/arguments/default/tutorial001_an.py hl[5] *} /// 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: --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: {* docs_src/arguments/default/tutorial002_an.py hl[7:8,11] *} 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: --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.15.2/docs/tutorial/arguments/envvar.md000066400000000000000000000052641476013476600214120ustar00rootroot00000000000000# CLI Arguments with Environment Variables You 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*. /// tip You can learn more about environment variables in the [Environment Variables](../../environment-variables.md){.internal-link target=_blank} page. /// To do that, use the `envvar` parameter for `typer.Argument()`: {* docs_src/arguments/envvar/tutorial001_an.py hl[5] *} 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: --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: {* docs_src/arguments/envvar/tutorial002_an.py hl[6] *} 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: --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`: {* docs_src/arguments/envvar/tutorial003_an.py hl[7] *} 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: --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.15.2/docs/tutorial/arguments/help.md000066400000000000000000000234101476013476600210320ustar00rootroot00000000000000# CLI Arguments with Help In 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: {* 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*: {* docs_src/arguments/help/tutorial001_an.py hl[5] *} 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: --help Show this message and exit. ```
## Combine help text and docstrings And of course, you can also combine that `help` with the docstring: {* docs_src/arguments/help/tutorial002_an.py hl[5:8] *} 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: --help Show this message and exit. ```
## Help with defaults If you have a *CLI argument* with a default value, like `"World"`: {* docs_src/arguments/help/tutorial003_an.py hl[5] *} 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: --help Show this message and exit. ```
But you can disable that if you want to, with `show_default=False`: {* docs_src/arguments/help/tutorial004_an.py hl[7] *} 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: --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: {* docs_src/arguments/help/tutorial005_an.py hl[9] *} 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: --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: {* docs_src/arguments/help/tutorial006_an.py hl[5] *} 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: --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: {* docs_src/arguments/help/tutorial007_an.py hl[8,12] *} 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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --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: {* docs_src/arguments/help/tutorial008_an.py hl[5] *} 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: --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.15.2/docs/tutorial/arguments/index.md000066400000000000000000000002761476013476600212160ustar00rootroot00000000000000# CLI Arguments In 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.15.2/docs/tutorial/arguments/optional.md000066400000000000000000000144611476013476600217350ustar00rootroot00000000000000# Optional CLI Arguments We 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*: {* docs_src/first_steps/tutorial002.py hl[4] *} Now let's see an alternative way to create the same *CLI argument*: {* docs_src/arguments/optional/tutorial001_an.py hl[5] *} /// 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: Annotated[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 make sure to provide a "default" value, for example `"World"`: {* docs_src/arguments/optional/tutorial002_an.py hl[5] *} Now we have: ```Python name: Annotated[str, typer.Argument()] = "World" ``` Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*). Check the help:
```console // First check the help $ python main.py --help Usage: main.py [OPTIONS] [NAME] Arguments: [NAME] Options: --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: {* docs_src/arguments/optional/tutorial001.py hl[4] *} /// 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". /// {* docs_src/arguments/optional/tutorial003.py hl[4] *} And the same way, you can make it optional by passing a different `default` value, for example `None`: {* docs_src/arguments/optional/tutorial002.py hl[6] *} 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.15.2/docs/tutorial/arguments/other-uses.md000066400000000000000000000002611476013476600221770ustar00rootroot00000000000000# Other uses `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.15.2/docs/tutorial/commands/000077500000000000000000000000001476013476600173545ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/commands/arguments.md000066400000000000000000000020721476013476600217040ustar00rootroot00000000000000# Command CLI Arguments The same way as with a CLI application with a single command, subcommands (or just "commands") can also have their own *CLI arguments*: {* docs_src/commands/arguments/tutorial001.py hl[7,12] *}
```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.15.2/docs/tutorial/commands/callback.md000066400000000000000000000112321476013476600214310ustar00rootroot00000000000000# Typer Callback When 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): {* docs_src/commands/callback/tutorial001.py hl[25,26,27,28,29,30,31,32] *} 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: {* docs_src/commands/callback/tutorial002.py hl[4,5,8] *} 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()`: {* docs_src/commands/callback/tutorial003.py hl[11,12,13] *} 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: {* docs_src/commands/callback/tutorial004.py hl[8,9,10,11,12,13,14,15,16] *} 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.15.2/docs/tutorial/commands/context.md000066400000000000000000000066331476013476600213720ustar00rootroot00000000000000# 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`. 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: {* docs_src/commands/context/tutorial001.py hl[17,21] *} 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`: {* docs_src/commands/context/tutorial002.py hl[16] *} 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: {* docs_src/commands/context/tutorial003.py hl[17,21] *} 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`: {* docs_src/commands/context/tutorial004.py hl[7,9,10] *}
```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.15.2/docs/tutorial/commands/help.md000066400000000000000000001002651476013476600206320ustar00rootroot00000000000000# Command Help The 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: {* docs_src/commands/help/tutorial001_an.py hl[4,9:11,22,26:30,43,47:51,60:62] *} 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()`: {* docs_src/commands/help/tutorial002.py hl[6,14] *} 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`: {* docs_src/commands/help/tutorial003.py hl[14] *} 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` if Rich is not installed, and `"rich"` if it is installed. In the latter case, you can set `rich_markup_mode` to `None` to disable 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: {* docs_src/commands/help/tutorial004_an.py hl[4,10,14:16,21,24,27] *} 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: {* docs_src/commands/help/tutorial005_an.py hl[4,9,12:20,25,27:28] *} 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: {* docs_src/commands/help/tutorial006.py hl[22,30,38,46] *} 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. {* docs_src/commands/help/tutorial007_an.py hl[15,21,27,37] *} 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: {* docs_src/commands/help/tutorial008.py hl[6] *} 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.15.2/docs/tutorial/commands/index.md000066400000000000000000000212311476013476600210040ustar00rootroot00000000000000# Commands We 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: {* docs_src/first_steps/tutorial002.py hl[9] *} 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: {* docs_src/commands/index/tutorial001.py hl[3,6,12] *} 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. Now the help shows two new *CLI options*: * `--install-completion` * `--show-completion` 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: {* docs_src/commands/index/tutorial002.py hl[6,11] *} 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. /// ## Show the help message if no command is given By default, we need to specify `--help` to get the command's help page. However, by setting `no_args_is_help=True` when defining the `typer.Typer()` application, the help function will be shown whenever no argument is given: {* docs_src/commands/index/tutorial003.py hl[3] *} Now we can run this:
```console // Check the help without having to type --help $ python main.py 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 ```
## Sorting of the commands Note that by design, **Typer** shows the commands in the order they've been declared. So, if we take our original example, with `create` and `delete` commands, and reverse the order in the Python file: {* docs_src/commands/index/tutorial004.py hl[7,12] *} Then we will see the `delete` command first in the help output:
```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: delete create ```
## 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.15.2/docs/tutorial/commands/name.md000066400000000000000000000025441476013476600206230ustar00rootroot00000000000000# Custom Command Name By 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: {* docs_src/commands/name/tutorial001.py hl[6,11] *} 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.15.2/docs/tutorial/commands/one-or-multiple.md000066400000000000000000000065011476013476600227300ustar00rootroot00000000000000# One or Multiple Commands You might have noticed that if you create a single command, as in the first example: {* docs_src/commands/index/tutorial001.py hl[3,6,12] *} **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: {* docs_src/commands/index/tutorial002.py hl[6,11] *} 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: {* docs_src/commands/one_or_multiple/tutorial001.py hl[11,12,13] *} 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: {* docs_src/commands/one_or_multiple/tutorial002.py hl[11,12,13,14,15,16,17] *} 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 more users. 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.15.2/docs/tutorial/commands/options.md000066400000000000000000000040021476013476600213650ustar00rootroot00000000000000# Command CLI Options Commands can also have their own *CLI options*. In fact, each command can have different *CLI arguments* and *CLI options*: {* docs_src/commands/options/tutorial001_an.py hl[8,14:17,27:29,38] *} 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 init ```
/// 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.15.2/docs/tutorial/exceptions.md000066400000000000000000000577721476013476600203000ustar00rootroot00000000000000# 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: {* docs_src/exceptions/tutorial001.py hl[5] *} 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, you 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`: {* docs_src/exceptions/tutorial002.py hl[3] *} 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`: {* docs_src/exceptions/tutorial003.py hl[3] *} 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`: {* docs_src/exceptions/tutorial004.py hl[3] *} 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.15.2/docs/tutorial/first-steps.md000066400000000000000000000524721476013476600203720ustar00rootroot00000000000000# First Steps ## The simplest example The simplest **Typer** file could look like this: {* 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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ --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`: {* docs_src/first_steps/tutorial002.py hl[4,5] *}
```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`: {* docs_src/first_steps/tutorial003.py hl[4,5] *}
```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*: {* docs_src/first_steps/tutorial004.py hl[4,5] *} 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] โ”‚ โ”‚ --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 `""`: {* docs_src/first_steps/tutorial005.py hl[4] *} 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] โ”‚ โ”‚ --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: {* docs_src/first_steps/tutorial006.py hl[5,6,7,8,9] *} 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] โ”‚ โ”‚ --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 parameters" or "Python functions arguments". 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*. ## The `typer` Command When you install `typer`, by default it adds a `typer` command to your shell. This `typer` command allows you to run your scripts with โœจ auto completion โœจ in your terminal. As an alternative to running with Python:
```console $ python main.py Hello World ```
You can run with the `typer` command:
```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 the `typer` command 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.15.2/docs/tutorial/index.md000066400000000000000000000044651476013476600172150ustar00rootroot00000000000000# Learn Learn how to use **Typer** in this step-by-step **Tutorial** - **User Guide**. It covers everything you need to know from the **simplest scripts** to **complex CLI applications**. You could consider this a **book**, a **course**, the **official** and recommended way to learn **Typer**. ๐Ÿ˜Ž ## 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. ## About this Tutorial 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 **inline errors**, **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. typer-0.15.2/docs/tutorial/install.md000066400000000000000000000012471476013476600175470ustar00rootroot00000000000000# Install **Typer** The first step is to install **Typer**. First, make sure you create your [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install it, for example with:
```console $ pip install typer ---> 100% Successfully installed typer click shellingham rich ```
By default, `typer` comes with `rich` and `shellingham`. /// note If you are an advanced user and want to opt out of these default extra dependencies, you can instead install `typer-slim`. ```bash pip install typer ``` ...includes the same optional dependencies as: ```bash pip install "typer-slim[standard]" ``` /// typer-0.15.2/docs/tutorial/launch.md000066400000000000000000000016721476013476600173550ustar00rootroot00000000000000# Launching Applications You 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: {* docs_src/launch/tutorial001.py hl[6] *} 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`: {* docs_src/launch/tutorial002.py hl[17] *} /// 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.15.2/docs/tutorial/multiple-values/000077500000000000000000000000001476013476600207035ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/multiple-values/arguments-with-multiple-values.md000066400000000000000000000032551476013476600273360ustar00rootroot00000000000000# CLI Arguments with Multiple Values *CLI arguments* can also receive multiple values. You can define the type of a *CLI argument* using `typing.List`. {* docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py hl[7] *} 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: {* docs_src/multiple_values/arguments_with_multiple_values/tutorial002_an.py hl[8:10] *} 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: --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.15.2/docs/tutorial/multiple-values/index.md000066400000000000000000000002301476013476600223270ustar00rootroot00000000000000# Multiple Values There are several ways to declare multiple values for *CLI options* and *CLI arguments*. We'll see them in the next short sections. typer-0.15.2/docs/tutorial/multiple-values/multiple-options.md000066400000000000000000000023621476013476600245540ustar00rootroot00000000000000# Multiple CLI Options You 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`: {* docs_src/multiple_values/multiple_options/tutorial001_an.py hl[1,7] *} You will receive the values as you declared them, as a `list` of `str`. Check it:
```console // The default value is 'None' $ python main.py No provided users (raw input = None) 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: {* docs_src/multiple_values/multiple_options/tutorial002_an.py hl[7] *} 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.15.2/docs/tutorial/multiple-values/options-with-multiple-values.md000066400000000000000000000041341476013476600270210ustar00rootroot00000000000000# CLI Options with Multiple Values You 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`: {* docs_src/multiple_values/options_with_multiple_values/tutorial001_an.py hl[1,7] *} 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>... --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: Option '--user' requires 3 arguments ```
typer-0.15.2/docs/tutorial/one-file-per-command.md000066400000000000000000000117641476013476600220040ustar00rootroot00000000000000# One File Per Command When your CLI application grows, you can split it into multiple files and modules. This pattern helps maintain a clean and organized code structure. โœจ This tutorial will show you how to use `add_typer` to create sub commands and organize your commands in multiple files. We will create a simple CLI with the following commands: - `version` - `users add NAME` - `users delete NAME` ## CLI structure Here is the structure we'll be working with: ```text mycli/ โ”œโ”€โ”€ __init__.py โ”œโ”€โ”€ main.py โ”œโ”€โ”€ users/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ”œโ”€โ”€ add.py โ”‚ โ””โ”€โ”€ delete.py โ””โ”€โ”€ version.py ``` `mycli` will be our package, and it will contain the following modules: - `main.py`: The main module that will import the `version` and `users` modules. - `version.py`: A module that will contain the `version` command. - `users/`: A package (inside of our `mycli` package) that will contain the `add` and `delete` commands. ## Implementation Let's start implementing our CLI! ๐Ÿš€ We'll create the `version` module, the `main` module, and the `users` package. ### Version Module (`version.py`) Let's start by creating the `version` module. This module will contain the `version` command. {* docs_src/one_file_per_command/version.py *} In this file we are creating a new Typer app instance for the `version` command. This is not required in single-file applications, but in the case of multi-file applications it will allow us to include this command in the main application using `app.add_typer()`. Let's see that next! ### Main Module (`main.py`) The main module will be the entry point of the application. It will import the version module and the users module. /// tip We'll see how to implement the users module in the next section. /// {* docs_src/one_file_per_command/main.py hl[8,9] *} In this module, we import the `version` and `users` modules and add them to the main app using `app.add_typer()`. For the `users` module, we specify the name as `"users"` to group the commands under the `users` sub-command. Notice that we didn't add a name for the `version_app` Typer app. Because of this, Typer will add the commands (just one in this case) declared in the `version_app` directly at the top level. So, there will be a top-level `version` sub-command. But for `users`, we add a name `"users"`, this way those commands will be under the sub-command `users` instead of at the top level. So, there will be a `users add` and `users delete` sub-sub-commands. ๐Ÿ˜… /// tip If you want a command to group the included commands in a sub-app, add a name. If you want to include the commands from a sub-app directly at the top level, don't add a name, or set it to `None`. ๐Ÿค“ /// Let's now create the `users` module with the `add` and `delete` commands. ### Users Add Command (`users/add.py`) {* docs_src/one_file_per_command/users/add.py *} Like the `version` module, we create a new Typer app instance for the `users/add` command. This allows us to include the `add` command in the users app. ### Users Delete Command (`users/delete.py`) {* docs_src/one_file_per_command/users/delete.py *} And once again, we create a new Typer app instance for the `users/delete` command. This allows us to include the `delete` command in the users app. ### Users' app (`users/__init__.py`) Finally, we need to create an `__init__.py` file in the `users` directory to define the `users` app. {* docs_src/one_file_per_command/users/__init__.py *} Similarly to the `version` module, we create a new `Typer` app instance for the `users` module. This allows us to include the `add` and `delete` commands in the users app. ## Running the Application Now we are ready to run the application! ๐Ÿ˜Ž To run the application, you can execute it as a Python module:
```console $ python -m mycli.main version My CLI Version 1.0 $ python -m mycli.main users add Camila Adding user: Camila ```
And if you built a package and installed your app, you can then use the `mycli` command:
```console $ mycli version My CLI Version 1.0 $ mycli users add Camila Adding user: Camila ```
## Callbacks Have in mind that if you include a sub-app with `app.add_typer()` **without a name**, the commands will be added to the top level, so **only the top level callback** (if there's any) will be used, the one declared in the main app. If you **want to use a callback** for a sub-app, you need to include the sub-app **with a name**, which creates a sub-command grouping the commands in that sub-app. ๐Ÿค“ In the example above, if the `users` sub-app had a callback, it would be used. But if the `version` sub-app had a callback, it would not be used, because the `version` sub-app was included without a name. typer-0.15.2/docs/tutorial/options-autocompletion.md000066400000000000000000000315571476013476600226430ustar00rootroot00000000000000# CLI Option autocompletion As you have seen, apps built with **Typer** have completion in your shell that works when you create a Python package or using the `typer` command. 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 using the `typer` command), when you use your CLI program and start adding a *CLI option* with `--` and 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, use the `typer` command. Then let's create a small example program: {* docs_src/options_autocompletion/tutorial001_an.py *} And let's try it with the `typer` command 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}: {* docs_src/options_autocompletion/tutorial002_an.py hl[5:6,15] *} 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: {* docs_src/options_autocompletion/tutorial003_an.py hl[7:12] *} 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`: {* docs_src/options_autocompletion/tutorial004_an.py hl[4:8,11:17] *} /// 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: {* docs_src/options_autocompletion/tutorial005_an.py hl[11:14] *} That simplifies our code a bit and works the same. /// tip If 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`: {* docs_src/options_autocompletion/tutorial006_an.py hl[9:14] *} 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. {* docs_src/options_autocompletion/tutorial007_an.py hl[13:14,16] *} 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". {* docs_src/options_autocompletion/tutorial008_an.py hl[13,16:17] *} /// 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`: {* docs_src/options_autocompletion/tutorial009_an.py hl[16] *} 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**" โœจ ## Comparison to Click functionality Note that Click 7 had a similar [`autocompletion` function](https://click.palletsprojects.com/en/7.x/bashcomplete/), but it worked slightly differently. It required the callback function to take exactly the 3 arguments `ctx`, `args` and `incomplete` in that exact order, instead of matching them dynamically based on types, as Typer does. Since Click 8, this functionality has been replaced by [`shell_complete`](https://click.palletsprojects.com/en/8.1.x/api/#click.ParamType.shell_complete), which still depends on the exact order of arguments for the callback function. However, Typer continues to use the `autocompletion` functionality as described on this page. typer-0.15.2/docs/tutorial/options/000077500000000000000000000000001476013476600172465ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/options/callback-and-context.md000066400000000000000000000154461476013476600235600ustar00rootroot00000000000000# CLI Option Callback and Context In 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. {* docs_src/options/callback/tutorial001_an.py hl[7:10,13] *} 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), when you use your CLI program and start adding a *CLI option* with `--` and then hit TAB, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc). To check it quickly with the previous script use the `typer` command:
```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: {* docs_src/options/callback/tutorial002_an.py hl[8] *} 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: {* docs_src/options/callback/tutorial003_an.py hl[7:9] *} 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. {* docs_src/options/callback/tutorial004_an.py hl[7,10] *} 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.15.2/docs/tutorial/options/help.md000066400000000000000000000150611476013476600205230ustar00rootroot00000000000000# CLI Options with Help You already saw how to add a help text for *CLI arguments* with the `help` parameter. Let's now do the same for *CLI options*: {* docs_src/options/help/tutorial001_an.py hl[7:8] *} 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] --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*: {* docs_src/options/help/tutorial002_an.py hl[11,17] *} 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. โ”‚ โ”‚ --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`: {* docs_src/options/help/tutorial003_an.py hl[5] *} 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 --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. ๐Ÿ‘€ /// ## 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: {* docs_src/options/help/tutorial004_an.py hl[7] *} And it will be used 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 [default: (Deadpoolio the amazing's name)] --help Show this message and exit. // Notice how it shows "(Deadpoolio the amazing's name)" instead of the actual default of "Wade Wilson" ```
typer-0.15.2/docs/tutorial/options/index.md000066400000000000000000000003311476013476600206740ustar00rootroot00000000000000# CLI Options In 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.15.2/docs/tutorial/options/name.md000066400000000000000000000171321476013476600205140ustar00rootroot00000000000000# CLI Option Name By 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()`: {* docs_src/options/name/tutorial001_an.py hl[5] *} /// 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] --help Show this message and exit. // Try it $ python main.py --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`: {* docs_src/options/name/tutorial002_an.py hl[5] *} 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] --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. {* docs_src/options/name/tutorial003_an.py hl[5] *} 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] --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: {* docs_src/options/name/tutorial004_an.py hl[5] *} 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] --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): {* docs_src/options/name/tutorial005_an.py hl[6:7] *} /// 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 --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.15.2/docs/tutorial/options/password.md000066400000000000000000000024231476013476600214330ustar00rootroot00000000000000# Password CLI Option and Confirmation Prompt Apart from having a prompt, you can make a *CLI option* have a `confirmation_prompt=True`: {* docs_src/options/password/tutorial001_an.py hl[7] *} 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: {* docs_src/options/password/tutorial002_an.py hl[8] *} 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.15.2/docs/tutorial/options/prompt.md000066400000000000000000000035001476013476600211070ustar00rootroot00000000000000# CLI Option Prompt It's also possible to, instead of just showing an error, ask for the missing value with `prompt=True`: {* docs_src/options/prompt/tutorial001_an.py hl[5] *} 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`: {* docs_src/options/prompt/tutorial002_an.py hl[7] *} 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: {* docs_src/options/prompt/tutorial003_an.py hl[6] *} 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.15.2/docs/tutorial/options/required.md000066400000000000000000000037511476013476600214160ustar00rootroot00000000000000# Required CLI Options We 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*: {* docs_src/options/required/tutorial001_an.py hl[5] *} 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. {* docs_src/options/required/tutorial001.py hl[4] *} Or you can explicitly pass `...` to `typer.Option(default=...)`: {* docs_src/options/required/tutorial002.py hl[4] *} /// 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] --help Show this message and exit. // It now tells you that --lastname is required ๐ŸŽ‰ ```
typer-0.15.2/docs/tutorial/options/version.md000066400000000000000000000062601476013476600212610ustar00rootroot00000000000000# Version CLI Option, `is_eager` You 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: {* docs_src/options/version/tutorial001_an.py hl[9:12,17:19] *} /// 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 --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: {* docs_src/options/version/tutorial002_an.py hl[15:17,22:24] *} 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: {* docs_src/options/version/tutorial003_an.py hl[23:26] *} 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.15.2/docs/tutorial/package.md000066400000000000000000000470441476013476600175010ustar00rootroot00000000000000# Building a Package When 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` command). 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` to your dependencies:
```console $ poetry add typer // It creates a virtual environment for your project Creating virtualenv rick-portal-gun-w31dJa0b-py3.10 in /home/rick/.cache/pypoetry/virtualenvs Using version ^0.12.0 for typer Updating dependencies Resolving dependencies... (1.2s) ---> 100% Package operations: 8 installs, 0 updates, 0 removals - Installing mdurl (0.1.2) - Installing markdown-it-py (3.0.0) - Installing pygments (2.17.2) - Installing click (8.1.7) - Installing rich (13.7.1) - Installing shellingham (1.5.4) - Installing typing-extensions (4.11.0) - Installing typer (0.12.3) Writing lock file // Activate that new virtual environment $ poetry shell Spawning shell within /home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.10 // 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.md โ”œโ”€โ”€ rick_portal_gun โ”‚ย ย  โ””โ”€โ”€ __init__.py โ””โ”€โ”€ tests โ””โ”€โ”€ __init__.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 to have something like: ```Markdown # Portal Gun The awesome Portal Gun ``` ## 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.10" typer = "^0.12.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.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 the current project: 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.10/bin/rick-portal-gun // Try it $ rick-portal-gun --help // 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/rick/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/rick/.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.md โ”œโ”€โ”€ rick_portal_gun โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ”œโ”€โ”€ __main__.py โ”‚ โ””โ”€โ”€ main.py โ””โ”€โ”€ tests โ””โ”€โ”€ __init__.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 --help 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 --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 ```
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/rick/.local/bin/rick-portal-gun /home/rick/.local/lib/python3.10/site-packages/rick_portal_gun-0.1.0.dist-info/* /home/rick/.local/lib/python3.10/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.metadata (435 bytes) Requirement already satisfied: typer<0.13.0,>=0.12.3 in ./.local/lib/python3.10/site-packages (from rick-portal-gun==0.1.0) (0.12.3) Requirement already satisfied: typing-extensions>=3.7.4.3 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (4.11.0) Requirement already satisfied: click>=8.0.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (8.1.7) Requirement already satisfied: shellingham>=1.3.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (1.5.4) Requirement already satisfied: rich>=10.11.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (13.7.1) Requirement already satisfied: pygments<3.0.0,>=2.13.0 in ./.local/lib/python3.10/site-packages (from rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (2.17.2) Requirement already satisfied: markdown-it-py>=2.2.0 in ./.local/lib/python3.10/site-packages (from rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (3.0.0) Requirement already satisfied: mdurl~=0.1 in ./.local/lib/python3.10/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (0.1.2) Downloading rick_portal_gun-0.1.0-py3-none-any.whl (1.8 kB) 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 You can use the `typer` command to generate docs for your package that you can put in your `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. /// tip If you installed `typer-slim` and don't have the `typer` command, you can use `python -m typer` instead. /// ### 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.10" typer = "^0.12.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.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.15.2/docs/tutorial/parameter-types/000077500000000000000000000000001476013476600206755ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/parameter-types/bool.md000066400000000000000000000063351476013476600221610ustar00rootroot00000000000000# Boolean CLI Options We 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: {* docs_src/parameter_types/bool/tutorial001_an.py hl[5] *} 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] --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 `/`: {* docs_src/parameter_types/bool/tutorial002_an.py hl[7] *} Check it:
```console // Check the help $ python main.py --help // Notice the --accept / --reject Usage: main.py [OPTIONS] Options: --accept / --reject --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`: {* docs_src/parameter_types/bool/tutorial003_an.py hl[5] *} 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] --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: {* docs_src/parameter_types/bool/tutorial004_an.py hl[5] *} /// 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] --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.15.2/docs/tutorial/parameter-types/custom-types.md000066400000000000000000000022121476013476600236700ustar00rootroot00000000000000# Custom Types You 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. {* docs_src/parameter_types/custom_types/tutorial001_an.py hl[13:14,18:19] *} 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. {* docs_src/parameter_types/custom_types/tutorial002_an.py hl[14:18,22:25] *} typer-0.15.2/docs/tutorial/parameter-types/datetime.md000066400000000000000000000042611476013476600230160ustar00rootroot00000000000000# DateTime You 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. {* docs_src/parameter_types/datetime/tutorial001.py hl[1,6,7,8] *} 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: --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 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': 'july-19-1989' does not match the formats '%Y-%m-%d', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S'. ```
## 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: {* docs_src/parameter_types/datetime/tutorial002_an.py hl[11] *} /// 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.15.2/docs/tutorial/parameter-types/enum.md000066400000000000000000000050161476013476600221650ustar00rootroot00000000000000# Enum - Choices To define a *CLI parameter* that can take a value from a predefined set of values you can use a standard Python `enum.Enum`: {* docs_src/parameter_types/enum/tutorial001.py hl[1,6,7,8,9,12,13] *} /// 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] --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': 'capsule' is not one of 'simple', 'conv', 'lstm'. // Note that enums are case sensitive by default $ python main.py --network CONV Usage: main.py [OPTIONS] Try "main.py --help" for help. Error: Invalid value for '--network': 'CONV' is not one of 'simple', 'conv', 'lstm'. ```
### Case insensitive Enum choices You can make an `Enum` (choice) *CLI parameter* be case-insensitive with the `case_sensitive` parameter: {* docs_src/parameter_types/enum/tutorial002_an.py hl[15] *} 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 ```
### List of Enum values A *CLI parameter* can also take a list of `Enum` values: {* docs_src/parameter_types/enum/tutorial003_an.py hl[14] *} This works just like any other parameter value taking a list of things:
```console $ python main.py --help // Notice the default values being shown Usage: main.py [OPTIONS] Options: --groceries [Eggs|Bacon|Cheese] [default: Eggs, Cheese] --help Show this message and exit. // Try it with the default values $ python main.py Buying groceries: Eggs, Cheese // Try it with a single value $ python main.py --groceries "Eggs" Buying groceries: Eggs // Try it with multiple values $ python main.py --groceries "Eggs" --groceries "Bacon" Buying groceries: Eggs, Bacon ```
typer-0.15.2/docs/tutorial/parameter-types/file.md000066400000000000000000000163211476013476600221410ustar00rootroot00000000000000# File Apart 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:` {* docs_src/parameter_types/file/tutorial001_an.py hl[5] *} 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`: {* docs_src/parameter_types/file/tutorial002_an.py hl[5:6] *} 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: {* docs_src/parameter_types/file/tutorial003_an.py hl[5] *} 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`. {* docs_src/parameter_types/file/tutorial004_an.py hl[5] *}
```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: {* docs_src/parameter_types/file/tutorial005_an.py hl[5] *} /// 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.15.2/docs/tutorial/parameter-types/index.md000066400000000000000000000037451476013476600223370ustar00rootroot00000000000000# CLI Parameter Types You 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: {* docs_src/parameter_types/index/tutorial001.py hl[4] *} 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] --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.15.2/docs/tutorial/parameter-types/number.md000066400000000000000000000056331476013476600225160ustar00rootroot00000000000000# Number You can define numeric validations with `max` and `min` values for `int` and `float` *CLI parameters*: {* docs_src/parameter_types/number/tutorial001_an.py hl[6:8] *} *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] --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 range 0<=x<=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 not in the range x>=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 not in the range x<=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: {* docs_src/parameter_types/number/tutorial002_an.py hl[6:8] *} 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 range 0<=x<=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: {* docs_src/parameter_types/number/tutorial003_an.py hl[5] *} 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.15.2/docs/tutorial/parameter-types/path.md000066400000000000000000000064341476013476600221620ustar00rootroot00000000000000# Path You can declare a *CLI parameter* to be a standard Python `pathlib.Path`. This is what you would do for directory paths, file paths, etc: {* docs_src/parameter_types/path/tutorial001_an.py hl[1,8] *} 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: {* docs_src/parameter_types/path/tutorial002_an.py hl[11:16] *} 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.15.2/docs/tutorial/parameter-types/uuid.md000066400000000000000000000032271476013476600221710ustar00rootroot00000000000000# UUID /// 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: {* docs_src/parameter_types/uuid/tutorial001.py hl[1,6,7,8] *} 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. ```
typer-0.15.2/docs/tutorial/printing.md000066400000000000000000000233541476013476600177360ustar00rootroot00000000000000# Printing and Colors You can use the normal `print()` to show information on the screen: {* docs_src/first_steps/tutorial001.py hl[5] *} 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. It comes by default when you install `typer`. ### Use Rich `print` For the simplest cases, you can just import `print` from `rich` and use it instead of the standard `print`: {* docs_src/printing/tutorial001.py hl[2,15] *} 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: {* docs_src/printing/tutorial002.py hl[6] *}
```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. {* docs_src/printing/tutorial003.py hl[2:3,5,9:12] *} 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". {* docs_src/printing/tutorial004.py hl[4,8] *} 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()`: {* docs_src/printing/tutorial005.py hl[7,9] *} /// 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()`: {* docs_src/printing/tutorial006.py hl[5] *} Check it:
python main.py Camila Welcome here Camila
typer-0.15.2/docs/tutorial/progressbar.md000066400000000000000000000140061476013476600204270ustar00rootroot00000000000000# Progress Bar If 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: {* docs_src/progressbar/tutorial001.py hl[4,9] *} 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: {* docs_src/progressbar/tutorial002.py hl[4,8:15] *} 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. {* docs_src/progressbar/tutorial003.py hl[8] *} 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()`. {* docs_src/progressbar/tutorial004.py hl[14] *} 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`: {* docs_src/progressbar/tutorial005.py hl[8] *} 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: {* docs_src/progressbar/tutorial006.py hl[8,12] *} Check it:
python main.py Processed 1000 things in batches.
typer-0.15.2/docs/tutorial/prompt.md000066400000000000000000000034571476013476600174270ustar00rootroot00000000000000# Ask with Prompt When 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()`: {* docs_src/prompt/tutorial001.py hl[5] *} 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}: {* docs_src/prompt/tutorial002.py hl[5] *} 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: {* docs_src/prompt/tutorial003.py hl[5] *}
```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: {* docs_src/prompt/tutorial004.py hl[2,6] *} And when you run it, it will look like:
```console $ python main.py # Enter your name ๐Ÿ˜Ž:$ Morty Hello Morty ```
typer-0.15.2/docs/tutorial/subcommands/000077500000000000000000000000001476013476600200665ustar00rootroot00000000000000typer-0.15.2/docs/tutorial/subcommands/add-typer.md000066400000000000000000000072551476013476600223120ustar00rootroot00000000000000# Add Typer We'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: {* 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: {* 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: {* docs_src/subcommands/tutorial001/main.py hl[3,4,7,8] *} 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.15.2/docs/tutorial/subcommands/callback-override.md000066400000000000000000000055531476013476600237710ustar00rootroot00000000000000# Sub-Typer Callback Override When 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: {* docs_src/subcommands/callback_override/tutorial001.py hl[9,10,11] *} 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: {* docs_src/subcommands/callback_override/tutorial002.py hl[6,7,10] *} 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: {* docs_src/subcommands/callback_override/tutorial003.py hl[6,7,10,14,15,16] *} 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: {* docs_src/subcommands/callback_override/tutorial004.py hl[13,14,17] *} 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.15.2/docs/tutorial/subcommands/index.md000066400000000000000000000023161476013476600215210ustar00rootroot00000000000000# SubCommands - Command Groups You 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/fastapi/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/fastapi/typer.git (fetch) upstream https://github.com/fastapi/typer.git (push) ```
In the next sections we'll see how to create subcommands like these. typer-0.15.2/docs/tutorial/subcommands/name-and-help.md000066400000000000000000000270211476013476600230200ustar00rootroot00000000000000# SubCommand Name and Help When 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` text while adding a Typer: {* docs_src/subcommands/name_help/tutorial001.py hl[6] *} 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 `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 help text from callback ### Inferring a command's help text 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 the help text 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. Here's an example: {* docs_src/subcommands/name_help/tutorial002.py hl[9,10,11,12,13] *} The help text for that 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 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 ```
/// note Before Typer 0.14.0, in addition to the help text, the command name was also inferred from the callback function name, this is no longer the case. /// ### 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 help text. This has the lowest priority, we'll see later what has a higher priority and can override it. Check the code: {* docs_src/subcommands/name_help/tutorial003.py hl[6,7,8,9,12] *} This achieves exactly the same as the previous example. Check it:
```console // Check the main help $ python main.py --help // Notice 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 help text will be inferred from the new callback: {* docs_src/subcommands/name_help/tutorial004.py hl[16,17,18,19,20] *} Now 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 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 ```
### Help from callback in `app.add_typer()` If you override the callback in `app.add_typer()` when including a sub-app, the help will be inferred from this callback function. This takes precedence over inferring the help from a callback set in `@sub_app.callback()` and `typer.Typer(callback=sub_app_callback)`. Check the code: {* docs_src/subcommands/name_help/tutorial005.py hl[15,16,17,18,21] *} 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 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 help text from functions. But if you set the 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 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 help text was inferred from the function docstring. If you set it explicitly, that takes precedence over inferring. You can set it when creating a new `typer.Typer()`: {* docs_src/subcommands/name_help/tutorial006.py hl[12] *} /// 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 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 ```
### Help text in `@app.callback()` Many parameters 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 `help` in `@user_app.callback()`: {* docs_src/subcommands/name_help/tutorial007.py hl[24] *} And now the help text will be `Help from callback for users.`. Check it:
```console // Check the help $ python main.py --help // The help text is now "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: users Help from callback for users. // Check the users command help $ python main.py users --help // Notice the main help text Usage: main.py 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: {* docs_src/subcommands/name_help/tutorial008.py hl[21] *} 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 **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(help="Some help.")` * Explicitly set on `app.add_typer(sub_app, help="Some help.")` And the priority to set the command's **name**, from lowest priority to highest, is: * Explicitly set on `sub_app = typer.Typer(name="some-name")` * Explicitly set on `app.add_typer(sub_app, name="some-name")` So, `app.add_typer(sub_app, name="some-name", help="Some help.")` always wins. typer-0.15.2/docs/tutorial/subcommands/nested-subcommands.md000066400000000000000000000134611476013476600242100ustar00rootroot00000000000000# Nested SubCommands We'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`: {* 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`: {* 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`: {* 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: {* docs_src/subcommands/tutorial003/main.py hl[4,10] *} 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`: {* docs_src/subcommands/tutorial003/reigns.py *} `towns.py`: {* docs_src/subcommands/tutorial003/towns.py *} `lands.py`: {* docs_src/subcommands/tutorial003/lands.py *} `users.py`: {* docs_src/subcommands/tutorial003/users.py *} `items.py`: {* docs_src/subcommands/tutorial003/items.py *} `main.py`: {* 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.15.2/docs/tutorial/subcommands/single-file.md000066400000000000000000000051601476013476600226100ustar00rootroot00000000000000# SubCommands in a Single File In some cases, it's possible that your application code needs to live on a single file. You can still use the same ideas: {* 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: {* docs_src/subcommands/tutorial002/main.py hl[4,5,6,7] *} 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: {* docs_src/subcommands/tutorial002/main.py hl[11,16,21,26,31] *} ## 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: {* docs_src/subcommands/tutorial002/main.py hl[10,15,20,25,30] *} ## 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.15.2/docs/tutorial/terminating.md000066400000000000000000000056161476013476600204260ustar00rootroot00000000000000# Terminating There 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: {* docs_src/terminating/tutorial001.py hl[9] *} 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: {* docs_src/terminating/tutorial002.py hl[7] *} 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: {* docs_src/terminating/tutorial003.py hl[7] *} 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.15.2/docs/tutorial/testing.md000066400000000000000000000137161476013476600175620ustar00rootroot00000000000000# Testing Testing **Typer** applications is very easy with pytest. Let's say you have an application `app/main.py` with: {* 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. {* docs_src/testing/app01/test_main.py hl[1,5] *} /// 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: {* docs_src/testing/app01/test_main.py hl[8,9] *} /// 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. {* docs_src/testing/app01/test_main.py hl[10,11,12] *} 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 output sent to "standard error" (`stderr`) instead of "standard output" (`stdout`). To do so, make sure your `CliRunner` instance is created with the `mix_stderr=False` argument. You need this setting to be able to access `result.stderr` in tests. /// /// 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.10, 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: {* docs_src/testing/app02_an/main.py hl[8] *} 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": {* docs_src/testing/app02/test_main.py hl[9] *} ## Test a function If you have a script and you never created an explicit `typer.Typer` app, like: {* docs_src/testing/app03/main.py hl[9] *} ...you can still test it, by creating an app during testing: {* docs_src/testing/app03/test_main.py hl[6,7,13] *} 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.15.2/docs/tutorial/typer-command.md000066400000000000000000000177131476013476600206650ustar00rootroot00000000000000# `typer` command The `typer` command provides โœจ completion โœจ in the Terminal for your own small scripts. Even if they don't use Typer internally. Of course, it works better if you use **Typer** in your script. 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 run your program with the `typer` command in your Terminal, and it will provide completion for your script. The `typer` command also has functionality to generate Markdown documentation for your own **Typer** programs ๐Ÿ“. ## Install When you install **Typer** with: ```bash pip install typer ``` ...it includes the `typer` command. If you don't want to have the `typer` command, you can install instead: ```bash pip install typer-slim ``` You can still use it by calling the Typer library as a module with: ```bash python -m typer ``` ## Install completion You can then install completion for the `typer` command with:
```console $ typer --install-completion bash 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 the `typer` command. You can also run the same script with the `typer` command:
```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 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 the `typer` command won't use the block with: ```Python if __name__ == "__main__": app() ``` ...you can also remove it if you are calling that script only with the `typer` command. ## Run other files The `typer` command can run any script with **Typer**, but the script doesn't even have to use **Typer** at all. You 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` 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 You can also use the `typer` command to generate Markdown documentation for your **Typer** application. ### Sample script with docs For example, you could have a script like: {* docs_src/commands/help/tutorial001.py *} ### Generate docs with the `typer` command Then you could generate docs for it with the `typer` command. You can use the subcommand `utils`. And then the subcommand `docs`.
```console $ typer some_script.py utils docs ```
/// tip If you installed only `typer-slim` and you don't have the `typer` command, you can still generate docs with: ```console $ python -m 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. * `--title TEXT`: A title to use in the docs, by default the name of the command. 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. typer-0.15.2/docs/tutorial/using-click.md000066400000000000000000000143711476013476600203130ustar00rootroot00000000000000# Using Click /// 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: {* docs_src/using_click/tutorial001.py hl[7,14] *} 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: {* docs_src/using_click/tutorial002.py hl[5,19,20] *} 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: {* docs_src/using_click/tutorial003.py hl[15,16,29,31,34] *} 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 --help // Notice we have both subcommands, top and hello 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: hello top // 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: {* docs_src/using_click/tutorial004.py hl[31,33,36] *} 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.15.2/docs/virtual-environments.md000066400000000000000000000532701476013476600204540ustar00rootroot00000000000000# Virtual Environments When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project. /// info If you already know about virtual environments, how to create them and use them, you might want to skip this section. ๐Ÿค“ /// /// tip A **virtual environment** is different than an **environment variable**. An **environment variable** is a variable in the system that can be used by programs. A **virtual environment** is a directory with some files in it. /// /// info This page will teach you how to use **virtual environments** and how they work. If you are ready to adopt a **tool that manages everything** for you (including installing Python), try uv. /// ## Create a Project First, create a directory for your project. What I normally do is that I create a directory named `code` inside my home/user directory. And inside of that I create one directory per project.
```console // Go to the home directory $ cd // Create a directory for all your code projects $ mkdir code // Enter into that code directory $ cd code // Create a directory for this project $ mkdir awesome-project // Enter into that project directory $ cd awesome-project ```
## Create a Virtual Environment When you start working on a Python project **for the first time**, create a virtual environment **inside your project**. /// tip You only need to do this **once per project**, not every time you work. /// //// tab | `venv` To create a virtual environment, you can use the `venv` module that comes with Python.
```console $ python -m venv .venv ```
/// details | What that command means * `python`: use the program called `python` * `-m`: call a module as a script, we'll tell it which module next * `venv`: use the module called `venv` that normally comes installed with Python * `.venv`: create the virtual environment in the new directory `.venv` /// //// //// tab | `uv` If you have `uv` installed, you can use it to create a virtual environment.
```console $ uv venv ```
/// tip By default, `uv` will create a virtual environment in a directory called `.venv`. But you could customize it passing an additional argument with the directory name. /// //// That command creates a new virtual environment in a directory called `.venv`. /// details | `.venv` or other name You could create the virtual environment in a different directory, but there's a convention of calling it `.venv`. /// ## Activate the Virtual Environment Activate the new virtual environment so that any Python command you run or package you install uses it. /// tip Do this **every time** you start a **new terminal session** to work on the project. /// //// tab | Linux, macOS
```console $ source .venv/bin/activate ```
//// //// tab | Windows PowerShell
```console $ .venv\Scripts\Activate.ps1 ```
//// //// tab | Windows Bash Or if you use Bash for Windows (e.g. Git Bash):
```console $ source .venv/Scripts/activate ```
//// /// tip Every time you install a **new package** in that environment, **activate** the environment again. This makes sure that if you use a **terminal (CLI) program** installed by that package, you use the one from your virtual environment and not any other that could be installed globally, probably with a different version than what you need. /// ## Check the Virtual Environment is Active Check that the virtual environment is active (the previous command worked). /// tip This is **optional**, but it's a good way to **check** that everything is working as expected and you are using the virtual environment you intended. /// //// tab | Linux, macOS, Windows Bash
```console $ which python /home/user/code/awesome-project/.venv/bin/python ```
If it shows the `python` binary at `.venv/bin/python`, inside of your project (in this case `awesome-project`), then it worked. ๐ŸŽ‰ //// //// tab | Windows PowerShell
```console $ Get-Command python C:\Users\user\code\awesome-project\.venv\Scripts\python ```
If it shows the `python` binary at `.venv\Scripts\python`, inside of your project (in this case `awesome-project`), then it worked. ๐ŸŽ‰ //// ## Upgrade `pip` /// tip If you use `uv` you would use it to install things instead of `pip`, so you don't need to upgrade `pip`. ๐Ÿ˜Ž /// If you are using `pip` to install packages (it comes by default with Python), you should **upgrade** it to the latest version. Many exotic errors while installing a package are solved by just upgrading `pip` first. /// tip You would normally do this **once**, right after you create the virtual environment. /// Make sure the virtual environment is active (with the command above) and then run:
```console $ python -m pip install --upgrade pip ---> 100% ```
## Add `.gitignore` If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git. /// tip If you used `uv` to create the virtual environment, it already did this for you, you can skip this step. ๐Ÿ˜Ž /// /// tip Do this **once**, right after you create the virtual environment. ///
```console $ echo "*" > .venv/.gitignore ```
/// details | What that command means * `echo "*"`: will "print" the text `*` in the terminal (the next part changes that a bit) * `>`: anything printed to the terminal by the command to the left of `>` should not be printed but instead written to the file that goes to the right of `>` * `.gitignore`: the name of the file where the text should be written And `*` for Git means "everything". So, it will ignore everything in the `.venv` directory. That command will create a file `.gitignore` with the content: ```gitignore * ``` /// ## Install Packages After activating the environment, you can install packages in it. /// tip Do this **once** when installing or upgrading the packages your project needs. If you need to upgrade a version or add a new package you would **do this again**. /// ### Install Packages Directly If you're in a hurry and don't want to use a file to declare your project's package requirements, you can install them directly. /// tip It's a (very) good idea to put the packages and versions your program needs in a file (for example `requirements.txt` or `pyproject.toml`). /// //// tab | `pip`
```console $ pip install typer ---> 100% ```
//// //// tab | `uv` If you have `uv`:
```console $ uv pip install typer ---> 100% ```
//// ### Install from `requirements.txt` If you have a `requirements.txt`, you can now use it to install its packages. //// tab | `pip`
```console $ pip install -r requirements.txt ---> 100% ```
//// //// tab | `uv` If you have `uv`:
```console $ uv pip install -r requirements.txt ---> 100% ```
//// /// details | `requirements.txt` A `requirements.txt` with some packages could look like: ```requirements.txt typer==0.13.0 rich==13.7.1 ``` /// ## Run Your Program After you activated the virtual environment, you can run your program, and it will use the Python inside of your virtual environment with the packages you installed there.
```console $ python main.py Hello World ```
## Configure Your Editor You would probably use an editor, make sure you configure it to use the same virtual environment you created (it will probably autodetect it) so that you can get autocompletion and inline errors. For example: * VS Code * PyCharm /// tip You normally have to do this only **once**, when you create the virtual environment. /// ## Deactivate the Virtual Environment Once you are done working on your project you can **deactivate** the virtual environment.
```console $ deactivate ```
This way, when you run `python` it won't try to run it from that virtual environment with the packages installed there. ## Ready to Work Now you're ready to start working on your project. /// tip Do you want to understand what's all that above? Continue reading. ๐Ÿ‘‡๐Ÿค“ /// ## Why Virtual Environments To work with Typer you need to install Python. After that, you would need to **install** Typer and any other **packages** you want to use. To install packages you would normally use the `pip` command that comes with Python (or similar alternatives). Nevertheless, if you just use `pip` directly, the packages would be installed in your **global Python environment** (the global installation of Python). ### The Problem So, what's the problem with installing packages in the global Python environment? At some point, you will probably end up writing many different programs that depend on **different packages**. And some of these projects you work on will depend on **different versions** of the same package. ๐Ÿ˜ฑ For example, you could create a project called `philosophers-stone`, this program depends on another package called **`harry`, using the version `1`**. So, you need to install `harry`. ```mermaid flowchart LR stone(philosophers-stone) -->|requires| harry-1[harry v1] ``` Then, at some point later, you create another project called `prisoner-of-azkaban`, and this project also depends on `harry`, but this project needs **`harry` version `3`**. ```mermaid flowchart LR azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] ``` But now the problem is, if you install the packages globally (in the global environment) instead of in a local **virtual environment**, you will have to choose which version of `harry` to install. If you want to run `philosophers-stone` you will need to first install `harry` version `1`, for example with:
```console $ pip install "harry==1" ```
And then you would end up with `harry` version `1` installed in your global Python environment. ```mermaid flowchart LR subgraph global[global env] harry-1[harry v1] end subgraph stone-project[philosophers-stone project] stone(philosophers-stone) -->|requires| harry-1 end ``` But then if you want to run `prisoner-of-azkaban`, you will need to uninstall `harry` version `1` and install `harry` version `3` (or just installing version `3` would automatically uninstall version `1`).
```console $ pip install "harry==3" ```
And then you would end up with `harry` version `3` installed in your global Python environment. And if you try to run `philosophers-stone` again, there's a chance it would **not work** because it needs `harry` version `1`. ```mermaid flowchart LR subgraph global[global env] harry-1[harry v1] style harry-1 fill:#ccc,stroke-dasharray: 5 5 harry-3[harry v3] end subgraph stone-project[philosophers-stone project] stone(philosophers-stone) -.-x|โ›”๏ธ| harry-1 end subgraph azkaban-project[prisoner-of-azkaban project] azkaban(prisoner-of-azkaban) --> |requires| harry-3 end ``` /// tip It's very common in Python packages to try the best to **avoid breaking changes** in **new versions**, but it's better to be safe, and install newer versions intentionally and when you can run the tests to check everything is working correctly. /// Now, imagine that with **many** other **packages** that all your **projects depend on**. That's very difficult to manage. And you would probably end up running some projects with some **incompatible versions** of the packages, and not knowing why something isn't working. Also, depending on your operating system (e.g. Linux, Windows, macOS), it could have come with Python already installed. And in that case it probably had some packages pre-installed with some specific versions **needed by your system**. If you install packages in the global Python environment, you could end up **breaking** some of the programs that came with your operating system. ## Where are Packages Installed When you install Python, it creates some directories with some files in your computer. Some of these directories are the ones in charge of having all the packages you install. When you run:
```console // Don't run this now, it's just an example ๐Ÿค“ $ pip install typer ---> 100% ```
That will download a compressed file with the Typer code, normally from PyPI. It will also **download** files for other packages that Typer depends on. Then it will **extract** all those files and put them in a directory in your computer. By default, it will put those files downloaded and extracted in the directory that comes with your Python installation, that's the **global environment**. ## What are Virtual Environments The solution to the problems of having all the packages in the global environment is to use a **virtual environment for each project** you work on. A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project. This way, each project will have its own virtual environment (`.venv` directory) with its own packages. ```mermaid flowchart TB subgraph stone-project[philosophers-stone project] stone(philosophers-stone) --->|requires| harry-1 subgraph venv1[.venv] harry-1[harry v1] end end subgraph azkaban-project[prisoner-of-azkaban project] azkaban(prisoner-of-azkaban) --->|requires| harry-3 subgraph venv2[.venv] harry-3[harry v3] end end stone-project ~~~ azkaban-project ``` ## What Does Activating a Virtual Environment Mean When you activate a virtual environment, for example with: //// tab | Linux, macOS
```console $ source .venv/bin/activate ```
//// //// tab | Windows PowerShell
```console $ .venv\Scripts\Activate.ps1 ```
//// //// tab | Windows Bash Or if you use Bash for Windows (e.g. Git Bash):
```console $ source .venv/Scripts/activate ```
//// That command will create or modify some [environment variables](environment-variables.md){.internal-link target=_blank} that will be available for the next commands. One of those variables is the `PATH` variable. /// tip You can learn more about the `PATH` environment variable in the [Environment Variables](environment-variables.md#path-environment-variable){.internal-link target=_blank} section. /// Activating a virtual environment adds its path `.venv/bin` (on Linux and macOS) or `.venv\Scripts` (on Windows) to the `PATH` environment variable. Let's say that before activating the environment, the `PATH` variable looked like this: //// tab | Linux, macOS ```plaintext /usr/bin:/bin:/usr/sbin:/sbin ``` That means that the system would look for programs in: * `/usr/bin` * `/bin` * `/usr/sbin` * `/sbin` //// //// tab | Windows ```plaintext C:\Windows\System32 ``` That means that the system would look for programs in: * `C:\Windows\System32` //// After activating the virtual environment, the `PATH` variable would look something like this: //// tab | Linux, macOS ```plaintext /home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin ``` That means that the system will now start looking first look for programs in: ```plaintext /home/user/code/awesome-project/.venv/bin ``` before looking in the other directories. So, when you type `python` in the terminal, the system will find the Python program in ```plaintext /home/user/code/awesome-project/.venv/bin/python ``` and use that one. //// //// tab | Windows ```plaintext C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 ``` That means that the system will now start looking first look for programs in: ```plaintext C:\Users\user\code\awesome-project\.venv\Scripts ``` before looking in the other directories. So, when you type `python` in the terminal, the system will find the Python program in ```plaintext C:\Users\user\code\awesome-project\.venv\Scripts\python ``` and use that one. //// An important detail is that it will put the virtual environment path at the **beginning** of the `PATH` variable. The system will find it **before** finding any other Python available. This way, when you run `python`, it will use the Python **from the virtual environment** instead of any other `python` (for example, a `python` from a global environment). Activating a virtual environment also changes a couple of other things, but this is one of the most important things it does. ## Checking a Virtual Environment When you check if a virtual environment is active, for example with: //// tab | Linux, macOS, Windows Bash
```console $ which python /home/user/code/awesome-project/.venv/bin/python ```
//// //// tab | Windows PowerShell
```console $ Get-Command python C:\Users\user\code\awesome-project\.venv\Scripts\python ```
//// That means that the `python` program that will be used is the one **in the virtual environment**. you use `which` in Linux and macOS and `Get-Command` in Windows PowerShell. The way that command works is that it will go and check in the `PATH` environment variable, going through **each path in order**, looking for the program called `python`. Once it finds it, it will **show you the path** to that program. The most important part is that when you call `python`, that is the exact "`python`" that will be executed. So, you can confirm if you are in the correct virtual environment. /// tip It's easy to activate one virtual environment, get one Python, and then **go to another project**. And the second project **wouldn't work** because you are using the **incorrect Python**, from a virtual environment for another project. It's useful being able to check what `python` is being used. ๐Ÿค“ /// ## Why Deactivate a Virtual Environment For example, you could be working on a project `philosophers-stone`, **activate that virtual environment**, install packages and work with that environment. And then you want to work on **another project** `prisoner-of-azkaban`. You go to that project:
```console $ cd ~/code/prisoner-of-azkaban ```
If you don't deactivate the virtual environment for `philosophers-stone`, when you run `python` in the terminal, it will try to use the Python from `philosophers-stone`.
```console $ cd ~/code/prisoner-of-azkaban $ python main.py // Error importing sirius, it's not installed ๐Ÿ˜ฑ Traceback (most recent call last): File "main.py", line 1, in import sirius ```
But if you deactivate the virtual environment and activate the new one for `prisoner-of-askaban` then when you run `python` it will use the Python from the virtual environment in `prisoner-of-azkaban`.
```console $ cd ~/code/prisoner-of-azkaban // You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project ๐Ÿ˜Ž $ deactivate // Activate the virtual environment in prisoner-of-azkaban/.venv ๐Ÿš€ $ source .venv/bin/activate // Now when you run python, it will find the package sirius installed in this virtual environment โœจ $ python main.py I solemnly swear ๐Ÿบ ```
## Alternatives This is a simple guide to get you started and teach you how everything works **underneath**. There are many **alternatives** to managing virtual environments, package dependencies (requirements), projects. Once you are ready and want to use a tool to **manage the entire project**, packages dependencies, virtual environments, etc. I would suggest you try uv. `uv` can do a lot of things, it can: * **Install Python** for you, including different versions * Manage the **virtual environment** for your projects * Install **packages** * Manage package **dependencies and versions** for your project * Make sure you have an **exact** set of packages and versions to install, including their dependencies, so that you can be sure that you can run your project in production exactly the same as in your computer while developing, this is called **locking** * And many other things ## Conclusion If you read and understood all this, now **you know much more** about virtual environments than many developers out there. ๐Ÿค“ Knowing these details will most probably be useful in a future time when you are debugging something that seems complex, but you will know **how it all works underneath**. ๐Ÿ˜Ž typer-0.15.2/docs_src/000077500000000000000000000000001476013476600145575ustar00rootroot00000000000000typer-0.15.2/docs_src/app_dir/000077500000000000000000000000001476013476600161755ustar00rootroot00000000000000typer-0.15.2/docs_src/app_dir/tutorial001.py000066400000000000000000000004661476013476600206410ustar00rootroot00000000000000from 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.15.2/docs_src/arguments/000077500000000000000000000000001476013476600165645ustar00rootroot00000000000000typer-0.15.2/docs_src/arguments/default/000077500000000000000000000000001476013476600202105ustar00rootroot00000000000000typer-0.15.2/docs_src/arguments/default/tutorial001.py000066400000000000000000000002201476013476600226400ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("Wade Wilson")): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/arguments/default/tutorial001_an.py000066400000000000000000000003051476013476600233220ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/default/tutorial002.py000066400000000000000000000003741476013476600226530ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/default/tutorial002_an.py000066400000000000000000000004561476013476600233320ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/envvar/000077500000000000000000000000001476013476600200655ustar00rootroot00000000000000typer-0.15.2/docs_src/arguments/envvar/tutorial001.py000066400000000000000000000002451476013476600225240ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", envvar="AWESOME_NAME")): print(f"Hello Mr. {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/arguments/envvar/tutorial001_an.py000066400000000000000000000003301476013476600231750ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/envvar/tutorial002.py000066400000000000000000000002631476013476600225250ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/envvar/tutorial002_an.py000066400000000000000000000003551476013476600232050ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/envvar/tutorial003.py000066400000000000000000000002701476013476600225240ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/envvar/tutorial003_an.py000066400000000000000000000004001476013476600231750ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/000077500000000000000000000000001476013476600175145ustar00rootroot00000000000000typer-0.15.2/docs_src/arguments/help/tutorial001.py000066400000000000000000000002541476013476600221530ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial001_an.py000066400000000000000000000003311476013476600226250ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial002.py000066400000000000000000000003471476013476600221570ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial002_an.py000066400000000000000000000004241476013476600226310ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial003.py000066400000000000000000000003321476013476600221520ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial003_an.py000066400000000000000000000004151476013476600226320ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial004.py000066400000000000000000000003561476013476600221610ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial004_an.py000066400000000000000000000004661476013476600226410ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial005.py000066400000000000000000000003501476013476600221540ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial005_an.py000066400000000000000000000004721476013476600226370ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial006.py000066400000000000000000000002441476013476600221570ustar00rootroot00000000000000import typer def main(name: str = typer.Argument("World", metavar="โœจusernameโœจ")): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/arguments/help/tutorial006_an.py000066400000000000000000000003271476013476600226370ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial007.py000066400000000000000000000006771476013476600221720ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial007_an.py000066400000000000000000000010131476013476600226310ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial008.py000066400000000000000000000003221476013476600221560ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/help/tutorial008_an.py000066400000000000000000000004051476013476600226360ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/optional/000077500000000000000000000000001476013476600204115ustar00rootroot00000000000000typer-0.15.2/docs_src/arguments/optional/tutorial001.py000066400000000000000000000002031476013476600230420ustar00rootroot00000000000000import typer def main(name: str = typer.Argument()): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/arguments/optional/tutorial001_an.py000066400000000000000000000002651476013476600235300ustar00rootroot00000000000000import 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.15.2/docs_src/arguments/optional/tutorial002.py000066400000000000000000000002231476013476600230450ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(default="World")): print(f"Hello {name}!") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/arguments/optional/tutorial002_an.py000066400000000000000000000003001476013476600235170ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main(name: Annotated[str, typer.Argument()] = "World"): print(f"Hello {name}!") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/arguments/optional/tutorial003.py000066400000000000000000000002161476013476600230500ustar00rootroot00000000000000import typer def main(name: str = typer.Argument(default=...)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/commands/000077500000000000000000000000001476013476600163605ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/arguments/000077500000000000000000000000001476013476600203655ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/arguments/tutorial001.py000066400000000000000000000003611476013476600230230ustar00rootroot00000000000000import 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.15.2/docs_src/commands/callback/000077500000000000000000000000001476013476600201145ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/callback/tutorial001.py000066400000000000000000000013211476013476600225470ustar00rootroot00000000000000import 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.15.2/docs_src/commands/callback/tutorial002.py000066400000000000000000000003301476013476600225470ustar00rootroot00000000000000import 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.15.2/docs_src/commands/callback/tutorial003.py000066400000000000000000000004601476013476600225540ustar00rootroot00000000000000import 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.15.2/docs_src/commands/callback/tutorial004.py000066400000000000000000000004731476013476600225610ustar00rootroot00000000000000import 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.15.2/docs_src/commands/context/000077500000000000000000000000001476013476600200445ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/context/tutorial001.py000066400000000000000000000006331476013476600225040ustar00rootroot00000000000000import 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.15.2/docs_src/commands/context/tutorial002.py000066400000000000000000000006061476013476600225050ustar00rootroot00000000000000import 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.15.2/docs_src/commands/context/tutorial003.py000066400000000000000000000007031476013476600225040ustar00rootroot00000000000000import 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.15.2/docs_src/commands/context/tutorial004.py000066400000000000000000000004251476013476600225060ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/000077500000000000000000000000001476013476600173105ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/help/tutorial001.py000066400000000000000000000022551476013476600217520ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial001_an.py000066400000000000000000000024251476013476600224270ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial002.py000066400000000000000000000006671476013476600217600ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial003.py000066400000000000000000000006021476013476600217460ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial004.py000066400000000000000000000014731476013476600217560ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial004_an.py000066400000000000000000000016071476013476600224330ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial005.py000066400000000000000000000014221476013476600217510ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial005_an.py000066400000000000000000000015271476013476600224350ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial006.py000066400000000000000000000021671476013476600217610ustar00rootroot00000000000000import 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.15.2/docs_src/commands/help/tutorial007.py000066400000000000000000000020241476013476600217520ustar00rootroot00000000000000from 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.15.2/docs_src/commands/help/tutorial007_an.py000066400000000000000000000022601476013476600224320ustar00rootroot00000000000000from 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.15.2/docs_src/commands/help/tutorial008.py000066400000000000000000000004451476013476600217600ustar00rootroot00000000000000import 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.15.2/docs_src/commands/index/000077500000000000000000000000001476013476600174675ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/index/tutorial001.py000066400000000000000000000002121476013476600221200ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def main(name: str): print(f"Hello {name}") if __name__ == "__main__": app() typer-0.15.2/docs_src/commands/index/tutorial002.py000066400000000000000000000003271476013476600221300ustar00rootroot00000000000000import 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.15.2/docs_src/commands/index/tutorial003.py000066400000000000000000000003531476013476600221300ustar00rootroot00000000000000import typer app = typer.Typer(no_args_is_help=True) @app.command() def create(): print("Creating user: Hiro Hamada") @app.command() def delete(): print("Deleting user: Hiro Hamada") if __name__ == "__main__": app() typer-0.15.2/docs_src/commands/index/tutorial004.py000066400000000000000000000003271476013476600221320ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def delete(): print("Deleting user: Hiro Hamada") @app.command() def create(): print("Creating user: Hiro Hamada") if __name__ == "__main__": app() typer-0.15.2/docs_src/commands/name/000077500000000000000000000000001476013476600173005ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/name/tutorial001.py000066400000000000000000000004231476013476600217350ustar00rootroot00000000000000import 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.15.2/docs_src/commands/one_or_multiple/000077500000000000000000000000001476013476600215545ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/one_or_multiple/tutorial001.py000066400000000000000000000002731476013476600242140ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def create(): print("Creating user: Hiro Hamada") @app.callback() def callback(): pass if __name__ == "__main__": app() typer-0.15.2/docs_src/commands/one_or_multiple/tutorial002.py000066400000000000000000000004371476013476600242170ustar00rootroot00000000000000import 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 more users. """ if __name__ == "__main__": app() typer-0.15.2/docs_src/commands/options/000077500000000000000000000000001476013476600200535ustar00rootroot00000000000000typer-0.15.2/docs_src/commands/options/tutorial001.py000066400000000000000000000013041476013476600225070ustar00rootroot00000000000000import 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.15.2/docs_src/commands/options/tutorial001_an.py000066400000000000000000000014041476013476600231660ustar00rootroot00000000000000import 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.15.2/docs_src/exceptions/000077500000000000000000000000001476013476600167405ustar00rootroot00000000000000typer-0.15.2/docs_src/exceptions/tutorial001.py000066400000000000000000000001631476013476600213760ustar00rootroot00000000000000import typer def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/exceptions/tutorial002.py000066400000000000000000000002561476013476600214020ustar00rootroot00000000000000import typer app = typer.Typer(pretty_exceptions_show_locals=False) @app.command() def main(password: str): print(password + 3) if __name__ == "__main__": app() typer-0.15.2/docs_src/exceptions/tutorial003.py000066400000000000000000000002521476013476600213770ustar00rootroot00000000000000import typer app = typer.Typer(pretty_exceptions_short=False) @app.command() def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": app() typer-0.15.2/docs_src/exceptions/tutorial004.py000066400000000000000000000002531476013476600214010ustar00rootroot00000000000000import typer app = typer.Typer(pretty_exceptions_enable=False) @app.command() def main(name: str = "morty"): print(name + 3) if __name__ == "__main__": app() typer-0.15.2/docs_src/first_steps/000077500000000000000000000000001476013476600171245ustar00rootroot00000000000000typer-0.15.2/docs_src/first_steps/tutorial001.py000066400000000000000000000001451476013476600215620ustar00rootroot00000000000000import typer def main(): print("Hello World") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/first_steps/tutorial002.py000066400000000000000000000001601476013476600215600ustar00rootroot00000000000000import typer def main(name: str): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/first_steps/tutorial003.py000066400000000000000000000002121476013476600215570ustar00rootroot00000000000000import typer def main(name: str, lastname: str): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/first_steps/tutorial004.py000066400000000000000000000003571476013476600215720ustar00rootroot00000000000000import 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.15.2/docs_src/first_steps/tutorial005.py000066400000000000000000000003641476013476600215710ustar00rootroot00000000000000import 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.15.2/docs_src/first_steps/tutorial006.py000066400000000000000000000005461476013476600215740ustar00rootroot00000000000000import 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.15.2/docs_src/launch/000077500000000000000000000000001476013476600160315ustar00rootroot00000000000000typer-0.15.2/docs_src/launch/tutorial001.py000066400000000000000000000002351476013476600204670ustar00rootroot00000000000000import typer def main(): print("Opening Typer's docs") typer.launch("https://typer.tiangolo.com") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/launch/tutorial002.py000066400000000000000000000010171476013476600204670ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/000077500000000000000000000000001476013476600177715ustar00rootroot00000000000000typer-0.15.2/docs_src/multiple_values/arguments_with_multiple_values/000077500000000000000000000000001476013476600263235ustar00rootroot00000000000000typer-0.15.2/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py000066400000000000000000000004461476013476600307650ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py000066400000000000000000000004441476013476600307640ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/arguments_with_multiple_values/tutorial002_an.py000066400000000000000000000005271476013476600314440ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/multiple_options/000077500000000000000000000000001476013476600233775ustar00rootroot00000000000000typer-0.15.2/docs_src/multiple_values/multiple_options/tutorial001.py000066400000000000000000000004751476013476600260430ustar00rootroot00000000000000from typing import List, Optional import typer def main(user: Optional[List[str]] = typer.Option(None)): if not user: print(f"No provided users (raw input = {user})") raise typer.Abort() for u in user: print(f"Processing user: {u}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/multiple_values/multiple_options/tutorial001_an.py000066400000000000000000000005621476013476600265160ustar00rootroot00000000000000from 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(f"No provided users (raw input = {user})") raise typer.Abort() for u in user: print(f"Processing user: {u}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/multiple_values/multiple_options/tutorial002.py000066400000000000000000000002621476013476600260360ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/multiple_options/tutorial002_an.py000066400000000000000000000003471476013476600265200ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/options_with_multiple_values/000077500000000000000000000000001476013476600260115ustar00rootroot00000000000000typer-0.15.2/docs_src/multiple_values/options_with_multiple_values/tutorial001.py000066400000000000000000000006231476013476600304500ustar00rootroot00000000000000from 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.15.2/docs_src/multiple_values/options_with_multiple_values/tutorial001_an.py000066400000000000000000000007101476013476600311230ustar00rootroot00000000000000from 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.15.2/docs_src/one_file_per_command/000077500000000000000000000000001476013476600207035ustar00rootroot00000000000000typer-0.15.2/docs_src/one_file_per_command/__init__.py000066400000000000000000000000001476013476600230020ustar00rootroot00000000000000typer-0.15.2/docs_src/one_file_per_command/main.py000066400000000000000000000003311476013476600221760ustar00rootroot00000000000000import typer from .users import app as users_app from .version import app as version_app app = typer.Typer() app.add_typer(version_app) app.add_typer(users_app, name="users") if __name__ == "__main__": app() typer-0.15.2/docs_src/one_file_per_command/users/000077500000000000000000000000001476013476600220445ustar00rootroot00000000000000typer-0.15.2/docs_src/one_file_per_command/users/__init__.py000066400000000000000000000002331476013476600241530ustar00rootroot00000000000000import typer from .add import app as add_app from .delete import app as delete_app app = typer.Typer() app.add_typer(add_app) app.add_typer(delete_app) typer-0.15.2/docs_src/one_file_per_command/users/add.py000066400000000000000000000001511476013476600231430ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def add(name: str): print(f"Adding user: {name}") typer-0.15.2/docs_src/one_file_per_command/users/delete.py000066400000000000000000000001561476013476600236620ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def delete(name: str): print(f"Deleting user: {name}") typer-0.15.2/docs_src/one_file_per_command/version.py000066400000000000000000000001421476013476600227370ustar00rootroot00000000000000import typer app = typer.Typer() @app.command() def version(): print("My CLI Version 1.0") typer-0.15.2/docs_src/options/000077500000000000000000000000001476013476600162525ustar00rootroot00000000000000typer-0.15.2/docs_src/options/callback/000077500000000000000000000000001476013476600200065ustar00rootroot00000000000000typer-0.15.2/docs_src/options/callback/tutorial001.py000066400000000000000000000005231476013476600224440ustar00rootroot00000000000000from typing import Optional import typer def name_callback(value: str): if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial001_an.py000066400000000000000000000005761476013476600231320ustar00rootroot00000000000000from typing import Optional import 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[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial002.py000066400000000000000000000005601476013476600224460ustar00rootroot00000000000000from typing import Optional import typer def name_callback(value: str): print("Validating name") if value != "Camila": raise typer.BadParameter("Only Camila is allowed") return value def main(name: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial002_an.py000066400000000000000000000006331476013476600231250ustar00rootroot00000000000000from typing import Optional import 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[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial003.py000066400000000000000000000006611476013476600224510ustar00rootroot00000000000000from typing import Optional import 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: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial003_an.py000066400000000000000000000007341476013476600231300ustar00rootroot00000000000000from typing import Optional import 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[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial004.py000066400000000000000000000007351476013476600224540ustar00rootroot00000000000000from typing import Optional import 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: Optional[str] = typer.Option(default=None, callback=name_callback)): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/callback/tutorial004_an.py000066400000000000000000000010101476013476600231150ustar00rootroot00000000000000from typing import Optional import 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[Optional[str], typer.Option(callback=name_callback)] = None): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/help/000077500000000000000000000000001476013476600172025ustar00rootroot00000000000000typer-0.15.2/docs_src/options/help/tutorial001.py000066400000000000000000000007201476013476600216370ustar00rootroot00000000000000import 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.15.2/docs_src/options/help/tutorial001_an.py000066400000000000000000000010161476013476600223140ustar00rootroot00000000000000import 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.15.2/docs_src/options/help/tutorial002.py000066400000000000000000000012031476013476600216350ustar00rootroot00000000000000import 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.15.2/docs_src/options/help/tutorial002_an.py000066400000000000000000000014121476013476600223150ustar00rootroot00000000000000import 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.15.2/docs_src/options/help/tutorial003.py000066400000000000000000000002521476013476600216410ustar00rootroot00000000000000import typer def main(fullname: str = typer.Option("Wade Wilson", show_default=False)): print(f"Hello {fullname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/help/tutorial003_an.py000066400000000000000000000003351476013476600223210ustar00rootroot00000000000000import 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.15.2/docs_src/options/help/tutorial004.py000066400000000000000000000003311476013476600216400ustar00rootroot00000000000000import typer def main( fullname: str = typer.Option( "Wade Wilson", show_default="Deadpoolio the amazing's name" ), ): print(f"Hello {fullname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/help/tutorial004_an.py000066400000000000000000000004141476013476600223200ustar00rootroot00000000000000import typer from typing_extensions import Annotated def main( fullname: Annotated[ str, typer.Option(show_default="Deadpoolio the amazing's name") ] = "Wade Wilson", ): print(f"Hello {fullname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/name/000077500000000000000000000000001476013476600171725ustar00rootroot00000000000000typer-0.15.2/docs_src/options/name/tutorial001.py000066400000000000000000000002301476013476600216230ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--name")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/name/tutorial001_an.py000066400000000000000000000003051476013476600223040ustar00rootroot00000000000000import 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.15.2/docs_src/options/name/tutorial002.py000066400000000000000000000002361476013476600216320ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--name", "-n")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/name/tutorial002_an.py000066400000000000000000000003131476013476600223040ustar00rootroot00000000000000import 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.15.2/docs_src/options/name/tutorial003.py000066400000000000000000000002241476013476600216300ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "-n")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/name/tutorial003_an.py000066400000000000000000000003011476013476600223020ustar00rootroot00000000000000import 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.15.2/docs_src/options/name/tutorial004.py000066400000000000000000000002431476013476600216320ustar00rootroot00000000000000import typer def main(user_name: str = typer.Option(..., "--user-name", "-n")): print(f"Hello {user_name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/name/tutorial004_an.py000066400000000000000000000003201476013476600223040ustar00rootroot00000000000000import 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.15.2/docs_src/options/name/tutorial005.py000066400000000000000000000004311476013476600216320ustar00rootroot00000000000000import 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.15.2/docs_src/options/name/tutorial005_an.py000066400000000000000000000005211476013476600223100ustar00rootroot00000000000000import 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.15.2/docs_src/options/password/000077500000000000000000000000001476013476600201145ustar00rootroot00000000000000typer-0.15.2/docs_src/options/password/tutorial001.py000066400000000000000000000003241476013476600225510ustar00rootroot00000000000000import 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.15.2/docs_src/options/password/tutorial001_an.py000066400000000000000000000004061476013476600232300ustar00rootroot00000000000000import 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.15.2/docs_src/options/password/tutorial002.py000066400000000000000000000005251476013476600225550ustar00rootroot00000000000000import 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.15.2/docs_src/options/password/tutorial002_an.py000066400000000000000000000006021476013476600232270ustar00rootroot00000000000000import 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.15.2/docs_src/options/prompt/000077500000000000000000000000001476013476600175735ustar00rootroot00000000000000typer-0.15.2/docs_src/options/prompt/tutorial001.py000066400000000000000000000002531476013476600222310ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option(..., prompt=True)): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/prompt/tutorial001_an.py000066400000000000000000000003301476013476600227030ustar00rootroot00000000000000import 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.15.2/docs_src/options/prompt/tutorial002.py000066400000000000000000000003141476013476600222300ustar00rootroot00000000000000import 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.15.2/docs_src/options/prompt/tutorial002_an.py000066400000000000000000000003761476013476600227160ustar00rootroot00000000000000import 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.15.2/docs_src/options/prompt/tutorial003.py000066400000000000000000000003061476013476600222320ustar00rootroot00000000000000import 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.15.2/docs_src/options/prompt/tutorial003_an.py000066400000000000000000000003721476013476600227130ustar00rootroot00000000000000import 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.15.2/docs_src/options/required/000077500000000000000000000000001476013476600200725ustar00rootroot00000000000000typer-0.15.2/docs_src/options/required/tutorial001.py000066400000000000000000000002331476013476600225260ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option()): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/required/tutorial001_an.py000066400000000000000000000003151476013476600232050ustar00rootroot00000000000000import 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.15.2/docs_src/options/required/tutorial002.py000066400000000000000000000002461476013476600225330ustar00rootroot00000000000000import typer def main(name: str, lastname: str = typer.Option(default=...)): print(f"Hello {name} {lastname}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/options/version/000077500000000000000000000000001476013476600177375ustar00rootroot00000000000000typer-0.15.2/docs_src/options/version/tutorial001.py000066400000000000000000000006561476013476600224040ustar00rootroot00000000000000from 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.15.2/docs_src/options/version/tutorial001_an.py000066400000000000000000000007561476013476600230630ustar00rootroot00000000000000from 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.15.2/docs_src/options/version/tutorial002.py000066400000000000000000000010661476013476600224010ustar00rootroot00000000000000from 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.15.2/docs_src/options/version/tutorial002_an.py000066400000000000000000000011561476013476600230570ustar00rootroot00000000000000from 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.15.2/docs_src/options/version/tutorial003.py000066400000000000000000000011251476013476600223760ustar00rootroot00000000000000from 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.15.2/docs_src/options/version/tutorial003_an.py000066400000000000000000000012261476013476600230560ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/000077500000000000000000000000001476013476600213745ustar00rootroot00000000000000typer-0.15.2/docs_src/options_autocompletion/tutorial001.py000066400000000000000000000003011476013476600240240ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial001_an.py000066400000000000000000000003641476013476600245130ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial002.py000066400000000000000000000004711476013476600240350ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial002_an.py000066400000000000000000000005541476013476600245150ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial003.py000066400000000000000000000007371476013476600240430ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial003_an.py000066400000000000000000000010221476013476600245050ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial004.py000066400000000000000000000012311476013476600240320ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial004_an.py000066400000000000000000000013141476013476600245120ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial005.py000066400000000000000000000010641476013476600240370ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial005_an.py000066400000000000000000000011471476013476600245170ustar00rootroot00000000000000import 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.15.2/docs_src/options_autocompletion/tutorial006.py000066400000000000000000000004061476013476600240370ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial006_an.py000066400000000000000000000005001476013476600245100ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial007.py000066400000000000000000000012741476013476600240440ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial007_an.py000066400000000000000000000013701476013476600245170ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial008.py000066400000000000000000000013401476013476600240370ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial008_an.py000066400000000000000000000014341476013476600245210ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial009.py000066400000000000000000000014631476013476600240460ustar00rootroot00000000000000from 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.15.2/docs_src/options_autocompletion/tutorial009_an.py000066400000000000000000000015571476013476600245300ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/000077500000000000000000000000001476013476600177635ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/bool/000077500000000000000000000000001476013476600207165ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/bool/__init__.py000066400000000000000000000000001476013476600230150ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/bool/tutorial001.py000066400000000000000000000003201476013476600233470ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/bool/tutorial001_an.py000066400000000000000000000004031476013476600240270ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/bool/tutorial002.py000066400000000000000000000005031476013476600233530ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/bool/tutorial002_an.py000066400000000000000000000005661476013476600240420ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/bool/tutorial003.py000066400000000000000000000003441476013476600233570ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/bool/tutorial003_an.py000066400000000000000000000004271476013476600240370ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/bool/tutorial004.py000066400000000000000000000003411476013476600233550ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/bool/tutorial004_an.py000066400000000000000000000004241476013476600240350ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/custom_types/000077500000000000000000000000001476013476600225215ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/custom_types/__init__.py000066400000000000000000000000001476013476600246200ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/custom_types/tutorial001.py000066400000000000000000000010401476013476600251520ustar00rootroot00000000000000import 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("Foo", 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.15.2/docs_src/parameter_types/custom_types/tutorial001_an.py000066400000000000000000000011351476013476600256350ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/custom_types/tutorial002.py000066400000000000000000000012061476013476600251570ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/custom_types/tutorial002_an.py000066400000000000000000000013211476013476600256330ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/datetime/000077500000000000000000000000001476013476600215575ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/datetime/__init__.py000066400000000000000000000000001476013476600236560ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/datetime/tutorial001.py000066400000000000000000000003231476013476600242130ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/datetime/tutorial002.py000066400000000000000000000004461476013476600242220ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/datetime/tutorial002_an.py000066400000000000000000000005621476013476600246770ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/enum/000077500000000000000000000000001476013476600207275ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/enum/__init__.py000066400000000000000000000000001476013476600230260ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/enum/tutorial001.py000066400000000000000000000004531476013476600233670ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/enum/tutorial002.py000066400000000000000000000005261476013476600233710ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/enum/tutorial002_an.py000066400000000000000000000006271476013476600240510ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/enum/tutorial003.py000066400000000000000000000005351476013476600233720ustar00rootroot00000000000000from enum import Enum from typing import List import typer class Food(str, Enum): food_1 = "Eggs" food_2 = "Bacon" food_3 = "Cheese" def main(groceries: List[Food] = typer.Option([Food.food_1, Food.food_3])): print(f"Buying groceries: {', '.join([f.value for f in groceries])}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/parameter_types/enum/tutorial003_an.py000066400000000000000000000006221476013476600240450ustar00rootroot00000000000000from enum import Enum from typing import List import typer from typing_extensions import Annotated class Food(str, Enum): food_1 = "Eggs" food_2 = "Bacon" food_3 = "Cheese" def main(groceries: Annotated[List[Food], typer.Option()] = [Food.food_1, Food.food_3]): print(f"Buying groceries: {', '.join([f.value for f in groceries])}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/parameter_types/file/000077500000000000000000000000001476013476600207025ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/file/__init__.py000066400000000000000000000000001476013476600230010ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/file/tutorial001.py000066400000000000000000000002641476013476600233420ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial001_an.py000066400000000000000000000003431476013476600240160ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial002.py000066400000000000000000000003121476013476600233350ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial002_an.py000066400000000000000000000003711476013476600240200ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial003.py000066400000000000000000000005011476013476600233360ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial003_an.py000066400000000000000000000005601476013476600240210ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial004.py000066400000000000000000000010431476013476600233410ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial004_an.py000066400000000000000000000011221476013476600240150ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial005.py000066400000000000000000000003151476013476600233430ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/file/tutorial005_an.py000066400000000000000000000003721476013476600240240ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/index/000077500000000000000000000000001476013476600210725ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/index/__init__.py000066400000000000000000000000001476013476600231710ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/index/tutorial001.py000066400000000000000000000006121476013476600235270ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/number/000077500000000000000000000000001476013476600212535ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/number/__init__.py000066400000000000000000000000001476013476600233520ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/number/tutorial001.py000066400000000000000000000004561476013476600237160ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/number/tutorial001_an.py000066400000000000000000000005611476013476600243710ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/number/tutorial002.py000066400000000000000000000005171476013476600237150ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/number/tutorial002_an.py000066400000000000000000000006221476013476600243700ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/number/tutorial003.py000066400000000000000000000002621476013476600237130ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/number/tutorial003_an.py000066400000000000000000000003451476013476600243730ustar00rootroot00000000000000import 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.15.2/docs_src/parameter_types/path/000077500000000000000000000000001476013476600207175ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/path/__init__.py000066400000000000000000000000001476013476600230160ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/path/tutorial001.py000066400000000000000000000010221476013476600233500ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/path/tutorial001_an.py000066400000000000000000000011071476013476600240320ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/path/tutorial002.py000066400000000000000000000005641476013476600233630ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/path/tutorial002_an.py000066400000000000000000000007141476013476600240360ustar00rootroot00000000000000from 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.15.2/docs_src/parameter_types/uuid/000077500000000000000000000000001476013476600207315ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/uuid/__init__.py000066400000000000000000000000001476013476600230300ustar00rootroot00000000000000typer-0.15.2/docs_src/parameter_types/uuid/tutorial001.py000066400000000000000000000003041476013476600233640ustar00rootroot00000000000000from 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.15.2/docs_src/printing/000077500000000000000000000000001476013476600164115ustar00rootroot00000000000000typer-0.15.2/docs_src/printing/tutorial001.py000066400000000000000000000004501476013476600210460ustar00rootroot00000000000000import 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.15.2/docs_src/printing/tutorial002.py000066400000000000000000000002671476013476600210550ustar00rootroot00000000000000import 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.15.2/docs_src/printing/tutorial003.py000066400000000000000000000004501476013476600210500ustar00rootroot00000000000000import 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.15.2/docs_src/printing/tutorial004.py000066400000000000000000000003261476013476600210530ustar00rootroot00000000000000import 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.15.2/docs_src/printing/tutorial005.py000066400000000000000000000005551476013476600210600ustar00rootroot00000000000000import 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.15.2/docs_src/printing/tutorial006.py000066400000000000000000000002261476013476600210540ustar00rootroot00000000000000import typer def main(name: str): typer.secho(f"Welcome here {name}", fg=typer.colors.MAGENTA) if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/progressbar/000077500000000000000000000000001476013476600171105ustar00rootroot00000000000000typer-0.15.2/docs_src/progressbar/tutorial001.py000066400000000000000000000004731476013476600215520ustar00rootroot00000000000000import 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.15.2/docs_src/progressbar/tutorial002.py000066400000000000000000000007341476013476600215530ustar00rootroot00000000000000import 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.15.2/docs_src/progressbar/tutorial003.py000066400000000000000000000004711476013476600215520ustar00rootroot00000000000000import 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.15.2/docs_src/progressbar/tutorial004.py000066400000000000000000000007071476013476600215550ustar00rootroot00000000000000import 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.15.2/docs_src/progressbar/tutorial005.py000066400000000000000000000005151476013476600215530ustar00rootroot00000000000000import 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.15.2/docs_src/progressbar/tutorial006.py000066400000000000000000000006731476013476600215610ustar00rootroot00000000000000import 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) # Increment by 250 on each loop iteration # (it will take 4 seconds to reach 1000) progress.update(250) print(f"Processed {total} things in batches.") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/prompt/000077500000000000000000000000001476013476600161005ustar00rootroot00000000000000typer-0.15.2/docs_src/prompt/tutorial001.py000066400000000000000000000002421476013476600205340ustar00rootroot00000000000000import typer def main(): person_name = typer.prompt("What's your name?") print(f"Hello {person_name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/prompt/tutorial002.py000066400000000000000000000003651476013476600205430ustar00rootroot00000000000000import 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.15.2/docs_src/prompt/tutorial003.py000066400000000000000000000002641476013476600205420ustar00rootroot00000000000000import 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.15.2/docs_src/prompt/tutorial004.py000066400000000000000000000003011476013476600205330ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/000077500000000000000000000000001476013476600170725ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/callback_override/000077500000000000000000000000001476013476600225255ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/callback_override/tutorial001.py000066400000000000000000000004601476013476600251630ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/callback_override/tutorial002.py000066400000000000000000000004621476013476600251660ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/callback_override/tutorial003.py000066400000000000000000000006311476013476600251650ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/callback_override/tutorial004.py000066400000000000000000000010251476013476600251640ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/name_help/000077500000000000000000000000001476013476600210225ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/name_help/tutorial001.py000066400000000000000000000003761476013476600234660ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/name_help/tutorial002.py000066400000000000000000000004571476013476600234670ustar00rootroot00000000000000import typer app = typer.Typer() users_app = typer.Typer() app.add_typer(users_app, name="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.15.2/docs_src/subcommands/name_help/tutorial003.py000066400000000000000000000004501476013476600234610ustar00rootroot00000000000000import typer app = typer.Typer() def users(): """ Manage users in the app. """ users_app = typer.Typer(callback=users, name="users") app.add_typer(users_app) @users_app.command() def create(name: str): print(f"Creating user: {name}") if __name__ == "__main__": app() typer-0.15.2/docs_src/subcommands/name_help/tutorial004.py000066400000000000000000000006021476013476600234610ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback) app.add_typer(users_app, name="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.15.2/docs_src/subcommands/name_help/tutorial005.py000066400000000000000000000007711476013476600234710ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback, name="users") def new_users(): """ I have the highland! Create some users. """ app.add_typer(users_app, callback=new_users, name="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.15.2/docs_src/subcommands/name_help/tutorial006.py000066400000000000000000000010021476013476600234560ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/name_help/tutorial007.py000066400000000000000000000010421476013476600234630ustar00rootroot00000000000000import typer app = typer.Typer() def old_callback(): """ Old callback help. """ users_app = typer.Typer(callback=old_callback, name="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(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.15.2/docs_src/subcommands/name_help/tutorial008.py000066400000000000000000000011661476013476600234730ustar00rootroot00000000000000import 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(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.15.2/docs_src/subcommands/tutorial001/000077500000000000000000000000001476013476600211565ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/tutorial001/__init__.py000066400000000000000000000000001476013476600232550ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/tutorial001/items.py000066400000000000000000000004521476013476600226520ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial001/main.py000066400000000000000000000002611476013476600224530ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial001/users.py000066400000000000000000000003651476013476600226750ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial002/000077500000000000000000000000001476013476600211575ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/tutorial002/main.py000066400000000000000000000012721476013476600224570ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial003/000077500000000000000000000000001476013476600211605ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/tutorial003/__init__.py000066400000000000000000000000001476013476600232570ustar00rootroot00000000000000typer-0.15.2/docs_src/subcommands/tutorial003/items.py000066400000000000000000000004521476013476600226540ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial003/lands.py000066400000000000000000000002641476013476600226350ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial003/main.py000066400000000000000000000003451476013476600224600ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial003/reigns.py000066400000000000000000000003511476013476600230200ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial003/towns.py000066400000000000000000000003351476013476600227050ustar00rootroot00000000000000import 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.15.2/docs_src/subcommands/tutorial003/users.py000066400000000000000000000003651476013476600226770ustar00rootroot00000000000000import 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.15.2/docs_src/terminating/000077500000000000000000000000001476013476600171005ustar00rootroot00000000000000typer-0.15.2/docs_src/terminating/tutorial001.py000066400000000000000000000011261476013476600215360ustar00rootroot00000000000000import 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.15.2/docs_src/terminating/tutorial002.py000066400000000000000000000003531476013476600215400ustar00rootroot00000000000000import 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.15.2/docs_src/terminating/tutorial003.py000066400000000000000000000003461476013476600215430ustar00rootroot00000000000000import 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.15.2/docs_src/testing/000077500000000000000000000000001476013476600162345ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app01/000077500000000000000000000000001476013476600171555ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app01/__init__.py000066400000000000000000000000001476013476600212540ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app01/main.py000066400000000000000000000004001476013476600204450ustar00rootroot00000000000000from 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.15.2/docs_src/testing/app01/test_main.py000066400000000000000000000004531476013476600215140ustar00rootroot00000000000000from 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.15.2/docs_src/testing/app02/000077500000000000000000000000001476013476600171565ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app02/__init__.py000066400000000000000000000000001476013476600212550ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app02/main.py000066400000000000000000000003171476013476600204550ustar00rootroot00000000000000import 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.15.2/docs_src/testing/app02/test_main.py000066400000000000000000000004341476013476600215140ustar00rootroot00000000000000from 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.15.2/docs_src/testing/app02_an/000077500000000000000000000000001476013476600176345ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app02_an/__init__.py000066400000000000000000000000001476013476600217330ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app02_an/main.py000066400000000000000000000003741476013476600211360ustar00rootroot00000000000000import 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.15.2/docs_src/testing/app02_an/test_main.py000066400000000000000000000004341476013476600221720ustar00rootroot00000000000000from 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.15.2/docs_src/testing/app03/000077500000000000000000000000001476013476600171575ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app03/__init__.py000066400000000000000000000000001476013476600212560ustar00rootroot00000000000000typer-0.15.2/docs_src/testing/app03/main.py000066400000000000000000000001721476013476600204550ustar00rootroot00000000000000import typer def main(name: str = "World"): print(f"Hello {name}") if __name__ == "__main__": typer.run(main) typer-0.15.2/docs_src/testing/app03/test_main.py000066400000000000000000000004341476013476600215150ustar00rootroot00000000000000import 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.15.2/docs_src/using_click/000077500000000000000000000000001476013476600170515ustar00rootroot00000000000000typer-0.15.2/docs_src/using_click/tutorial001.py000066400000000000000000000005611476013476600215110ustar00rootroot00000000000000import 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(f"Hello {name}!") if __name__ == "__main__": hello() typer-0.15.2/docs_src/using_click/tutorial002.py000066400000000000000000000004371476013476600215140ustar00rootroot00000000000000import 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.15.2/docs_src/using_click/tutorial003.py000066400000000000000000000011601476013476600215070ustar00rootroot00000000000000import 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(f"Hello {name}!") typer_click_object = typer.main.get_command(app) typer_click_object.add_command(hello, "hello") if __name__ == "__main__": typer_click_object() typer-0.15.2/docs_src/using_click/tutorial004.py000066400000000000000000000007771476013476600215250ustar00rootroot00000000000000import 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.15.2/mkdocs.insiders.yml000066400000000000000000000001621476013476600166010ustar00rootroot00000000000000plugins: typeset: markdown_extensions: material.extensions.preview: targets: include: - "*" typer-0.15.2/mkdocs.maybe-insiders.yml000066400000000000000000000004371476013476600177010ustar00rootroot00000000000000# Define this here and not in the main mkdocs.yml file because that one could be auto # updated and written, and the script would remove the env var INHERIT: !ENV [INSIDERS_FILE, './mkdocs.no-insiders.yml'] markdown_extensions: pymdownx.highlight: linenums: !ENV [LINENUMS, false] typer-0.15.2/mkdocs.no-insiders.yml000066400000000000000000000000001476013476600172020ustar00rootroot00000000000000typer-0.15.2/mkdocs.yml000066400000000000000000000143751476013476600147750ustar00rootroot00000000000000INHERIT: ./mkdocs.maybe-insiders.yml site_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: - media: "(prefers-color-scheme)" toggle: icon: material/lightbulb-auto name: Switch to light mode - media: '(prefers-color-scheme: light)' scheme: default primary: black accent: teal toggle: icon: material/lightbulb name: Switch to dark mode - media: '(prefers-color-scheme: dark)' scheme: slate primary: black accent: teal toggle: icon: material/lightbulb-outline name: Switch to system preference features: - content.code.annotate - content.code.copy # - content.code.select - content.footnote.tooltips - content.tabs.link - content.tooltips - navigation.footer - navigation.indexes - navigation.instant - navigation.instant.prefetch # - navigation.instant.preview - navigation.instant.progress - navigation.path - navigation.tabs - navigation.tabs.sticky - navigation.top - navigation.tracking - search.highlight - search.share - search.suggest - toc.follow icon: repo: fontawesome/brands/github-alt logo: img/icon.svg favicon: img/favicon.png language: en repo_name: fastapi/typer repo_url: https://github.com/fastapi/typer plugins: # Material for MkDocs search: social: # Other plugins macros: include_yaml: - members: data/members.yml redirects: redirect_maps: typer-cli.md: tutorial/typer-command.md nav: - Typer: index.md - features.md - Tutorial - User Guide: - tutorial/index.md - environment-variables.md - virtual-environments.md - tutorial/install.md - tutorial/first-steps.md - tutorial/printing.md - tutorial/terminating.md - CLI Arguments: - tutorial/arguments/index.md - tutorial/arguments/optional.md - tutorial/arguments/default.md - tutorial/arguments/help.md - tutorial/arguments/envvar.md - tutorial/arguments/other-uses.md - CLI Options: - tutorial/options/index.md - tutorial/options/help.md - tutorial/options/required.md - tutorial/options/prompt.md - tutorial/options/password.md - tutorial/options/name.md - tutorial/options/callback-and-context.md - tutorial/options/version.md - Commands: - tutorial/commands/index.md - tutorial/commands/arguments.md - tutorial/commands/options.md - tutorial/commands/help.md - tutorial/commands/name.md - tutorial/commands/callback.md - tutorial/commands/one-or-multiple.md - tutorial/commands/context.md - tutorial/options-autocompletion.md - CLI Parameter Types: - tutorial/parameter-types/index.md - tutorial/parameter-types/number.md - tutorial/parameter-types/bool.md - tutorial/parameter-types/uuid.md - tutorial/parameter-types/datetime.md - tutorial/parameter-types/enum.md - tutorial/parameter-types/path.md - tutorial/parameter-types/file.md - tutorial/parameter-types/custom-types.md - SubCommands - Command Groups: - tutorial/subcommands/index.md - tutorial/subcommands/add-typer.md - tutorial/subcommands/single-file.md - tutorial/subcommands/nested-subcommands.md - tutorial/subcommands/callback-override.md - tutorial/subcommands/name-and-help.md - Multiple Values: - tutorial/multiple-values/index.md - tutorial/multiple-values/multiple-options.md - tutorial/multiple-values/options-with-multiple-values.md - tutorial/multiple-values/arguments-with-multiple-values.md - tutorial/prompt.md - tutorial/progressbar.md - tutorial/app-dir.md - tutorial/launch.md - tutorial/testing.md - tutorial/using-click.md - tutorial/package.md - tutorial/exceptions.md - tutorial/one-file-per-command.md - tutorial/typer-command.md - Resources: - resources/index.md - help-typer.md - contributing.md - management-tasks.md - About: - about/index.md - alternatives.md - management.md - release-notes.md markdown_extensions: # Python Markdown abbr: attr_list: footnotes: md_in_html: tables: toc: permalink: true # Python Markdown Extensions pymdownx.betterem: smart_enable: all pymdownx.caret: pymdownx.highlight: line_spans: __span pymdownx.inlinehilite: pymdownx.keys: pymdownx.mark: pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format pymdownx.tilde: # pymdownx blocks pymdownx.blocks.admonition: types: - note - attention - caution - danger - error - tip - hint - warning # Custom types - info - check pymdownx.blocks.details: pymdownx.blocks.tab: alternate_style: True # Other extensions mdx_include: markdown_include_variants: extra: analytics: provider: google property: G-T78C5GNRXK feedback: title: Was this page helpful? ratings: - icon: material/emoticon-happy-outline name: This page was helpful data: 1 note: >- Thanks for your feedback! - icon: material/emoticon-sad-outline name: This page could be improved data: 0 note: >- Thanks for your feedback! social: - icon: fontawesome/brands/github-alt link: https://github.com/fastapi/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: - js/termynal.js - js/custom.js hooks: - scripts/mkdocs_hooks.py typer-0.15.2/pdm_build.py000066400000000000000000000044351476013476600152770ustar00rootroot00000000000000import os from typing import Any, Dict, List from pdm.backend.hooks import Context TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "typer") def pdm_build_initialize(context: Context): metadata = context.config.metadata # Get main version version = metadata["version"] # Get package names to keep in sync with the same main version sync_dependencies: List[str] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" ]["sync-dependencies"] # Get custom config for the current package, from the env var config: Dict[str, Any] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" ]["packages"][TIANGOLO_BUILD_PACKAGE] project_config: Dict[str, Any] = config["project"] # Get main optional dependencies, extras optional_dependencies: Dict[str, List[str]] = metadata.get( "optional-dependencies", {} ) # Get custom optional dependencies name to always include in this (non-slim) package include_optional_dependencies: List[str] = config.get( "include-optional-dependencies", [] ) # Override main [project] configs with custom configs for this package for key, value in project_config.items(): metadata[key] = value # Get custom build config for the current package build_config: Dict[str, Any] = ( config.get("tool", {}).get("pdm", {}).get("build", {}) ) # Override PDM build config with custom build config for this package for key, value in build_config.items(): context.config.build_config[key] = value # Get main dependencies dependencies: List[str] = metadata.get("dependencies", []) # Add optional dependencies to the default dependencies for this (non-slim) package for include_optional in include_optional_dependencies: optional_dependencies_group = optional_dependencies.get(include_optional, []) dependencies.extend(optional_dependencies_group) # Sync versions in dependencies new_dependencies = [] for dep in dependencies: if dep in sync_dependencies: new_dep = f"{dep}=={version}" new_dependencies.append(new_dep) else: new_dependencies.append(dep) if new_dependencies != dependencies: metadata["dependencies"] = new_dependencies typer-0.15.2/pyproject.toml000066400000000000000000000141541476013476600157010ustar00rootroot00000000000000[build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" [project] name = "typer" dynamic = ["version"] description = "Typer, build great CLIs. Easy to code. Based on Python type hints." authors = [ {name = "Sebastiรกn Ramรญrez", email = "tiangolo@gmail.com"}, ] requires-python = ">=3.7" 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.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", ] dependencies = [ "click >= 8.0.0", "typing-extensions >= 3.7.4.3", ] readme = "README.md" [project.urls] Homepage = "https://github.com/fastapi/typer" Documentation = "https://typer.tiangolo.com" Repository = "https://github.com/fastapi/typer" Issues = "https://github.com/fastapi/typer/issues" Changelog = "https://typer.tiangolo.com/release-notes/" [project.optional-dependencies] standard = [ "shellingham >=1.3.0", "rich >=10.11.0", ] [tool.pdm] version = { source = "file", path = "typer/__init__.py" } distribution = true [tool.pdm.build] source-includes = [ "tests/", "docs_src/", "requirements*.txt", "scripts/", ] [tool.tiangolo._internal-slim-build] sync-dependencies = [ "typer-slim[standard]", "typer-cli", "typer" ] [tool.tiangolo._internal-slim-build.packages.typer-slim.project] name = "typer-slim" [tool.tiangolo._internal-slim-build.packages.typer] include-optional-dependencies = ["standard"] [tool.tiangolo._internal-slim-build.packages.typer.project] optional-dependencies = {} [tool.tiangolo._internal-slim-build.packages.typer.project.scripts] typer = "typer.cli:main" [tool.tiangolo._internal-slim-build.packages.typer-cli.project] name = "typer-cli" readme = "typer-cli/README.md" dependencies = [ "typer", ] optional-dependencies = {} [tool.tiangolo._internal-slim-build.packages.typer-cli.tool.pdm.build] # excludes needs to explicitly exclude the top level python packages, # otherwise PDM includes them by default # A "*" glob pattern can't be used here because in PDM internals, the patterns are put # in a set (unordered, order varies) and each excluded file is assigned one of the # glob patterns that matches, as the set is unordered, the matched pattern could be "*" # independent of the order here. And then the internal code would give it a lower score # than the one for a default included file. # By not using "*" and explicitly excluding the top level packages, they get a higher # score than the default inclusion excludes = ["typer", "tests", "pdm_build.py"] # source-includes needs to explicitly define some value because PDM will check the # truthy value of the list, and if empty, will include some defaults, including "tests", # an empty string doesn't match anything, but makes the list truthy, so that PDM # doesn't override it during the build. source-includes = [""] [tool.pytest.ini_options] addopts = [ "--strict-config", "--strict-markers", ] xfail_strict = true junit_family = "xunit2" filterwarnings = [ "error", # For pytest-xdist 'ignore::DeprecationWarning:xdist', ] [tool.coverage.run] parallel = true data_file = "coverage/.coverage" source = [ "docs_src", "tests", "typer" ] omit = [ "typer/_typing.py" ] context = '${CONTEXT}' relative_files = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@overload", 'if __name__ == "__main__":', "if TYPE_CHECKING:", ] [tool.mypy] strict = true [[tool.mypy.overrides]] module = "docs_src.*" disallow_incomplete_defs = false disallow_untyped_defs = false disallow_untyped_calls = false [[tool.mypy.overrides]] module = "shellingham" ignore_missing_imports = true [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade ] ignore = [ "E501", # line too long, handled by black "B008", # do not perform function calls in argument defaults "C901", # too complex "W191", # indentation contains tabs ] [tool.ruff.lint.per-file-ignores] # "__init__.py" = ["F401"] # This file is more readable without yield from "docs_src/progressbar/tutorial004.py" = ["UP028", "B007"] # Default mutable data structure "docs_src/options_autocompletion/tutorial006_an.py" = ["B006"] "docs_src/multiple_values/multiple_options/tutorial002_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial007_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial008_an.py" = ["B006"] "docs_src/options_autocompletion/tutorial009_an.py" = ["B006"] "docs_src/parameter_types/enum/tutorial003_an.py" = ["B006"] # Loop control variable `value` not used within loop body "docs_src/progressbar/tutorial001.py" = ["B007"] "docs_src/progressbar/tutorial003.py" = ["B007"] "docs_src/progressbar/tutorial005.py" = ["B007"] "docs_src/progressbar/tutorial006.py" = ["B007"] # Local variable `delete` is assigned to but never used "docs_src/prompt/tutorial003.py" = ["F841"] # Loop control variable `x` not used within loop body "docs_src/using_click/tutorial001.py" = ["B007"] [tool.ruff.lint.isort] known-third-party = ["typer", "click"] # For docs_src/subcommands/tutorial003/ known-first-party = ["reigns", "towns", "lands", "items", "users"] [tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true typer-0.15.2/requirements-docs-insiders.txt000066400000000000000000000003661476013476600210150ustar00rootroot00000000000000git+https://${TOKEN}@github.com/squidfunk/mkdocs-material-insiders.git@9.5.30-insiders-4.53.11 git+https://${TOKEN}@github.com/pawamoy-insiders/griffe-typing-deprecated.git git+https://${TOKEN}@github.com/pawamoy-insiders/mkdocstrings-python.git typer-0.15.2/requirements-docs.txt000066400000000000000000000010401476013476600171650ustar00rootroot00000000000000-e . mkdocs-material==9.5.50 mdx-include >=1.4.1,<2.0.0 mkdocs-redirects>=1.2.1,<1.3.0 pyyaml >=5.3.1,<7.0.0 # For Material for MkDocs, Chinese search # jieba==0.42.1 # For image processing by Material for MkDocs pillow==11.1.0 # For image processing by Material for MkDocs cairosvg==2.7.1 # mkdocstrings[python]==0.25.1 # Enable griffe-typingdoc once dropping Python 3.7 and upgrading typing-extensions # griffe-typingdoc==0.2.5 # For griffe, it formats with black # black==24.3.0 mkdocs-macros-plugin==1.3.7 markdown-include-variants==0.0.4 typer-0.15.2/requirements-github-actions.txt000066400000000000000000000001561476013476600211640ustar00rootroot00000000000000PyGithub>=2.3.0,<3.0.0 pydantic>=2.5.3,<3.0.0 pydantic-settings>=2.1.0,<3.0.0 httpx>=0.27.0,<0.29.0 smokeshow typer-0.15.2/requirements-tests.txt000066400000000000000000000003511476013476600174030ustar00rootroot00000000000000-e . pytest >=4.4.0,<9.0.0 pytest-cov >=2.10.0,<7.0.0 coverage[toml] >=6.2,<8.0 pytest-xdist >=1.32.0,<4.0.0 pytest-sugar >=0.9.4,<1.1.0 mypy ==1.4.1 ruff ==0.9.7 # Needed explicitly by typer-slim rich >=10.11.0 shellingham >=1.3.0 typer-0.15.2/requirements.txt000066400000000000000000000001251476013476600162420ustar00rootroot00000000000000-e . -r requirements-tests.txt -r requirements-docs.txt pre-commit >=2.17.0,<5.0.0 typer-0.15.2/scripts/000077500000000000000000000000001476013476600144475ustar00rootroot00000000000000typer-0.15.2/scripts/deploy_docs_status.py000066400000000000000000000057331476013476600207400ustar00rootroot00000000000000import logging import re from github import Github from pydantic import BaseModel, SecretStr from pydantic_settings import BaseSettings site_domain = "typer.tiangolo.com" class Settings(BaseSettings): github_repository: str github_token: SecretStr deploy_url: str | None = None commit_sha: str run_id: int is_done: bool = False class LinkData(BaseModel): previous_link: str preview_link: str def main() -> None: logging.basicConfig(level=logging.INFO) settings = Settings() logging.info(f"Using config: {settings.model_dump_json()}") g = Github(settings.github_token.get_secret_value()) repo = g.get_repo(settings.github_repository) use_pr = next( (pr for pr in repo.get_pulls() if pr.head.sha == settings.commit_sha), None ) if not use_pr: logging.error(f"No PR found for hash: {settings.commit_sha}") return commits = list(use_pr.get_commits()) current_commit = [c for c in commits if c.sha == settings.commit_sha][0] run_url = f"https://github.com/{settings.github_repository}/actions/runs/{settings.run_id}" if settings.is_done and not settings.deploy_url: current_commit.create_status( state="success", description="No Docs Changes", context="deploy-docs", target_url=run_url, ) logging.info("No docs changes found") return if not settings.deploy_url: current_commit.create_status( state="pending", description="Deploying Docs", context="deploy-docs", target_url=run_url, ) logging.info("No deploy URL available yet") return current_commit.create_status( state="success", description="Docs Deployed", context="deploy-docs", target_url=run_url, ) files = list(use_pr.get_files()) docs_files = [f for f in files if f.filename.startswith("docs/")] deploy_url = settings.deploy_url.rstrip("/") links: list[LinkData] = [] for f in docs_files: match = re.match(r"docs/(.*)", f.filename) if not match: continue path = match.group(1) if path.endswith("index.md"): use_path = path.replace("index.md", "") else: use_path = path.replace(".md", "/") link = LinkData( previous_link=f"https://{site_domain}/{use_path}", preview_link=f"{deploy_url}/{use_path}", ) links.append(link) links.sort(key=lambda x: x.preview_link) message = f"๐Ÿ“ Docs preview for commit {settings.commit_sha} at: {deploy_url}" if links: message += "\n\n### Modified Pages\n\n" for link in links: message += f"* {link.preview_link}" message += f" - ([before]({link.previous_link}))" message += "\n" print(message) use_pr.as_issue().create_comment(message) logging.info("Finished") if __name__ == "__main__": main() typer-0.15.2/scripts/docker/000077500000000000000000000000001476013476600157165ustar00rootroot00000000000000typer-0.15.2/scripts/docker/Dockerfile000066400000000000000000000015771476013476600177220ustar00rootroot00000000000000FROM python:latest # Add Fish RUN echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/3/Debian_12/ /' | tee /etc/apt/sources.list.d/shells:fish:release:3.list RUN curl -fsSL https://download.opensuse.org/repositories/shells:fish:release:3/Debian_12/Release.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/shells_fish_release_3.gpg > /dev/null # Install packages including Fish, Zsh, PowerShell RUN apt-get update && apt-get install -y \ wget \ apt-transport-https \ software-properties-common \ nano \ vim \ fish \ zsh \ && wget https://github.com/PowerShell/PowerShell/releases/download/v7.4.4/powershell_7.4.4-1.deb_amd64.deb \ && dpkg -i powershell_7.4.4-1.deb_amd64.deb # Install uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv ENV UV_SYSTEM_PYTHON=1 COPY . /code WORKDIR /code RUN uv pip install -r requirements.txt typer-0.15.2/scripts/docker/compose.yaml000066400000000000000000000002361476013476600202500ustar00rootroot00000000000000services: typer: build: context: ../../ dockerfile: scripts/docker/Dockerfile volumes: - ../../:/code command: sleep infinity typer-0.15.2/scripts/docs.py000066400000000000000000000074141476013476600157570ustar00rootroot00000000000000import logging import os import re import subprocess from functools import lru_cache from http.server import HTTPServer, SimpleHTTPRequestHandler from importlib import metadata from pathlib import Path import typer logging.basicConfig(level=logging.INFO) mkdocs_name = "mkdocs.yml" en_docs_path = Path("") app = typer.Typer() @lru_cache def is_mkdocs_insiders() -> bool: version = metadata.version("mkdocs-material") return "insiders" in version @app.callback() def callback() -> None: if is_mkdocs_insiders(): os.environ["INSIDERS_FILE"] = "./mkdocs.insiders.yml" # For MacOS with insiders and Cairo os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib" def generate_readme_content() -> str: en_index = en_docs_path / "docs" / "index.md" content = en_index.read_text("utf-8") match_pre = re.search(r"\n\n", content) if not match_pre: raise RuntimeError("Couldn't find pre section (