diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index aede7ea1a7..8e589cd6ab 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,10 +4,10 @@ contact_links: about: Please report security vulnerabilities to security@tiangolo.com - name: Question or Problem about: Ask a question or ask about a problem in GitHub Discussions. - url: https://github.com/tiangolo/typer/discussions/categories/questions + 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/tiangolo/typer/discussions/categories/questions + 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/tiangolo/typer/discussions/categories/show-and-tell + url: https://github.com/fastapi/typer/discussions/categories/show-and-tell diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml index f8acd76f2e..8037408426 100644 --- a/.github/ISSUE_TEMPLATE/privileged.yml +++ b/.github/ISSUE_TEMPLATE/privileged.yml @@ -6,7 +6,7 @@ body: value: | Thanks for your interest in Typer! ๐Ÿš€ - If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/typer/discussions/categories/questions) instead. + 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: diff --git a/.github/actions/comment-docs-preview-in-pr/Dockerfile b/.github/actions/comment-docs-preview-in-pr/Dockerfile deleted file mode 100644 index 4f20c5f10b..0000000000 --- a/.github/actions/comment-docs-preview-in-pr/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.7 - -RUN pip install httpx "pydantic==1.5.1" pygithub - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/comment-docs-preview-in-pr/action.yml b/.github/actions/comment-docs-preview-in-pr/action.yml deleted file mode 100644 index 0eb64402d2..0000000000 --- a/.github/actions/comment-docs-preview-in-pr/action.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Comment Docs Preview in PR -description: Comment with the docs URL preview in the PR -author: Sebastiรกn Ramรญrez -inputs: - token: - description: Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }} - required: true - deploy_url: - description: The deployment URL to comment in the PR - required: true -runs: - using: docker - image: Dockerfile diff --git a/.github/actions/comment-docs-preview-in-pr/app/main.py b/.github/actions/comment-docs-preview-in-pr/app/main.py deleted file mode 100644 index c9fb7cbbef..0000000000 --- a/.github/actions/comment-docs-preview-in-pr/app/main.py +++ /dev/null @@ -1,68 +0,0 @@ -import logging -import sys -from pathlib import Path -from typing import Optional - -import httpx -from github import Github -from github.PullRequest import PullRequest -from pydantic import BaseModel, BaseSettings, SecretStr, ValidationError - -github_api = "https://api.github.com" - - -class Settings(BaseSettings): - github_repository: str - github_event_path: Path - github_event_name: Optional[str] = None - input_token: SecretStr - input_deploy_url: str - - -class PartialGithubEventHeadCommit(BaseModel): - id: str - - -class PartialGithubEventWorkflowRun(BaseModel): - head_commit: PartialGithubEventHeadCommit - - -class PartialGithubEvent(BaseModel): - workflow_run: PartialGithubEventWorkflowRun - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - settings = Settings() - logging.info(f"Using config: {settings.json()}") - g = Github(settings.input_token.get_secret_value()) - repo = g.get_repo(settings.github_repository) - try: - event = PartialGithubEvent.parse_file(settings.github_event_path) - except ValidationError as e: - logging.error(f"Error parsing event file: {e.errors()}") - sys.exit(0) - use_pr: Optional[PullRequest] = None - for pr in repo.get_pulls(): - if pr.head.sha == event.workflow_run.head_commit.id: - use_pr = pr - break - if not use_pr: - logging.error(f"No PR found for hash: {event.workflow_run.head_commit.id}") - sys.exit(0) - github_headers = { - "Authorization": f"token {settings.input_token.get_secret_value()}" - } - url = f"{github_api}/repos/{settings.github_repository}/issues/{use_pr.number}/comments" - logging.info(f"Using comments URL: {url}") - response = httpx.post( - url, - headers=github_headers, - json={ - "body": f"๐Ÿ“ Docs preview for commit {use_pr.head.sha} at: {settings.input_deploy_url}" - }, - ) - if not (200 <= response.status_code <= 300): - logging.error(f"Error posting comment: {response.text}") - sys.exit(1) - logging.info("Finished") diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..4290a118b9 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,24 @@ +docs: + - 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' diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml new file mode 100644 index 0000000000..dccea83f35 --- /dev/null +++ b/.github/workflows/add-to-project.yml @@ -0,0 +1,18 @@ +name: 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 }} diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 8291f117f1..a38691714e 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -18,7 +18,7 @@ jobs: docs: ${{ steps.filter.outputs.docs }} steps: - uses: actions/checkout@v4 - # For pull requests it's not necessary to checkout the code but for master it is + # 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: @@ -28,9 +28,12 @@ jobs: - 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 @@ -49,31 +52,27 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" - - uses: actions/cache@v3 + - uses: actions/cache@v4 id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v02 + key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt', 'requirements-docs-insiders.txt') }}-v02 - name: Install docs extras if: steps.cache.outputs.cache-hit != 'true' run: pip install -r requirements-docs.txt - name: Install Material for MkDocs Insiders if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true' - run: | - pip install git+https://${{ secrets.TYPER_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git - pip install git+https://${{ secrets.TYPER_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/griffe-typing-deprecated.git - pip install git+https://${{ secrets.TYPER_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/mkdocstrings-python.git - - uses: actions/cache@v3 + run: 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 - if: github.event_name == 'pull_request' && github.secret_source != 'Actions' - run: python -m mkdocs build - - name: Build Docs with Insiders - if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) - run: python -m mkdocs build --config-file mkdocs.insiders.yml - + run: python ./scripts/docs.py build - uses: actions/upload-artifact@v4 with: name: docs-site diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 206d1199e1..28f54d90db 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -6,6 +6,12 @@ on: types: - completed +permissions: + deployments: write + issues: write + pull-requests: write + statuses: write + jobs: deploy-docs: runs-on: ubuntu-latest @@ -15,6 +21,25 @@ jobs: 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" + - uses: actions/cache@v4 + id: cache + with: + path: ${{ env.pythonLocation }} + key: ${{ runner.os }}-python-github-actions-${{ env.pythonLocation }}-${{ hashFiles('requirements-github-actions.txt') }}-v01 + - name: Install GitHub Actions dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: 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 @@ -39,8 +64,10 @@ jobs: gitHubToken: ${{ secrets.GITHUB_TOKEN }} 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 ) }} - name: Comment Deploy - if: steps.deploy.outputs.url != '' - uses: ./.github/actions/comment-docs-preview-in-pr - with: - token: ${{ secrets.GITHUB_TOKEN }} - deploy_url: "${{ steps.deploy.outputs.url }}" + run: python ./scripts/deploy_docs_status.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEPLOY_URL: ${{ steps.deploy.outputs.url }} + COMMIT_SHA: ${{ github.event.workflow_run.head_sha }} + RUN_ID: ${{ github.run_id }} + IS_DONE: "true" diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index 31bf065b01..ec2ed2bbc2 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -14,9 +14,12 @@ on: - labeled workflow_dispatch: +permissions: + issues: write + jobs: issue-manager: - if: github.repository_owner == 'tiangolo' + if: github.repository_owner == 'fastapi' runs-on: ubuntu-latest steps: - name: Dump GitHub context diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..c3bb83f9a5 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,31 @@ +name: 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 + # 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 }} diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 31c4ea724b..6a3ac059a5 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -34,8 +34,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: docker://tiangolo/latest-changes:0.3.0 - # - uses: tiangolo/latest-changes@main + - uses: tiangolo/latest-changes@0.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/release-notes.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 80cae43a7f..60e6c43836 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -36,4 +36,4 @@ jobs: TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} run: python -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.8.11 + uses: pypa/gh-action-pypi-publish@v1.9.0 diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml index a2d92c788e..9b7847c1d6 100644 --- a/.github/workflows/test-redistribute.yml +++ b/.github/workflows/test-redistribute.yml @@ -57,3 +57,15 @@ jobs: 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) }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index db194b150b..5030d0bfdd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,7 +44,7 @@ jobs: # Issue ref: https://github.com/actions/setup-python/issues/436 # cache: "pip" # cache-dependency-path: pyproject.toml - - uses: actions/cache@v3 + - uses: actions/cache@v4 if: ${{ runner.os != 'macOS' }} id: cache with: diff --git a/.gitignore b/.gitignore index a18be48595..230c8c0c1f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ dist .mypy_cache .idea site -.coverage htmlcov .pytest_cache coverage.xml .coverage* +.cache diff --git a/CITATION.cff b/CITATION.cff index 43da1f3c89..5fe4aa2d34 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -12,7 +12,7 @@ authors: family-names: Ramรญrez email: tiangolo@gmail.com identifiers: -repository-code: 'https://github.com/tiangolo/typer' +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. diff --git a/README.md b/README.md index 6bb4983a9f..b1e8c17eb2 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@

- Typer + Typer +

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

- - Test + + Test - - Publish + + Publish - - Coverage + + Coverage Package version @@ -22,7 +23,7 @@ **Documentation**: https://typer.tiangolo.com -**Source Code**: https://github.com/tiangolo/typer +**Source Code**: https://github.com/fastapi/typer --- @@ -45,6 +46,8 @@ The key features are: ## Installation +Create and activate a virtual environment and then install **Typer**: +

```console diff --git a/data/members.yml b/data/members.yml new file mode 100644 index 0000000000..2c30d19420 --- /dev/null +++ b/data/members.yml @@ -0,0 +1,3 @@ +members: +- login: tiangolo +- login: svlandeg diff --git a/docs/about/index.md b/docs/about/index.md new file mode 100644 index 0000000000..1688bf193d --- /dev/null +++ b/docs/about/index.md @@ -0,0 +1,3 @@ +# About + +About **Typer**, its design, inspiration, and more. ๐Ÿค“ diff --git a/docs/alternatives.md b/docs/alternatives.md index 1938bb9944..1f1b7904de 100644 --- a/docs/alternatives.md +++ b/docs/alternatives.md @@ -1,3 +1,5 @@ +# Alternatives, Inspiration and Comparisons + What inspired **Typer**, how it compares to other alternatives and what it learned from them. ## Intro @@ -14,24 +16,33 @@ There have been many tools created before that have helped inspire its creation. 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. +/// check | Inspired **Typer** to + +Provide a better development experience than just reading *CLI Parameters* by hand. -### Hug +/// + +### 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. +/// 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)`. +/// 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 @@ -41,8 +52,11 @@ 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. +/// 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 @@ -56,12 +70,15 @@ It uses decorators on top of functions to modify the actual value of those funct 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. ๐Ÿš€ +/// 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. +**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!" +As someone pointed out: "Nice to see it is built on Click but adds the type stuff. Me gusta!" + +/// ### `click-completion` @@ -71,8 +88,11 @@ Previous versions of **Typer** had deep integrations with `click-completion` and 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. +/// check | Inspired **Typer** to + +Provide auto completion for all the shells. + +/// ### FastAPI diff --git a/docs/contributing.md b/docs/contributing.md index dd038fc408..7600780afe 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,213 +1,232 @@ +# 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 repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment. +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 -### Virtual environment with `venv` +Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `typer`. -You can create a virtual environment in a directory using Python's `venv` module: +### Install Requirements Using `pip` + +After activating the environment, install the required packages:
```console -$ python -m venv env +$ pip install -r requirements.txt + +---> 100% ```
-That will create a directory `./env/` with the Python binaries and then you will be able to install packages for that isolated environment. +It will install all the dependencies and your local Typer in your local environment. -### Activate the environment +### Using your Local Typer -Activate the new environment with: +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. -=== "Linux, macOS" +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. - ```console - $ source ./env/bin/activate - ``` +/// note | "Technical Details" -
+This only happens when you install using this included `requirements.txt` instead of running `pip install typer` directly. -=== "Windows PowerShell" +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. -
+/// - ```console - $ .\env\Scripts\Activate.ps1 - ``` +### Format -
+There is a script that you can run that will format and clean all your code: -=== "Windows Bash" +
- Or if you use Bash for Windows (e.g. Git Bash): +```console +$ bash scripts/format.sh +``` -
+
- ```console - $ source ./env/Scripts/activate - ``` +It will also auto-sort all your imports. -
+## Tests -To check it worked, use: +There is a script that you can run locally to test all the code and generate coverage reports in HTML: -=== "Linux, macOS, Windows Bash" +
-
+```console +$ bash scripts/test-cov-html.sh +``` - ```console - $ which pip +
- some/directory/typer/env/bin/pip - ``` +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 -=== "Windows PowerShell" +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 a Docker Compose file `compose.yaml` at `./scripts/docker/`. - ```console - $ Get-Command pip +It has installed `bash`, `zsh`, `fish`, and `pwsh` (PowerShell for Linux). - some/directory/typer/env/bin/pip - ``` +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. -If it shows the `pip` binary at `env/bin/pip` then it worked. ๐ŸŽ‰ +The Docker Compose file mounts the main directory as `/code` inside the container, so you can change things and try them out. -!!! tip - Every time you install a new package with `pip` under that environment, activate the environment again. +Go to the `./scripts/docker/` directory: - This makes sure that if you use a terminal program installed by that package (like `flit`), you use the one from your local environment and not any other that could be installed globally. +```console +$ cd scripts/docker/ +``` -### Flit +Then run an interactive session with `bash` inside the container: -**Typer** uses Flit to build, package and publish the project. +```console +$ docker compose run typer bash -After activating the environment as described above, install `flit`: +root@79c4b9b70cbe:/code# +``` -
+Then inside the container, you can install `typer` with: ```console -$ pip install flit - ----> 100% +$ uv pip install -r requirements.txt ``` -
- -Now re-activate the environment to make sure you are using the `flit` you just installed (and not a global one). +Then, you can start the shell you want to use, the one where you want to try out completion: -And now use `flit` to install the development dependencies: +* `bash` +* `fish` +* `pwsh` +* `zsh` -=== "Linux, macOS" +For example: -
+```console +$ zsh +``` - ```console - $ flit install --deps develop --symlink +Then install `typer` completion: - ---> 100% - ``` +```console +$ typer --install-completion +``` -
+/// info -=== "Windows" +In `pwsh` you will probably get a warning of: - If you are on Windows, use `--pth-file` instead of `--symlink`: +```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. - ```console - $ flit install --deps develop --pth-file +/// - ---> 100% - ``` +For completion to take effect, you need to restart the shell. So, exit the current shell: -
+```console +$ exit +``` -It will install all the dependencies and your local Typer in your local environment. +and start a new shell (for the same shell you installed completion in) again. For example: -#### Using your local Typer +```console +$ zsh +``` -If you create a Python file that imports and uses Typer, and run it with the Python from your local environment, it will use your local Typer source code. +Now you could create a demo file on the same Typer directory in your editor, for example `demo.py`: -And if you update that local Typer source code, as it is installed with `--symlink` (or `--pth-file` on Windows), when you run that Python file again, it will use the fresh version of Typer you just edited. +```python +import typer -That way, you don't have to "install" your local version to be able to test every change. +app = typer.Typer() -### Format -There is a script that you can run that will format and clean all your code: +@app.command() +def hello(): + print("Hello") -
-```console -$ bash scripts/format.sh -``` +@app.command() +def goodbye(): + print("Goodbye") -
-It will also auto-sort all your imports. +if __name__ == "__main__": + app() +``` -For it to sort them correctly, you need to have Typer installed locally in your environment, with the command in the section above using `--symlink` (or `--pth-file` on Windows). +Because the directory is mounted as a volume, you will be able to access the file from inside the container. -### Format imports +So, you can try running it with the `typer` command, that will use the installed shell completion: -There is another script that formats all the imports and makes sure you don't have unused imports: +```console +$ typer demo.py +``` -
+And you should see the completion working: ```console -$ bash scripts/format-imports.sh +run -- Run the provided Typer app. +utils -- Extra utility commands for Typer apps. ``` -
+And the same for the commands in your `demo.py` file: -As it runs one command after the other and modifies and reverts many files, it takes a bit longer to run, so it might be easier to use `scripts/format.sh` frequently and `scripts/format-imports.sh` only before committing. +```console +$ typer demo.py run -## Docs +hello goodbye +``` -The documentation uses MkDocs. +You can also check the configuration file using `nano` or `vim`, for example: -All the documentation is in Markdown format in the directory `./docs`. +```bash +nano ~/.zshrc +``` -Many of the tutorials have blocks of code. +It will show some content like: -In most of the cases, these blocks of code are actual complete applications that can be run as is. +```bash +fpath+=~/.zfunc; autoload -Uz compinit; compinit -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. +zstyle ':completion:*' menu select +``` -### Docs for tests +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. -Most of the tests actually run against the example source files in the documentation. +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. -This helps making sure that: +## Docs -* 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. +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 -$ bash scripts/docs-live.sh +$ python ./scripts/docs.py live -[INFO] - Building documentation... -[INFO] - Cleaning site directory -[INFO] - Documentation built in 2.74 seconds -[INFO] - Serving on http://127.0.0.1:8008 +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes ```
@@ -216,16 +235,71 @@ It will serve the documentation on `http://127.0.0.1:8008`. That way, you can edit the documentation/source files and see the changes live. -## Tests +/// tip -There is a script that you can run locally to test all the code and generate coverage reports in HTML: +Alternatively, you can perform the same steps that scripts does manually. + +Go into the docs director 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 -$ bash scripts/test-cov-html.sh +$ typer --install-completion + +zsh completion installed in /home/user/.bashrc. +Completion will take effect once you restart the terminal. ```
-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. +### 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. diff --git a/docs/css/custom.css b/docs/css/custom.css index 954b0cf485..200ac45cd6 100644 --- a/docs/css/custom.css +++ b/docs/css/custom.css @@ -8,6 +8,10 @@ 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 @@ -21,3 +25,47 @@ a.internal-link::after { */ 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; +} diff --git a/docs/css/termynal.css b/docs/css/termynal.css index af2fbe6700..8534f91021 100644 --- a/docs/css/termynal.css +++ b/docs/css/termynal.css @@ -26,6 +26,7 @@ position: relative; -webkit-box-sizing: border-box; box-sizing: border-box; + /* Custom line-height */ line-height: 1.2; } diff --git a/docs/environment-variables.md b/docs/environment-variables.md new file mode 100644 index 0000000000..1cc66b928a --- /dev/null +++ b/docs/environment-variables.md @@ -0,0 +1,304 @@ +# 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). diff --git a/docs/features.md b/docs/features.md index f1fb6adef6..5a8643fa35 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1,3 +1,5 @@ +# Features + ## Design based on **FastAPI** @@ -48,27 +50,33 @@ The resulting CLI apps created with **Typer** have the nice features of many "pr * 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. +/// 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. - **Typer** uses `shellingham` to auto-detect the current shell when installing completion. If you don't want to include `shellingham`, install `typer-slim`. +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.: - **Typer** will automatically create 2 *CLI options*: +* `--install-completion bash`. +* `--show-completion powershell`. - * `--install-completion`: Install completion for the current shell. - * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. +Then you can tell the user to install completion after installing your CLI program and the rest will just work. - 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`. +/// tip - Then you can tell the user to install completion after installing your CLI program and the rest will just work. +**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. -!!! 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. - 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 diff --git a/docs/help-typer.md b/docs/help-typer.md index 221492db38..26c5cd6474 100644 --- a/docs/help-typer.md +++ b/docs/help-typer.md @@ -1,3 +1,5 @@ +# Help Typer - Get Help + Are you liking **Typer**? Would you like to help Typer, other users, and the author? @@ -20,13 +22,13 @@ You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/news ## Star **Typer** in GitHub -You can "star" Typer in GitHub (clicking the star button at the top right): https://github.com/tiangolo/typer. +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/tiangolo/typer. +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". @@ -52,7 +54,7 @@ You can: ## Tweet about **Typer** -Tweet about **Typer** and let me and others know why you like it. +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. @@ -60,8 +62,8 @@ I love to hear about how **Typer** is being used, what have you liked in it, in You can try and help others with their questions in: -* GitHub Discussions -* GitHub Issues +* GitHub Discussions +* GitHub Issues In many cases you might already know the answer for those questions. ๐Ÿค“ @@ -110,7 +112,7 @@ If they reply, there's a high chance you would have solved their problem, congra ## Watch the GitHub repository -You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/typer. +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. @@ -118,7 +120,7 @@ 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: +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**. @@ -155,12 +157,15 @@ And if there's any other style or consistency need, I'll ask directly for that, * 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. +/// 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. ๐Ÿ˜… - 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. ๐Ÿค“ - 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. @@ -207,10 +212,13 @@ If you can help me with that, **you are helping me maintain Typer** and making s 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. +/// 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. - Use the chat only for other general conversations. +/// ### Don't use the chat for questions diff --git a/docs/img/favicon.png b/docs/img/favicon.png old mode 100755 new mode 100644 index b5ce368d78..05752c5c07 Binary files a/docs/img/favicon.png and b/docs/img/favicon.png differ diff --git a/docs/img/github-social-preview.png b/docs/img/github-social-preview.png index 73674f2e24..12ec338123 100644 Binary files a/docs/img/github-social-preview.png and b/docs/img/github-social-preview.png differ diff --git a/docs/img/github-social-preview.svg b/docs/img/github-social-preview.svg index 3743f3ceff..dd99136570 100644 --- a/docs/img/github-social-preview.svg +++ b/docs/img/github-social-preview.svg @@ -1,22 +1,22 @@ + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:current-layer="g818" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="mm" /> + id="defs2"> + + + @@ -63,32 +102,71 @@ - + Typer - + style="font-style:normal;font-weight:normal;font-size:127.57px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:3.18925" + xml:space="preserve">Typer + + + + + + + Build great CLIs. Easy to code. Based on Python type hints. diff --git a/docs/img/icon-black.svg b/docs/img/icon-black.svg deleted file mode 100644 index bc987f38b6..0000000000 --- a/docs/img/icon-black.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/img/icon-square.svg b/docs/img/icon-square.svg new file mode 100644 index 0000000000..8e2b3fcd1b --- /dev/null +++ b/docs/img/icon-square.svg @@ -0,0 +1,68 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/docs/img/icon-white.svg b/docs/img/icon-white.svg deleted file mode 100644 index 1de612ccd4..0000000000 --- a/docs/img/icon-white.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/docs/img/icon.svg b/docs/img/icon.svg new file mode 100644 index 0000000000..6bdf83703d --- /dev/null +++ b/docs/img/icon.svg @@ -0,0 +1,68 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/docs/img/logo-margin/logo-margin-vector.svg b/docs/img/logo-margin/logo-margin-vector.svg index 815a4e77fc..ef28325eb0 100644 --- a/docs/img/logo-margin/logo-margin-vector.svg +++ b/docs/img/logo-margin/logo-margin-vector.svg @@ -1,39 +1,15 @@ - + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> image/svg+xml - @@ -56,33 +31,43 @@ id="rect824" style="opacity:0.98000004;fill:none;fill-opacity:1;stroke-width:0.42965442" /> - - - - + id="g818" + transform="translate(-5.3444232)"> + style="font-weight:bold;font-size:127.57px;line-height:1.25;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono Bold';letter-spacing:0px;word-spacing:0px;stroke-width:3.18925" + d="m 294.86852,68.451101 v 13.01214 H 274.07461 V 147.41693 H 258.25593 V 81.463241 h -20.79391 v -13.01214 z m 63.40204,19.00793 q -2.42383,8.41962 -4.72009,16.584099 -2.16869,8.03691 -4.59252,15.81868 -2.42383,7.6542 -5.23037,15.05326 -2.67897,7.39906 -5.86822,14.41541 -2.42383,5.1028 -4.84766,8.80233 -2.29626,3.69953 -5.23037,5.99579 -2.80654,2.42383 -6.3785,3.57196 -3.44439,1.14813 -8.16448,1.14813 -3.95467,0 -7.27149,-0.63785 -3.18925,-0.63785 -5.35794,-1.65841 l 2.93411,-13.13971 q 2.5514,1.14813 4.46495,1.53084 2.04112,0.51028 4.33738,0.51028 4.59252,0 7.39906,-3.06168 2.80654,-3.06168 4.46495,-7.27149 -5.61308,-11.22616 -11.22616,-25.64157 -5.61308,-14.54298 -10.2056,-32.020069 h 16.32896 q 1.14813,4.72009 2.5514,10.07803 1.53084,5.357939 3.18925,10.715879 1.65841,5.35794 3.44439,10.58831 1.91355,5.1028 3.8271,9.56775 1.2757,-4.46495 2.67897,-9.56775 1.40327,-5.23037 2.67897,-10.46074 1.2757,-5.35794 2.42383,-10.715879 1.2757,-5.35794 2.29626,-10.2056 z m 47.4558,30.489229 q 0,-8.03691 -3.18925,-13.13971 -3.18925,-5.230369 -9.69532,-5.230369 -1.78598,0 -3.8271,0.25514 -2.04112,0.12757 -3.44439,0.255139 v 32.91306 q 1.53084,1.02056 4.08224,1.65841 2.5514,0.63785 5.1028,0.63785 5.61308,0 8.29205,-4.46495 2.67897,-4.59252 2.67897,-12.88457 z m 15.94625,-0.51028 q 0,6.88878 -1.53084,12.757 -1.53084,5.74065 -4.46495,9.95046 -2.93411,4.08224 -7.39906,6.3785 -4.46495,2.29626 -10.33317,2.29626 -5.99579,0 -12.37429,-2.93411 v 22.57989 H 369.87919 V 89.372581 q 4.33738,-1.40327 10.58831,-2.29626 6.3785,-0.89299 12.50186,-0.89299 13.90513,0 21.30419,8.41962 7.39906,8.419619 7.39906,22.835029 z m 7.90909,0.51028 q 0,-7.90934 2.42383,-13.90513 2.42383,-5.995789 6.3785,-9.950459 3.95467,-4.08224 9.05747,-6.12336 5.1028,-2.04112 10.46074,-2.04112 13.26728,0 20.15606,7.90934 6.88878,7.781769 6.88878,22.835029 0,1.53084 -0.12757,3.18925 0,1.65841 -0.12757,2.67897 h -38.90885 q 0,5.86822 4.84766,9.31261 4.84766,3.31682 12.50186,3.31682 4.72009,0 8.9299,-1.02056 4.33738,-1.02056 7.27149,-2.04112 l 2.16869,13.39485 q -4.08224,1.40327 -8.67476,2.29626 -4.59252,1.02056 -10.33317,1.02056 -7.6542,0 -13.77756,-1.91355 -5.99579,-2.04112 -10.33317,-5.86822 -4.20981,-3.95467 -6.50607,-9.69532 -2.29626,-5.74065 -2.29626,-13.39485 z m 40.18455,-6.25093 q 0,-2.42383 -0.63785,-4.59252 -0.63785,-2.29626 -2.04112,-4.08224 -1.40327,-1.78598 -3.57196,-2.80654 -2.16869,-1.148129 -5.35794,-1.148129 -3.06168,0 -5.35794,1.020559 -2.16869,1.02056 -3.69953,2.80654 -1.40327,1.78598 -2.29626,4.08224 -0.76542,2.29626 -1.02056,4.72009 z m 75.39363,-8.29205 q -1.53084,-0.38271 -3.69953,-0.76542 -2.04112,-0.38271 -4.20981,-0.63785 -2.16869,-0.38271 -4.20981,-0.51028 -2.04112,-0.12757 -3.44439,-0.12757 -3.31682,0 -6.50607,0.38271 -3.18925,0.25514 -6.50607,1.14813 v 44.52193 H 500.76552 V 91.413701 q 6.25093,-2.29626 12.757,-3.69953 6.63364,-1.40327 15.43597,-1.40327 1.2757,0 3.57196,0.12757 2.42383,0.12757 5.1028,0.51028 2.67897,0.25514 5.35794,0.76542 2.80654,0.38271 4.97523,1.14813 z" + id="text861" + aria-label="Typer" /> + + + + + + - diff --git a/docs/img/logo-margin/logo-margin-white-vector.svg b/docs/img/logo-margin/logo-margin-white-vector.svg new file mode 100644 index 0000000000..d4c32dddc4 --- /dev/null +++ b/docs/img/logo-margin/logo-margin-white-vector.svg @@ -0,0 +1,73 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/docs/img/logo-margin/logo-margin-white.svg b/docs/img/logo-margin/logo-margin-white.svg new file mode 100644 index 0000000000..b758dc2481 --- /dev/null +++ b/docs/img/logo-margin/logo-margin-white.svg @@ -0,0 +1,78 @@ + + + + + + + image/svg+xml + + + + + + + Typer + + + + + + + + diff --git a/docs/img/logo-margin/logo-margin.svg b/docs/img/logo-margin/logo-margin.svg index a7cf9b14f5..4501f53abd 100644 --- a/docs/img/logo-margin/logo-margin.svg +++ b/docs/img/logo-margin/logo-margin.svg @@ -1,39 +1,15 @@ - + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> image/svg+xml - @@ -62,17 +37,42 @@ id="text861" y="147.41693" x="234.27277" - style="font-style:normal;font-weight:normal;font-size:127.57019043px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.189255" + style="font-style:normal;font-weight:normal;font-size:127.57px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.18925" xml:space="preserve">Typer - + id="tspan859">Typer + + + + + + diff --git a/docs/index.md b/docs/index.md index 6bb4983a9f..355380729a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,18 +1,25 @@ + +

- Typer + Typer + + Typer +

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

- - Test + + Test - - Publish + + Publish - - Coverage + + Coverage Package version @@ -22,7 +29,7 @@ **Documentation**: https://typer.tiangolo.com -**Source Code**: https://github.com/tiangolo/typer +**Source Code**: https://github.com/fastapi/typer --- @@ -45,6 +52,8 @@ The key features are: ## Installation +Create and activate a virtual environment and then install **Typer**: +

```console diff --git a/docs/js/custom.js b/docs/js/custom.js index 45243eb7ef..ef64c612a9 100644 --- a/docs/js/custom.js +++ b/docs/js/custom.js @@ -1,105 +1,115 @@ -document.querySelectorAll(".use-termynal").forEach(node => { - node.style.display = "block"; - new Termynal(node, { - lineDelay: 500 +function 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 = []; + const progressLiteralStart = "---> 100%"; + const promptLiteralStart = "$ "; + const customPromptLiteralStart = "# "; + const termynalActivateClass = "termy"; + let termynals = []; -function createTermynals() { - document - .querySelectorAll(`.${termynalActivateClass} .highlight`) - .forEach(node => { - const text = node.textContent; - const lines = text.split("\n"); - const useLines = []; - let buffer = []; - function saveBuffer() { - if (buffer.length) { - let isBlankSpace = true; - buffer.forEach(line => { - if (line) { - isBlankSpace = false; + 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; } - }); - 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(""); + 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 = []; } - 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) + 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); } - 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 + saveBuffer(); + const div = document.createElement("div"); + node.replaceWith(div); + const termynal = new Termynal(div, { + lineData: useLines, + noInit: true, + lineDelay: 500 + }); + termynals.push(termynal); }); - 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(); } -function loadVisibleTermynals() { - termynals = termynals.filter(termynal => { - if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) { - termynal.init(); - return false; - } - return true; - }); +async function main() { + setupTermynal() } -window.addEventListener("scroll", loadVisibleTermynals); -createTermynals(); -loadVisibleTermynals(); + +document$.subscribe(() => { + main() +}) diff --git a/docs/js/termynal.js b/docs/js/termynal.js index 4ac32708a3..45bb371c83 100644 --- a/docs/js/termynal.js +++ b/docs/js/termynal.js @@ -249,7 +249,6 @@ class Termynal { attrs += `${this.pfx}-${prop}="${line[prop]}" ` } } - return attrs; } } diff --git a/docs/management-tasks.md b/docs/management-tasks.md new file mode 100644 index 0000000000..879e0a0c3f --- /dev/null +++ b/docs/management-tasks.md @@ -0,0 +1,115 @@ +# 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`. diff --git a/docs/management.md b/docs/management.md new file mode 100644 index 0000000000..5c1f66e90c --- /dev/null +++ b/docs/management.md @@ -0,0 +1,45 @@ +# 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}. diff --git a/docs/release-notes.md b/docs/release-notes.md index fd8c426750..ad028087d5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,11 +1,108 @@ +# Release Notes + ## Latest Changes ### Features +* โœจ Show help items in order of definition. PR [#944](https://github.com/fastapi/typer/pull/944) by [@svlandeg](https://github.com/svlandeg). + +### Internal + +* ๐Ÿ‘ท 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). diff --git a/docs/resources/index.md b/docs/resources/index.md new file mode 100644 index 0000000000..d233a7833b --- /dev/null +++ b/docs/resources/index.md @@ -0,0 +1,3 @@ +# Resources + +Additional resources, how to **help** and get help, how to **contribute**, and more. โœˆ๏ธ diff --git a/docs/tutorial/app-dir.md b/docs/tutorial/app-dir.md index 3d09346b22..440d1dc01a 100644 --- a/docs/tutorial/app-dir.md +++ b/docs/tutorial/app-dir.md @@ -1,3 +1,5 @@ +# CLI Application Directory + You can get the application directory where you can, for example, save configuration files with `typer.get_app_dir()`: ```Python hl_lines="9" diff --git a/docs/tutorial/arguments/default.md b/docs/tutorial/arguments/default.md index 8a8f98017f..60a041abb1 100644 --- a/docs/tutorial/arguments/default.md +++ b/docs/tutorial/arguments/default.md @@ -1,3 +1,5 @@ +# 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. @@ -6,25 +8,35 @@ That way the *CLI argument* will be optional *and also* have a default value. We can also use `typer.Argument()` to make a *CLI argument* have a default value other than `None`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/arguments/default/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="5" - {!> ../docs_src/arguments/default/tutorial001_an.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/arguments/default/tutorial001.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="4" - {!> ../docs_src/arguments/default/tutorial001.py!} - ``` +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]`. -!!! 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. - 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: @@ -60,27 +72,37 @@ Hello Camila And we can even make the default value be dynamically generated by passing a function as the `default_factory` argument: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="7-8 11" - {!> ../docs_src/arguments/default/tutorial002_an.py!} - ``` +```Python hl_lines="7-8 11" +{!> ../docs_src/arguments/default/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="6-7 10" - {!> ../docs_src/arguments/default/tutorial002.py!} - ``` +```Python hl_lines="6-7 10" +{!> ../docs_src/arguments/default/tutorial002.py!} +``` + +//// In this case, we created the function `get_name` that will just return a random `str` each time. And we pass it as the first function argument to `typer.Argument()`. -!!! tip - The word "factory" in `default_factory` is just a fancy way of saying "function that will create the default value". +/// tip + +The word "factory" in `default_factory` is just a fancy way of saying "function that will create the default value". + +/// Check it: diff --git a/docs/tutorial/arguments/envvar.md b/docs/tutorial/arguments/envvar.md index 57d49e84bf..a53d42685e 100644 --- a/docs/tutorial/arguments/envvar.md +++ b/docs/tutorial/arguments/envvar.md @@ -1,21 +1,36 @@ +# 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()`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/arguments/envvar/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip - ```Python hl_lines="5" - {!> ../docs_src/arguments/envvar/tutorial001_an.py!} - ``` +Prefer to use the `Annotated` version if possible. -=== "Python 3.7+ non-Annotated" +/// - !!! tip - Prefer to use the `Annotated` version if possible. +```Python hl_lines="4" +{!> ../docs_src/arguments/envvar/tutorial001.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/arguments/envvar/tutorial001.py!} - ``` +//// In this case, the *CLI argument* `name` will have a default value of `"World"`, but will also read any value passed to the environment variable `AWESOME_NAME` if no value is provided in the command line: @@ -60,20 +75,27 @@ Hello Mr. Czernobog You are not restricted to a single environment variable, you can declare a list of environment variables that could be used to get a value if it was not passed in the command line: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="6" +{!> ../docs_src/arguments/envvar/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip - ```Python hl_lines="6" - {!> ../docs_src/arguments/envvar/tutorial002_an.py!} - ``` +Prefer to use the `Annotated` version if possible. -=== "Python 3.7+ non-Annotated" +/// - !!! tip - Prefer to use the `Annotated` version if possible. +```Python hl_lines="4" +{!> ../docs_src/arguments/envvar/tutorial002.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/arguments/envvar/tutorial002.py!} - ``` +//// Check it: @@ -108,20 +130,27 @@ Hello Mr. Anubis By default, environment variables used will be shown in the help text, but you can disable them with `show_envvar=False`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7" +{!> ../docs_src/arguments/envvar/tutorial003_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip - ```Python hl_lines="7" - {!> ../docs_src/arguments/envvar/tutorial003_an.py!} - ``` +Prefer to use the `Annotated` version if possible. -=== "Python 3.7+ non-Annotated" +/// - !!! tip - Prefer to use the `Annotated` version if possible. +```Python hl_lines="4" +{!> ../docs_src/arguments/envvar/tutorial003.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/arguments/envvar/tutorial003.py!} - ``` +//// Check it: @@ -148,7 +177,10 @@ Hello Mr. Wednesday
-!!! note "Technical Details" - In Click applications the env vars are hidden by default. ๐Ÿ™ˆ +/// note | Technical Details + +In Click applications the env vars are hidden by default. ๐Ÿ™ˆ + +In **Typer** these env vars are shown by default. ๐Ÿ‘€ - In **Typer** these env vars are shown by default. ๐Ÿ‘€ +/// diff --git a/docs/tutorial/arguments/help.md b/docs/tutorial/arguments/help.md index bbbb5e0eff..6203143871 100644 --- a/docs/tutorial/arguments/help.md +++ b/docs/tutorial/arguments/help.md @@ -1,3 +1,5 @@ +# 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: @@ -12,20 +14,27 @@ Now that you also know how to use `typer.Argument()`, let's use it to add docume You can use the `help` parameter to add a help text for a *CLI argument*: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/arguments/help/tutorial001_an.py!} +``` + +//// - ```Python hl_lines="5" - {!> ../docs_src/arguments/help/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/arguments/help/tutorial001.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/arguments/help/tutorial001.py!} - ``` +//// And it will be used in the automatic `--help` option: @@ -50,20 +59,27 @@ Options: And of course, you can also combine that `help` with the docstring: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="5-8" - {!> ../docs_src/arguments/help/tutorial002_an.py!} - ``` +```Python hl_lines="5-8" +{!> ../docs_src/arguments/help/tutorial002_an.py!} +``` + +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="4-7" - {!> ../docs_src/arguments/help/tutorial002.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4-7" +{!> ../docs_src/arguments/help/tutorial002.py!} +``` + +//// And the `--help` option will combine all the information: @@ -90,20 +106,27 @@ Options: If you have a *CLI argument* with a default value, like `"World"`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/arguments/help/tutorial003_an.py!} +``` + +//// - ```Python hl_lines="5" - {!> ../docs_src/arguments/help/tutorial003_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="4" - {!> ../docs_src/arguments/help/tutorial003.py!} - ``` +/// + +```Python hl_lines="4" +{!> ../docs_src/arguments/help/tutorial003.py!} +``` + +//// It will show that default value in the help text: @@ -128,20 +151,27 @@ Options: But you can disable that if you want to, with `show_default=False`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7" +{!> ../docs_src/arguments/help/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="7" - {!> ../docs_src/arguments/help/tutorial004_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// + +```Python hl_lines="4" +{!> ../docs_src/arguments/help/tutorial004.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/arguments/help/tutorial004.py!} - ``` +//// And then it won't show the default value: @@ -164,29 +194,39 @@ Options:
-!!! note "Technical Details" - In Click applications the default values are hidden by default. ๐Ÿ™ˆ +/// note | Technical Details - In **Typer** these default values are shown by default. ๐Ÿ‘€ +In Click applications the default values are hidden by default. ๐Ÿ™ˆ + +In **Typer** these default values are shown by default. ๐Ÿ‘€ + +/// ## Custom default string You can use the same `show_default` to pass a custom string (instead of a `bool`) to customize the default value to be shown in the help text: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="9" - {!> ../docs_src/arguments/help/tutorial005_an.py!} - ``` +```Python hl_lines="9" +{!> ../docs_src/arguments/help/tutorial005_an.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="6" - {!> ../docs_src/arguments/help/tutorial005.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6" +{!> ../docs_src/arguments/help/tutorial005.py!} +``` + +//// And it will be used in the help text: @@ -231,20 +271,27 @@ But you can customize it with the `metavar` parameter for `typer.Argument()`. For example, let's say you don't want to have the default of `NAME`, you want to have `username`, in lowercase, and you really want โœจ emojis โœจ everywhere: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/arguments/help/tutorial006_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="5" - {!> ../docs_src/arguments/help/tutorial006_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// + +```Python hl_lines="4" +{!> ../docs_src/arguments/help/tutorial006.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/arguments/help/tutorial006.py!} - ``` +//// Now the generated help text will have `โœจusernameโœจ` instead of `NAME`: @@ -270,20 +317,27 @@ You might want to show the help information for *CLI arguments* in different pan If you have installed Rich as described in the docs for [Printing and Colors](../printing.md){.internal-link target=_blank}, you can set the `rich_help_panel` parameter to the name of the panel where you want this *CLI argument* to be shown: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="8 12" +{!> ../docs_src/arguments/help/tutorial007_an.py!} +``` + +//// - ```Python hl_lines="8 12" - {!> ../docs_src/arguments/help/tutorial007_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="7 10" - {!> ../docs_src/arguments/help/tutorial007.py!} - ``` +/// + +```Python hl_lines="7 10" +{!> ../docs_src/arguments/help/tutorial007.py!} +``` + +//// Then, if you check the `--help` option, you will see a default panel named "`Arguments`" for the *CLI arguments* that don't have a custom `rich_help_panel`. @@ -326,20 +380,27 @@ If you want, you can make a *CLI argument* **not** show up in the `Arguments` se You will probably not want to do this normally, but it's possible: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="5" - {!> ../docs_src/arguments/help/tutorial008_an.py!} - ``` +```Python hl_lines="5" +{!> ../docs_src/arguments/help/tutorial008_an.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4" - {!> ../docs_src/arguments/help/tutorial008.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/arguments/help/tutorial008.py!} +``` + +//// Check it: @@ -359,10 +420,13 @@ Options: -!!! info - Have in mind that the *CLI argument* will still show up in the first line with `Usage`. +/// 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. +But it won't show up in the main help text under the `Arguments` section. + +/// ### Help text for *CLI arguments* in Click @@ -384,5 +448,8 @@ This is also to help you create CLI programs that are โœจ awesome โœจ *by defaul 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. +/// 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. + +/// diff --git a/docs/tutorial/arguments/index.md b/docs/tutorial/arguments/index.md index 23c97706ce..a52816e457 100644 --- a/docs/tutorial/arguments/index.md +++ b/docs/tutorial/arguments/index.md @@ -1,3 +1,5 @@ +# 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. diff --git a/docs/tutorial/arguments/optional.md b/docs/tutorial/arguments/optional.md index 6bda570843..e33b9cdc46 100644 --- a/docs/tutorial/arguments/optional.md +++ b/docs/tutorial/arguments/optional.md @@ -1,3 +1,5 @@ +# Optional CLI Arguments + We said before that *by default*: * *CLI options* are **optional** @@ -44,12 +46,15 @@ Now let's see an alternative way to create the same *CLI argument*: {!> ../docs_src/arguments/optional/tutorial001_an.py!} ``` -!!! info - Typer added support for `Annotated` (and started recommending it) in version 0.9.0. +/// 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`. +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`. +Make sure you upgrade the Typer version to at least 0.9.0 before using `Annotated`. + +/// Before, you had this function parameter: @@ -122,8 +127,11 @@ name: Annotated[Optional[str], typer.Argument()] = None Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*). -!!! tip - By using `Optional` your editor will be able to know that the value *could* be `None`, and will be able to warn you if you do something assuming it is a `str` that would break if it was `None`. +/// tip + +By using `Optional` your editor will be able to know that the value *could* be `None`, and will be able to warn you if you do something assuming it is a `str` that would break if it was `None`. + +/// Check the help: @@ -144,10 +152,13 @@ Options: -!!! tip - Notice that `NAME` is still a *CLI argument*, it's shown up there in the "`Usage: main.py` ...". +/// 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**. - 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: @@ -167,8 +178,11 @@ 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. +/// 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 @@ -180,8 +194,11 @@ Instead of using `Annotated`, you can use `typer.Argument()` as the default valu {!> ../docs_src/arguments/optional/tutorial001.py!} ``` -!!! tip - Prefer to use the `Annotated` version if possible. +/// 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. @@ -203,8 +220,11 @@ Not passing any value to the `default` argument is the same as marking it as req 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". +/// info + +If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis". + +/// ```Python hl_lines="4" {!> ../docs_src/arguments/optional/tutorial003.py!} diff --git a/docs/tutorial/arguments/other-uses.md b/docs/tutorial/arguments/other-uses.md index f7f398f341..1870e9c6c3 100644 --- a/docs/tutorial/arguments/other-uses.md +++ b/docs/tutorial/arguments/other-uses.md @@ -1,3 +1,5 @@ +# 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. diff --git a/docs/tutorial/commands/arguments.md b/docs/tutorial/commands/arguments.md index 9829082c39..43bc423245 100644 --- a/docs/tutorial/commands/arguments.md +++ b/docs/tutorial/commands/arguments.md @@ -1,3 +1,5 @@ +# 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*: ```Python hl_lines="7 12" @@ -28,12 +30,18 @@ Deleting user: Camila -!!! tip - Everything to the *right* of the *command* are *CLI parameters* (*CLI arguments* and *CLI options*) for that command. +/// 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*. -!!! 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*. - 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. - You will see about them later in another section. +/// diff --git a/docs/tutorial/commands/callback.md b/docs/tutorial/commands/callback.md index db8d18f2fd..ca10243c75 100644 --- a/docs/tutorial/commands/callback.md +++ b/docs/tutorial/commands/callback.md @@ -1,3 +1,5 @@ +# Typer Callback + When you create an `app = typer.Typer()` it works as a group of commands. And you can create multiple commands with it. @@ -16,10 +18,13 @@ It's very similar to `@app.command()`, but it declares the *CLI parameters* for 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. +/// 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. - 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. @@ -179,7 +184,10 @@ def cli(): 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. +/// 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** 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. +/// diff --git a/docs/tutorial/commands/context.md b/docs/tutorial/commands/context.md index 51e0a8912e..2600225507 100644 --- a/docs/tutorial/commands/context.md +++ b/docs/tutorial/commands/context.md @@ -1,3 +1,5 @@ +# 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`. @@ -126,5 +128,8 @@ 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. +/// 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. + +/// diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index 0310b36d41..5a6668038a 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -1,21 +1,30 @@ +# 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: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="4 9-11 22 26-30 43 47-51 60-62" +{!> ../docs_src/commands/help/tutorial001_an.py!} +``` - ```Python hl_lines="4 9-11 22 26-30 43 47-51 60-62" - {!> ../docs_src/commands/help/tutorial001_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="3 8-10 20 23-27 39 42-46 55-57" - {!> ../docs_src/commands/help/tutorial001.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="3 8-10 20 23-27 39 42-46 55-57" +{!> ../docs_src/commands/help/tutorial001.py!} +``` + +//// Check it: @@ -91,10 +100,13 @@ Options: -!!! tip - `typer.Typer()` receives several other parameters for other things, we'll see that later. +/// 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. - 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 @@ -194,27 +206,37 @@ If you have **Rich** installed as described in [Printing and Colors](../printing Then you can use more formatting in the docstrings and the `help` parameter for *CLI arguments* and *CLI options*. You will see more about it below. ๐Ÿ‘‡ -!!! info - By default, `rich_markup_mode` is `None`, which disables any rich text formatting. +/// 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: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="4 10 14-16 21 24 27" +{!> ../docs_src/commands/help/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4 10 14-16 21 24 27" - {!> ../docs_src/commands/help/tutorial004_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// + +```Python hl_lines="3 9 13-15 20 22 24" +{!> ../docs_src/commands/help/tutorial004.py!} +``` - ```Python hl_lines="3 9 13-15 20 22 24" - {!> ../docs_src/commands/help/tutorial004.py!} - ``` +//// With that, you can use Rich Console Markup to format the text in the docstring for the command `create`, make the word "`create`" bold and green, and even use an emoji. @@ -277,20 +299,27 @@ $ python main.py delete --help If you set `rich_markup_mode="markdown"` when creating the `typer.Typer()` app, you will be able to use Markdown in the docstring: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="4 9 12-20 25 27-28" - {!> ../docs_src/commands/help/tutorial005_an.py!} - ``` +```Python hl_lines="4 9 12-20 25 27-28" +{!> ../docs_src/commands/help/tutorial005_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="3 7 9-17 22 24-25" - {!> ../docs_src/commands/help/tutorial005.py!} - ``` +```Python hl_lines="3 7 9-17 22 24-25" +{!> ../docs_src/commands/help/tutorial005.py!} +``` + +//// With that, you can use Markdown to format the text in the docstring for the command `create`, make the word "`create`" bold, show a list of items, and even use an emoji. @@ -350,8 +379,11 @@ $ python main.py delete --help -!!! info - Notice that in Markdown you cannot define colors. For colors you might prefer to use Rich markup. +/// info + +Notice that in Markdown you cannot define colors. For colors you might prefer to use Rich markup. + +/// ## Help Panels @@ -363,11 +395,13 @@ If you installed ../docs_src/commands/help/tutorial006.py!} - ``` +```Python hl_lines="22 30 38 46" +{!> ../docs_src/commands/help/tutorial006.py!} +``` + +//// Commands without a panel will be shown in the default panel `Commands`, and the rest will be shown in the next panels: @@ -408,20 +442,27 @@ The same way, you can configure the panels for *CLI arguments* and *CLI options* And of course, in the same application you can also set the `rich_help_panel` for commands. -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="15 21 27 37" +{!> ../docs_src/commands/help/tutorial007_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="15 21 27 37" - {!> ../docs_src/commands/help/tutorial007_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// + +```Python hl_lines="12 16 21 30" +{!> ../docs_src/commands/help/tutorial007.py!} +``` - ```Python hl_lines="12 16 21 30" - {!> ../docs_src/commands/help/tutorial007.py!} - ``` +//// Then if you run the application you will see all the *CLI parameters* in their respective panels. diff --git a/docs/tutorial/commands/index.md b/docs/tutorial/commands/index.md index 1afc7dd1ee..f531c7cfb3 100644 --- a/docs/tutorial/commands/index.md +++ b/docs/tutorial/commands/index.md @@ -1,3 +1,5 @@ +# 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). @@ -28,8 +30,11 @@ 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. +/// 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 @@ -67,21 +72,27 @@ When you use `typer.run()`, **Typer** is doing more or less the same as above, i * 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". +/// 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). - 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. - 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`". - 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. +/// tip - 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 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: @@ -208,8 +219,11 @@ Deleting user: Hiro Hamada 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. +/// tip + +By default, the names of the commands are generated from the function name. + +/// ## Show the help message if no command is given @@ -243,14 +257,50 @@ Commands: + +## 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: + +```Python hl_lines="7 12" +{!../docs_src/commands/index/tutorial004.py!} +``` + +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. +/// 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. - 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 @@ -260,7 +310,10 @@ 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. +/// 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. - In Click, when you add a `@click.command()` decorator it actually modifies the function underneath and replaces it with an object. +/// diff --git a/docs/tutorial/commands/name.md b/docs/tutorial/commands/name.md index 7d3ddc1a5c..4f0faa047b 100644 --- a/docs/tutorial/commands/name.md +++ b/docs/tutorial/commands/name.md @@ -1,3 +1,5 @@ +# Custom Command Name + By default, the command names are generated from the function name. So, if your function is something like: diff --git a/docs/tutorial/commands/one-or-multiple.md b/docs/tutorial/commands/one-or-multiple.md index cc72a6ba45..5f744c5a2b 100644 --- a/docs/tutorial/commands/one-or-multiple.md +++ b/docs/tutorial/commands/one-or-multiple.md @@ -1,3 +1,5 @@ +# One or Multiple Commands + You might have noticed that if you create a single command, as in the first example: ```Python hl_lines="3 6 12" @@ -35,8 +37,11 @@ Options: -!!! tip - Notice that it doesn't show a command `main`, even though the function name is `main`. +/// 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: diff --git a/docs/tutorial/commands/options.md b/docs/tutorial/commands/options.md index 1bb3921ef8..a9c21b66b9 100644 --- a/docs/tutorial/commands/options.md +++ b/docs/tutorial/commands/options.md @@ -1,21 +1,30 @@ +# Command CLI Options + Commands can also have their own *CLI options*. In fact, each command can have different *CLI arguments* and *CLI options*: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="8 14-17 27-29 38" +{!> ../docs_src/commands/options/tutorial001_an.py!} +``` - ```Python hl_lines="8 14-17 27-29 38" - {!> ../docs_src/commands/options/tutorial001_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="7 13-14 24 33" - {!> ../docs_src/commands/options/tutorial001.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7 13-14 24 33" +{!> ../docs_src/commands/options/tutorial001.py!} +``` + +//// Here we have multiple commands, with different *CLI parameters*: @@ -51,8 +60,11 @@ Commands: -!!! tip - Check the command `delete-all`, by default command names are generated from the function name, replacing `_` with `-`. +/// tip + +Check the command `delete-all`, by default command names are generated from the function name, replacing `_` with `-`. + +/// Test it: diff --git a/docs/tutorial/first-steps.md b/docs/tutorial/first-steps.md index 6ef4212dde..53dc5eab4d 100644 --- a/docs/tutorial/first-steps.md +++ b/docs/tutorial/first-steps.md @@ -1,3 +1,5 @@ +# First Steps + ## The simplest example The simplest **Typer** file could look like this: @@ -93,8 +95,11 @@ Hello Camila Gutiรฉrrez -!!! tip - If you need to pass a single value that contains spaces to a *CLI argument*, use quotes (`"`) around it. +/// tip + +If you need to pass a single value that contains spaces to a *CLI argument*, use quotes (`"`) around it. + +/// ## Two CLI arguments @@ -145,16 +150,19 @@ Hello Camila Gutiรฉrrez -!!! tip - Notice that the order is important. The last name has to go after the first name. +/// tip + +Notice that the order is important. The last name has to go after the first name. - If you called it with: +If you called it with: + +``` +$ python main.py Gutiรฉrrez Camila +``` - ``` - $ 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`. - 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** @@ -214,10 +222,13 @@ 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. +/// tip - This one doesn't receive any values. But *CLI options* can also receive values like *CLI arguments*. You'll see how later. +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* @@ -250,8 +261,11 @@ $ python main.py --help -!!! tip - Notice that it automatically creates a `--formal` and a `--no-formal` because it detected that `formal` is a `bool`. +/// tip + +Notice that it automatically creates a `--formal` and a `--no-formal` because it detected that `formal` is a `bool`. + +/// Now call it normally: @@ -309,10 +323,13 @@ $ python main.py --help -!!! tip - Notice the `--lastname`, and notice that it takes a textual value. +/// 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*. - 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*. +///
@@ -330,8 +347,11 @@ 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*. +/// 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: @@ -379,8 +399,11 @@ $ python main.py --help -!!! 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. +/// 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 @@ -397,25 +420,28 @@ def main(name: str, lastname: str = ""): 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". +/// note | Technical Details - It's quite technical... and somewhat pedantic. +There's actually a very small distinction in Python between "parameter" and "argument". - *Parameter* refers to the variable name in a function *declaration*. Like: +It's quite technical... and somewhat pedantic. - ``` - def bring_person(name: str, lastname: str = ""): - pass - ``` +*Parameter* refers to the variable name in a function *declaration*. Like: - *Argument* refers to the value passed when *calling* a function. Like: +``` +def bring_person(name: str, lastname: str = ""): + pass +``` - ``` - person = bring_person("Camila", lastname="Gutiรฉrrez") - ``` +*Argument* refers to the value passed when *calling* a function. Like: - ...but you will probably see them used interchangeably in most of the places (including here). +``` +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 @@ -486,7 +512,10 @@ Hello World 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. +/// 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. - But for short scripts and for learning, before creating a Python package, it might be useful. +/// diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index 6cec65fe23..bad806c88a 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -1,4 +1,12 @@ -## Python types +# 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. @@ -23,17 +31,15 @@ These type hints are what give you autocomplete in your editor and several other **Typer** is based on these type hints. -## Intro +## 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. +It is also built to work as a future reference so you can come back and see exactly what you need. -## Run the code +## Run the Code All the code blocks can be copied and used directly (they are tested Python files). @@ -51,39 +57,8 @@ $ python main.py It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally. -Using it in your editor is what really shows you the benefits of **Typer**, seeing how little code you have to write, all the type checks, autocompletion, etc. - -And running the examples is what will really help you understand what is going on. - -You can learn a lot more by running some examples and playing around with them than by reading all the docs here. - ---- - -## Install **Typer** - -The first step is to install **Typer**: - -
- -```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 - ``` +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. - ...includes the same optional dependencies as: +And running the examples is what will really help you **understand** what is going on. - ```bash - pip install "typer-slim[standard]" - ``` +You can learn a lot more by **running some examples** and **playing around** with them than by reading all the docs here. diff --git a/docs/tutorial/install.md b/docs/tutorial/install.md new file mode 100644 index 0000000000..09ee0cfc2c --- /dev/null +++ b/docs/tutorial/install.md @@ -0,0 +1,33 @@ +# 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]" +``` + +/// diff --git a/docs/tutorial/launch.md b/docs/tutorial/launch.md index 90b3767b25..935bd7e632 100644 --- a/docs/tutorial/launch.md +++ b/docs/tutorial/launch.md @@ -1,3 +1,5 @@ +# 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: @@ -28,10 +30,13 @@ You can also make the operating system open the file browser indicating where a {!../docs_src/launch/tutorial002.py!} ``` -!!! tip - The rest of the code in this example is just making sure the app directory exists and creating the config file. +/// 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`. - But the most important part is the `typer.launch(config_file_str, locate=True)` with the argument `locate=True`. +/// Check it: diff --git a/docs/tutorial/multiple-values/arguments-with-multiple-values.md b/docs/tutorial/multiple-values/arguments-with-multiple-values.md index 07e6cd888a..b48484c105 100644 --- a/docs/tutorial/multiple-values/arguments-with-multiple-values.md +++ b/docs/tutorial/multiple-values/arguments-with-multiple-values.md @@ -1,3 +1,5 @@ +# CLI Arguments with Multiple Values + *CLI arguments* can also receive multiple values. You can define the type of a *CLI argument* using `typing.List`. @@ -21,30 +23,43 @@ 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. +/// 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*. +/// info + +A `List` can only be used in the last command (if there are subcommands), as this will take anything to the right and assume it's part of the expected *CLI arguments*. + +/// ## *CLI arguments* with tuples If you want a specific number of values and types, you can use a tuple, and it can even have default values: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="8-10" - {!> ../docs_src/multiple_values/arguments_with_multiple_values/tutorial002_an.py!} - ``` +```Python hl_lines="8-10" +{!> ../docs_src/multiple_values/arguments_with_multiple_values/tutorial002_an.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7-8" +{!> ../docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py!} +``` - ```Python hl_lines="7-8" - {!> ../docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py!} - ``` +//// Check it: diff --git a/docs/tutorial/multiple-values/index.md b/docs/tutorial/multiple-values/index.md index ee1bfa4da7..b511eef10a 100644 --- a/docs/tutorial/multiple-values/index.md +++ b/docs/tutorial/multiple-values/index.md @@ -1,3 +1,5 @@ +# 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. diff --git a/docs/tutorial/multiple-values/multiple-options.md b/docs/tutorial/multiple-values/multiple-options.md index a7c5c88fb6..0c876eab52 100644 --- a/docs/tutorial/multiple-values/multiple-options.md +++ b/docs/tutorial/multiple-values/multiple-options.md @@ -1,23 +1,32 @@ +# 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`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="1 7" +{!> ../docs_src/multiple_values/multiple_options/tutorial001_an.py!} +``` + +//// - ```Python hl_lines="1 7" - {!> ../docs_src/multiple_values/multiple_options/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="1 6" +{!> ../docs_src/multiple_values/multiple_options/tutorial001.py!} +``` - ```Python hl_lines="1 6" - {!> ../docs_src/multiple_values/multiple_options/tutorial001.py!} - ``` +//// You will receive the values as you declared them, as a `list` of `str`. @@ -51,20 +60,27 @@ Processing user: Morty The same way, you can use other types and they will be converted by **Typer** to their declared type: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="7" - {!> ../docs_src/multiple_values/multiple_options/tutorial002_an.py!} - ``` +```Python hl_lines="7" +{!> ../docs_src/multiple_values/multiple_options/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6" +{!> ../docs_src/multiple_values/multiple_options/tutorial002.py!} +``` - ```Python hl_lines="6" - {!> ../docs_src/multiple_values/multiple_options/tutorial002.py!} - ``` +//// Check it: diff --git a/docs/tutorial/multiple-values/options-with-multiple-values.md b/docs/tutorial/multiple-values/options-with-multiple-values.md index feeb45eea9..1d3f5deff4 100644 --- a/docs/tutorial/multiple-values/options-with-multiple-values.md +++ b/docs/tutorial/multiple-values/options-with-multiple-values.md @@ -1,23 +1,32 @@ +# 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`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="1 7" +{!> ../docs_src/multiple_values/options_with_multiple_values/tutorial001_an.py!} +``` - ```Python hl_lines="1 7" - {!> ../docs_src/multiple_values/options_with_multiple_values/tutorial001_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="1 6" - {!> ../docs_src/multiple_values/options_with_multiple_values/tutorial001.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="1 6" +{!> ../docs_src/multiple_values/options_with_multiple_values/tutorial001.py!} +``` + +//// Each of the internal types defines the type of each value in the tuple. @@ -59,10 +68,13 @@ coins = user[1] is_wizard = user[2] ``` -!!! tip - Notice that the default is a tuple with `(None, None, None)`. +/// 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. - You cannot simply use `None` here as the default because Click doesn't support it. +/// ## Check it diff --git a/docs/tutorial/options-autocompletion.md b/docs/tutorial/options-autocompletion.md index 7ac42dff8d..43777d04fb 100644 --- a/docs/tutorial/options-autocompletion.md +++ b/docs/tutorial/options-autocompletion.md @@ -1,3 +1,5 @@ +# 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). @@ -14,20 +16,27 @@ To check it quickly without creating a new Python package, use the `typer` comma Then let's create small example program: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python +{!> ../docs_src/options_autocompletion/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python - {!> ../docs_src/options_autocompletion/tutorial001_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python - {!> ../docs_src/options_autocompletion/tutorial001.py!} - ``` +```Python +{!> ../docs_src/options_autocompletion/tutorial001.py!} +``` + +//// And let's try it with the `typer` command to get completion: @@ -61,20 +70,27 @@ Right now we get completion for the *CLI option* names, but not for the values. We can provide completion for the values creating an `autocompletion` function, similar to the `callback` functions from [CLI Option Callback and Context](./options/callback-and-context.md){.internal-link target=_blank}: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5-6 15" +{!> ../docs_src/options_autocompletion/tutorial002_an.py!} +``` + +//// - ```Python hl_lines="5-6 15" - {!> ../docs_src/options_autocompletion/tutorial002_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="4-5 14" - {!> ../docs_src/options_autocompletion/tutorial002.py!} - ``` +/// + +```Python hl_lines="4-5 14" +{!> ../docs_src/options_autocompletion/tutorial002.py!} +``` + +//// We return a `list` of strings from the `complete_name()` function. @@ -103,20 +119,27 @@ Modify the `complete_name()` function to receive a parameter of type `str`, it w Then we can check and return only the values that start with the incomplete value from the command line: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7-12" +{!> ../docs_src/options_autocompletion/tutorial003_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="7-12" - {!> ../docs_src/options_autocompletion/tutorial003_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="6-11" - {!> ../docs_src/options_autocompletion/tutorial003.py!} - ``` +```Python hl_lines="6-11" +{!> ../docs_src/options_autocompletion/tutorial003.py!} +``` + +//// Now let's try it: @@ -133,12 +156,15 @@ 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. +/// 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. +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. +And the same way, you can only return `str`, not `int`, etc. + +/// ## Add help to completions @@ -152,32 +178,45 @@ In the `complete_name()` function, instead of providing one `str` per completion So, in the end, we return a `list` of `tuples` of `str`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="4-8 11-17" +{!> ../docs_src/options_autocompletion/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="3-7 10-16" +{!> ../docs_src/options_autocompletion/tutorial004.py!} +``` + +//// - ```Python hl_lines="4-8 11-17" - {!> ../docs_src/options_autocompletion/tutorial004_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +If you want to have help text for each item, make sure each item in the list is a `tuple`. Not a `list`. - !!! tip - Prefer to use the `Annotated` version if possible. +Click checks specifically for a `tuple` when extracting the help text. - ```Python hl_lines="3-7 10-16" - {!> ../docs_src/options_autocompletion/tutorial004.py!} - ``` +So in the end, the return will be a `list` (or other iterable) of `tuples` of 2 `str`. -!!! 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. +/// info - So in the end, the return will be a `list` (or other iterable) of `tuples` of 2 `str`. +The help text will be visible in Zsh, Fish, and PowerShell. -!!! 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. - 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: @@ -200,32 +239,45 @@ Instead of creating and returning a list with values (`str` or `tuple`), we can That way our function will be a generator that **Typer** (actually Click) can iterate: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="11-14" +{!> ../docs_src/options_autocompletion/tutorial005_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="11-14" - {!> ../docs_src/options_autocompletion/tutorial005_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="10-13" - {!> ../docs_src/options_autocompletion/tutorial005.py!} - ``` +```Python hl_lines="10-13" +{!> ../docs_src/options_autocompletion/tutorial005.py!} +``` + +//// That simplifies our code a bit and works the same. -!!! tip - If all the `yield` part seems complex for you, don't worry, you can just use the version with the `list` above. +/// tip + +If all the `yield` part seems complex for you, don't worry, you can just use the version with the `list` above. + +In the end, that's just to save us a couple of lines of code. + +/// - In the end, that's just to save us a couple of lines of code. +/// info -!!! info - The function can use `yield`, so it doesn't have to return strictly a `list`, it just has to be iterable. +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). +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 @@ -233,27 +285,37 @@ Let's say that now we want to modify the program to be able to "say hi" to multi So, we will allow multiple `--name` *CLI options*. -!!! tip - You will learn more about *CLI parameters* with multiple values later in the tutorial. +/// tip + +You will learn more about *CLI parameters* with multiple values later in the tutorial. + +So, for now, take this as a sneak peek ๐Ÿ˜‰. - So, for now, take this as a sneak peek ๐Ÿ˜‰. +/// For this we use a `List` of `str`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="9-14" +{!> ../docs_src/options_autocompletion/tutorial006_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="9-14" - {!> ../docs_src/options_autocompletion/tutorial006_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="8-11" - {!> ../docs_src/options_autocompletion/tutorial006.py!} - ``` +```Python hl_lines="8-11" +{!> ../docs_src/options_autocompletion/tutorial006.py!} +``` + +//// And then we can use it like: @@ -278,20 +340,27 @@ But you can access the context by declaring a function parameter of type `typer. And from that context you can get the current values for each parameter. -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="13-14 16" +{!> ../docs_src/options_autocompletion/tutorial007_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="13-14 16" - {!> ../docs_src/options_autocompletion/tutorial007_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// + +```Python hl_lines="12-13 15" +{!> ../docs_src/options_autocompletion/tutorial007.py!} +``` - ```Python hl_lines="12-13 15" - {!> ../docs_src/options_autocompletion/tutorial007.py!} - ``` +//// We are getting the `names` already provided with `--name` in the command line before this completion was triggered. @@ -329,8 +398,11 @@ 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. +/// 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* @@ -338,10 +410,13 @@ You can also get the raw *CLI parameters*, just a `list` of `str` with everythin 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. +/// 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. +But it's still possible if you need it. + +/// As a simple example, let's show it on the screen before completion. @@ -349,8 +424,11 @@ Because completion is based on the output printed by your program (handled inter ### 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}. +/// 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. ๐Ÿš€ @@ -358,30 +436,43 @@ You can print to "standard error" with a **Rich** `Console(stderr=True)`. Using `stderr=True` tells **Rich** that the output should be shown in "standard error". -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="13 16-17" +{!> ../docs_src/options_autocompletion/tutorial008_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="13 16-17" - {!> ../docs_src/options_autocompletion/tutorial008_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="12 15-16" - {!> ../docs_src/options_autocompletion/tutorial008.py!} - ``` +```Python hl_lines="12 15-16" +{!> ../docs_src/options_autocompletion/tutorial008.py!} +``` + +//// + +/// info -!!! 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. +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. +/// 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`. +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". @@ -401,29 +492,39 @@ 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. +/// 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. - But it's probably useful only in very advanced use cases. +/// ## Getting the Context and the raw *CLI parameters* Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, and the incomplete `str`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="16" +{!> ../docs_src/options_autocompletion/tutorial009_an.py!} +``` - ```Python hl_lines="16" - {!> ../docs_src/options_autocompletion/tutorial009_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="15" +{!> ../docs_src/options_autocompletion/tutorial009.py!} +``` - ```Python hl_lines="15" - {!> ../docs_src/options_autocompletion/tutorial009.py!} - ``` +//// Check it: diff --git a/docs/tutorial/options/callback-and-context.md b/docs/tutorial/options/callback-and-context.md index 4179eeff94..249616f072 100644 --- a/docs/tutorial/options/callback-and-context.md +++ b/docs/tutorial/options/callback-and-context.md @@ -1,3 +1,5 @@ +# 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. @@ -6,20 +8,27 @@ In those cases you can use a *CLI parameter* callback function. For example, you could do some validation before the rest of the code is executed. -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7-10 13" +{!> ../docs_src/options/callback/tutorial001_an.py!} +``` + +//// - ```Python hl_lines="7-10 13" - {!> ../docs_src/options/callback/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6-9 12" +{!> ../docs_src/options/callback/tutorial001.py!} +``` - ```Python hl_lines="6-9 12" - {!> ../docs_src/options/callback/tutorial001.py!} - ``` +//// Here you pass a function to `typer.Option()` or `typer.Argument()` with the keyword argument `callback`. @@ -105,20 +114,27 @@ But the main **important point** is that it is all based on values printed by yo Let's say that when the callback is running, we want to show a message saying that it's validating the name: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="8" - {!> ../docs_src/options/callback/tutorial002_an.py!} - ``` +```Python hl_lines="8" +{!> ../docs_src/options/callback/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="7" +{!> ../docs_src/options/callback/tutorial002.py!} +``` - ```Python hl_lines="7" - {!> ../docs_src/options/callback/tutorial002.py!} - ``` +//// And because the callback will be called when the shell calls your program asking for completion, that message `"Validating name"` will be printed and it will break completion. @@ -153,20 +169,27 @@ But you can access the context by declaring a function parameter of type `typer. The "context" has some additional data about the current execution of your program: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="7-9" - {!> ../docs_src/options/callback/tutorial003_an.py!} - ``` +```Python hl_lines="7-9" +{!> ../docs_src/options/callback/tutorial003_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6-8" +{!> ../docs_src/options/callback/tutorial003.py!} +``` - ```Python hl_lines="6-8" - {!> ../docs_src/options/callback/tutorial003.py!} - ``` +//// The `ctx.resilient_parsing` will be `True` when handling completion, so you can just return without printing anything else. @@ -198,20 +221,27 @@ Hello Camila The same way you can access the `typer.Context` by declaring a function parameter with its value, you can declare another function parameter with type `typer.CallbackParam` to get the specific Click `Parameter` object. -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="7 10" - {!> ../docs_src/options/callback/tutorial004_an.py!} - ``` +```Python hl_lines="7 10" +{!> ../docs_src/options/callback/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6 9" +{!> ../docs_src/options/callback/tutorial004.py!} +``` - ```Python hl_lines="6 9" - {!> ../docs_src/options/callback/tutorial004.py!} - ``` +//// It's probably not very common, but you could do it if you need it. diff --git a/docs/tutorial/options/help.md b/docs/tutorial/options/help.md index 772daaf1bb..f2ee34db51 100644 --- a/docs/tutorial/options/help.md +++ b/docs/tutorial/options/help.md @@ -1,21 +1,30 @@ +# 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*: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7-8" +{!> ../docs_src/options/help/tutorial001_an.py!} +``` + +//// - ```Python hl_lines="7-8" - {!> ../docs_src/options/help/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6-7" +{!> ../docs_src/options/help/tutorial001.py!} +``` - ```Python hl_lines="6-7" - {!> ../docs_src/options/help/tutorial001.py!} - ``` +//// The same way as with `typer.Argument()`, we can put `typer.Option()` inside of `Annotated`. @@ -67,20 +76,27 @@ The same as with *CLI arguments*, you can put the help for some *CLI options* in If you have installed Rich as described in the docs for [Printing and Colors](../printing.md){.internal-link target=_blank}, you can set the `rich_help_panel` parameter to the name of the panel you want for each *CLI option*: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="11 17" - {!> ../docs_src/options/help/tutorial002_an.py!} - ``` +```Python hl_lines="11 17" +{!> ../docs_src/options/help/tutorial002_an.py!} +``` + +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="8 11" - {!> ../docs_src/options/help/tutorial002.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="8 11" +{!> ../docs_src/options/help/tutorial002.py!} +``` + +//// Now, when you check the `--help` option, you will see a default panel named "`Options`" for the *CLI options* that don't have a custom `rich_help_panel`. @@ -126,20 +142,27 @@ If you are in a hurry you can jump there, but otherwise, it would be better to c You can tell Typer to not show the default value in the help text with `show_default=False`: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="5" - {!> ../docs_src/options/help/tutorial003_an.py!} - ``` +```Python hl_lines="5" +{!> ../docs_src/options/help/tutorial003_an.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4" - {!> ../docs_src/options/help/tutorial003.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/options/help/tutorial003.py!} +``` + +//// And it will no longer show the default value in the help text: @@ -164,29 +187,39 @@ Options: -!!! note "Technical Details" - In Click applications the default values are hidden by default. ๐Ÿ™ˆ +/// note | Technical Details + +In Click applications the default values are hidden by default. ๐Ÿ™ˆ + +In **Typer** these default values are shown by default. ๐Ÿ‘€ - In **Typer** these default values are shown by default. ๐Ÿ‘€ +/// ## Custom default string You can use the same `show_default` to pass a custom string (instead of a `bool`) to customize the default value to be shown in the help text: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="7" - {!> ../docs_src/options/help/tutorial004_an.py!} - ``` +```Python hl_lines="7" +{!> ../docs_src/options/help/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6" +{!> ../docs_src/options/help/tutorial004.py!} +``` - ```Python hl_lines="6" - {!> ../docs_src/options/help/tutorial004.py!} - ``` +//// And it will be used in the help text: diff --git a/docs/tutorial/options/index.md b/docs/tutorial/options/index.md index 13c4505eb3..f71c2167e7 100644 --- a/docs/tutorial/options/index.md +++ b/docs/tutorial/options/index.md @@ -1,3 +1,5 @@ +# 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. diff --git a/docs/tutorial/options/name.md b/docs/tutorial/options/name.md index 2392edcaa5..a724a5b563 100644 --- a/docs/tutorial/options/name.md +++ b/docs/tutorial/options/name.md @@ -1,3 +1,5 @@ +# CLI Option Name + By default **Typer** will create a *CLI option* name from the function parameter. So, if you have a function with: @@ -26,33 +28,43 @@ Let's say the function parameter name is `user_name` as above, but you want the You can pass the *CLI option* name that you want to have in the following positional argument passed to `typer.Option()`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/options/name/tutorial001_an.py!} +``` + +Here you are passing the string `"--name"` as the first positional argument to `typer.Option()`. + +//// - ```Python hl_lines="5" - {!> ../docs_src/options/name/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated - Here you are passing the string `"--name"` as the first positional argument to `typer.Option()`. +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="4" - {!> ../docs_src/options/name/tutorial001.py!} - ``` +```Python hl_lines="4" +{!> ../docs_src/options/name/tutorial001.py!} +``` + +Here you are passing the string `"--name"` as the second positional argument to `typer.Option()`, as the first argument is `...` to mark it as required. + +//// - Here you are passing the string `"--name"` as the second positional argument to `typer.Option()`, as the first argument is `...` to mark it as required. +/// info -!!! info - "Positional" means that it's not a function argument with a keyword name. +"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. +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()`. +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. +That's a "positional argument" in a function. + +/// Check it: @@ -69,7 +81,7 @@ Options: --help Show this message and exit. // Try it -$ python --name Camila +$ python main.py --name Camila Hello Camila ``` @@ -185,29 +197,39 @@ In **Typer** you can also define *CLI option* short names the same way you can c 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. +/// 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. +All the other function arguments/parameters you pass to `typer.Option()` like `prompt=True` and `help="This option blah, blah"` require the keyword. + +/// You can overwrite the *CLI option* name to use as in the previous example, but you can also declare extra alternatives, including short names. For example, extending the previous example, let's add a *CLI option* short name `-n`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/options/name/tutorial002_an.py!} +``` - ```Python hl_lines="5" - {!> ../docs_src/options/name/tutorial002_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="4" - {!> ../docs_src/options/name/tutorial002.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/options/name/tutorial002.py!} +``` + +//// Here we are overwriting the *CLI option* name that by default would be `--user-name`, and we are defining it to be `--name`. And we are also declaring a *CLI option* short name of `-n`. @@ -238,20 +260,27 @@ Hello Camila If you only declare a short name like `-n` then that will be the only *CLI option* name. And neither `--name` nor `--user-name` will be available. -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/options/name/tutorial003_an.py!} +``` - ```Python hl_lines="5" - {!> ../docs_src/options/name/tutorial003_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="4" - {!> ../docs_src/options/name/tutorial003.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/options/name/tutorial003.py!} +``` + +//// Check it: @@ -279,20 +308,27 @@ Hello Camila Continuing with the example above, as **Typer** allows you to declare a *CLI option* as having only a short name, if you want to have the default long name plus a short name, you have to declare both explicitly: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/options/name/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip - ```Python hl_lines="5" - {!> ../docs_src/options/name/tutorial004_an.py!} - ``` +Prefer to use the `Annotated` version if possible. -=== "Python 3.7+ non-Annotated" +/// - !!! tip - Prefer to use the `Annotated` version if possible. +```Python hl_lines="4" +{!> ../docs_src/options/name/tutorial004.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/options/name/tutorial004.py!} - ``` +//// Check it: @@ -326,23 +362,33 @@ You can create multiple short names and use them together. You don't have to do anything special for it to work (apart from declaring those short versions): -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="6-7" +{!> ../docs_src/options/name/tutorial005_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="6-7" - {!> ../docs_src/options/name/tutorial005_an.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="5-6" +{!> ../docs_src/options/name/tutorial005.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="5-6" - {!> ../docs_src/options/name/tutorial005.py!} - ``` +Notice that, again, we are declaring the long and short version of the *CLI option* names. -!!! tip - Notice that, again, we are declaring the long and short version of the *CLI option* names. +/// Check it: diff --git a/docs/tutorial/options/password.md b/docs/tutorial/options/password.md index abe33e5411..8601d848b6 100644 --- a/docs/tutorial/options/password.md +++ b/docs/tutorial/options/password.md @@ -1,19 +1,28 @@ +# Password CLI Option and Confirmation Prompt + Apart from having a prompt, you can make a *CLI option* have a `confirmation_prompt=True`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7" +{!> ../docs_src/options/password/tutorial001_an.py!} +``` + +//// - ```Python hl_lines="7" - {!> ../docs_src/options/password/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="5" +{!> ../docs_src/options/password/tutorial001.py!} +``` - ```Python hl_lines="5" - {!> ../docs_src/options/password/tutorial001.py!} - ``` +//// And the CLI program will ask for confirmation: @@ -41,20 +50,27 @@ You can achieve the same using `hide_input=True`. And if you combine it with `confirmation_prompt=True` you can easily receive a password with double confirmation: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="8" - {!> ../docs_src/options/password/tutorial002_an.py!} - ``` +```Python hl_lines="8" +{!> ../docs_src/options/password/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6-8" +{!> ../docs_src/options/password/tutorial002.py!} +``` - ```Python hl_lines="6-8" - {!> ../docs_src/options/password/tutorial002.py!} - ``` +//// Check it: diff --git a/docs/tutorial/options/prompt.md b/docs/tutorial/options/prompt.md index 9f7472eac4..e55a09551e 100644 --- a/docs/tutorial/options/prompt.md +++ b/docs/tutorial/options/prompt.md @@ -1,19 +1,28 @@ +# CLI Option Prompt + It's also possible to, instead of just showing an error, ask for the missing value with `prompt=True`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/options/prompt/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="5" - {!> ../docs_src/options/prompt/tutorial001_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="4" - {!> ../docs_src/options/prompt/tutorial001.py!} - ``` +```Python hl_lines="4" +{!> ../docs_src/options/prompt/tutorial001.py!} +``` + +//// And then your program will ask the user for it in the terminal: @@ -35,20 +44,27 @@ Hello Camila Gutiรฉrrez You can also set a custom prompt, passing the string that you want to use instead of just `True`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="7" +{!> ../docs_src/options/prompt/tutorial002_an.py!} +``` + +//// - ```Python hl_lines="7" - {!> ../docs_src/options/prompt/tutorial002_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="5" - {!> ../docs_src/options/prompt/tutorial002.py!} - ``` +/// + +```Python hl_lines="5" +{!> ../docs_src/options/prompt/tutorial002.py!} +``` + +//// And then your program will ask for it using with your custom prompt: @@ -74,20 +90,27 @@ You can do it passing the parameter `confirmation_prompt=True`. Let's say it's a CLI app to delete a project: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="6" +{!> ../docs_src/options/prompt/tutorial003_an.py!} +``` - ```Python hl_lines="6" - {!> ../docs_src/options/prompt/tutorial003_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/options/prompt/tutorial003.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/options/prompt/tutorial003.py!} - ``` +//// And it will prompt the user for a value and then for the confirmation: diff --git a/docs/tutorial/options/required.md b/docs/tutorial/options/required.md index 311fbd93ae..4b2c2226eb 100644 --- a/docs/tutorial/options/required.md +++ b/docs/tutorial/options/required.md @@ -1,3 +1,5 @@ +# Required CLI Options + We said before that *by default*: * *CLI options* are **optional** @@ -11,35 +13,47 @@ To make a *CLI option* required, you can put `typer.Option()` inside of `Annotat Let's make `--lastname` a required *CLI option*: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/options/required/tutorial001_an.py!} +``` - ```Python hl_lines="5" - {!> ../docs_src/options/required/tutorial001_an.py!} - ``` +//// The same way as with `typer.Argument()`, the old style of using the function parameter default value is also supported, in that case you would just not pass anything to the `default` parameter. -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4" - {!> ../docs_src/options/required/tutorial001.py!} - ``` +```Python hl_lines="4" +{!> ../docs_src/options/required/tutorial001.py!} +``` + +//// Or you can explictily pass `...` to `typer.Option(default=...)`: -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4" - {!> ../docs_src/options/required/tutorial002.py!} - ``` +```Python hl_lines="4" +{!> ../docs_src/options/required/tutorial002.py!} +``` -!!! info - If you hadn't seen that `...` before: it is a special single value, it is part of Python and is called "Ellipsis". +//// + +/// 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**. +/// 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: diff --git a/docs/tutorial/options/version.md b/docs/tutorial/options/version.md index 497a30262c..f76112ce39 100644 --- a/docs/tutorial/options/version.md +++ b/docs/tutorial/options/version.md @@ -1,3 +1,5 @@ +# 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. @@ -6,23 +8,33 @@ It would show the version of your CLI program and then it would terminate it. Ev Let's see a first version of how it could look like: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="9-12 17-19" +{!> ../docs_src/options/version/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="9-12 17-19" - {!> ../docs_src/options/version/tutorial001_an.py!} - ``` +/// -=== "Python 3.7+ non-Annotated" +```Python hl_lines="8-11 16-18" +{!> ../docs_src/options/version/tutorial001.py!} +``` + +//// - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="8-11 16-18" - {!> ../docs_src/options/version/tutorial001.py!} - ``` +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. -!!! 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. @@ -65,20 +77,27 @@ Awesome CLI Version: 0.1.0 But now let's say that the `--name` *CLI option* that we declared before `--version` is required, and it has a callback that could exit the program: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="15-17 22-24" +{!> ../docs_src/options/version/tutorial002_an.py!} +``` - ```Python hl_lines="15-17 22-24" - {!> ../docs_src/options/version/tutorial002_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="14-16 21-23" - {!> ../docs_src/options/version/tutorial002.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="14-16 21-23" +{!> ../docs_src/options/version/tutorial002.py!} +``` + +//// Then our CLI program could not work as expected in some cases as it is *right now*, because if we use `--version` after `--name` then the callback for `--name` will be processed before and we can get its error: @@ -93,14 +112,23 @@ 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`. +/// 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 -!!! 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. +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}. -!!! 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` @@ -108,20 +136,27 @@ For those cases, we can mark a *CLI parameter* (a *CLI option* or *CLI argument* That will tell **Typer** (actually Click) that it should process this *CLI parameter* before the others: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="23-26" - {!> ../docs_src/options/version/tutorial003_an.py!} - ``` +```Python hl_lines="23-26" +{!> ../docs_src/options/version/tutorial003_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="22-24" +{!> ../docs_src/options/version/tutorial003.py!} +``` - ```Python hl_lines="22-24" - {!> ../docs_src/options/version/tutorial003.py!} - ``` +//// Check it: diff --git a/docs/tutorial/package.md b/docs/tutorial/package.md index 17fe5a4149..617c93cd0b 100644 --- a/docs/tutorial/package.md +++ b/docs/tutorial/package.md @@ -1,3 +1,5 @@ +# 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. @@ -10,8 +12,11 @@ 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. +/// tip + +If you already have a favorite way of creating Python packages, feel free to skip this. + +/// ## Prerequisites @@ -44,41 +49,34 @@ cd ./rick-portal-gun ## Dependencies and environment -Add `typer[all]` to your dependencies: +Add `typer` to your dependencies:
```console -$ poetry add "typer[all]" +$ 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.1.0 for typer +Using version ^0.12.0 for typer Updating dependencies Resolving dependencies... (1.2s) -Writing lock file - ---> 100% -Package operations: 15 installs, 0 updates, 0 removals - - - Installing zipp (3.1.0) - - Installing importlib-metadata (1.5.0) - - Installing pyparsing (2.4.6) - - Installing six (1.14.0) - - Installing attrs (19.3.0) - - Installing click (7.1.1) - - Installing colorama (0.4.3) - - Installing more-itertools (8.2.0) - - Installing packaging (20.3) - - Installing pluggy (0.13.1) - - Installing py (1.8.1) - - Installing shellingham (1.3.2) - - Installing wcwidth (0.1.8) - - Installing pytest (5.4.1) - - Installing typer (0.0.11) +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 @@ -101,8 +99,7 @@ You can see that you have a generated project structure that looks like: โ”œโ”€โ”€ rick_portal_gun โ”‚ย ย  โ””โ”€โ”€ __init__.py โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_rick_portal_gun.py + โ””โ”€โ”€ __init__.py ``` ## Create your app @@ -141,8 +138,11 @@ def load(): 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__":`. +/// tip + +As we are creating an installable Python package, there's no need to add a section with `if __name__ == "__main__":`. + +/// ## Modify the README @@ -175,14 +175,11 @@ rick-portal-gun = "rick_portal_gun.main:app" [tool.poetry.dependencies] python = "^3.10" -typer = {extras = ["all"], version = "^0.1.0"} - -[tool.poetry.dev-dependencies] -pytest = "^5.2" +typer = "^0.12.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" ``` Here's what that line means: @@ -231,7 +228,7 @@ Installing dependencies from lock file No dependencies to install or update - - Installing rick-portal-gun (0.1.0) + - Installing the current project: rick-portal-gun (0.1.0) ```
@@ -250,7 +247,7 @@ $ which rick-portal-gun /home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.10/bin/rick-portal-gun // Try it -$ rick-portal-gun +$ rick-portal-gun --help // You get all the standard help Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]... @@ -284,7 +281,6 @@ $ 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 ``` @@ -312,20 +308,26 @@ Now you can open another terminal and install that package from the file for you
```console -$ pip install --user /home/rock/code/rick-portal-gun/dist/rick_portal_gun-0.1.0-py3-none-any.whl +$ 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. +/// 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. - 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 ๐Ÿš€ +/// 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: @@ -347,14 +349,17 @@ Having it installed globally (and not in a single environment), you can now inst ```console $ rick-portal-gun --install-completion -zsh completion installed in /home/user/.zshrc. +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. +/// 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: @@ -398,8 +403,11 @@ Here we pass `pip` as the value for `-m`, so, Python will execute the module `pi 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. +/// 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` @@ -419,8 +427,7 @@ The file would live right beside `__init__.py`: โ”‚ โ”œโ”€โ”€ __main__.py โ”‚ โ””โ”€โ”€ main.py โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_rick_portal_gun.py + โ””โ”€โ”€ __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. @@ -437,7 +444,7 @@ Now, after installing your package, if you call it with `python -m` it will work
```console -$ python -m rick_portal_gun +$ python -m rick_portal_gun --help Usage: __main__.py [OPTIONS] COMMAND [ARGS]... @@ -456,8 +463,11 @@ Commands:
-!!! tip - Notice that you have to pass the importable version of the package name, so `rick_portal_gun` instead of `rick-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... ๐Ÿค” @@ -481,13 +491,16 @@ 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`. +/// 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 +$ python -m rick_portal_gun --help Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]... @@ -577,7 +590,6 @@ $ poetry publish --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 @@ -604,10 +616,10 @@ $ pip uninstall rick-portal-gun Found existing installation: rick-portal-gun 0.1.0 Uninstalling rick-portal-gun-0.1.0: Would remove: - /home/user/.local/bin/rick-portal-gun - /home/user/.local/lib/python3.10/site-packages/rick_portal_gun-0.1.0.dist-info/* - /home/user/.local/lib/python3.10/site-packages/rick_portal_gun/* -# Proceed (y/n)? $ y + /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 ``` @@ -622,11 +634,16 @@ $ pip install --user rick-portal-gun // Notice that it says "Downloading" ๐Ÿš€ Collecting rick-portal-gun - Downloading rick_portal_gun-0.1.0-py3-none-any.whl (1.8 kB) -Requirement already satisfied: typer[all]<0.0.12,>=0.0.11 in ./.local/lib/python3.10/site-packages (from rick-portal-gun) (0.0.11) -Requirement already satisfied: click<7.2.0,>=7.1.1 in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (7.1.1) -Requirement already satisfied: colorama; extra == "all" in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (0.4.3) -Requirement already satisfied: shellingham; extra == "all" in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (1.3.1) + 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 ``` @@ -664,8 +681,11 @@ You just have to pass it the module to import (`rick_portal_gun.main`) and it wi 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. +/// 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 @@ -686,14 +706,11 @@ rick-portal-gun = "rick_portal_gun.main:app" [tool.poetry.dependencies] python = "^3.10" -typer = {extras = ["all"], version = "^0.1.0"} - -[tool.poetry.dev-dependencies] -pytest = "^5.2" +typer = "^0.12.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" ``` And in the file `rick_portal_gun/__init__.py`: @@ -714,7 +731,6 @@ $ poetry publish --build 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 diff --git a/docs/tutorial/parameter-types/bool.md b/docs/tutorial/parameter-types/bool.md index 68ef638062..14adc6a6dd 100644 --- a/docs/tutorial/parameter-types/bool.md +++ b/docs/tutorial/parameter-types/bool.md @@ -1,3 +1,5 @@ +# 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. @@ -8,20 +10,27 @@ Let's say that we want a `--force` *CLI option* only, we want to discard `--no-f We can do that by specifying the exact name we want: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/bool/tutorial001_an.py!} +``` + +//// - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/bool/tutorial001_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/parameter_types/bool/tutorial001.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/bool/tutorial001.py!} - ``` +//// Now there's only a `--force` *CLI option*: @@ -69,20 +78,27 @@ We might want to instead have `--accept` and `--reject`. We can do that by passing a single `str` with the 2 names for the `bool` *CLI option* separated by `/`: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="7" - {!> ../docs_src/parameter_types/bool/tutorial002_an.py!} - ``` +```Python hl_lines="7" +{!> ../docs_src/parameter_types/bool/tutorial002_an.py!} +``` + +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="6" - {!> ../docs_src/parameter_types/bool/tutorial002.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="6" +{!> ../docs_src/parameter_types/bool/tutorial002.py!} +``` + +//// Check it: @@ -123,20 +139,27 @@ The same way, you can declare short versions of the names for these *CLI options For example, let's say we want `-f` for `--force` and `-F` for `--no-force`: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/bool/tutorial003_an.py!} - ``` +```Python hl_lines="5" +{!> ../docs_src/parameter_types/bool/tutorial003_an.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/bool/tutorial003.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/parameter_types/bool/tutorial003.py!} +``` + +//// Check it: @@ -172,25 +195,35 @@ If you want to (although it might not be a good idea), you can declare only *CLI To do that, use a space and a single `/` and pass the negative name after: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/bool/tutorial004_an.py!} +``` - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/bool/tutorial004_an.py!} - ``` +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/parameter_types/bool/tutorial004.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/bool/tutorial004.py!} - ``` +Have in mind that it's a string with a preceding space and then a `/`. -!!! tip - Have in mind that it's a string with a preceding space and then a `/`. +So, it's `" /-S"` not `"/-S"`. - So, it's `" /-S"` not `"/-S"`. +/// Check it: diff --git a/docs/tutorial/parameter-types/custom-types.md b/docs/tutorial/parameter-types/custom-types.md index fd0d395832..b048c85bc9 100644 --- a/docs/tutorial/parameter-types/custom-types.md +++ b/docs/tutorial/parameter-types/custom-types.md @@ -1,3 +1,5 @@ +# 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. @@ -11,20 +13,27 @@ There are two ways to achieve this: `typer.Argument` and `typer.Option` can create custom parameter types with a `parser` callable. -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="13-14 18-19" +{!> ../docs_src/parameter_types/custom_types/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="13-14 18-19" - {!> ../docs_src/parameter_types/custom_types/tutorial001_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="12-13 17-18" - {!> ../docs_src/parameter_types/custom_types/tutorial001.py!} - ``` +```Python hl_lines="12-13 17-18" +{!> ../docs_src/parameter_types/custom_types/tutorial001.py!} +``` + +//// The function (or callable) that you pass to the parameter `parser` will receive the input value as a string and should return the parsed value with your own custom type. @@ -32,17 +41,10 @@ The function (or callable) that you pass to the parameter `parser` will receive If you already have a Click Custom Type, you can use it in `typer.Argument()` and `typer.Option()` with the `click_type` parameter. -=== "Python 3.7+" - - ```Python hl_lines="14-18 22-25" - {!> ../docs_src/parameter_types/custom_types/tutorial002_an.py!} - ``` - -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ - !!! tip - Prefer to use the `Annotated` version if possible. +```Python hl_lines="14-18 22-25" +{!> ../docs_src/parameter_types/custom_types/tutorial002_an.py!} +``` - ```Python hl_lines="13-17 21-22" - {!> ../docs_src/parameter_types/custom_types/tutorial002.py!} - ``` +//// diff --git a/docs/tutorial/parameter-types/datetime.md b/docs/tutorial/parameter-types/datetime.md index 04a3eb71a8..84f3524eee 100644 --- a/docs/tutorial/parameter-types/datetime.md +++ b/docs/tutorial/parameter-types/datetime.md @@ -1,3 +1,5 @@ +# 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. @@ -58,23 +60,33 @@ For example, let's imagine that you want to accept an ISO formatted datetime, bu ...It's a crazy example, but let's say you also needed that strange format: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="11" +{!> ../docs_src/parameter_types/datetime/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="11" - {!> ../docs_src/parameter_types/datetime/tutorial002_an.py!} - ``` +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="8" +{!> ../docs_src/parameter_types/datetime/tutorial002.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="8" - {!> ../docs_src/parameter_types/datetime/tutorial002.py!} - ``` +Notice the last string in `formats`: `"%m/%d/%Y"`. -!!! tip - Notice the last string in `formats`: `"%m/%d/%Y"`. +/// Check it: diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md index b3b2c149f0..288eee5a92 100644 --- a/docs/tutorial/parameter-types/enum.md +++ b/docs/tutorial/parameter-types/enum.md @@ -1,13 +1,18 @@ +# 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`: ```Python hl_lines="1 6 7 8 9 12 13" {!../docs_src/parameter_types/enum/tutorial001.py!} ``` -!!! tip - Notice that the function parameter `network` will be an `Enum`, not a `str`. +/// 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`. +To get the `str` value in your function's code use `network.value`. + +/// Check it: @@ -51,20 +56,27 @@ Error: Invalid value for '--network': 'CONV' is not one of 'simple', 'conv', 'ls You can make an `Enum` (choice) *CLI parameter* be case-insensitive with the `case_sensitive` parameter: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="15" +{!> ../docs_src/parameter_types/enum/tutorial002_an.py!} +``` + +//// - ```Python hl_lines="15" - {!> ../docs_src/parameter_types/enum/tutorial002_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="13" - {!> ../docs_src/parameter_types/enum/tutorial002.py!} - ``` +/// + +```Python hl_lines="13" +{!> ../docs_src/parameter_types/enum/tutorial002.py!} +``` + +//// And then the values of the `Enum` will be checked no matter if lower case, upper case, or a mix: @@ -88,20 +100,27 @@ Training neural network of type: lstm A *CLI parameter* can also take a list of `Enum` values: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="14" +{!> ../docs_src/parameter_types/enum/tutorial003_an.py!} +``` + +//// - ```Python hl_lines="14" - {!> ../docs_src/parameter_types/enum/tutorial003_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="13" +{!> ../docs_src/parameter_types/enum/tutorial003.py!} +``` - ```Python hl_lines="13" - {!> ../docs_src/parameter_types/enum/tutorial003.py!} - ``` +//// This works just like any other parameter value taking a list of things: diff --git a/docs/tutorial/parameter-types/file.md b/docs/tutorial/parameter-types/file.md index 3bb1cf51ce..274962cf07 100644 --- a/docs/tutorial/parameter-types/file.md +++ b/docs/tutorial/parameter-types/file.md @@ -1,9 +1,14 @@ +# 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`. +/// tip + +In most of the cases you are probably fine just using `Path`. + +You can read and write data with `Path` the same way. - 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. @@ -42,20 +47,27 @@ content = b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o" You will get all the correct editor support, attributes, methods, etc for the file-like object:` -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/file/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/file/tutorial001_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/file/tutorial001.py!} - ``` +```Python hl_lines="4" +{!> ../docs_src/parameter_types/file/tutorial001.py!} +``` + +//// Check it: @@ -82,20 +94,27 @@ Config line: some more settings For writing text, you can use `typer.FileTextWrite`: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5-6" +{!> ../docs_src/parameter_types/file/tutorial002_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="5-6" - {!> ../docs_src/parameter_types/file/tutorial002_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// + +```Python hl_lines="4-5" +{!> ../docs_src/parameter_types/file/tutorial002.py!} +``` - ```Python hl_lines="4-5" - {!> ../docs_src/parameter_types/file/tutorial002.py!} - ``` +//// This would be for writing human text, like: @@ -123,10 +142,13 @@ Some config written by the app
-!!! info "Technical Details" - `typer.FileTextWrite` is a just a convenience class. +/// info | Technical Details - It's the same as using `typer.FileText` and setting `mode="w"`. You will learn about `mode` later below. +`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` @@ -136,20 +158,27 @@ You will receive `bytes` from it. It's useful for reading binary files like images: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/file/tutorial003_an.py!} +``` + +//// - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/file/tutorial003_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/file/tutorial003.py!} - ``` +/// + +```Python hl_lines="4" +{!> ../docs_src/parameter_types/file/tutorial003.py!} +``` + +//// Check it: @@ -178,20 +207,27 @@ Have in mind that you have to pass `bytes` to its `.write()` method, not `str`. If you have a `str`, you have to encode it first to get `bytes`. -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/file/tutorial004_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/file/tutorial004_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/file/tutorial004.py!} - ``` +```Python hl_lines="4" +{!> ../docs_src/parameter_types/file/tutorial004.py!} +``` + +////
@@ -241,23 +277,33 @@ You can override the `mode` from the defaults above. For example, you could use `mode="a"` to write "appending" to the same file: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/file/tutorial005_an.py!} - ``` +```Python hl_lines="5" +{!> ../docs_src/parameter_types/file/tutorial005_an.py!} +``` -=== "Python 3.7+ non-Annotated" +//// - !!! tip - Prefer to use the `Annotated` version if possible. +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/file/tutorial005.py!} - ``` +/// tip -!!! tip - As you are manually setting `mode="a"`, you can use `typer.FileText` or `typer.FileTextWrite`, both will work. +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/parameter_types/file/tutorial005.py!} +``` + +//// + +/// tip + +As you are manually setting `mode="a"`, you can use `typer.FileText` or `typer.FileTextWrite`, both will work. + +/// Check it: @@ -289,10 +335,13 @@ This is a single line ## About the different types -!!! info - These are technical details about why the different types/classes provided by **Typer**. +/// 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. - 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. diff --git a/docs/tutorial/parameter-types/index.md b/docs/tutorial/parameter-types/index.md index ed3b7ad186..f7889944f0 100644 --- a/docs/tutorial/parameter-types/index.md +++ b/docs/tutorial/parameter-types/index.md @@ -1,3 +1,5 @@ +# 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 @@ -62,5 +64,8 @@ Error: Invalid value for '--age': '15.3' is not a valid integer 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. +/// info | Technical Details + +All the types you will see in the next sections are handled underneath by Click's Parameter Types. + +/// diff --git a/docs/tutorial/parameter-types/number.md b/docs/tutorial/parameter-types/number.md index 001e4fccbb..062fa7bce3 100644 --- a/docs/tutorial/parameter-types/number.md +++ b/docs/tutorial/parameter-types/number.md @@ -1,19 +1,28 @@ +# Number + You can define numeric validations with `max` and `min` values for `int` and `float` *CLI parameters*: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="6-8" +{!> ../docs_src/parameter_types/number/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="6-8" - {!> ../docs_src/parameter_types/number/tutorial001_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="5-7" - {!> ../docs_src/parameter_types/number/tutorial001.py!} - ``` +```Python hl_lines="5-7" +{!> ../docs_src/parameter_types/number/tutorial001.py!} +``` + +//// *CLI arguments* and *CLI options* can both use these validations. @@ -84,20 +93,27 @@ You might want to, instead of showing an error, use the closest minimum or maxim You can do it with the `clamp` parameter: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="6-8" +{!> ../docs_src/parameter_types/number/tutorial002_an.py!} +``` + +//// - ```Python hl_lines="6-8" - {!> ../docs_src/parameter_types/number/tutorial002_an.py!} - ``` +//// tab | Python 3.7+ non-Annotated -=== "Python 3.7+ non-Annotated" +/// tip - !!! tip - Prefer to use the `Annotated` version if possible. +Prefer to use the `Annotated` version if possible. - ```Python hl_lines="5-7" - {!> ../docs_src/parameter_types/number/tutorial002.py!} - ``` +/// + +```Python hl_lines="5-7" +{!> ../docs_src/parameter_types/number/tutorial002.py!} +``` + +//// And then, when you pass data that is out of the valid range, it will be "clamped", the closest valid value will be used: @@ -126,20 +142,27 @@ ID is 5 You can make a *CLI option* work as a counter with the `counter` parameter: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/number/tutorial003_an.py!} +``` - ```Python hl_lines="5" - {!> ../docs_src/parameter_types/number/tutorial003_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip + +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="4" +{!> ../docs_src/parameter_types/number/tutorial003.py!} +``` - ```Python hl_lines="4" - {!> ../docs_src/parameter_types/number/tutorial003.py!} - ``` +//// It means that the *CLI option* will be like a boolean flag, e.g. `--verbose`. diff --git a/docs/tutorial/parameter-types/path.md b/docs/tutorial/parameter-types/path.md index f6617db4d8..0268630bcd 100644 --- a/docs/tutorial/parameter-types/path.md +++ b/docs/tutorial/parameter-types/path.md @@ -1,21 +1,30 @@ +# 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: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="1 8" +{!> ../docs_src/parameter_types/path/tutorial001_an.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated - ```Python hl_lines="1 8" - {!> ../docs_src/parameter_types/path/tutorial001_an.py!} - ``` +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="1 7" - {!> ../docs_src/parameter_types/path/tutorial001.py!} - ``` +```Python hl_lines="1 7" +{!> ../docs_src/parameter_types/path/tutorial001.py!} +``` + +//// And again, as you receive a standard Python `Path` object the same as the type annotation, your editor will give you autocompletion for all its attributes and methods. @@ -62,28 +71,41 @@ You can perform several validations for `Path` *CLI parameters*: * `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. +/// 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 -!!! tip - All these parameters come directly from Click. +All these parameters come directly from Click. + +/// For example: -=== "Python 3.7+" +//// tab | Python 3.7+ + +```Python hl_lines="11-16" +{!> ../docs_src/parameter_types/path/tutorial002_an.py!} +``` - ```Python hl_lines="11-16" - {!> ../docs_src/parameter_types/path/tutorial002_an.py!} - ``` +//// -=== "Python 3.7+ non-Annotated" +//// tab | Python 3.7+ non-Annotated - !!! tip - Prefer to use the `Annotated` version if possible. +/// tip - ```Python hl_lines="9-14" - {!> ../docs_src/parameter_types/path/tutorial002.py!} - ``` +Prefer to use the `Annotated` version if possible. + +/// + +```Python hl_lines="9-14" +{!> ../docs_src/parameter_types/path/tutorial002.py!} +``` + +//// Check it: @@ -118,10 +140,13 @@ 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. +/// 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. - 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. diff --git a/docs/tutorial/parameter-types/uuid.md b/docs/tutorial/parameter-types/uuid.md index 8b96f6ad45..afe5986377 100644 --- a/docs/tutorial/parameter-types/uuid.md +++ b/docs/tutorial/parameter-types/uuid.md @@ -1,21 +1,24 @@ -!!! info - A UUID is a "Universally Unique Identifier". +# UUID - It's a standard format for identifiers, like passport numbers, but for anything, not just people in countries. +/// info - They look like this: +A UUID is a "Universally Unique Identifier". - ``` - d48edaa6-871a-4082-a196-4daab372d4a1 - ``` +It's a standard format for identifiers, like passport numbers, but for anything, not just people in countries. - 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. +They look like this: - 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. +``` +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. - This wouldn't be true if you just used `int`s as identifiers, as most databases do. +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: diff --git a/docs/tutorial/printing.md b/docs/tutorial/printing.md index 058998473e..75150d329d 100644 --- a/docs/tutorial/printing.md +++ b/docs/tutorial/printing.md @@ -1,3 +1,5 @@ +# Printing and Colors + You can use the normal `print()` to show information on the screen: ```Python hl_lines="5" @@ -136,19 +138,25 @@ And there's another "**virtual file**" called "**standard error**" that is norma 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. +/// 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`. - In PowerShell, to see "standard error" you would have to check the variable `$Error`. +But it will work normally in Bash, Zsh, and Fish. - 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". +/// tip + +`stderr` is short for "standard error". + +/// Using `stderr=True` tells **Rich** that the output should be shown in "standard error". @@ -182,10 +190,13 @@ But understanding that will come handy in the future, for example for autocomple ## Typer Echo -!!! warning - In most of the cases, for displaying advanced information, it is recommended to use Rich. +/// warning - You can probably skip the rest of this section. ๐ŸŽ‰๐Ÿ˜Ž +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. @@ -203,18 +214,27 @@ If you have some `bytes` objects, you would probably want to decode them intenti 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. +/// 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. +/// 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`. +So, a colored text is still just a `str`. -!!! tip - Again, you are much better off using Rich for this. ๐Ÿ˜Ž +/// + +/// 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()`: @@ -222,10 +242,13 @@ You can create colored strings to output to the terminal with `typer.style()`, t {!../docs_src/printing/tutorial005.py!} ``` -!!! tip - The parameters `fg` and `bg` receive strings with the color names for the "**f**ore**g**round" and "**b**ack**g**round" colors. You could simply pass `fg="green"` and `bg="red"`. +/// 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. - But **Typer** provides them all as variables like `typer.colors.GREEN` just so you can use autocompletion while selecting them. +/// Check it: @@ -247,13 +270,19 @@ You can pass these function arguments to `typer.style()`: * `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()` +/// 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. ๐Ÿ˜Ž +/// 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()`: diff --git a/docs/tutorial/progressbar.md b/docs/tutorial/progressbar.md index 7b7bc166ab..c56cf50b29 100644 --- a/docs/tutorial/progressbar.md +++ b/docs/tutorial/progressbar.md @@ -1,3 +1,5 @@ +# Progress Bar + If you are executing an operation that can take some time, you can inform it to the user. ๐Ÿค“ ## Progress Bar @@ -71,19 +73,27 @@ You can learn more about it in the Click's docs. +/// 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. ๐Ÿ˜Ž +/// tip + +Remember, you are much better off using Rich for this. ๐Ÿ˜Ž + +/// You can use `typer.progressbar()` with a `with` statement, as in: @@ -119,13 +129,19 @@ with typer.progressbar(users) as 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. +/// 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()`. +/// info + +This is mostly useful for operations that take some time. + +In the example above we are faking it with `time.sleep()`. + +/// Check it: @@ -143,8 +159,11 @@ Processed 100 things. ### Setting a Progress Bar `length` -!!! tip - Remember, you are much better off using Rich for this. ๐Ÿ˜Ž +/// 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). @@ -192,8 +211,11 @@ 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. ๐Ÿ˜Ž +/// tip + +Remember, you are much better off using Rich for this. ๐Ÿ˜Ž + +/// You can also set a `label`: @@ -224,5 +246,5 @@ Check it:
python main.py -Processed 100 things in batches. +Processed 1000 things in batches.
diff --git a/docs/tutorial/prompt.md b/docs/tutorial/prompt.md index ef3c3e0a12..3203edbfa1 100644 --- a/docs/tutorial/prompt.md +++ b/docs/tutorial/prompt.md @@ -1,3 +1,5 @@ +# 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()`: diff --git a/docs/tutorial/subcommands/add-typer.md b/docs/tutorial/subcommands/add-typer.md index 0e867558cc..55439acf29 100644 --- a/docs/tutorial/subcommands/add-typer.md +++ b/docs/tutorial/subcommands/add-typer.md @@ -1,3 +1,5 @@ +# Add Typer + We'll start with the core idea. To add a `typer.Typer()` app inside of another. @@ -127,8 +129,11 @@ Selling item: Vase
-!!! tip - Notice that we are still calling `$ python main.py` but now we are using the command `items`. +/// 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: diff --git a/docs/tutorial/subcommands/callback-override.md b/docs/tutorial/subcommands/callback-override.md index 6a2683e9e3..fa82d4ee3b 100644 --- a/docs/tutorial/subcommands/callback-override.md +++ b/docs/tutorial/subcommands/callback-override.md @@ -1,3 +1,5 @@ +# 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. diff --git a/docs/tutorial/subcommands/index.md b/docs/tutorial/subcommands/index.md index 5a8df35958..b20e21b578 100644 --- a/docs/tutorial/subcommands/index.md +++ b/docs/tutorial/subcommands/index.md @@ -1,3 +1,5 @@ +# 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. @@ -21,7 +23,7 @@ origin git@github.com:yourusername/typer.git (fetch) origin git@github.com:yourusername/typer.git (push) // git remote add takes 2 CLI arguments, a name and URL -$ git remote add upstream https://github.com/tiangolo/typer.git +$ git remote add upstream https://github.com/fastapi/typer.git // Doesn't output anything, but now you have another remote repository called upstream @@ -30,8 +32,8 @@ $ git remote -v origin git@github.com:yourusername/typer.git (fetch) origin git@github.com:yourusername/typer.git (push) -upstream https://github.com/tiangolo/typer.git (fetch) -upstream https://github.com/tiangolo/typer.git (push) +upstream https://github.com/fastapi/typer.git (fetch) +upstream https://github.com/fastapi/typer.git (push) ``` diff --git a/docs/tutorial/subcommands/name-and-help.md b/docs/tutorial/subcommands/name-and-help.md index 474dc91ae9..e6d96caa54 100644 --- a/docs/tutorial/subcommands/name-and-help.md +++ b/docs/tutorial/subcommands/name-and-help.md @@ -1,3 +1,5 @@ +# 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`: @@ -52,10 +54,13 @@ We can set the `name` and `help` in several places, each one taking precedence o 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. +/// 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. +But those are documented later in another section. + +/// ## Inferring name and help from callback @@ -296,8 +301,11 @@ But if you set the name and help text explicitly, that has a higher priority tha Let's now see the places where you can set the command name and help text, from lowest priority to highest. -!!! tip - Setting the name and help text explicitly always has a higher precedence than inferring from a callback function. +/// tip + +Setting the name and help text explicitly always has a higher precedence than inferring from a callback function. + +/// ### Name and help in `typer.Typer()` @@ -311,8 +319,11 @@ You can set it when creating a new `typer.Typer()`: {!../docs_src/subcommands/name_help/tutorial006.py!} ``` -!!! info - The rest of the callbacks and overrides are there only to show you that they don't affect the name and help text when you set it explicitly. +/// info + +The rest of the callbacks and overrides are there only to show you that they don't affect the name and help text when you set it explicitly. + +/// We set an explicit name `exp-users`, and an explicit help `Explicit help.`. diff --git a/docs/tutorial/subcommands/nested-subcommands.md b/docs/tutorial/subcommands/nested-subcommands.md index a3fbe6c827..c991faa9da 100644 --- a/docs/tutorial/subcommands/nested-subcommands.md +++ b/docs/tutorial/subcommands/nested-subcommands.md @@ -1,3 +1,5 @@ +# 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`. @@ -278,10 +280,13 @@ Here are all the files if you want to review/copy them: {!../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*. +/// 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. - But for your final application, only `main.py` would need it. +/// ## Recap @@ -289,7 +294,10 @@ That's it, you can just add **Typer** applications one inside another as much as 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. +/// tip + +Auto completion helps a lot, specially with complex programs. + +Check the docs about adding auto completion to your *CLI apps*. - Check the docs about adding auto completion to your *CLI apps*. +/// diff --git a/docs/tutorial/subcommands/single-file.md b/docs/tutorial/subcommands/single-file.md index 88abd9578c..50645267cd 100644 --- a/docs/tutorial/subcommands/single-file.md +++ b/docs/tutorial/subcommands/single-file.md @@ -1,3 +1,5 @@ +# 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: diff --git a/docs/tutorial/terminating.md b/docs/tutorial/terminating.md index 94f6d239e0..a8d0b49fa3 100644 --- a/docs/tutorial/terminating.md +++ b/docs/tutorial/terminating.md @@ -1,3 +1,5 @@ +# 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. @@ -40,12 +42,15 @@ The user already exists -!!! tip - Even though you are raising an exception, it doesn't necessarily mean there's an error. +/// 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. - 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. - But then **Typer** (actually Click) catches it and just terminates the program normally. +/// ## Exit with an error @@ -86,8 +91,11 @@ $ echo $? -!!! tip - The error code might be used by other programs (for example a Bash script) that execute your CLI program. +/// tip + +The error code might be used by other programs (for example a Bash script) that execute your CLI program. + +/// ## Abort diff --git a/docs/tutorial/testing.md b/docs/tutorial/testing.md index 2df3460ba4..2fc7e54f09 100644 --- a/docs/tutorial/testing.md +++ b/docs/tutorial/testing.md @@ -1,3 +1,5 @@ +# Testing + Testing **Typer** applications is very easy with pytest. Let's say you have an application `app/main.py` with: @@ -37,8 +39,11 @@ This runner is what will "invoke" or "call" your command line application. {!../docs_src/testing/app01/test_main.py!} ``` -!!! tip - It's important that the name of the file starts with `test_`, that way pytest will be able to detect it and use it automatically. +/// 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 @@ -54,8 +59,11 @@ The second parameter is a `list` of `str`, with all the text you would pass in t {!../docs_src/testing/app01/test_main.py!} ``` -!!! tip - The name of the function has to start with `test_`, that way pytest can detect it and use it automatically. +/// tip + +The name of the function has to start with `test_`, that way pytest can detect it and use it automatically. + +/// ### Check the result @@ -69,11 +77,17 @@ Here we are checking that the exit code is 0, as it is for programs that exit wi Then we check that the text printed to "standard output" contains the text that our CLI program prints. -!!! tip - You could also check `result.stderr` for "standard error" independently from "standard output" if your `CliRunner` instance is created with the `mix_stderr=False` argument. +/// tip + +You could also check `result.stderr` for "standard error" independently from "standard output" if your `CliRunner` instance is created with the `mix_stderr=False` argument. + +/// + +/// info + +If you need a refresher about what is "standard output" and "standard error" check the section in [Printing and Colors: "Standard Output" and "Standard Error"](printing.md#standard-output-and-standard-error){.internal-link target=_blank}. -!!! 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` @@ -103,20 +117,27 @@ test_main.py . If you have a CLI with prompts, like: -=== "Python 3.7+" +//// tab | Python 3.7+ - ```Python hl_lines="8" - {!> ../docs_src/testing/app02_an/main.py!} - ``` +```Python hl_lines="8" +{!> ../docs_src/testing/app02_an/main.py!} +``` + +//// + +//// tab | Python 3.7+ non-Annotated + +/// tip -=== "Python 3.7+ non-Annotated" +Prefer to use the `Annotated` version if possible. - !!! tip - Prefer to use the `Annotated` version if possible. +/// - ```Python hl_lines="7" - {!> ../docs_src/testing/app02/main.py!} - ``` +```Python hl_lines="7" +{!> ../docs_src/testing/app02/main.py!} +``` + +//// That you would use like: @@ -136,8 +157,11 @@ 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}. +/// 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"`. diff --git a/docs/tutorial/typer-command.md b/docs/tutorial/typer-command.md index 021dee002d..004415a6ae 100644 --- a/docs/tutorial/typer-command.md +++ b/docs/tutorial/typer-command.md @@ -261,12 +261,15 @@ $ 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: +/// tip - ```console - $ python -m typer some_script.py utils docs - ``` +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**: diff --git a/docs/tutorial/using-click.md b/docs/tutorial/using-click.md index d4d32057e6..950531ff7f 100644 --- a/docs/tutorial/using-click.md +++ b/docs/tutorial/using-click.md @@ -1,7 +1,12 @@ -!!! warning - This is a more advanced topic, if you are starting with **Typer**, feel free to skip it. +# Using Click - It will be mostly useful for people that already work with Click and have questions around it. +/// 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. @@ -47,8 +52,11 @@ For example: 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}. +/// 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. @@ -66,8 +74,11 @@ But **Typer** creates a Click `Group` object if your app has any of: * 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}. +/// 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** @@ -181,7 +192,10 @@ Most of the functionality provided by decorators in Click has an alternative way 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} +/// 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. diff --git a/docs/virtual-environments.md b/docs/virtual-environments.md new file mode 100644 index 0000000000..d4db3a77c3 --- /dev/null +++ b/docs/virtual-environments.md @@ -0,0 +1,844 @@ +# 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**. ๐Ÿ˜Ž diff --git a/docs_src/commands/index/tutorial004.py b/docs_src/commands/index/tutorial004.py new file mode 100644 index 0000000000..83419b695f --- /dev/null +++ b/docs_src/commands/index/tutorial004.py @@ -0,0 +1,17 @@ +import 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() diff --git a/docs_src/progressbar/tutorial006.py b/docs_src/progressbar/tutorial006.py index ac94a3ed3e..d83b0da7cb 100644 --- a/docs_src/progressbar/tutorial006.py +++ b/docs_src/progressbar/tutorial006.py @@ -9,6 +9,8 @@ def main(): 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.") diff --git a/docs_src/using_click/tutorial001.py b/docs_src/using_click/tutorial001.py index 6fbf00649a..b3260c6494 100644 --- a/docs_src/using_click/tutorial001.py +++ b/docs_src/using_click/tutorial001.py @@ -7,7 +7,7 @@ def hello(count, name): """Simple program that greets NAME for a total of COUNT times.""" for x in range(count): - click.echo("Hello %s!" % name) + click.echo(f"Hello {name}!") if __name__ == "__main__": diff --git a/docs_src/using_click/tutorial003.py b/docs_src/using_click/tutorial003.py index a8f99e4623..5b3967c015 100644 --- a/docs_src/using_click/tutorial003.py +++ b/docs_src/using_click/tutorial003.py @@ -23,7 +23,7 @@ def callback(): @click.option("--name", prompt="Your name", help="The person to greet.") def hello(name): """Simple program that greets NAME for a total of COUNT times.""" - click.echo("Hello %s!" % name) + click.echo(f"Hello {name}!") typer_click_object = typer.main.get_command(app) diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml index bb475642cd..80d2d4b640 100644 --- a/mkdocs.insiders.yml +++ b/mkdocs.insiders.yml @@ -1,4 +1,7 @@ -INHERIT: mkdocs.yml plugins: - social: typeset: +markdown_extensions: + material.extensions.preview: + targets: + include: + - "*" diff --git a/mkdocs.maybe-insiders.yml b/mkdocs.maybe-insiders.yml new file mode 100644 index 0000000000..07aefaaa99 --- /dev/null +++ b/mkdocs.maybe-insiders.yml @@ -0,0 +1,6 @@ +# 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] diff --git a/mkdocs.no-insiders.yml b/mkdocs.no-insiders.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mkdocs.yml b/mkdocs.yml index 77024d83bb..ead95508a0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,3 +1,4 @@ +INHERIT: ./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/ @@ -6,120 +7,213 @@ 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-white.svg + logo: img/icon.svg favicon: img/favicon.png - -repo_name: tiangolo/typer -repo_url: https://github.com/tiangolo/typer -edit_uri: "" + language: en +repo_name: fastapi/typer +repo_url: https://github.com/fastapi/typer plugins: - search: null + # 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: features.md + - features.md - Tutorial - User Guide: - - Tutorial - User Guide - Intro: tutorial/index.md - - First Steps: tutorial/first-steps.md - - Printing and Colors: tutorial/printing.md - - Terminating: tutorial/terminating.md + - tutorial/index.md + - environment-variables.md + - virtual-environments.md + - tutorial/install.md + - tutorial/first-steps.md + - tutorial/printing.md + - tutorial/terminating.md - CLI Arguments: - - CLI Arguments Intro: tutorial/arguments/index.md - - Optional CLI Arguments: tutorial/arguments/optional.md - - CLI Arguments with Default: tutorial/arguments/default.md - - CLI Arguments with Help: tutorial/arguments/help.md - - CLI Arguments with Environment Variables: tutorial/arguments/envvar.md - - Other uses: tutorial/arguments/other-uses.md + - 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: - - CLI Options Intro: tutorial/options/index.md - - CLI Options with Help: tutorial/options/help.md - - Required CLI Options: tutorial/options/required.md - - CLI Option Prompt: tutorial/options/prompt.md - - Password CLI Option and Confirmation Prompt: tutorial/options/password.md - - CLI Option Name: tutorial/options/name.md - - CLI Option Callback and Context: tutorial/options/callback-and-context.md - - Version CLI Option, is_eager: tutorial/options/version.md + - 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: - - Commands Intro: tutorial/commands/index.md - - Command CLI Arguments: tutorial/commands/arguments.md - - Command CLI Options: tutorial/commands/options.md - - Command Help: tutorial/commands/help.md - - Custom Command Name: tutorial/commands/name.md - - Typer Callback: tutorial/commands/callback.md - - One or Multiple Commands: tutorial/commands/one-or-multiple.md - - Using the Context: tutorial/commands/context.md - - CLI Option autocompletion: tutorial/options-autocompletion.md + - 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: - - CLI Parameter Types Intro: tutorial/parameter-types/index.md - - Number: tutorial/parameter-types/number.md - - Boolean CLI Options: tutorial/parameter-types/bool.md - - UUID: tutorial/parameter-types/uuid.md - - DateTime: tutorial/parameter-types/datetime.md - - Enum - Choices: tutorial/parameter-types/enum.md - - Path: tutorial/parameter-types/path.md - - File: tutorial/parameter-types/file.md - - Custom Types: tutorial/parameter-types/custom-types.md + - 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: - - SubCommands - Command Groups - Intro: tutorial/subcommands/index.md - - Add Typer: tutorial/subcommands/add-typer.md - - SubCommands in a Single File: tutorial/subcommands/single-file.md - - Nested SubCommands: tutorial/subcommands/nested-subcommands.md - - Sub-Typer Callback Override: tutorial/subcommands/callback-override.md - - SubCommand Name and Help: tutorial/subcommands/name-and-help.md + - 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: - - Multiple Values Intro: tutorial/multiple-values/index.md - - Multiple CLI Options: tutorial/multiple-values/multiple-options.md - - CLI Options with Multiple Values: tutorial/multiple-values/options-with-multiple-values.md - - CLI Arguments with Multiple Values: tutorial/multiple-values/arguments-with-multiple-values.md - - Ask with Prompt: tutorial/prompt.md - - Progress Bar: tutorial/progressbar.md - - CLI Application Directory: tutorial/app-dir.md - - Launching Applications: tutorial/launch.md - - Testing: tutorial/testing.md - - Using Click: tutorial/using-click.md - - Building a Package: tutorial/package.md + - tutorial/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/typer-command.md - - Alternatives, Inspiration and Comparisons: alternatives.md - - Help Typer - Get Help: help-typer.md - - Development - Contributing: contributing.md - - Release Notes: release-notes.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: - - toc: - permalink: true - - markdown.extensions.codehilite: - guess_lang: false - - admonition - - codehilite - - extra - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' - - pymdownx.tabbed: - alternate_style: true - - mdx_include: - base_path: docs - # Enable while writing docs to simplify highlighting - # - pymdownx.highlight: - # linenums: true + # 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: + base_path: docs 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/tiangolo/typer + link: https://github.com/fastapi/typer - icon: fontawesome/brands/twitter link: https://twitter.com/tiangolo - icon: fontawesome/brands/linkedin @@ -136,6 +230,8 @@ extra_css: - css/custom.css extra_javascript: - - https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js - js/termynal.js - js/custom.js + +hooks: + - scripts/mkdocs_hooks.py diff --git a/pyproject.toml b/pyproject.toml index c69e7c190c..b64b18d911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,11 @@ dependencies = [ ] readme = "README.md" [project.urls] -Documentation = "https://typer.tiangolo.com/" -homepage = "https://github.com/tiangolo/typer" +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 = [ diff --git a/requirements-docs-insiders.txt b/requirements-docs-insiders.txt new file mode 100644 index 0000000000..d8d3c37a9f --- /dev/null +++ b/requirements-docs-insiders.txt @@ -0,0 +1,3 @@ +git+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 diff --git a/requirements-docs.txt b/requirements-docs.txt index bad2afad2a..5635b9558e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,17 +1,18 @@ -e . -mkdocs-material==9.4.7 +mkdocs-material==9.5.33 mdx-include >=1.4.1,<2.0.0 -mkdocs-markdownextradata-plugin >=0.1.7,<0.3.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 +# jieba==0.42.1 # For image processing by Material for MkDocs -pillow==10.1.0 +pillow==10.4.0 # For image processing by Material for MkDocs -cairosvg==2.7.0 -mkdocstrings[python]==0.23.0 -griffe-typingdoc==0.2.2 +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==23.3.0 +# black==24.3.0 +mkdocs-macros-plugin==1.0.5 diff --git a/requirements-github-actions.txt b/requirements-github-actions.txt new file mode 100644 index 0000000000..559dc06fb2 --- /dev/null +++ b/requirements-github-actions.txt @@ -0,0 +1,4 @@ +PyGithub>=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.28.0 diff --git a/requirements-tests.txt b/requirements-tests.txt index 510dd17ab6..ac6377c10d 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,10 +1,10 @@ -e . -pytest >=4.4.0,<8.0.0 -pytest-cov >=2.10.0,<5.0.0 +pytest >=4.4.0,<9.0.0 +pytest-cov >=2.10.0,<6.0.0 coverage[toml] >=6.2,<8.0 pytest-xdist >=1.32.0,<4.0.0 -pytest-sugar >=0.9.4,<0.10.0 +pytest-sugar >=0.9.4,<1.1.0 mypy ==1.4.1 ruff ==0.2.0 # Needed explicitly by typer-slim diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh deleted file mode 100755 index 4f4ae2f74e..0000000000 --- a/scripts/build-docs.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -python -m mkdocs build - -cp ./docs/index.md ./README.md diff --git a/scripts/clean.sh b/scripts/clean.sh deleted file mode 100755 index d5a4b790ae..0000000000 --- a/scripts/clean.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -e - -if [ -d 'dist' ] ; then - rm -r dist -fi -if [ -d 'site' ] ; then - rm -r site -fi diff --git a/scripts/deploy_docs_status.py b/scripts/deploy_docs_status.py new file mode 100644 index 0000000000..8cef2f7581 --- /dev/null +++ b/scripts/deploy_docs_status.py @@ -0,0 +1,89 @@ +import logging +import re + +from github import Github +from pydantic import SecretStr +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + github_repository: str + github_token: SecretStr + deploy_url: str | None = None + commit_sha: str + run_id: int + is_done: bool = False + + +def main(): + 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[str] = [] + for f in docs_files: + match = re.match(r"docs/(.*)", f.filename) + assert match + path = match.group(1) + if path.endswith("index.md"): + path = path.replace("index.md", "") + else: + path = path.replace(".md", "/") + link = f"{deploy_url}/{path}" + links.append(link) + links.sort() + + message = f"๐Ÿ“ Docs preview for commit {settings.commit_sha} at: {deploy_url}" + + if links: + message += "\n\n### Modified Pages\n\n" + message += "\n".join([f"* {link}" for link in links]) + + print(message) + use_pr.as_issue().create_comment(message) + + logging.info("Finished") + + +if __name__ == "__main__": + main() diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile new file mode 100644 index 0000000000..b158e2db04 --- /dev/null +++ b/scripts/docker/Dockerfile @@ -0,0 +1,28 @@ +FROM 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 diff --git a/scripts/docker/compose.yaml b/scripts/docker/compose.yaml new file mode 100644 index 0000000000..efe17f9efd --- /dev/null +++ b/scripts/docker/compose.yaml @@ -0,0 +1,8 @@ +services: + typer: + build: + context: ../../ + dockerfile: scripts/docker/Dockerfile + volumes: + - ../../:/code + command: sleep infinity diff --git a/scripts/docs-live.sh b/scripts/docs-live.sh deleted file mode 100755 index 5342a9e59f..0000000000 --- a/scripts/docs-live.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -e - -mkdocs serve --dev-addr 127.0.0.1:8008 diff --git a/scripts/docs.py b/scripts/docs.py new file mode 100644 index 0000000000..acd3b30fff --- /dev/null +++ b/scripts/docs.py @@ -0,0 +1,135 @@ +import 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 (