diff --git a/.coveragerc b/.coveragerc
index 18cdff1942..54df9e71c1 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -6,3 +6,4 @@ source =
docs_src
parallel = True
+context = '${CONTEXT}'
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index 2b7f2d3c09..faa58a6d08 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -8,9 +8,9 @@ body:
Thanks for your interest in Typer! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
-
+
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
-
+
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
@@ -18,7 +18,7 @@ body:
If more Typer users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
-
+
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
@@ -46,7 +46,7 @@ body:
label: Commit to Help
description: |
After submitting this, I commit to one of:
-
+
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml
index e947e0931c..62ea9eb30d 100644
--- a/.github/ISSUE_TEMPLATE/question.yml
+++ b/.github/ISSUE_TEMPLATE/question.yml
@@ -8,9 +8,9 @@ body:
Thanks for your interest in Typer! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
-
+
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
-
+
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
@@ -18,7 +18,7 @@ body:
If more Typer users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
-
+
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
@@ -46,7 +46,7 @@ body:
label: Commit to Help
description: |
After submitting this, I commit to one of:
-
+
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
diff --git a/.github/actions/comment-docs-preview-in-pr/app/main.py b/.github/actions/comment-docs-preview-in-pr/app/main.py
index 3b10e0ee08..c9fb7cbbef 100644
--- a/.github/actions/comment-docs-preview-in-pr/app/main.py
+++ b/.github/actions/comment-docs-preview-in-pr/app/main.py
@@ -48,9 +48,7 @@ class PartialGithubEvent(BaseModel):
use_pr = pr
break
if not use_pr:
- logging.error(
- f"No PR found for hash: {event.workflow_run.head_commit.id}"
- )
+ 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()}"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b38df29f46..cd972a0ba4 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,6 +1,16 @@
version: 2
updates:
+ # GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ commit-message:
+ prefix: ⬆
+ # Python
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
+ commit-message:
+ prefix: ⬆
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index 1fd1bae68d..569b4e34f6 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
inputs:
debug_enabled:
- description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
+ description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
jobs:
@@ -19,9 +19,9 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: "3.7"
# Allow debugging with tmate
@@ -30,11 +30,11 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
with:
limit-access-to-actor: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
- key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-docs
+ key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v01
- name: Install Flit
if: steps.cache.outputs.cache-hit != 'true'
run: python3.7 -m pip install flit
@@ -42,26 +42,26 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: python3.7 -m flit install --extras doc
- name: Install Material for MkDocs Insiders
- if: github.event.pull_request.head.repo.fork == false && steps.cache.outputs.cache-hit != 'true'
+ if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true'
run: python3.7 -m pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
with:
- key: mkdocs-cards-${{ github.ref }}
+ key: mkdocs-cards-${{ github.ref }}-v1
path: .cache
- name: Build Docs
- if: github.event.pull_request.head.repo.fork == true
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
run: python3.7 -m mkdocs build
- name: Build Docs with Insiders
- if: github.event.pull_request.head.repo.fork == false
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
run: python3.7 -m mkdocs build --config-file mkdocs.insiders.yml
- name: Zip docs
run: bash ./scripts/zip-docs.sh
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v3
with:
name: docs-zip
- path: ./docs.zip
+ path: ./site/docs.zip
- name: Deploy to Netlify
- uses: nwtgck/actions-netlify@v1.1.5
+ uses: nwtgck/actions-netlify@v2.0.0
with:
publish-dir: './site'
production-branch: master
diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml
index 7b074fd1a7..69fce64d4a 100644
--- a/.github/workflows/issue-manager.yml
+++ b/.github/workflows/issue-manager.yml
@@ -15,7 +15,7 @@ jobs:
issue-manager:
runs-on: ubuntu-latest
steps:
- - uses: tiangolo/issue-manager@0.2.0
+ - uses: tiangolo/issue-manager@0.4.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml
index e22efd8156..6a618fdc97 100644
--- a/.github/workflows/latest-changes.yml
+++ b/.github/workflows/latest-changes.yml
@@ -12,7 +12,7 @@ on:
description: PR number
required: true
debug_enabled:
- description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
+ description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
@@ -20,7 +20,7 @@ jobs:
latest-changes:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
# To allow latest-changes to commit to the main branch
token: ${{ secrets.ACTIONS_TOKEN }}
diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml
index e335e81f91..35d7c97a6e 100644
--- a/.github/workflows/preview-docs.yml
+++ b/.github/workflows/preview-docs.yml
@@ -3,29 +3,34 @@ on:
workflow_run:
workflows:
- Build Docs
- types:
+ types:
- completed
jobs:
preview-docs:
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
+ - name: Clean site
+ run: |
+ rm -rf ./site
+ mkdir ./site
- name: Download Artifact Docs
- uses: dawidd6/action-download-artifact@v2.9.0
+ uses: dawidd6/action-download-artifact@v2.24.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
run_id: ${{ github.event.workflow_run.id }}
name: docs-zip
+ path: ./site/
- name: Unzip docs
run: |
- rm -rf ./site
+ cd ./site
unzip docs.zip
rm -f docs.zip
- name: Deploy to Netlify
id: netlify
- uses: nwtgck/actions-netlify@v1.1.5
+ uses: nwtgck/actions-netlify@v2.0.0
with:
publish-dir: './site'
production-deploy: false
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index a0feaebb8a..1786d2dbb5 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,9 +9,9 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: "3.7"
- name: Install Flit
diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml
new file mode 100644
index 0000000000..7559c24c06
--- /dev/null
+++ b/.github/workflows/smokeshow.yml
@@ -0,0 +1,35 @@
+name: Smokeshow
+
+on:
+ workflow_run:
+ workflows: [Test]
+ types: [completed]
+
+permissions:
+ statuses: write
+
+jobs:
+ smokeshow:
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.9'
+
+ - run: pip install smokeshow
+
+ - uses: dawidd6/action-download-artifact@v2.24.2
+ with:
+ workflow: test.yml
+ commit: ${{ github.event.workflow_run.head_sha }}
+
+ - run: smokeshow upload coverage-html
+ env:
+ SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
+ SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100
+ SMOKESHOW_GITHUB_CONTEXT: coverage
+ SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+ SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 68857fe326..875263fe73 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,30 +9,71 @@ on:
jobs:
test:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
strategy:
matrix:
- python-version: [3.6, 3.7, 3.8, 3.9]
+ python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
click-7: [true, false]
fail-fast: false
-
+
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Flit
run: pip install flit
- name: Install Dependencies
- run: flit install --deps=develop --symlink
+ if: ${{ matrix.python-version != '3.6' }}
+ run: python -m flit install --symlink
+ - name: Install Dependencies
+ if: ${{ matrix.python-version == '3.6' }}
+ # This doesn't install the editable install, so coverage doesn't get subprocesses
+ run: python -m pip install ".[test]"
- name: Install Click 7
if: matrix.click-7
run: pip install "click<8.0.0"
- name: Lint
if: ${{ matrix.python-version != '3.6' && matrix.click-7 == false }}
run: bash scripts/lint.sh
+ - run: mkdir coverage
- name: Test
run: bash scripts/test.sh
- - name: Upload coverage
- uses: codecov/codecov-action@v1
+ env:
+ COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-click-7-${{ matrix.click-7 }}
+ CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-click-7-${{ matrix.click-7 }}
+ - name: Store coverage files
+ uses: actions/upload-artifact@v3
+ with:
+ name: coverage
+ path: coverage
+ coverage-combine:
+ needs: [test]
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.8'
+
+ - name: Get coverage files
+ uses: actions/download-artifact@v3
+ with:
+ name: coverage
+ path: coverage
+
+ - run: pip install coverage[toml]
+
+ - run: ls -la coverage
+ - run: coverage combine coverage
+ - run: coverage report
+ - run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
+
+ - name: Store coverage HTML
+ uses: actions/upload-artifact@v3
+ with:
+ name: coverage-html
+ path: htmlcov
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000..2c80942355
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,53 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.3.0
+ hooks:
+ - id: check-added-large-files
+ - id: check-toml
+ - id: check-yaml
+ args:
+ - --unsafe
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+- repo: https://github.com/asottile/pyupgrade
+ rev: v3.2.2
+ hooks:
+ - id: pyupgrade
+ args:
+ - --py3-plus
+ - --keep-runtime-typing
+ # This file is more readable without yield from
+ exclude: ^docs_src/progressbar/tutorial004\.py
+- repo: https://github.com/PyCQA/autoflake
+ rev: v1.7.7
+ hooks:
+ - id: autoflake
+ args:
+ - --recursive
+ - --in-place
+ - --remove-all-unused-imports
+ - --remove-unused-variables
+ - --expand-star-imports
+ - --exclude
+ - __init__.py
+ - --remove-duplicate-keys
+- repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+ name: isort (python)
+ - id: isort
+ name: isort (cython)
+ types: [cython]
+ - id: isort
+ name: isort (pyi)
+ types: [pyi]
+- repo: https://github.com/psf/black
+ rev: 22.10.0
+ hooks:
+ - id: black
+ci:
+ autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
+ autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
diff --git a/README.md b/README.md
index bb1ccf96fc..de9420074c 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,8 @@
-
-
-
+
+
@@ -56,13 +55,15 @@ Python 3.6+
+**Note**: that will include Rich. Rich is the recommended library to *display* information on the terminal, it is optional, but when installed, it's deeply integrated into **Typer** to display beautiful output.
+
## Example
### The absolute minimum
@@ -74,7 +75,7 @@ import typer
def main(name: str):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
@@ -92,23 +93,33 @@ Run your application:
$ python main.py
// You get a nice error, you are missing NAME
-Usage: main.py [OPTIONS] NAME
-Try "main.py --help" for help.
+Usage: main.py [OPTIONS] NAME
+Try 'main.py --help' for help.
+╭─ Error ───────────────────────────────────────────╮
+│ Missing argument 'NAME'. │
+╰───────────────────────────────────────────────────╯
-Error: Missing argument 'NAME'.
// You get a --help for free
$ python main.py --help
-Usage: main.py [OPTIONS] NAME
-
-Arguments:
- NAME [required]
-
-Options:
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+Usage: main.py [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────╮
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
// When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion
@@ -144,15 +155,15 @@ app = typer.Typer()
@app.command()
def hello(name: str):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
@app.command()
def goodbye(name: str, formal: bool = False):
if formal:
- typer.echo(f"Goodbye Ms. {name}. Have a good day.")
+ print(f"Goodbye Ms. {name}. Have a good day.")
else:
- typer.echo(f"Bye {name}!")
+ print(f"Bye {name}!")
if __name__ == "__main__":
@@ -168,53 +179,85 @@ And that will:
### Run the upgraded example
+Check the new help:
+
```console
-// Check the --help
$ python main.py --help
-Usage: main.py [OPTIONS] COMMAND [ARGS]...
+Usage: main.py [OPTIONS] COMMAND [ARGS]...
+
+╭─ Options ─────────────────────────────────────────╮
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
+╭─ Commands ────────────────────────────────────────╮
+│ goodbye │
+│ hello │
+╰───────────────────────────────────────────────────╯
-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.
+// You have 2 subcommands (the 2 functions): goodbye and hello
+```
-Commands:
- goodbye
- hello
+
-// You have 2 subcommands (the 2 functions): goodbye and hello
+Now check the help for the `hello` command:
-// Now get the --help for hello
+
+```console
$ python main.py hello --help
-Usage: main.py hello [OPTIONS] NAME
+Usage: main.py hello [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────╮
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────╯
+```
-Arguments:
- NAME [required]
+
-Options:
- --help Show this message and exit.
+And now check the help for the `goodbye` command:
-// And now get the --help for goodbye
+
+```console
$ python main.py goodbye --help
-Usage: main.py goodbye [OPTIONS] NAME
+Usage: main.py goodbye [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────╮
+│ --formal--no-formal [default: no-formal] │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
-Arguments:
- NAME [required]
+// Automatic --formal and --no-formal for the bool option 🎉
+```
-Options:
- --formal / --no-formal [default: False]
- --help Show this message and exit.
+
-// Automatic --formal and --no-formal for the bool option 🎉
+Now you can try out the new command line application:
-// And if you use it with the hello command
+
+
+```console
+// Use it with the hello command
$ python main.py hello Camila
@@ -271,26 +314,12 @@ Typer uses colorama: and Click will automatically use it to make sure your terminal's colors always work correctly, even in Windows.
- * Then you can use any tool you want to output your terminal's colors in all the systems, including the integrated `typer.style()` and `typer.secho()` (provided by Click).
- * Or any other tool, e.g. wasabi, blessings.
+* rich: and Typer will show nicely formatted errors automatically.
* shellingham: and Typer will automatically detect the current shell when installing completion.
* With `shellingham` you can just use `--install-completion`.
* Without `shellingham`, you have to pass the name of the shell to install completion for, e.g. `--install-completion bash`.
-You can install `typer` with `colorama` and `shellingham` with `pip install typer[all]`.
-
-## Other tools and plug-ins
-
-Click has many plug-ins available that you can use. And there are many tools that help with command line applications that you can use as well, even if they are not related to Typer or Click.
-
-For example:
-
-* click-spinner: to show the user that you are loading data. A Click plug-in.
- * There are several other Click plug-ins at click-contrib that you can explore.
-* tabulate: to automatically display tabular data nicely. Independent of Click or Typer.
-* tqdm: a fast, extensible progress bar, alternative to Typer's own `typer.progressbar()`.
-* etc... you can re-use many of the great available tools for building CLIs.
+You can install `typer` with `rich` and `shellingham` with `pip install typer[all]`.
## License
diff --git a/docs/alternatives.md b/docs/alternatives.md
index 0f93697557..1938bb9944 100644
--- a/docs/alternatives.md
+++ b/docs/alternatives.md
@@ -26,7 +26,7 @@ It inspired a lot of the ideas in **FastAPI** and **Typer**.
!!! check "Inspired **Typer** to"
Use function parameters to declare *CLI arguments* and *CLI options* as it simplifies a lot the development experience.
-### Plac
+### Plac
Plac is another library to create CLIs using parameters in functions, similar to Hug.
diff --git a/docs/css/termynal.css b/docs/css/termynal.css
index 0484e65d4d..af2fbe6700 100644
--- a/docs/css/termynal.css
+++ b/docs/css/termynal.css
@@ -17,7 +17,8 @@
max-width: 100%;
background: var(--color-bg);
color: var(--color-text);
- font-size: 18px;
+ /* font-size: 18px; */
+ font-size: 15px;
/* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */
font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
border-radius: 4px;
@@ -25,6 +26,7 @@
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
+ line-height: 1.2;
}
[data-termynal]:before {
diff --git a/docs/features.md b/docs/features.md
index b569ab23b5..6b2bbb2b51 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -59,7 +59,7 @@ The resulting CLI apps created with **Typer** have the nice features of many "pr
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
If you didn't add `shellingham` those *CLI options* take a value with the name of the shell to install completion for, e.g.:
-
+
* `--install-completion bash`.
* `--show-completion powershell`.
diff --git a/docs/img/icon-black.svg b/docs/img/icon-black.svg
index 1cba56baf9..bc987f38b6 100644
--- a/docs/img/icon-black.svg
+++ b/docs/img/icon-black.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/docs/index.md b/docs/index.md
index bb1ccf96fc..de9420074c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -11,9 +11,8 @@
-
-
-
+
+
@@ -56,13 +55,15 @@ Python 3.6+
+**Note**: that will include Rich. Rich is the recommended library to *display* information on the terminal, it is optional, but when installed, it's deeply integrated into **Typer** to display beautiful output.
+
## Example
### The absolute minimum
@@ -74,7 +75,7 @@ import typer
def main(name: str):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
@@ -92,23 +93,33 @@ Run your application:
$ python main.py
// You get a nice error, you are missing NAME
-Usage: main.py [OPTIONS] NAME
-Try "main.py --help" for help.
+Usage: main.py [OPTIONS] NAME
+Try 'main.py --help' for help.
+╭─ Error ───────────────────────────────────────────╮
+│ Missing argument 'NAME'. │
+╰───────────────────────────────────────────────────╯
-Error: Missing argument 'NAME'.
// You get a --help for free
$ python main.py --help
-Usage: main.py [OPTIONS] NAME
-
-Arguments:
- NAME [required]
-
-Options:
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+Usage: main.py [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────╮
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
// When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion
@@ -144,15 +155,15 @@ app = typer.Typer()
@app.command()
def hello(name: str):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
@app.command()
def goodbye(name: str, formal: bool = False):
if formal:
- typer.echo(f"Goodbye Ms. {name}. Have a good day.")
+ print(f"Goodbye Ms. {name}. Have a good day.")
else:
- typer.echo(f"Bye {name}!")
+ print(f"Bye {name}!")
if __name__ == "__main__":
@@ -168,53 +179,85 @@ And that will:
### Run the upgraded example
+Check the new help:
+
```console
-// Check the --help
$ python main.py --help
-Usage: main.py [OPTIONS] COMMAND [ARGS]...
+Usage: main.py [OPTIONS] COMMAND [ARGS]...
+
+╭─ Options ─────────────────────────────────────────╮
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
+╭─ Commands ────────────────────────────────────────╮
+│ goodbye │
+│ hello │
+╰───────────────────────────────────────────────────╯
-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.
+// You have 2 subcommands (the 2 functions): goodbye and hello
+```
-Commands:
- goodbye
- hello
+
-// You have 2 subcommands (the 2 functions): goodbye and hello
+Now check the help for the `hello` command:
-// Now get the --help for hello
+
+```console
$ python main.py hello --help
-Usage: main.py hello [OPTIONS] NAME
+Usage: main.py hello [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────╮
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────╯
+```
-Arguments:
- NAME [required]
+
-Options:
- --help Show this message and exit.
+And now check the help for the `goodbye` command:
-// And now get the --help for goodbye
+
+```console
$ python main.py goodbye --help
-Usage: main.py goodbye [OPTIONS] NAME
+Usage: main.py goodbye [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────╮
+│ --formal--no-formal [default: no-formal] │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
-Arguments:
- NAME [required]
+// Automatic --formal and --no-formal for the bool option 🎉
+```
-Options:
- --formal / --no-formal [default: False]
- --help Show this message and exit.
+
-// Automatic --formal and --no-formal for the bool option 🎉
+Now you can try out the new command line application:
-// And if you use it with the hello command
+
+
+```console
+// Use it with the hello command
$ python main.py hello Camila
@@ -271,26 +314,12 @@ Typer uses colorama: and Click will automatically use it to make sure your terminal's colors always work correctly, even in Windows.
- * Then you can use any tool you want to output your terminal's colors in all the systems, including the integrated `typer.style()` and `typer.secho()` (provided by Click).
- * Or any other tool, e.g. wasabi, blessings.
+* rich: and Typer will show nicely formatted errors automatically.
* shellingham: and Typer will automatically detect the current shell when installing completion.
* With `shellingham` you can just use `--install-completion`.
* Without `shellingham`, you have to pass the name of the shell to install completion for, e.g. `--install-completion bash`.
-You can install `typer` with `colorama` and `shellingham` with `pip install typer[all]`.
-
-## Other tools and plug-ins
-
-Click has many plug-ins available that you can use. And there are many tools that help with command line applications that you can use as well, even if they are not related to Typer or Click.
-
-For example:
-
-* click-spinner: to show the user that you are loading data. A Click plug-in.
- * There are several other Click plug-ins at click-contrib that you can explore.
-* tabulate: to automatically display tabular data nicely. Independent of Click or Typer.
-* tqdm: a fast, extensible progress bar, alternative to Typer's own `typer.progressbar()`.
-* etc... you can re-use many of the great available tools for building CLIs.
+You can install `typer` with `rich` and `shellingham` with `pip install typer[all]`.
## License
diff --git a/docs/js/termynal.js b/docs/js/termynal.js
index 8b0e9339e8..4ac32708a3 100644
--- a/docs/js/termynal.js
+++ b/docs/js/termynal.js
@@ -72,14 +72,14 @@ class Termynal {
* Initialise the widget, get lines, clear container and start animation.
*/
init() {
- /**
+ /**
* Calculates width and height of Termynal container.
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
- */
+ */
const containerStyle = getComputedStyle(this.container);
- this.container.style.width = containerStyle.width !== '0px' ?
+ this.container.style.width = containerStyle.width !== '0px' ?
containerStyle.width : undefined;
- this.container.style.minHeight = containerStyle.height !== '0px' ?
+ this.container.style.minHeight = containerStyle.height !== '0px' ?
containerStyle.height : undefined;
this.container.setAttribute('data-termynal', '');
@@ -138,7 +138,7 @@ class Termynal {
restart.innerHTML = "restart ↻"
return restart
}
-
+
generateFinish() {
const finish = document.createElement('a')
finish.onclick = (e) => {
@@ -215,7 +215,7 @@ class Termynal {
/**
* Converts line data objects into line elements.
- *
+ *
* @param {Object[]} lineData - Dynamically loaded lines.
* @param {Object} line - Line data object.
* @returns {Element[]} - Array of line elements.
@@ -231,7 +231,7 @@ class Termynal {
/**
* Helper function for generating attributes string.
- *
+ *
* @param {Object} line - Line data object.
* @returns {string} - String of attributes.
*/
diff --git a/docs/overrides/main.html b/docs/overrides/main.html
new file mode 100644
index 0000000000..7e501ba0fb
--- /dev/null
+++ b/docs/overrides/main.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+{%- block scripts %}
+{{ super() }}
+
+
+
+
+
You can ask questions about Typer. Try:
+
How can I terminate a program?
+
How to launch applications?
+
How to add help to CLI argument?
+
+
+{%- endblock %}
diff --git a/docs/release-notes.md b/docs/release-notes.md
index ea723dc6cd..40a7300205 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,147 @@
## Latest Changes
+* ⬆ Bump nwtgck/actions-netlify from 1.2.4 to 2.0.0. PR [#513](https://github.com/tiangolo/typer/pull/513) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷 Refactor CI artifact upload/download for docs previews. PR [#516](https://github.com/tiangolo/typer/pull/516) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#500](https://github.com/tiangolo/typer/pull/500) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* ⬆ Bump actions/cache from 2 to 3. PR [#496](https://github.com/tiangolo/typer/pull/496) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ✏️ Fix typo in datetime docs. PR [#495](https://github.com/tiangolo/typer/pull/495) by [@huxuan](https://github.com/huxuan).
+* ⬆ Bump dawidd6/action-download-artifact from 2.24.1 to 2.24.2. PR [#494](https://github.com/tiangolo/typer/pull/494) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump dawidd6/action-download-artifact from 2.9.0 to 2.24.1. PR [#491](https://github.com/tiangolo/typer/pull/491) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump actions/setup-python from 2 to 4. PR [#492](https://github.com/tiangolo/typer/pull/492) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷♂️ Consistently use `sys.executable` to run subprocesses, needed by OpenSUSE. PR [#408](https://github.com/tiangolo/typer/pull/408) by [@theMarix](https://github.com/theMarix).
+* 👷♂️ Ensure the `PYTHONPATH` is set properly when testing the tutorial scripts. PR [#407](https://github.com/tiangolo/typer/pull/407) by [@theMarix](https://github.com/theMarix).
+* ✏️ Add quotes to package name that includes brackets in docs. PR [#475](https://github.com/tiangolo/typer/pull/475) by [@gjolga](https://github.com/gjolga).
+
+## 0.7.0
+
+### Features
+
+* ✨ Make `typer.run()` not add completion scripts by default, it only makes sense in installed apps. Also update docs for handling [autocompletion in CLI options](https://typer.tiangolo.com/tutorial/options-autocompletion/). PR [#488](https://github.com/tiangolo/typer/pull/488) by [@tiangolo](https://github.com/tiangolo).
+* ✨ Add support for Python 3.11, tests in CI and official marker. PR [#487](https://github.com/tiangolo/typer/pull/487) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add CI for Python 3.10. PR [#384](https://github.com/tiangolo/typer/pull/384) by [@tiangolo](https://github.com/tiangolo).
+
+### Fixes
+
+* 🎨 Fix type annotation of `typer.run()`. PR [#284](https://github.com/tiangolo/typer/pull/284) by [@yassu](https://github.com/yassu).
+* 🎨 Fix type annotations for `get_group`. PR [#430](https://github.com/tiangolo/typer/pull/430) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add note about how subcommands with function names using underscores are converted to dashes. PR [#403](https://github.com/tiangolo/typer/pull/403) by [@targhs](https://github.com/targhs).
+* 📝 Fix typo in docs at `docs/tutorial/commands/help.md`. PR [#466](https://github.com/tiangolo/typer/pull/466) by [@fepegar](https://github.com/fepegar).
+* ✏ Fix link in docs to `datetime.strptime()`. PR [#464](https://github.com/tiangolo/typer/pull/464) by [@Kobu](https://github.com/Kobu).
+* ✏ Update `first-steps.md`, clarify distinction between parameter and argument. PR [#176](https://github.com/tiangolo/typer/pull/176) by [@mccarthysean](https://github.com/mccarthysean).
+* ✏ Fix broken plac link. PR [#275](https://github.com/tiangolo/typer/pull/275) by [@mgielda](https://github.com/mgielda).
+
+### Internal
+
+* ✅ Add extra tests just for coverage because monkeypatching with strange imports confuses coverage. PR [#490](https://github.com/tiangolo/typer/pull/490) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Tweak pytest coverage. PR [#485](https://github.com/tiangolo/typer/pull/485) by [@tiangolo](https://github.com/tiangolo).
+* ➕ Bring back pytest-cov because coverage can't detect pytest-xdist. PR [#484](https://github.com/tiangolo/typer/pull/484) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Bump actions/upload-artifact from 2 to 3. PR [#477](https://github.com/tiangolo/typer/pull/477) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump actions/checkout from 2 to 3. PR [#478](https://github.com/tiangolo/typer/pull/478) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#411](https://github.com/tiangolo/typer/pull/411) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* ⬆ Bump nwtgck/actions-netlify from 1.1.5 to 1.2.4. PR [#479](https://github.com/tiangolo/typer/pull/479) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump tiangolo/issue-manager from 0.2.0 to 0.4.0. PR [#481](https://github.com/tiangolo/typer/pull/481) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 👷 Move from pytest-cov to coverage and Codecov to Smokeshow. PR [#483](https://github.com/tiangolo/typer/pull/483) by [@tiangolo](https://github.com/tiangolo).
+* ➕ Add extra Material for MkDocs deps for docs. PR [#482](https://github.com/tiangolo/typer/pull/482) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update Dependabot config. PR [#476](https://github.com/tiangolo/typer/pull/476) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.6.1
+
+### Fixes
+
+* 🐛 Fix setting `FORCE_TERMINAL` with colors 2. PR [#424](https://github.com/tiangolo/typer/pull/424) by [@tiangolo](https://github.com/tiangolo).
+* 🐛 Fix setting `FORCE_TERMINAL` with colors. PR [#423](https://github.com/tiangolo/typer/pull/423) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.6.0
+
+This release adds deep integrations with [Rich](https://rich.readthedocs.io/en/stable/). ✨
+
+`rich` is an optional dependency, you can install it directly or it will be included when you install with:
+
+```console
+$ pip install "typer[all]"
+```
+
+If Rich is available, it will be used to show the content from `--help` options, validation errors, and even errors in your app (exception tracebacks).
+
+There are new options to group commands, *CLI arguments*, and *CLI options*, support for [Rich Console Markup](https://rich.readthedocs.io/en/stable/markup.html), and more! 🎉
+
+### Features
+
+* ✨ Richify, add integrations with Rich everywhere. PR [#419](https://github.com/tiangolo/typer/pull/419) by [@tiangolo](https://github.com/tiangolo).
+ * Recommend Rich as the main information displaying tool, new docs: [Printing and Colors](https://typer.tiangolo.com/tutorial/printing/).
+ * For most use cases not using Rich, use plain `print()` instead of `typer.echo()` in the docs, to simplify the concepts and avoid confusions. New docs: [Printing and Colors - typer Echo](https://typer.tiangolo.com/tutorial/printing/#typer-echo).
+ * Define help panels for *CLI arguments*, new docs: [CLI Arguments with Help - CLI Argument help panels](https://typer.tiangolo.com/tutorial/arguments/help/#cli-argument-help-panels).
+ * Define help panels for *CLI options*, new docs: [CLI Options with Help - CLI Options help panels](https://typer.tiangolo.com/tutorial/options/help/#cli-options-help-panels).
+ * New docs for deprecating commands: [Commands - Command Help - Deprecate a Command](https://typer.tiangolo.com/tutorial/commands/help/#deprecate-a-command).
+ * Support for Rich Markdown in docstrings, *CLI parameters* `help`, and `epilog` with the new parameter `typer.Typer(rich_markup_mode="markdown")`, new docs: [Commands - Command Help - Rich Markdown and Markup](https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup).
+ * Support for Rich Markup (different from Markdown) in docstrings, *CLI parameters* `help`, and `epilog` with the new parameter `typer.Typer(rich_markup_mode="rich")`, new docs: [Commands - Command Help - Rich Markdown and Markup](https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup).
+ * Define help panels for *commands*, new docs: [Commands - Command Help - Help Panels](https://typer.tiangolo.com/tutorial/commands/help/#help-panels).
+ * New docs for setting an `epilog`, with support for Rich Markdown and Console Markup, new docs: [Commands - Command Help - Epilog](https://typer.tiangolo.com/tutorial/commands/help/#epilog).
+* ✨ Refactor and document handling pretty exceptions. PR [#422](https://github.com/tiangolo/typer/pull/422) by [@tiangolo](https://github.com/tiangolo).
+ * Add support for customizing pretty short errors, new docs: [Exceptions and Errors](https://typer.tiangolo.com/tutorial/exceptions/).
+* ✨ Allow configuring pretty errors when creating the Typer instance. PR [#416](https://github.com/tiangolo/typer/pull/416) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add docs for using Rich with Typer. PR [#421](https://github.com/tiangolo/typer/pull/421) by [@tiangolo](https://github.com/tiangolo).
+ * Add new docs: [Ask with Prompt - Prompt with Rich](https://typer.tiangolo.com/tutorial/prompt/#prompt-with-rich).
+ * Add new docs to handle progress bars and spinners with Rich: [Progress Par](https://typer.tiangolo.com/tutorial/progressbar/).
+
+### Internal
+
+* ⬆️ Upgrade codecov GitHub Action. PR [#420](https://github.com/tiangolo/typer/pull/420) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.5.0
+
+### Features
+
+* ✨ Add pretty error tracebacks for user errors and support for Rich. PR [#412](https://github.com/tiangolo/typer/pull/412) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* ✏ Fix typo, "ASCII codes" to "ANSI escape sequences". PR [#308](https://github.com/tiangolo/typer/pull/308) by [@septatrix](https://github.com/septatrix).
+
+## 0.4.2
+
+### Fixes
+
+* 🐛 Fix type conversion for `List` and `Tuple` and their internal types. PR [#143](https://github.com/tiangolo/typer/pull/143) by [@hellowhistler](https://github.com/hellowhistler).
+* 🐛 Fix `context_settings` for a Typer app with a single command. PR [#210](https://github.com/tiangolo/typer/pull/210) by [@daddycocoaman](https://github.com/daddycocoaman).
+
+### Docs
+
+* 📝 Clarify testing documentation about checking `stderr`. PR [#335](https://github.com/tiangolo/typer/pull/335) by [@cgabard](https://github.com/cgabard).
+* ✏ Fix typo in docs for CLI Option autocompletion. PR [#288](https://github.com/tiangolo/typer/pull/288) by [@graue70](https://github.com/graue70).
+* 🎨 Fix header format for "Standard Input" in `docs/tutorial/printing.md`. PR [#386](https://github.com/tiangolo/typer/pull/386) by [@briancohan](https://github.com/briancohan).
+* ✏ Fix typo in `docs/tutorial/terminating.md`. PR [#382](https://github.com/tiangolo/typer/pull/382) by [@kianmeng](https://github.com/kianmeng).
+* ✏ Fix syntax typo in `docs/tutorial/package.md`. PR [#333](https://github.com/tiangolo/typer/pull/333) by [@ryanstreur](https://github.com/ryanstreur).
+* ✏ Fix typo, duplicated word in `docs/tutorial/options/required.md`.. PR [#316](https://github.com/tiangolo/typer/pull/316) by [@michaelriri](https://github.com/michaelriri).
+* ✏ Fix minor typo in `index.md`. PR [#274](https://github.com/tiangolo/typer/pull/274) by [@RmStorm](https://github.com/RmStorm).
+* ✏ Fix double "and" typo in first-steps tutorial. PR [#225](https://github.com/tiangolo/typer/pull/225) by [@softwarebloat](https://github.com/softwarebloat).
+* 🎨 Fix format in docs explaining `datetime` parameter type. PR [#220](https://github.com/tiangolo/typer/pull/220) by [@DiegoPiloni](https://github.com/DiegoPiloni).
+
+### Internal
+
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#404](https://github.com/tiangolo/typer/pull/404) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* 👷 Fix Material for MkDocs install in CI. PR [#395](https://github.com/tiangolo/typer/pull/395) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add pre-commit CI config. PR [#394](https://github.com/tiangolo/typer/pull/394) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Clear MkDocs Insiders cache. PR [#393](https://github.com/tiangolo/typer/pull/393) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add pre-commit config and formatting. PR [#392](https://github.com/tiangolo/typer/pull/392) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Disable installing MkDocs Insiders in forks. PR [#391](https://github.com/tiangolo/typer/pull/391) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade Codecov GitHub Action. PR [#383](https://github.com/tiangolo/typer/pull/383) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.4.1
+
+### Fixes
+
+* 🐛 Fix import of `get_terminal_size` for Click 8.1.0 support and upgrade Black to fix CI. PR [#380](https://github.com/tiangolo/typer/pull/380) by [@tiangolo](https://github.com/tiangolo) based on original PR [#375](https://github.com/tiangolo/typer/pull/375) by [@madkinsz](https://github.com/madkinsz).
+
+### Internal
+
+* 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#368](https://github.com/tiangolo/typer/pull/368) by [@tiangolo](https://github.com/tiangolo).
* 💚 Only test on push when on master, avoid duplicate CI runs from PRs. PR [#358](https://github.com/tiangolo/typer/pull/358) by [@tiangolo](https://github.com/tiangolo).
* ✨ Add support for previewing docs in PRs from forks and enable MkDocs Insiders. PR [#357](https://github.com/tiangolo/typer/pull/357) by [@tiangolo](https://github.com/tiangolo).
* ⬆️ Upgrade MkDocs Material, MDX-Include, and MkDocs structure. PR [#356](https://github.com/tiangolo/typer/pull/356) by [@tiangolo](https://github.com/tiangolo).
diff --git a/docs/tutorial/arguments/help.md b/docs/tutorial/arguments/help.md
index 4a18d0d8a9..ddd2dfede3 100644
--- a/docs/tutorial/arguments/help.md
+++ b/docs/tutorial/arguments/help.md
@@ -211,6 +211,56 @@ Options:
+## *CLI Argument* help panels
+
+You might want to show the help information for *CLI arguments* in different panels when using the `--help` option.
+
+If you have installed Rich as described in the docs for [Printing and Colors](../printing.md){.internal-link target=_blank}, you can set the `rich_help_panel` parameter to the name of the panel where you want this *CLI argument* to be shown:
+
+```Python hl_lines="7 10"
+{!../docs_src/arguments/help/tutorial007.py!}
+```
+
+Then, if you check the `--help` option, you will see a default panel named "`Arguments`" for the *CLI arguments* that don't have a custom `rich_help_panel`.
+
+And next you will see other panels for the *CLI arguments* that have a custom panel set in the `rich_help_panel` parameter:
+
+
+
+```console
+$ python main.py --help
+
+Usage: main.py [OPTIONS] NAME [LASTNAME] [AGE]
+
+ Say hi to NAME very gently, like Dirk.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * name TEXT Who to greet [default: None] [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Secondary Arguments ─────────────────────────────────────────────╮
+│ lastname [LASTNAME] The last name │
+│ age [AGE] The user's age │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --install-completion Install completion for the current │
+│ shell. │
+│ --show-completion Show completion for the current │
+│ shell, to copy it or customize the │
+│ installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+In this example we have a custom *CLI arguments* panel named "`Secondary Arguments`".
+
+## Help with style using Rich
+
+In a future section you will see how to use custom markup in the `help` for *CLI arguments* when reading about [Commands - Command Help](../commands/help.md#rich-markdown-and-markup){.internal-link target=_blank}.
+
+If you are in a hurry you can jump there, but otherwise, it would be better to continue reading here and following the tutorial in order.
+
## Hide a *CLI argument* from the help text
If you want, you can make a *CLI argument* **not** show up in the `Arguments` section in the help text.
@@ -218,7 +268,7 @@ 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 hl_lines="4"
-{!../docs_src/arguments/help/tutorial007.py!}
+{!../docs_src/arguments/help/tutorial008.py!}
```
Check it:
diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md
index 7e08cc29a9..0cc08c87fb 100644
--- a/docs/tutorial/commands/help.md
+++ b/docs/tutorial/commands/help.md
@@ -118,3 +118,369 @@ Commands:
```
+
+## Deprecate a Command
+
+There could be cases where you have a command in your app that you need to deprecate, so that your users stop using it, even while it's still supported for a while.
+
+You can mark it with the parameter `deprecated=True`:
+
+```Python hl_lines="14"
+{!../docs_src/commands/help/tutorial003.py!}
+```
+
+And when you show the `--help` option you will see it's marked as "`deprecated`":
+
+
+
+```console
+$ python main.py --help
+
+Usage: main.py [OPTIONS] COMMAND [ARGS]...
+
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --install-completion Install completion for the current │
+│ shell. │
+│ --show-completion Show completion for the current │
+│ shell, to copy it or customize the │
+│ installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Commands ────────────────────────────────────────────────────────╮
+│ create Create a user. │
+│ delete Delete a user. (deprecated) │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+And if you check the `--help` for the deprecated command (in this example, the command `delete`), it also shows it as deprecated:
+
+
+
+```console
+$ python main.py delete --help
+
+Usage: main.py delete [OPTIONS] USERNAME
+
+ (deprecated)
+ Delete a user.
+ This is deprecated and will stop being supported soon.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+## Rich Markdown and Markup
+
+If you have **Rich** installed as described in [Printing and Colors](../printing.md){.internal-link target=_blank}, you can configure your app to enable markup text with the parameter `rich_markup_mode`.
+
+Then you can use more formatting in the docstrings and the `help` parameter for *CLI arguments* and *CLI options*. You will see more about it below. 👇
+
+!!! info
+ By default, `rich_markup_mode` is `None`, which disables any rich text formatting.
+
+### Rich Markup
+
+If you set `rich_markup_mode="rich"` when creating the `typer.Typer()` app, you will be able to use Rich Console Markup in the docstring, and even in the help for the *CLI arguments* and options:
+
+```Python hl_lines="3 9 13-15 20 22 24"
+{!../docs_src/commands/help/tutorial004.py!}
+```
+
+With that, you can use Rich Console Markup to format the text in the docstring for the command `create`, make the word "`create`" bold and green, and even use an emoji.
+
+You can also use markup in the help for the `username` CLI Argument.
+
+And the same as before, the help text overwritten for the command `delete` can also use Rich Markup, the same in the CLI Argument and CLI Option.
+
+If you run the program and check the help, you will see that **Typer** uses **Rich** internally to format the help.
+
+Check the help for the `create` command:
+
+
+
+```console
+$ python main.py create --help
+
+Usage: main.py create [OPTIONS] USERNAME
+
+ Create a new shinny user. ✨
+ This requires a username.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT The username to be created │
+│ [default: None] │
+│ [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+And check the help for the `delete` command:
+
+
+
+```console
+$ python main.py delete --help
+
+Usage: main.py delete [OPTIONS] USERNAME
+
+ Delete a user with USERNAME.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT The username to be deleted │
+│ [default: None] │
+│ [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --force--no-force Force the deletion 💥 │
+│ [default: no-force] │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+### Rich Markdown
+
+If you set `rich_markup_mode="markdown"` when creating the `typer.Typer()` app, you will be able to use Markdown in the docstring:
+
+```Python hl_lines="3 7 9-17 22 24-25"
+{!../docs_src/commands/help/tutorial005.py!}
+```
+
+With that, you can use Markdown to format the text in the docstring for the command `create`, make the word "`create`" bold, show a list of items, and even use an emoji.
+
+And the same as before, the help text overwritten for the command `delete` can also use Markdown.
+
+Check the help for the `create` command:
+
+
+
+```console
+$ python main.py create --help
+
+Usage: main.py create [OPTIONS] USERNAME
+
+ Create a new shinny user. ✨
+
+ • Create a username
+ • Show that the username is created
+
+ ───────────────────────────────────────────────────────────────────
+ Learn more at the Typer docs website
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT The username to be created │
+│ [default: None] │
+│ [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+And the same for the `delete` command:
+
+
+
+```console
+$ python main.py delete --help
+
+Usage: main.py delete [OPTIONS] USERNAME
+
+ Delete a user with USERNAME.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT The username to be deleted │
+│ [default: None] │
+│ [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --force--no-force Force the deletion 💥 │
+│ [default: no-force] │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+!!! info
+ Notice that in Markdown you cannot define colors. For colors you might prefer to use Rich markup.
+
+## Help Panels
+
+If you have many commands or CLI parameters, you might want to show their documentation in different panels when using the `--help` option.
+
+If you installed Rich as described in [Printing and Colors](../printing.md){.internal-link target=_blank}, you can configure the panel to use for each command or CLI parameter.
+
+### Help Panels for Commands
+
+To set the panel for a command you can pass the argument `rich_help_panel` with the name of the panel you want to use:
+
+```Python hl_lines="22 30 38 46"
+{!../docs_src/commands/help/tutorial006.py!}
+```
+
+Commands without a panel will be shown in the default panel `Commands`, and the rest will be shown in the next panels:
+
+
+
+```console
+$ python main.py --help
+
+Usage: main.py [OPTIONS] COMMAND [ARGS]...
+
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --install-completion Install completion for the current │
+│ shell. │
+│ --show-completion Show completion for the current │
+│ shell, to copy it or customize the │
+│ installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Commands ────────────────────────────────────────────────────────╮
+│ create Create a new user. ✨ │
+│ delete Delete a user. 🔥 │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Utils and Configs ───────────────────────────────────────────────╮
+│ config Configure the system. 🔧 │
+│ sync Synchronize the system or something fancy like that. ♻ │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Help and Others ─────────────────────────────────────────────────╮
+│ help Get help with the system. ❓ │
+│ report Report an issue. 🐛 │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+### Help Panels for CLI Parameters
+
+The same way, you can configure the panels for *CLI arguments* and *CLI options* with `rich_help_panel`.
+
+And of course, in the same application you can also set the `rich_help_panel` for commands.
+
+```Python hl_lines="12 16 21 30"
+{!../docs_src/commands/help/tutorial007.py!}
+```
+
+Then if you run the application you will see all the *CLI parameters* in their respective panels.
+
+* First the ***CLI arguments*** that don't have a panel name set in a **default** one named "`Arguments`".
+* Next the ***CLI arguments*** with a **custom panel**. In this example named "`Secondary Arguments`".
+* After that, the ***CLI options*** that don't have a panel in a **default** one named "`Options`".
+* And finally, the ***CLI options*** with a **custom panel** set. In this example named "`Additional Data`".
+
+You can check the `--help` option for the command `create`:
+
+
+
+```console
+$ python main.py create --help
+
+Usage: main.py create [OPTIONS] USERNAME [LASTNAME]
+
+ Create a new user. ✨
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT The username to create [default: None] │
+│ [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Secondary Arguments ─────────────────────────────────────────────╮
+│ lastname [LASTNAME] The last name of the new user │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --force--no-force Force the creation of the user │
+│ [default: no-force] │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Additional Data ─────────────────────────────────────────────────╮
+│ --ageINTEGER The age of the new user │
+│ [default: None] │
+│ --favorite-colorTEXT The favorite color of the new │
+│ user │
+│ [default: None] │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+And of course, the `rich_help_panel` can be used in the same way for commands in the same application.
+
+And those panels will be shown when you use the main `--help` option.
+
+
+
+```console
+$ python main.py --help
+
+Usage: main.py [OPTIONS] COMMAND [ARGS]...
+
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --install-completion Install completion for the current │
+│ shell. │
+│ --show-completion Show completion for the current │
+│ shell, to copy it or customize the │
+│ installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Commands ────────────────────────────────────────────────────────╮
+│ create Create a new user. ✨ │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Utils and Configs ───────────────────────────────────────────────╮
+│ config Configure the system. 🔧 │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+You can see the custom panel for the commands for "`Utils and Configs`".
+
+## Epilog
+
+If you need, you can also add an epilog section to the help of your commands:
+
+```Python hl_lines="6"
+{!../docs_src/commands/help/tutorial008.py!}
+```
+
+And when you check the `--help` option it will look like:
+
+
+
+```console
+$ python main.py --help
+
+Usage: main.py [OPTIONS] USERNAME
+
+ Create a new user. ✨
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * username TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --install-completion Install completion for the current │
+│ shell. │
+│ --show-completion Show completion for the current │
+│ shell, to copy it or customize the │
+│ installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+
+ Made with ❤ in Venus
+```
+
+
diff --git a/docs/tutorial/commands/index.md b/docs/tutorial/commands/index.md
index 0aaf8cd492..373f1187d3 100644
--- a/docs/tutorial/commands/index.md
+++ b/docs/tutorial/commands/index.md
@@ -76,7 +76,7 @@ When you use `typer.run()`, **Typer** is doing more or less the same as above, i
In our case, this decorator tells **Typer** that the function below is a "`command`".
-Both ways, with `typer.run()` and creating the explicit application, achieve the same.
+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.
@@ -114,6 +114,44 @@ Options:
+## CLI application completion
+
+There's a little detail that is worth noting here.
+
+To get shell/tab completion, it's necessary to build a package that you and your users can install and **call directly**.
+
+So instead of running a Python script like:
+
+
+
+```console
+$ python main.py
+
+✨ Some magic here ✨
+```
+
+
+
+...It would be called like:
+
+
+
+```console
+$ magic-app
+
+✨ Some magic here ✨
+```
+
+
+
+Having a standalone program like that allows setting up shell/tab completion.
+
+The first step to be able to create an installable package like that is to use an explicit `typer.Typer()` app.
+
+Later you can learn all the process to create a standalone CLI application and [Build a Package](../package.md){.internal-link target=_blank}.
+
+But for now, it's just good to know that you are on that path. 😎
+
## A CLI application with multiple commands
Coming back to the CLI applications with multiple commands/subcommands, **Typer** allows creating CLI applications with multiple of them.
diff --git a/docs/tutorial/commands/name.md b/docs/tutorial/commands/name.md
index 458f54101c..7d3ddc1a5c 100644
--- a/docs/tutorial/commands/name.md
+++ b/docs/tutorial/commands/name.md
@@ -44,3 +44,13 @@ Creating user: Camila
```
+
+Note that any underscores in the function name will be replaced with dashes.
+
+So if your function is something like:
+
+```Python
+def create_user(username: str):
+ ...
+```
+Then the command name will be `create-user`.
diff --git a/docs/tutorial/exceptions.md b/docs/tutorial/exceptions.md
new file mode 100644
index 0000000000..6a0cee1689
--- /dev/null
+++ b/docs/tutorial/exceptions.md
@@ -0,0 +1,281 @@
+# Exceptions and Errors
+
+When your code has errors and you run it, it will show the error and an exception.
+
+Typer does some tricks to help you detect those errors quickly.
+
+## Example Broken App
+
+Let's take this example broken app:
+
+```Python hl_lines="5"
+{!../docs_src/exceptions/tutorial001.py!}
+```
+
+This code is broken because you can't sum a string and a number (`name + 3`).
+
+## Exceptions with Rich
+
+If you have **Rich** installed (for example if you installed `"typer[all]"`), **Typer** will use it to automatically show you nicely printed errors.
+
+It will **omit** all the parts of the traceback (the chain of things that called your function) that come from the internal parts in Typer and Click.
+
+So, the error you see will be **much clearer** and simpler, to help you detect the problem in your code quickly:
+
+
+
+## Exceptions without Rich
+
+If you don't have Rich installed, Typer will still do some tricks to show you the information **as clearly as possible**:
+
+
+
+```console
+$ python main.py
+
+Traceback (most recent call last):
+
+ File "main.py", line 12, in
+ typer.run(main)
+
+ File "main.py", line 8, in main
+ print(name + 3)
+
+TypeError: can only concatenate str (not "int") to str
+```
+
+
+
+## Disable Local Variables for Security
+
+If your Typer application handles **delicate information**, for example a **password**, a **key**, a **token**, then it could be problematic if the automatic errors show the value in those local variables.
+
+This would be relevant in particular if your CLI application is being run on some CI (continuous integration) system that is recording the logs.
+
+The default errors above, when using Rich, show a section with:
+
+```Python
+name = 'morty'
+```
+
+In this case, `name` is a local variable, it comes from a parameter passed to the function.
+
+But if it was something like a password, would would have liked to hide it.
+
+In that case, you can create the `typer.Typer()` application explicitly and set the parameter `pretty_exceptions_show_locals=False`:
+
+```Python hl_lines="3"
+{!../docs_src/exceptions/tutorial002.py!}
+```
+
+And now when you run it, you will see the error without the local variables:
+
+
+
+Note that you passed the password `supersecret`, but it's not shown anywhere in the error message.
+
+Being able to see the values of local variables is normally very **helpful** to diagnose, **debug**, and fix problems, but if you are dealing with delicate information, now you know how to secure it. 🔒
+
+## Disable Short Output
+
+If you want to show the full exception, including the parts in Typer and Click, you can use the parameter `pretty_exceptions_short=False`:
+
+```Python hl_lines="3"
+{!../docs_src/exceptions/tutorial003.py!}
+```
+
+Now when you run it, you will see the whole output:
+
+
+
+## Disable Pretty Exceptions
+
+You can also entirely disable pretty exceptions with the parameter `pretty_exceptions_enable=False`:
+
+```Python hl_lines="3"
+{!../docs_src/exceptions/tutorial004.py!}
+```
+
+And now you will see the full standard exception as with any other Python program:
+
+
+
+```console
+$ python main.py
+
+Traceback (most recent call last):
+ File "main.py", line 12, in
+ app()
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 328, in __call__
+ raise e
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 311, in __call__
+ return get_command(self)(*args, **kwargs)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1130, in __call__
+ return self.main(*args, **kwargs)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 723, in main
+ **extra,
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 216, in _main
+ rv = self.invoke(ctx)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1404, in invoke
+ return ctx.invoke(self.callback, **ctx.params)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 760, in invoke
+ return __callback(*args, **kwargs)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 683, in wrapper
+ return callback(**use_params) # type: ignore
+ File "main.py", line 8, in main
+ print(name + 3)
+TypeError: can only concatenate str (not "int") to str
+```
+
+
+
+You could also achieve the same with the environment variable `_TYPER_STANDARD_TRACEBACK=1`.
+
+This will work for any other Typer program too, in case you need to debug a problem in a Typer program made by someone else:
+
+
+
+```console
+export _TYPER_STANDARD_TRACEBACK=1
+$ python main.py
+
+
+Traceback (most recent call last):
+ File "main.py", line 12, in
+ app()
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 328, in __call__
+ raise e
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 311, in __call__
+ return get_command(self)(*args, **kwargs)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1130, in __call__
+ return self.main(*args, **kwargs)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 723, in main
+ **extra,
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/core.py", line 216, in _main
+ rv = self.invoke(ctx)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 1404, in invoke
+ return ctx.invoke(self.callback, **ctx.params)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/click/core.py", line 760, in invoke
+ return __callback(*args, **kwargs)
+ File "/home/user/code/superapp/env/lib/python3.7/site-packages/typer/main.py", line 683, in wrapper
+ return callback(**use_params) # type: ignore
+ File "main.py", line 8, in main
+ print(name + 3)
+TypeError: can only concatenate str (not "int") to str
+```
+
+
diff --git a/docs/tutorial/first-steps.md b/docs/tutorial/first-steps.md
index bd69323a19..9ccccaabc7 100644
--- a/docs/tutorial/first-steps.md
+++ b/docs/tutorial/first-steps.md
@@ -6,9 +6,6 @@ The simplest **Typer** file could look like this:
{!../docs_src/first_steps/tutorial001.py!}
```
-!!! tip
- You will learn more about `typer.echo()` later in the docs.
-
Copy that to a file `main.py`.
Test it:
@@ -25,12 +22,20 @@ Hello World
// Now check the --help
$ python main.py --help
-Usage: main.py [OPTIONS]
-
-Options:
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+Usage: main.py [OPTIONS]
+
+╭─ Options ─────────────────────────────────────────╮
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────╯
```
@@ -75,10 +80,11 @@ Update the previous example with an argument `name`:
$ python main.py
// If you run it without the argument, it shows a nice error
-Usage: main.py [OPTIONS] NAME
-Try "main.py --help" for help.
-
-Error: Missing argument 'NAME'.
+Usage: main.py [OPTIONS] NAME
+Try 'main.py --help' for help.
+╭─ Error ───────────────────────────────────────────╮
+│ Missing argument 'NAME'. │
+╰───────────────────────────────────────────────────╯
// Now pass that NAME CLI argument
$ python main.py Camila
@@ -114,26 +120,30 @@ So, extend that to have 2 arguments, `name` and `lastname`:
// Check the main --help
$ python main.py --help
-Usage: main.py [OPTIONS] NAME LASTNAME
+Usage: main.py [OPTIONS] NAME
+Try 'main.py --help' for help.
+╭─ Error ───────────────────────────────────────────╮
+│ Missing argument 'NAME'. │
+╰───────────────────────────────────────────────────╯
-Arguments:
- NAME [required]
- LASTNAME [required]
-
-Options:
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+typer on richify[»!?] via 🐍 v3.7.5 (env3.7)
+❯ python main.py
+Usage: main.py [OPTIONS] NAME LASTNAME
+Try 'main.py --help' for help.
+╭─ Error ───────────────────────────────────────────╮
+│ Missing argument 'NAME'. │
+╰───────────────────────────────────────────────────╯
// There are now 2 CLI arguments, name and lastname
// Now pass a single name argument
$ python main.py Camila
-Usage: main.py [OPTIONS] NAME LASTNAME
-Try "main.py --help" for help.
-
-Error: Missing argument 'LASTNAME'.
+Usage: main.py [OPTIONS] NAME LASTNAME
+Try 'main.py --help' for help.
+╭─ Error ───────────────────────────────────────────╮
+│ Missing argument 'LASTNAME'. │
+╰───────────────────────────────────────────────────╯
// These 2 arguments are required, so, pass both:
$ python main.py Camila Gutiérrez
@@ -190,7 +200,7 @@ $ ls --size ./myproject
-The main visual difference between a *CLI option* and and a *CLI argument* is that the *CLI option* has `--` prepended to the name, like in "`--size`".
+The main visual difference between a *CLI option* and a *CLI argument* is that the *CLI option* has `--` prepended to the name, like in "`--size`".
A *CLI option* doesn't depend on the order because it has a predefined name (here it's `--size`). This is because the CLI app is looking specifically for a literal `--size` parameter (also known as "flag" or "switch"), with that specific "name" (here the specific name is "`--size`"). The CLI app will check if you typed it or not, it will be actively looking for `--size` even if you didn't type it (to check if it's there or not).
@@ -233,17 +243,23 @@ Here `formal` is a `bool` that is `False` by default.
// Get the help
$ python main.py --help
-Usage: main.py [OPTIONS] NAME LASTNAME
-
-Arguments:
- NAME [required]
- LASTNAME [required]
-
-Options:
- --formal / --no-formal [default: False]
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+Usage: main.py [OPTIONS] NAME LASTNAME
+
+╭─ Arguments ─────────────────────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+│ * lastname TEXT [default: None] [required] │
+╰─────────────────────────────────────────────────────────────────╯
+╭─ Options ───────────────────────────────────────────────────────╮
+│ --formal--no-formal [default: no-formal] │
+│ --install-completion Install completion for │
+│ the current shell. │
+│ --show-completion Show completion for │
+│ the current shell, to │
+│ copy it or customize │
+│ the installation. │
+│ --help Show this message and │
+│ exit. │
+╰─────────────────────────────────────────────────────────────────╯
```
@@ -292,17 +308,25 @@ As `lastname` now has a default value of `""` (an empty string) it is no longer
```console
$ python main.py --help
-Usage: main.py [OPTIONS] NAME
-
-Arguments:
- NAME [required]
-
-Options:
- --lastname TEXT [default: ]
- --formal / --no-formal [default: False]
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+Usage: main.py [OPTIONS] NAME
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --lastnameTEXT │
+│ --formal--no-formal [default: no-formal] │
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────────────────────╯
```
@@ -359,21 +383,28 @@ Now see it with the `--help` option:
```console
$ python main.py --help
-Usage: main.py [OPTIONS] NAME
-
- Say hi to NAME, optionally with a --lastname.
-
- If --formal is used, say hi very formally.
-
-Arguments:
- NAME [required]
-
-Options:
- --lastname TEXT [default: ]
- --formal / --no-formal [default: False]
- --install-completion Install completion for the current shell.
- --show-completion Show completion for the current shell, to copy it or customize the installation.
- --help Show this message and exit.
+Usage: main.py [OPTIONS] NAME
+
+ Say hi to NAME, optionally with a --lastname.
+ If --formal is used, say hi very formally.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --lastnameTEXT │
+│ --formal--no-formal [default: no-formal] │
+│ --install-completion Install completion │
+│ for the current │
+│ shell. │
+│ --show-completion Show completion for │
+│ the current shell, │
+│ to copy it or │
+│ customize the │
+│ installation. │
+│ --help Show this message │
+│ and exit. │
+╰───────────────────────────────────────────────────────────────────╯
```
@@ -401,14 +432,14 @@ are called "Python function parameters" or "Python function arguments".
It's quite technical... and somewhat pedantic.
- One refers to the variable name in a function *declaration*. Like:
-
+ *Parameter* refers to the variable name in a function *declaration*. Like:
+
```
def bring_person(name: str, lastname: str = ""):
pass
```
- The other refers to the value passed when *calling* a function. Like:
+ *Argument* refers to the value passed when *calling* a function. Like:
```
person = bring_person("Camila", lastname="Gutiérrez")
diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md
index 19ffc63f0a..7937d570e4 100644
--- a/docs/tutorial/index.md
+++ b/docs/tutorial/index.md
@@ -1,6 +1,6 @@
## Python types
-If you need a refreshed about how to use Python type hints, check the first part of FastAPI's Python types intro.
+If you need a refresher about how to use Python type hints, check the first part of FastAPI's Python types intro.
You can also check the mypy cheat sheet.
@@ -68,11 +68,11 @@ For the tutorial, you might want to install it with all the optional dependencie
-...that also includes `colorama` and `shellingham`.
+...that also includes `rich` and `shellingham`.
diff --git a/docs/tutorial/launch.md b/docs/tutorial/launch.md
index f328043f4d..90b3767b25 100644
--- a/docs/tutorial/launch.md
+++ b/docs/tutorial/launch.md
@@ -30,7 +30,7 @@ You can also make the operating system open the file browser indicating where a
!!! tip
The rest of the code in this example is just making sure the app directory exists and creating the config file.
-
+
But the most important part is the `typer.launch(config_file_str, locate=True)` with the argument `locate=True`.
Check it:
diff --git a/docs/tutorial/options/autocompletion.md b/docs/tutorial/options-autocompletion.md
similarity index 91%
rename from docs/tutorial/options/autocompletion.md
rename to docs/tutorial/options-autocompletion.md
index 4b4489efa4..c22f964aec 100644
--- a/docs/tutorial/options/autocompletion.md
+++ b/docs/tutorial/options-autocompletion.md
@@ -12,10 +12,10 @@ After installing completion (for your own Python package or for **Typer CLI**),
To check it quickly without creating a new Python package, install [Typer CLI](../../typer-cli.md){.internal-link target=_blank}.
-Then let's create small example script:
+Then let's create small example program:
```Python
-{!../docs_src/options/autocompletion/tutorial001.py!}
+{!../docs_src/options_autocompletion/tutorial001.py!}
```
And let's try it with **Typer CLI** to get completion:
@@ -50,8 +50,8 @@ Right now we get completion for the *CLI option* names, but not for the values.
We can provide completion for the values creating an `autocompletion` function, similar to the `callback` functions from [CLI Option Callback and Context](./callback-and-context.md){.internal-link target=_blank}:
-```Python hl_lines="4 5 10"
-{!../docs_src/options/autocompletion/tutorial002.py!}
+```Python hl_lines="4-5 14"
+{!../docs_src/options_autocompletion/tutorial002.py!}
```
We return a `list` of strings from the `complete_name()` function.
@@ -81,8 +81,8 @@ 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 hl_lines="6 7 8 9 10 11"
-{!../docs_src/options/autocompletion/tutorial003.py!}
+```Python hl_lines="6-11"
+{!../docs_src/options_autocompletion/tutorial003.py!}
```
Now let's try it:
@@ -120,7 +120,7 @@ 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 hl_lines="3 4 5 6 7 10 11 12 13 14 15 16"
-{!../docs_src/options/autocompletion/tutorial004.py!}
+{!../docs_src/options_autocompletion/tutorial004.py!}
```
!!! tip
@@ -157,7 +157,7 @@ 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 hl_lines="10 11 12 13"
-{!../docs_src/options/autocompletion/tutorial005.py!}
+{!../docs_src/options_autocompletion/tutorial005.py!}
```
That simplifies our code a bit and works the same.
@@ -185,8 +185,8 @@ So, we will allow multiple `--name` *CLI options*.
For this we use a `List` of `str`:
-```Python hl_lines="6 7 8"
-{!../docs_src/options/autocompletion/tutorial006.py!}
+```Python hl_lines="8-11"
+{!../docs_src/options_autocompletion/tutorial006.py!}
```
And then we can use it like:
@@ -213,7 +213,7 @@ 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 hl_lines="12 13 15"
-{!../docs_src/options/autocompletion/tutorial007.py!}
+{!../docs_src/options_autocompletion/tutorial007.py!}
```
We are getting the `names` already provided with `--name` in the command line before this completion was triggered.
@@ -222,7 +222,7 @@ If there's no `--name` in the command line, it will be `None`, so we use `or []`
Then, when we have a completion candidate, we check if each `name` was already provided with `--name` by checking if it's in that list of `names` with `name not in names`.
-And then we `yield` each item that has not being used yet.
+And then we `yield` each item that has not been used yet.
Check it:
@@ -277,14 +277,17 @@ Because completion is based on the output printed by your program (handled inter
The completion system only reads from "standard output", so, printing to "standard error" won't break completion. 🚀
-You can print to "standard error" with `typer.echo("some text", err=True)`.
+You can print to "standard error" with a **Rich** `Console(stderr=True)`.
-Using `err=True` tells **Typer** (actually Click) that the output should be shown in "standard error".
+Using `stderr=True` tells **Rich** that the output should be shown in "standard error".
-```Python hl_lines="12 13"
-{!../docs_src/options/autocompletion/tutorial008.py!}
+```Python hl_lines="12 15-16"
+{!../docs_src/options_autocompletion/tutorial008.py!}
```
+!!! info
+ If you can't install and use Rich, you can also use `print(lastname, file=sys.stderr)` or `typer.echo("some text", err=True)` instead.
+
We get all the *CLI parameters* as a raw `list` of `str` by declaring a parameter with type `List[str]`, here it's named `args`.
!!! tip
@@ -319,8 +322,8 @@ Sebastian -- The type hints guy.
Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, and the incomplete `str`:
-```Python hl_lines="12"
-{!../docs_src/options/autocompletion/tutorial009.py!}
+```Python hl_lines="15"
+{!../docs_src/options_autocompletion/tutorial009.py!}
```
Check it:
diff --git a/docs/tutorial/options/help.md b/docs/tutorial/options/help.md
index 0fa075c0aa..d139d1f13b 100644
--- a/docs/tutorial/options/help.md
+++ b/docs/tutorial/options/help.md
@@ -62,12 +62,67 @@ Options:
+## *CLI Options* help panels
+
+The same as with *CLI arguments*, you can put the help for some *CLI options* in different panels to be shown with the `--help` option.
+
+If you have installed Rich as described in the docs for [Printing and Colors](../printing.md){.internal-link target=_blank}, you can set the `rich_help_panel` parameter to the name of the panel you want for each *CLI option*:
+
+```Python hl_lines="8 11"
+{!../docs_src/options/help/tutorial002.py!}
+```
+
+Now, when you check the `--help` option, you will see a default panel named "`Options`" for the *CLI options* that don't have a custom `rich_help_panel`.
+
+And below you will see other panels for the *CLI options* that have a custom panel set in the `rich_help_panel` parameter:
+
+
+
+```console
+$ python main.py --help
+
+Usage: main.py [OPTIONS] NAME
+
+ Say hi to NAME, optionally with a --lastname.
+ If --formal is used, say hi very formally.
+
+╭─ Arguments ───────────────────────────────────────────────────────╮
+│ * name TEXT [default: None] [required] │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Options ─────────────────────────────────────────────────────────╮
+│ --lastnameTEXT Last name of person to greet. │
+│ --install-completion Install completion for the │
+│ current shell. │
+│ --show-completion Show completion for the current │
+│ shell, to copy it or customize │
+│ the installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────╯
+╭─ Customization and Utils ─────────────────────────────────────────╮
+│ --formal--no-formal Say hi formally. │
+│ [default: no-formal] │
+│ --debug--no-debug Enable debugging. │
+│ [default: no-debug] │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+
+
+Here we have a custom *CLI options* panel named "`Customization and Utils`".
+
+## Help with style using Rich
+
+In a future section you will see how to use custom markup in the `help` for *CLI options* when reading about [Commands - Command Help](../commands/help.md#rich-markdown-and-markup){.internal-link target=_blank}.
+
+If you are in a hurry you can jump there, but otherwise, it would be better to continue reading here and following the tutorial in order.
+
+
## Hide default from help
You can tell Typer to not show the default value in the help text with `show_default=False`:
```Python hl_lines="4"
-{!../docs_src/options/help/tutorial002.py!}
+{!../docs_src/options/help/tutorial003.py!}
```
And it will no longer show the default value in the help text:
diff --git a/docs/tutorial/options/name.md b/docs/tutorial/options/name.md
index cf037b8551..982e7e4d63 100644
--- a/docs/tutorial/options/name.md
+++ b/docs/tutorial/options/name.md
@@ -34,7 +34,7 @@ Here you are passing the string `"--name"` as the second positional argument to
!!! info
"Positional" means that it's not a function argument with a keyword name.
-
+
For example `show_default=True` is a keyword argument. "`show_default`" is the keyword.
But in `"--name"` there's no `option_name="--name"` or something similar, it's just the string value `"--name"` that goes in `typer.Option()` after the `...` value passed in the first position.
diff --git a/docs/tutorial/options/password.md b/docs/tutorial/options/password.md
index 2d5e1de0ac..2d2840f4bf 100644
--- a/docs/tutorial/options/password.md
+++ b/docs/tutorial/options/password.md
@@ -42,8 +42,8 @@ Check it:
$ python main.py Camila
// It prompts for the password, but doesn't show anything when you type
-# Password: $
-# Repeat for confirmation: $
+# Password: $
+# Repeat for confirmation: $
// Let's imagine the password typed was "typerrocks"
Hello Camila. Doing something very secure with password.
diff --git a/docs/tutorial/options/required.md b/docs/tutorial/options/required.md
index c7a6334f87..c81f3ec173 100644
--- a/docs/tutorial/options/required.md
+++ b/docs/tutorial/options/required.md
@@ -10,7 +10,7 @@ But if you really want, you can change that.
To make a *CLI option* required, pass `...` to `typer.Option()`.
!!! info
- If you hadn't seen that `...` before: it is a a special single value, it is part of Python and is called "Ellipsis".
+ 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.
diff --git a/docs/tutorial/package.md b/docs/tutorial/package.md
index dd4703f744..8ad35e60ab 100644
--- a/docs/tutorial/package.md
+++ b/docs/tutorial/package.md
@@ -49,7 +49,7 @@ Add `typer[all]` to your dependencies:
```console
-$ poetry add typer[all]
+$ poetry add "typer[all]"
// It creates a virtual environment for your project
Creating virtualenv rick-portal-gun-w31dJa0b-py3.6 in /home/rick/.cache/pypoetry/virtualenvs
@@ -142,7 +142,7 @@ def load():
```
!!! tip
- As we are creating an installable Python package, there's no need to add a section with `if __name__ == "__main__:`.
+ As we are creating an installable Python package, there's no need to add a section with `if __name__ == "__main__":`.
## Modify the README
diff --git a/docs/tutorial/parameter-types/bool.md b/docs/tutorial/parameter-types/bool.md
index 1d69f4796d..1f493eca8d 100644
--- a/docs/tutorial/parameter-types/bool.md
+++ b/docs/tutorial/parameter-types/bool.md
@@ -151,7 +151,7 @@ To do that, use a space and a single `/` and pass the negative name after:
!!! tip
Have in mind that it's a string with a preceding space and then a `/`.
-
+
So, it's `" /-S"` not `"/-S"`.
Check it:
diff --git a/docs/tutorial/parameter-types/datetime.md b/docs/tutorial/parameter-types/datetime.md
index afc4155bfb..a5a8b5183b 100644
--- a/docs/tutorial/parameter-types/datetime.md
+++ b/docs/tutorial/parameter-types/datetime.md
@@ -7,9 +7,10 @@ Your function will receive a standard Python `datetime` object, and again, your
```
Typer will accept any string from the following formats:
+
* `%Y-%m-%d`
* `%Y-%m-%dT%H:%M:%S`
-* `%Y-%m%d %H:%M:%S`
+* `%Y-%m-%d %H:%M:%S`
Check it:
@@ -48,7 +49,7 @@ Error: Invalid value for '[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': inval
You can also customize the formats received for the `datetime` with the `formats` parameter.
-`formats` receives a list of strings with the date formats that would be passed to datetime.strptime().
+`formats` receives a list of strings with the date formats that would be passed to datetime.strptime().
For example, let's imagine that you want to accept an ISO formatted datetime, but for some strange reason, you also want to accept a format with:
diff --git a/docs/tutorial/parameter-types/number.md b/docs/tutorial/parameter-types/number.md
index 4d29ea398c..d41322d6fe 100644
--- a/docs/tutorial/parameter-types/number.md
+++ b/docs/tutorial/parameter-types/number.md
@@ -148,4 +148,4 @@ $ python main.py -vvv
Verbose level is 3
```
-
\ No newline at end of file
+
diff --git a/docs/tutorial/parameter-types/uuid.md b/docs/tutorial/parameter-types/uuid.md
index 242c2063fd..0042f47061 100644
--- a/docs/tutorial/parameter-types/uuid.md
+++ b/docs/tutorial/parameter-types/uuid.md
@@ -8,11 +8,11 @@
```
d48edaa6-871a-4082-a196-4daab372d4a1
```
-
+
The way they are generated makes them sufficiently long and random that you could assume that every UUID generated is unique. Even if it was generated by a different application, database, or system.
So, if your system uses UUIDs to identify your data, you could mix it with the data from some other system that also uses UUIDs with some confidence that their IDs (UUIDs) won't clash with yours.
-
+
This wouldn't be true if you just used `int`s as identifiers, as most databases do.
diff --git a/docs/tutorial/printing.md b/docs/tutorial/printing.md
index 0ff754ffed..120c0facbc 100644
--- a/docs/tutorial/printing.md
+++ b/docs/tutorial/printing.md
@@ -1,15 +1,10 @@
-You can use `typer.echo()` to print to the screen:
+You can use the normal `print()` to show information on the screen:
```Python hl_lines="5"
{!../docs_src/first_steps/tutorial001.py!}
```
-The reason to use `typer.echo()` instead of just `print()` is that it applies some error corrections in case the terminal is misconfigured, and it will properly output color if it's supported.
-
-!!! info
- `typer.echo()` comes directly from Click, you can read more about it in Click's docs.
-
-Check it:
+It will show the output normally:
@@ -21,69 +16,130 @@ Hello World
-## Color
+## Use Rich
-!!! info
- For colors to work correctly on Windows you need to also install `colorama`.
+You can also display beautiful and more complex information using Rich.
- You don't need to call `colorama.init()`. Typer (actually Click) will handle it underneath.
+### Install Rich
- And make sure you use `typer.echo()` instead of `print()`.
+First, you need to install it:
-!!! note "Technical Details"
- The way color works in terminals is by using some codes (ASCII codes) as part of the text.
+
- So, a colored text is still just a `str`.
+```console
+// Rich comes with typer[all]
+$ pip install "typer[all]"
+---> 100%
+Successfully installed typer rich
+
+// Alternatively, you can install Rich independently
+$ pip install rich
+---> 100%
+Successfully installed rich
+```
-You can create colored strings to output to the terminal with `typer.style()`, that gives you `str`s that you can then pass to `typer.echo()`:
+
-```Python hl_lines="7 9"
+### Use Rich `print`
+
+For the simplest cases, you can just import `print` from `rich` and use it instead of the standard `print`:
+
+```Python hl_lines="2 15"
{!../docs_src/printing/tutorial001.py!}
```
-!!! 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"`.
+Just with that, **Rich** will be able to print your data with nice colors and structure:
- But **Typer** provides them all as variables like `typer.colors.GREEN` just so you can use autocompletion while selecting them.
+
-python main.py
-everything is good
-python main.py --no-good
-everything is bad
-You can pass these function arguments to `typer.style()`:
+### Rich Markup
-* `fg`: the foreground color.
-* `bg`: the background color.
-* `bold`: enable or disable bold mode.
-* `dim`: enable or disable dim mode. This is badly supported.
-* `underline`: enable or disable underline.
-* `blink`: enable or disable blinking.
-* `reverse`: enable or disable inverse rendering (foreground becomes background and the other way round).
-* `reset`: by default a reset-all code is added at the end of the string which means that styles do not carry over. This can be disabled to compose styles.
+Rich also supports a custom markup syntax to set colors and styles, for example:
-!!! info
- You can read more about it in Click's docs about `style()`
+```Python hl_lines="6"
+{!../docs_src/printing/tutorial002.py!}
+```
-## `typer.secho()` - style and print
+
-There's a shorter form to style and print at the same time with `typer.secho()` it's like `typer.echo()` but also adds style like `typer.style()`:
+```console
+$ python main.py
-```Python hl_lines="5"
-{!../docs_src/printing/tutorial002.py!}
+Alert! Portal gun shooting! 💥
```
-Check it:
+
+
+In this example you can see how to use font styles, colors, and even emojis.
+
+To learn more check out the Rich docs.
+
+### Rich Tables
+
+The way Rich works internally is that it uses a `Console` object to display the information.
+
+When you call Rich's `print`, it automatically creates this object and uses it.
+
+But for advanced use cases, you could create a `Console` yourself.
+
+```Python hl_lines="2-3 5 9-12"
+{!../docs_src/printing/tutorial003.py!}
+```
+
+In this example, we create a `Console`, and a `Table`. And then we can add some rows to the table, and print it.
+
+If you run it, you will see a nicely formatted table:
+
+
+Rich has many other features, as an example, you can check the docs for:
+
+* Prompt
+* Markdown
+* Panel
+* ...and more.
+
+### Typer and Rich
+
+If you are wondering what tool should be used for what, **Typer** is useful for structuring the command line application, with options, arguments, subcommands, data validation, etc.
+
+In general, **Typer** tends to be the entry point to your program, taking the first input from the user.
+
+**Rich** is useful for the parts that need to *display* information. Showing beautiful content on the screen.
+
+The best results for your command line application would be achieved combining both **Typer** and **Rich**.
+
## "Standard Output" and "Standard Error"
The way printing works underneath is that the **operating system** (Linux, Windows, macOS) treats what we print as if our CLI program was **writing text** to a "**virtual file**" called "**standard output**".
@@ -109,12 +165,15 @@ But we can also "print" to "standard error". And both are shown on the terminal
### Printing to "standard error"
-You can print to "standard error" with `typer.echo("some text", err=True)`.
+You can print to "standard error" creating a Rich `Console` with `stderr=True`.
-Using `err=True` tells **Typer** (actually Click) that the output should be shown in "standard error".
+!!! tip
+ `stderr` is short for "standard error".
-```Python hl_lines="5"
-{!../docs_src/printing/tutorial003.py!}
+Using `stderr=True` tells **Rich** that the output should be shown in "standard error".
+
+```Python hl_lines="4 8"
+{!../docs_src/printing/tutorial004.py!}
```
When you try it in the terminal, it will probably just look the same:
@@ -129,7 +188,7 @@ Here is something written to standard error
-### "Standard Input"
+## "Standard Input"
As a final detail, when you type text in your keyboard to your terminal, the operating system also considers it another "**virtual file**" that you are writing text to.
@@ -140,3 +199,91 @@ This virtual file is called "**standard input**".
Right now this probably seems quite useless 🤷♂.
But understanding that will come handy in the future, for example for autocompletion and testing.
+
+## Typer Echo
+
+!!! warning
+ In most of the cases, for displaying advanced information, it is recommended to use Rich.
+
+ You can probably skip the rest of this section. 🎉😎
+
+**Typer** also has a small utility `typer.echo()` to print information on the screen, it comes directly from Click. But normally you shouldn't need it.
+
+For the simplest cases, you can use the standard Python `print()`.
+
+And for the cases where you want to display data more beautifully, or more advanced content, you should use **Rich** instead.
+
+### Why `typer.echo`
+
+`typer.echo()` (which is actually just `click.echo()`) applies some checks to try and convert binary data to strings, and other similar things.
+
+But in most of the cases you wouldn't need it, as in modern Python strings (`str`) already support and use Unicode, and you would rarely deal with pure `bytes` that you want to print on the screen.
+
+If you have some `bytes` objects, you would probably want to decode them intentionally and directly before trying to print them.
+
+And if you want to print data with colors and other features, you are much better off with the more advanced tools in **Rich**.
+
+!!! info
+ `typer.echo()` comes directly from Click, you can read more about it in Click's docs.
+
+### Color
+
+!!! note "Technical Details"
+ The way color works in terminals is by using some codes (ANSI escape sequences) as part of the text.
+
+ So, a colored text is still just a `str`.
+
+!!! tip
+ Again, you are much better off using Rich for this. 😎
+
+You can create colored strings to output to the terminal with `typer.style()`, that gives you `str`s that you can then pass to `typer.echo()`:
+
+```Python hl_lines="7 9"
+{!../docs_src/printing/tutorial005.py!}
+```
+
+!!! tip
+ The parameters `fg` and `bg` receive strings with the color names for the "**f**ore**g**round" and "**b**ack**g**round" colors. You could simply pass `fg="green"` and `bg="red"`.
+
+ But **Typer** provides them all as variables like `typer.colors.GREEN` just so you can use autocompletion while selecting them.
+
+Check it:
+
+
+python main.py
+everything is good
+python main.py --no-good
+everything is bad
+
+
+You can pass these function arguments to `typer.style()`:
+
+* `fg`: the foreground color.
+* `bg`: the background color.
+* `bold`: enable or disable bold mode.
+* `dim`: enable or disable dim mode. This is badly supported.
+* `underline`: enable or disable underline.
+* `blink`: enable or disable blinking.
+* `reverse`: enable or disable inverse rendering (foreground becomes background and the other way round).
+* `reset`: by default a reset-all code is added at the end of the string which means that styles do not carry over. This can be disabled to compose styles.
+
+!!! info
+ You can read more about it in Click's docs about `style()`
+
+### `typer.secho()` - style and print
+
+!!! tip
+ In case you didn't see above, you are much better off using Rich for this. 😎
+
+There's a shorter form to style and print at the same time with `typer.secho()` it's like `typer.echo()` but also adds style like `typer.style()`:
+
+```Python hl_lines="5"
+{!../docs_src/printing/tutorial006.py!}
+```
+
+Check it:
+
+
+python main.py Camila
+Welcome here Camila
+
diff --git a/docs/tutorial/progressbar.md b/docs/tutorial/progressbar.md
index 3e2416404c..7b7bc166ab 100644
--- a/docs/tutorial/progressbar.md
+++ b/docs/tutorial/progressbar.md
@@ -1,12 +1,91 @@
-If you are executing an operation that can take some time, you can inform it to the user with a progress bar.
+If you are executing an operation that can take some time, you can inform it to the user. 🤓
-For this, you can use `typer.progressbar()`:
+## Progress Bar
-```Python hl_lines="8"
+You can use Rich's Progress Display to show a progress bar, for example:
+
+```Python hl_lines="4 9"
{!../docs_src/progressbar/tutorial001.py!}
```
-You use `typer.progressbar()` with a `with` statement, as in:
+You put the thing that you want to iterate over inside of Rich's `track()`, and then iterate over that.
+
+Check it:
+
+
+
+...actually, it will look a lot prettier. ✨ But I can't show you the animation here in the docs. 😅
+
+The colors and information will look something like this:
+
+
+
+## Spinner
+
+When you don't know how long the operation will take, you can use a spinner instead.
+
+Rich allows you to display many things in complex and advanced ways.
+
+For example, this will show two spinners:
+
+```Python hl_lines="4 8-15"
+{!../docs_src/progressbar/tutorial002.py!}
+```
+
+I can't show you the beautiful animation here in the docs. 😅
+
+But at some point in time it will look like this (imagine it's spinning). 🤓
+
+
+
+You can learn more about it in the Rich docs for Progress Display.
+
+## Typer `progressbar`
+
+If you can, you should use **Rich** as explained above, it has more features, it's more advanced, and can display information more beautifully. ✨
+
+!!! tip
+ If you can use Rich, use the information above, the Rich docs, and skip the rest of this page. 😎
+
+But if you can't use Rich, Typer (actually Click) comes with a simple utility to show progress bars.
+
+!!! info
+ `typer.progressbar()` comes directly from Click, you can read more about it in Click's docs.
+
+
+### Use `typer.progressbar`
+
+!!! tip
+ Remember, you are much better off using Rich for this. 😎
+
+You can use `typer.progressbar()` with a `with` statement, as in:
```Python
with typer.progressbar(something) as progress:
@@ -15,6 +94,10 @@ with typer.progressbar(something) as progress:
And you pass as function argument to `typer.progressbar()` the thing that you would normally iterate over.
+```Python hl_lines="8"
+{!../docs_src/progressbar/tutorial003.py!}
+```
+
So, if you have a list of users, this could be:
```Python
@@ -58,14 +141,17 @@ Processed 100 things.
-## Setting a Progress Bar `length`
+### Setting a Progress Bar `length`
+
+!!! tip
+ Remember, you are much better off using Rich for this. 😎
The progress bar is generated from the length of the iterable (e.g. the list of users).
But if the length is not available (for example, with something that fetches a new user from a web API each time) you can pass an explicit `length` to `typer.progressbar()`.
```Python hl_lines="14"
-{!../docs_src/progressbar/tutorial002.py!}
+{!../docs_src/progressbar/tutorial004.py!}
```
Check it:
@@ -82,7 +168,7 @@ Processed 100 user IDs.
-### About the function with `yield`
+#### About the function with `yield`
If you hadn't seen something like that `yield` above, that's a "generator".
@@ -104,12 +190,15 @@ for i in iterate_user_ids():
would print each of the "user IDs" (here it's just the numbers from `0` to `99`).
-## Add a `label`
+### Add a `label`
+
+!!! tip
+ Remember, you are much better off using Rich for this. 😎
You can also set a `label`:
```Python hl_lines="8"
-{!../docs_src/progressbar/tutorial003.py!}
+{!../docs_src/progressbar/tutorial005.py!}
```
Check it:
@@ -127,7 +216,7 @@ If you need to manually iterate over something and update the progress bar irreg
And then calling the `.update()` method in the object from the `with` statement:
```Python hl_lines="8 12"
-{!../docs_src/progressbar/tutorial004.py!}
+{!../docs_src/progressbar/tutorial006.py!}
```
Check it:
diff --git a/docs/tutorial/prompt.md b/docs/tutorial/prompt.md
index 6928019cb3..ef3c3e0a12 100644
--- a/docs/tutorial/prompt.md
+++ b/docs/tutorial/prompt.md
@@ -76,3 +76,25 @@ Aborted!
```
+
+## Prompt with Rich
+
+If you installed Rich as described in [Printing and Colors](printing.md){.internal-link target=_blank}, you can use Rich to prompt the user for input:
+
+```Python hl_lines="2 6"
+{!../docs_src/prompt/tutorial004.py!}
+```
+
+And when you run it, it will look like:
+
+
+
+```console
+$ python main.py
+
+# Enter your name 😎:$ Morty
+
+Hello Morty
+```
+
+
diff --git a/docs/tutorial/subcommands/callback-override.md b/docs/tutorial/subcommands/callback-override.md
index 341de9552f..6a2683e9e3 100644
--- a/docs/tutorial/subcommands/callback-override.md
+++ b/docs/tutorial/subcommands/callback-override.md
@@ -101,4 +101,4 @@ I have the high land! Running users command
Creating user: Camila
```
-
\ No newline at end of file
+
diff --git a/docs/tutorial/terminating.md b/docs/tutorial/terminating.md
index b227ac5b22..94f6d239e0 100644
--- a/docs/tutorial/terminating.md
+++ b/docs/tutorial/terminating.md
@@ -41,10 +41,10 @@ The user already exists
!!! tip
- Even though you are rasing an exception, it doesn't necessarily mean there's an error.
+ Even though you are raising an exception, it doesn't necessarily mean there's an error.
This is done with an exception because it works as an "error" and stops all execution.
-
+
But then **Typer** (actually Click) catches it and just terminates the program normally.
## Exit with an error
diff --git a/docs/tutorial/testing.md b/docs/tutorial/testing.md
index 108ffc344f..c8b2b76121 100644
--- a/docs/tutorial/testing.md
+++ b/docs/tutorial/testing.md
@@ -70,7 +70,7 @@ 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".
+ 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}.
diff --git a/docs_src/app_dir/tutorial001.py b/docs_src/app_dir/tutorial001.py
index b288b5fa84..2154957ec7 100644
--- a/docs_src/app_dir/tutorial001.py
+++ b/docs_src/app_dir/tutorial001.py
@@ -9,7 +9,7 @@ def main():
app_dir = typer.get_app_dir(APP_NAME)
config_path: Path = Path(app_dir) / "config.json"
if not config_path.is_file():
- typer.echo("Config file doesn't exist yet")
+ print("Config file doesn't exist yet")
if __name__ == "__main__":
diff --git a/docs_src/arguments/default/tutorial001.py b/docs_src/arguments/default/tutorial001.py
index 538397c960..eaaa85d4bb 100644
--- a/docs_src/arguments/default/tutorial001.py
+++ b/docs_src/arguments/default/tutorial001.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument("Wade Wilson")):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/default/tutorial002.py b/docs_src/arguments/default/tutorial002.py
index 4f7b900bed..71edea2754 100644
--- a/docs_src/arguments/default/tutorial002.py
+++ b/docs_src/arguments/default/tutorial002.py
@@ -8,7 +8,7 @@ def get_name():
def main(name: str = typer.Argument(get_name)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/envvar/tutorial001.py b/docs_src/arguments/envvar/tutorial001.py
index 568ac99779..5f16273e63 100644
--- a/docs_src/arguments/envvar/tutorial001.py
+++ b/docs_src/arguments/envvar/tutorial001.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument("World", envvar="AWESOME_NAME")):
- typer.echo(f"Hello Mr. {name}")
+ print(f"Hello Mr. {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/envvar/tutorial002.py b/docs_src/arguments/envvar/tutorial002.py
index 93f35ab9ff..cf9d0303cd 100644
--- a/docs_src/arguments/envvar/tutorial002.py
+++ b/docs_src/arguments/envvar/tutorial002.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument("World", envvar=["AWESOME_NAME", "GOD_NAME"])):
- typer.echo(f"Hello Mr. {name}")
+ print(f"Hello Mr. {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/envvar/tutorial003.py b/docs_src/arguments/envvar/tutorial003.py
index 2b64059ce3..4dfb62f1e5 100644
--- a/docs_src/arguments/envvar/tutorial003.py
+++ b/docs_src/arguments/envvar/tutorial003.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument("World", envvar="AWESOME_NAME", show_envvar=False)):
- typer.echo(f"Hello Mr. {name}")
+ print(f"Hello Mr. {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial001.py b/docs_src/arguments/help/tutorial001.py
index e6774df4b1..dadbd7b626 100644
--- a/docs_src/arguments/help/tutorial001.py
+++ b/docs_src/arguments/help/tutorial001.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument(..., help="The name of the user to greet")):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial002.py b/docs_src/arguments/help/tutorial002.py
index 25868287fb..170a411594 100644
--- a/docs_src/arguments/help/tutorial002.py
+++ b/docs_src/arguments/help/tutorial002.py
@@ -5,7 +5,7 @@ def main(name: str = typer.Argument(..., help="The name of the user to greet")):
"""
Say hi to NAME very gently, like Dirk.
"""
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial003.py b/docs_src/arguments/help/tutorial003.py
index 4cc9407c71..237985f0e9 100644
--- a/docs_src/arguments/help/tutorial003.py
+++ b/docs_src/arguments/help/tutorial003.py
@@ -5,7 +5,7 @@ def main(name: str = typer.Argument("World", help="Who to greet")):
"""
Say hi to NAME very gently, like Dirk.
"""
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial004.py b/docs_src/arguments/help/tutorial004.py
index c0fcdb9586..179eb67b92 100644
--- a/docs_src/arguments/help/tutorial004.py
+++ b/docs_src/arguments/help/tutorial004.py
@@ -5,7 +5,7 @@ def main(name: str = typer.Argument("World", help="Who to greet", show_default=F
"""
Say hi to NAME very gently, like Dirk.
"""
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial005.py b/docs_src/arguments/help/tutorial005.py
index 407e3ab45e..55b66b9715 100644
--- a/docs_src/arguments/help/tutorial005.py
+++ b/docs_src/arguments/help/tutorial005.py
@@ -6,7 +6,7 @@ def main(
"Wade Wilson", help="Who to greet", show_default="Deadpoolio the amazing's name"
)
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial006.py b/docs_src/arguments/help/tutorial006.py
index 139292465d..e502a506f4 100644
--- a/docs_src/arguments/help/tutorial006.py
+++ b/docs_src/arguments/help/tutorial006.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument("World", metavar="✨username✨")):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial007.py b/docs_src/arguments/help/tutorial007.py
index 440f2eb481..9738f4240e 100644
--- a/docs_src/arguments/help/tutorial007.py
+++ b/docs_src/arguments/help/tutorial007.py
@@ -1,11 +1,19 @@
import typer
-def main(name: str = typer.Argument("World", hidden=True)):
+def main(
+ name: str = typer.Argument(..., help="Who to greet"),
+ lastname: str = typer.Argument(
+ "", help="The last name", rich_help_panel="Secondary Arguments"
+ ),
+ age: str = typer.Argument(
+ "", help="The user's age", rich_help_panel="Secondary Arguments"
+ ),
+):
"""
Say hi to NAME very gently, like Dirk.
"""
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/help/tutorial008.py b/docs_src/arguments/help/tutorial008.py
new file mode 100644
index 0000000000..6701ec1de5
--- /dev/null
+++ b/docs_src/arguments/help/tutorial008.py
@@ -0,0 +1,12 @@
+import typer
+
+
+def main(name: str = typer.Argument("World", hidden=True)):
+ """
+ Say hi to NAME very gently, like Dirk.
+ """
+ print(f"Hello {name}")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/arguments/optional/tutorial001.py b/docs_src/arguments/optional/tutorial001.py
index e0412c8335..8a30344394 100644
--- a/docs_src/arguments/optional/tutorial001.py
+++ b/docs_src/arguments/optional/tutorial001.py
@@ -2,7 +2,7 @@
def main(name: str = typer.Argument(...)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/arguments/optional/tutorial002.py b/docs_src/arguments/optional/tutorial002.py
index db1a660897..9d0014da04 100644
--- a/docs_src/arguments/optional/tutorial002.py
+++ b/docs_src/arguments/optional/tutorial002.py
@@ -5,9 +5,9 @@
def main(name: Optional[str] = typer.Argument(None)):
if name is None:
- typer.echo("Hello World!")
+ print("Hello World!")
else:
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/commands/arguments/tutorial001.py b/docs_src/commands/arguments/tutorial001.py
index 32f8eb53fa..8178b2dfa7 100644
--- a/docs_src/commands/arguments/tutorial001.py
+++ b/docs_src/commands/arguments/tutorial001.py
@@ -5,12 +5,12 @@
@app.command()
def create(username: str):
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command()
def delete(username: str):
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
if __name__ == "__main__":
diff --git a/docs_src/commands/callback/tutorial001.py b/docs_src/commands/callback/tutorial001.py
index 26eff8bccb..5dd5784f6e 100644
--- a/docs_src/commands/callback/tutorial001.py
+++ b/docs_src/commands/callback/tutorial001.py
@@ -7,19 +7,19 @@
@app.command()
def create(username: str):
if state["verbose"]:
- typer.echo("About to create a user")
- typer.echo(f"Creating user: {username}")
+ print("About to create a user")
+ print(f"Creating user: {username}")
if state["verbose"]:
- typer.echo("Just created a user")
+ print("Just created a user")
@app.command()
def delete(username: str):
if state["verbose"]:
- typer.echo("About to delete a user")
- typer.echo(f"Deleting user: {username}")
+ print("About to delete a user")
+ print(f"Deleting user: {username}")
if state["verbose"]:
- typer.echo("Just deleted a user")
+ print("Just deleted a user")
@app.callback()
@@ -28,7 +28,7 @@ def main(verbose: bool = False):
Manage users in the awesome CLI app.
"""
if verbose:
- typer.echo("Will write verbose output")
+ print("Will write verbose output")
state["verbose"] = True
diff --git a/docs_src/commands/callback/tutorial002.py b/docs_src/commands/callback/tutorial002.py
index 4ab4d5342b..cb20ace884 100644
--- a/docs_src/commands/callback/tutorial002.py
+++ b/docs_src/commands/callback/tutorial002.py
@@ -2,7 +2,7 @@
def callback():
- typer.echo("Running a command")
+ print("Running a command")
app = typer.Typer(callback=callback)
@@ -10,7 +10,7 @@ def callback():
@app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/commands/callback/tutorial003.py b/docs_src/commands/callback/tutorial003.py
index 72975deb0c..75c5bc1fa1 100644
--- a/docs_src/commands/callback/tutorial003.py
+++ b/docs_src/commands/callback/tutorial003.py
@@ -2,7 +2,7 @@
def callback():
- typer.echo("Running a command")
+ print("Running a command")
app = typer.Typer(callback=callback)
@@ -10,12 +10,12 @@ def callback():
@app.callback()
def new_callback():
- typer.echo("Override callback, running a command")
+ print("Override callback, running a command")
@app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/commands/callback/tutorial004.py b/docs_src/commands/callback/tutorial004.py
index 4bc2b0c7c1..fcc9dca3cc 100644
--- a/docs_src/commands/callback/tutorial004.py
+++ b/docs_src/commands/callback/tutorial004.py
@@ -9,14 +9,14 @@ def callback():
Manage users CLI app.
Use it with the create command.
-
+
A new user with the given NAME will be created.
"""
@app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/commands/context/tutorial001.py b/docs_src/commands/context/tutorial001.py
index 2a9bb682ab..5262df9146 100644
--- a/docs_src/commands/context/tutorial001.py
+++ b/docs_src/commands/context/tutorial001.py
@@ -5,12 +5,12 @@
@app.command()
def create(username: str):
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command()
def delete(username: str):
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
@app.callback()
@@ -18,7 +18,7 @@ def main(ctx: typer.Context):
"""
Manage users in the awesome CLI app.
"""
- typer.echo(f"About to execute command: {ctx.invoked_subcommand}")
+ print(f"About to execute command: {ctx.invoked_subcommand}")
if __name__ == "__main__":
diff --git a/docs_src/commands/context/tutorial002.py b/docs_src/commands/context/tutorial002.py
index 096058dd95..e396485242 100644
--- a/docs_src/commands/context/tutorial002.py
+++ b/docs_src/commands/context/tutorial002.py
@@ -5,12 +5,12 @@
@app.command()
def create(username: str):
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command()
def delete(username: str):
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
@app.callback(invoke_without_command=True)
@@ -18,7 +18,7 @@ def main():
"""
Manage users in the awesome CLI app.
"""
- typer.echo("Initializing database")
+ print("Initializing database")
if __name__ == "__main__":
diff --git a/docs_src/commands/context/tutorial003.py b/docs_src/commands/context/tutorial003.py
index e4470e0b26..4782d47e18 100644
--- a/docs_src/commands/context/tutorial003.py
+++ b/docs_src/commands/context/tutorial003.py
@@ -5,12 +5,12 @@
@app.command()
def create(username: str):
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command()
def delete(username: str):
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
@app.callback(invoke_without_command=True)
@@ -19,7 +19,7 @@ def main(ctx: typer.Context):
Manage users in the awesome CLI app.
"""
if ctx.invoked_subcommand is None:
- typer.echo("Initializing database")
+ print("Initializing database")
if __name__ == "__main__":
diff --git a/docs_src/commands/context/tutorial004.py b/docs_src/commands/context/tutorial004.py
index 9280aed636..d040103457 100644
--- a/docs_src/commands/context/tutorial004.py
+++ b/docs_src/commands/context/tutorial004.py
@@ -8,7 +8,7 @@
)
def main(ctx: typer.Context):
for extra_arg in ctx.args:
- typer.echo(f"Got extra arg: {extra_arg}")
+ print(f"Got extra arg: {extra_arg}")
if __name__ == "__main__":
diff --git a/docs_src/commands/help/tutorial001.py b/docs_src/commands/help/tutorial001.py
index 3ac6bf7e41..dda530655d 100644
--- a/docs_src/commands/help/tutorial001.py
+++ b/docs_src/commands/help/tutorial001.py
@@ -8,7 +8,7 @@ def create(username: str):
"""
Create a new user with USERNAME.
"""
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command()
@@ -26,9 +26,9 @@ def delete(
If --force is not used, will ask for confirmation.
"""
if force:
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
else:
- typer.echo("Operation cancelled")
+ print("Operation cancelled")
@app.command()
@@ -45,9 +45,9 @@ def delete_all(
If --force is not used, will ask for confirmation.
"""
if force:
- typer.echo("Deleting all users")
+ print("Deleting all users")
else:
- typer.echo("Operation cancelled")
+ print("Operation cancelled")
@app.command()
@@ -55,7 +55,7 @@ def init():
"""
Initialize the users database.
"""
- typer.echo("Initializing user database")
+ print("Initializing user database")
if __name__ == "__main__":
diff --git a/docs_src/commands/help/tutorial002.py b/docs_src/commands/help/tutorial002.py
index d098d23518..c3442f071c 100644
--- a/docs_src/commands/help/tutorial002.py
+++ b/docs_src/commands/help/tutorial002.py
@@ -8,7 +8,7 @@ def create(username: str):
"""
Some internal utility function to create.
"""
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command(help="Delete a user with USERNAME.")
@@ -16,7 +16,7 @@ def delete(username: str):
"""
Some internal utility function to delete.
"""
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
if __name__ == "__main__":
diff --git a/docs_src/commands/help/tutorial003.py b/docs_src/commands/help/tutorial003.py
new file mode 100644
index 0000000000..6ea50b081b
--- /dev/null
+++ b/docs_src/commands/help/tutorial003.py
@@ -0,0 +1,25 @@
+import typer
+
+app = typer.Typer()
+
+
+@app.command()
+def create(username: str):
+ """
+ Create a user.
+ """
+ print(f"Creating user: {username}")
+
+
+@app.command(deprecated=True)
+def delete(username: str):
+ """
+ Delete a user.
+
+ This is deprecated and will stop being supported soon.
+ """
+ print(f"Deleting user: {username}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/commands/help/tutorial004.py b/docs_src/commands/help/tutorial004.py
new file mode 100644
index 0000000000..c3ed3f7385
--- /dev/null
+++ b/docs_src/commands/help/tutorial004.py
@@ -0,0 +1,34 @@
+import typer
+
+app = typer.Typer(rich_markup_mode="rich")
+
+
+@app.command()
+def create(
+ username: str = typer.Argument(
+ ..., help="The username to be [green]created[/green]"
+ )
+):
+ """
+ [bold green]Create[/bold green] a new [italic]shinny[/italic] user. :sparkles:
+
+ This requires a [underline]username[/underline].
+ """
+ print(f"Creating user: {username}")
+
+
+@app.command(help="[bold red]Delete[/bold red] a user with [italic]USERNAME[/italic].")
+def delete(
+ username: str = typer.Argument(..., help="The username to be [red]deleted[/red]"),
+ force: bool = typer.Option(
+ False, help="Force the [bold red]deletion[/bold red] :boom:"
+ ),
+):
+ """
+ Some internal utility function to delete.
+ """
+ print(f"Deleting user: {username}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/commands/help/tutorial005.py b/docs_src/commands/help/tutorial005.py
new file mode 100644
index 0000000000..d87c117ee3
--- /dev/null
+++ b/docs_src/commands/help/tutorial005.py
@@ -0,0 +1,34 @@
+import typer
+
+app = typer.Typer(rich_markup_mode="markdown")
+
+
+@app.command()
+def create(username: str = typer.Argument(..., help="The username to be **created**")):
+ """
+ **Create** a new *shinny* user. :sparkles:
+
+ * Create a username
+
+ * Show that the username is created
+
+ ---
+
+ Learn more at the [Typer docs website](https://typer.tiangolo.com)
+ """
+ print(f"Creating user: {username}")
+
+
+@app.command(help="**Delete** a user with *USERNAME*.")
+def delete(
+ username: str = typer.Argument(..., help="The username to be **deleted**"),
+ force: bool = typer.Option(False, help="Force the **deletion** :boom:"),
+):
+ """
+ Some internal utility function to delete.
+ """
+ print(f"Deleting user: {username}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/commands/help/tutorial006.py b/docs_src/commands/help/tutorial006.py
new file mode 100644
index 0000000000..b10ad75ad1
--- /dev/null
+++ b/docs_src/commands/help/tutorial006.py
@@ -0,0 +1,55 @@
+import typer
+
+app = typer.Typer(rich_markup_mode="rich")
+
+
+@app.command()
+def create(username: str):
+ """
+ [green]Create[/green] a new user. :sparkles:
+ """
+ print(f"Creating user: {username}")
+
+
+@app.command()
+def delete(username: str):
+ """
+ [red]Delete[/red] a user. :fire:
+ """
+ print(f"Deleting user: {username}")
+
+
+@app.command(rich_help_panel="Utils and Configs")
+def config(configuration: str):
+ """
+ [blue]Configure[/blue] the system. :wrench:
+ """
+ print(f"Configuring the system with: {configuration}")
+
+
+@app.command(rich_help_panel="Utils and Configs")
+def sync():
+ """
+ [blue]Synchronize[/blue] the system or something fancy like that. :recycle:
+ """
+ print("Syncing the system")
+
+
+@app.command(rich_help_panel="Help and Others")
+def help():
+ """
+ Get [yellow]help[/yellow] with the system. :question:
+ """
+ print("Opening help portal...")
+
+
+@app.command(rich_help_panel="Help and Others")
+def report():
+ """
+ [yellow]Report[/yellow] an issue. :bug:
+ """
+ print("Please open a new issue online, not a direct message")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/commands/help/tutorial007.py b/docs_src/commands/help/tutorial007.py
new file mode 100644
index 0000000000..6d59ed6ab3
--- /dev/null
+++ b/docs_src/commands/help/tutorial007.py
@@ -0,0 +1,39 @@
+from typing import Union
+
+import typer
+
+app = typer.Typer(rich_markup_mode="rich")
+
+
+@app.command()
+def create(
+ username: str = typer.Argument(..., help="The username to create"),
+ lastname: str = typer.Argument(
+ "", help="The last name of the new user", rich_help_panel="Secondary Arguments"
+ ),
+ force: bool = typer.Option(False, help="Force the creation of the user"),
+ age: Union[int, None] = typer.Option(
+ None, help="The age of the new user", rich_help_panel="Additional Data"
+ ),
+ favorite_color: Union[str, None] = typer.Option(
+ None,
+ help="The favorite color of the new user",
+ rich_help_panel="Additional Data",
+ ),
+):
+ """
+ [green]Create[/green] a new user. :sparkles:
+ """
+ print(f"Creating user: {username}")
+
+
+@app.command(rich_help_panel="Utils and Configs")
+def config(configuration: str):
+ """
+ [blue]Configure[/blue] the system. :wrench:
+ """
+ print(f"Configuring the system with: {configuration}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/commands/help/tutorial008.py b/docs_src/commands/help/tutorial008.py
new file mode 100644
index 0000000000..a4f8330e93
--- /dev/null
+++ b/docs_src/commands/help/tutorial008.py
@@ -0,0 +1,15 @@
+import typer
+
+app = typer.Typer(rich_markup_mode="rich")
+
+
+@app.command(epilog="Made with :heart: in [blue]Venus[/blue]")
+def create(username: str):
+ """
+ [green]Create[/green] a new user. :sparkles:
+ """
+ print(f"Creating user: {username}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/commands/index/tutorial001.py b/docs_src/commands/index/tutorial001.py
index 3c37104fa1..2377d549ed 100644
--- a/docs_src/commands/index/tutorial001.py
+++ b/docs_src/commands/index/tutorial001.py
@@ -5,7 +5,7 @@
@app.command()
def main(name: str):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/commands/index/tutorial002.py b/docs_src/commands/index/tutorial002.py
index 6afab4aaae..1ed8610db8 100644
--- a/docs_src/commands/index/tutorial002.py
+++ b/docs_src/commands/index/tutorial002.py
@@ -5,12 +5,12 @@
@app.command()
def create():
- typer.echo("Creating user: Hiro Hamada")
+ print("Creating user: Hiro Hamada")
@app.command()
def delete():
- typer.echo("Deleting user: Hiro Hamada")
+ print("Deleting user: Hiro Hamada")
if __name__ == "__main__":
diff --git a/docs_src/commands/name/tutorial001.py b/docs_src/commands/name/tutorial001.py
index 4f6aea4ebe..cda0ef09e6 100644
--- a/docs_src/commands/name/tutorial001.py
+++ b/docs_src/commands/name/tutorial001.py
@@ -5,12 +5,12 @@
@app.command("create")
def cli_create_user(username: str):
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command("delete")
def cli_delete_user(username: str):
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
if __name__ == "__main__":
diff --git a/docs_src/commands/one_or_multiple/tutorial001.py b/docs_src/commands/one_or_multiple/tutorial001.py
index d0ad1155ae..cf6bdb7ee6 100644
--- a/docs_src/commands/one_or_multiple/tutorial001.py
+++ b/docs_src/commands/one_or_multiple/tutorial001.py
@@ -5,7 +5,7 @@
@app.command()
def create():
- typer.echo("Creating user: Hiro Hamada")
+ print("Creating user: Hiro Hamada")
@app.callback()
diff --git a/docs_src/commands/one_or_multiple/tutorial002.py b/docs_src/commands/one_or_multiple/tutorial002.py
index 27ac47f814..db786fc235 100644
--- a/docs_src/commands/one_or_multiple/tutorial002.py
+++ b/docs_src/commands/one_or_multiple/tutorial002.py
@@ -5,7 +5,7 @@
@app.command()
def create():
- typer.echo("Creating user: Hiro Hamada")
+ print("Creating user: Hiro Hamada")
@app.callback()
diff --git a/docs_src/commands/options/tutorial001.py b/docs_src/commands/options/tutorial001.py
index 0b694cd5c0..bdc795f520 100644
--- a/docs_src/commands/options/tutorial001.py
+++ b/docs_src/commands/options/tutorial001.py
@@ -5,7 +5,7 @@
@app.command()
def create(username: str):
- typer.echo(f"Creating user: {username}")
+ print(f"Creating user: {username}")
@app.command()
@@ -14,9 +14,9 @@ def delete(
force: bool = typer.Option(..., prompt="Are you sure you want to delete the user?"),
):
if force:
- typer.echo(f"Deleting user: {username}")
+ print(f"Deleting user: {username}")
else:
- typer.echo("Operation cancelled")
+ print("Operation cancelled")
@app.command()
@@ -24,14 +24,14 @@ def delete_all(
force: bool = typer.Option(..., prompt="Are you sure you want to delete ALL users?")
):
if force:
- typer.echo("Deleting all users")
+ print("Deleting all users")
else:
- typer.echo("Operation cancelled")
+ print("Operation cancelled")
@app.command()
def init():
- typer.echo("Initializing user database")
+ print("Initializing user database")
if __name__ == "__main__":
diff --git a/docs_src/exceptions/tutorial001.py b/docs_src/exceptions/tutorial001.py
new file mode 100644
index 0000000000..071c28c3af
--- /dev/null
+++ b/docs_src/exceptions/tutorial001.py
@@ -0,0 +1,9 @@
+import typer
+
+
+def main(name: str = "morty"):
+ print(name + 3)
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/exceptions/tutorial002.py b/docs_src/exceptions/tutorial002.py
new file mode 100644
index 0000000000..9c66a0bf0c
--- /dev/null
+++ b/docs_src/exceptions/tutorial002.py
@@ -0,0 +1,12 @@
+import typer
+
+app = typer.Typer(pretty_exceptions_show_locals=False)
+
+
+@app.command()
+def main(password: str):
+ print(password + 3)
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/exceptions/tutorial003.py b/docs_src/exceptions/tutorial003.py
new file mode 100644
index 0000000000..656203c566
--- /dev/null
+++ b/docs_src/exceptions/tutorial003.py
@@ -0,0 +1,12 @@
+import typer
+
+app = typer.Typer(pretty_exceptions_short=False)
+
+
+@app.command()
+def main(name: str = "morty"):
+ print(name + 3)
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/exceptions/tutorial004.py b/docs_src/exceptions/tutorial004.py
new file mode 100644
index 0000000000..660c58ecc3
--- /dev/null
+++ b/docs_src/exceptions/tutorial004.py
@@ -0,0 +1,12 @@
+import typer
+
+app = typer.Typer(pretty_exceptions_enable=False)
+
+
+@app.command()
+def main(name: str = "morty"):
+ print(name + 3)
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/first_steps/tutorial001.py b/docs_src/first_steps/tutorial001.py
index 4c5d803239..94897dafef 100644
--- a/docs_src/first_steps/tutorial001.py
+++ b/docs_src/first_steps/tutorial001.py
@@ -2,7 +2,7 @@
def main():
- typer.echo("Hello World")
+ print("Hello World")
if __name__ == "__main__":
diff --git a/docs_src/first_steps/tutorial002.py b/docs_src/first_steps/tutorial002.py
index da2fac3e99..bb1e5c7b5f 100644
--- a/docs_src/first_steps/tutorial002.py
+++ b/docs_src/first_steps/tutorial002.py
@@ -2,7 +2,7 @@
def main(name: str):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/first_steps/tutorial003.py b/docs_src/first_steps/tutorial003.py
index daa11ca889..377b045fa1 100644
--- a/docs_src/first_steps/tutorial003.py
+++ b/docs_src/first_steps/tutorial003.py
@@ -2,7 +2,7 @@
def main(name: str, lastname: str):
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/first_steps/tutorial004.py b/docs_src/first_steps/tutorial004.py
index 1e5b964424..4a11dc020f 100644
--- a/docs_src/first_steps/tutorial004.py
+++ b/docs_src/first_steps/tutorial004.py
@@ -3,9 +3,9 @@
def main(name: str, lastname: str, formal: bool = False):
if formal:
- typer.echo(f"Good day Ms. {name} {lastname}.")
+ print(f"Good day Ms. {name} {lastname}.")
else:
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/first_steps/tutorial005.py b/docs_src/first_steps/tutorial005.py
index 3c4036296b..25bc963a90 100644
--- a/docs_src/first_steps/tutorial005.py
+++ b/docs_src/first_steps/tutorial005.py
@@ -3,9 +3,9 @@
def main(name: str, lastname: str = "", formal: bool = False):
if formal:
- typer.echo(f"Good day Ms. {name} {lastname}.")
+ print(f"Good day Ms. {name} {lastname}.")
else:
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/first_steps/tutorial006.py b/docs_src/first_steps/tutorial006.py
index d373bbcb5b..102ce8d70e 100644
--- a/docs_src/first_steps/tutorial006.py
+++ b/docs_src/first_steps/tutorial006.py
@@ -8,9 +8,9 @@ def main(name: str, lastname: str = "", formal: bool = False):
If --formal is used, say hi very formally.
"""
if formal:
- typer.echo(f"Good day Ms. {name} {lastname}.")
+ print(f"Good day Ms. {name} {lastname}.")
else:
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/launch/tutorial001.py b/docs_src/launch/tutorial001.py
index 892faa2091..4b5145f386 100644
--- a/docs_src/launch/tutorial001.py
+++ b/docs_src/launch/tutorial001.py
@@ -2,7 +2,7 @@
def main():
- typer.echo("Opening Typer's docs")
+ print("Opening Typer's docs")
typer.launch("https://typer.tiangolo.com")
diff --git a/docs_src/launch/tutorial002.py b/docs_src/launch/tutorial002.py
index 5ef13ec941..752513ca9f 100644
--- a/docs_src/launch/tutorial002.py
+++ b/docs_src/launch/tutorial002.py
@@ -13,7 +13,7 @@ def main():
if not config_path.is_file():
config_path.write_text('{"version": "1.0.0"}')
config_file_str = str(config_path)
- typer.echo("Opening config directory")
+ print("Opening config directory")
typer.launch(config_file_str, locate=True)
diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py
index 5e04072142..32782fefb4 100644
--- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py
+++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial001.py
@@ -7,8 +7,8 @@
def main(files: List[Path], celebration: str):
for path in files:
if path.is_file():
- typer.echo(f"This file exists: {path.name}")
- typer.echo(celebration)
+ print(f"This file exists: {path.name}")
+ print(celebration)
if __name__ == "__main__":
diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py
index 9d032d4355..c7003f35d9 100644
--- a/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py
+++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial002.py
@@ -9,7 +9,7 @@ def main(
)
):
for name in names:
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/multiple_values/multiple_options/tutorial001.py b/docs_src/multiple_values/multiple_options/tutorial001.py
index 5c601ffae6..141e9aa1ce 100644
--- a/docs_src/multiple_values/multiple_options/tutorial001.py
+++ b/docs_src/multiple_values/multiple_options/tutorial001.py
@@ -5,10 +5,10 @@
def main(user: Optional[List[str]] = typer.Option(None)):
if not user:
- typer.echo("No provided users")
+ print("No provided users")
raise typer.Abort()
for u in user:
- typer.echo(f"Processing user: {u}")
+ print(f"Processing user: {u}")
if __name__ == "__main__":
diff --git a/docs_src/multiple_values/multiple_options/tutorial002.py b/docs_src/multiple_values/multiple_options/tutorial002.py
index db373e7527..74728a005d 100644
--- a/docs_src/multiple_values/multiple_options/tutorial002.py
+++ b/docs_src/multiple_values/multiple_options/tutorial002.py
@@ -4,7 +4,7 @@
def main(number: List[float] = typer.Option([])):
- typer.echo(f"The sum is {sum(number)}")
+ print(f"The sum is {sum(number)}")
if __name__ == "__main__":
diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial001.py b/docs_src/multiple_values/options_with_multiple_values/tutorial001.py
index 475b50bc1c..24da362162 100644
--- a/docs_src/multiple_values/options_with_multiple_values/tutorial001.py
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial001.py
@@ -6,11 +6,11 @@
def main(user: Tuple[str, int, bool] = typer.Option((None, None, None))):
username, coins, is_wizard = user
if not username:
- typer.echo("No user provided")
+ print("No user provided")
raise typer.Abort()
- typer.echo(f"The username {username} has {coins} coins")
+ print(f"The username {username} has {coins} coins")
if is_wizard:
- typer.echo("And this user is a wizard!")
+ print("And this user is a wizard!")
if __name__ == "__main__":
diff --git a/docs_src/options/callback/tutorial001.py b/docs_src/options/callback/tutorial001.py
index c717ca8a8d..6af7c09fa8 100644
--- a/docs_src/options/callback/tutorial001.py
+++ b/docs_src/options/callback/tutorial001.py
@@ -8,7 +8,7 @@ def name_callback(value: str):
def main(name: str = typer.Option(..., callback=name_callback)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/callback/tutorial002.py b/docs_src/options/callback/tutorial002.py
index d579e56b96..df9f24b43e 100644
--- a/docs_src/options/callback/tutorial002.py
+++ b/docs_src/options/callback/tutorial002.py
@@ -2,14 +2,14 @@
def name_callback(value: str):
- typer.echo("Validating name")
+ print("Validating name")
if value != "Camila":
raise typer.BadParameter("Only Camila is allowed")
return value
def main(name: str = typer.Option(..., callback=name_callback)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/callback/tutorial003.py b/docs_src/options/callback/tutorial003.py
index f93ac566d5..1891b9793e 100644
--- a/docs_src/options/callback/tutorial003.py
+++ b/docs_src/options/callback/tutorial003.py
@@ -4,14 +4,14 @@
def name_callback(ctx: typer.Context, value: str):
if ctx.resilient_parsing:
return
- typer.echo("Validating name")
+ print("Validating name")
if value != "Camila":
raise typer.BadParameter("Only Camila is allowed")
return value
def main(name: str = typer.Option(..., callback=name_callback)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/callback/tutorial004.py b/docs_src/options/callback/tutorial004.py
index 27d13e9ff5..61c9c19991 100644
--- a/docs_src/options/callback/tutorial004.py
+++ b/docs_src/options/callback/tutorial004.py
@@ -4,14 +4,14 @@
def name_callback(ctx: typer.Context, param: typer.CallbackParam, value: str):
if ctx.resilient_parsing:
return
- typer.echo(f"Validating param: {param.name}")
+ print(f"Validating param: {param.name}")
if value != "Camila":
raise typer.BadParameter("Only Camila is allowed")
return value
def main(name: str = typer.Option(..., callback=name_callback)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/help/tutorial001.py b/docs_src/options/help/tutorial001.py
index 1e3bddcd30..adb0dcc917 100644
--- a/docs_src/options/help/tutorial001.py
+++ b/docs_src/options/help/tutorial001.py
@@ -12,9 +12,9 @@ def main(
If --formal is used, say hi very formally.
"""
if formal:
- typer.echo(f"Good day Ms. {name} {lastname}.")
+ print(f"Good day Ms. {name} {lastname}.")
else:
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/options/help/tutorial002.py b/docs_src/options/help/tutorial002.py
index c422c0b6dd..b28bf30e7e 100644
--- a/docs_src/options/help/tutorial002.py
+++ b/docs_src/options/help/tutorial002.py
@@ -1,8 +1,25 @@
import typer
-def main(fullname: str = typer.Option("Wade Wilson", show_default=False)):
- typer.echo(f"Hello {fullname}")
+def main(
+ name: str,
+ lastname: str = typer.Option("", help="Last name of person to greet."),
+ formal: bool = typer.Option(
+ False, help="Say hi formally.", rich_help_panel="Customization and Utils"
+ ),
+ debug: bool = typer.Option(
+ False, help="Enable debugging.", rich_help_panel="Customization and Utils"
+ ),
+):
+ """
+ Say hi to NAME, optionally with a --lastname.
+
+ If --formal is used, say hi very formally.
+ """
+ if formal:
+ print(f"Good day Ms. {name} {lastname}.")
+ else:
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/options/help/tutorial003.py b/docs_src/options/help/tutorial003.py
new file mode 100644
index 0000000000..40b554329e
--- /dev/null
+++ b/docs_src/options/help/tutorial003.py
@@ -0,0 +1,9 @@
+import typer
+
+
+def main(fullname: str = typer.Option("Wade Wilson", show_default=False)):
+ print(f"Hello {fullname}")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/options/name/tutorial001.py b/docs_src/options/name/tutorial001.py
index 7bc212f424..c2094051ff 100644
--- a/docs_src/options/name/tutorial001.py
+++ b/docs_src/options/name/tutorial001.py
@@ -2,7 +2,7 @@
def main(user_name: str = typer.Option(..., "--name")):
- typer.echo(f"Hello {user_name}")
+ print(f"Hello {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/options/name/tutorial002.py b/docs_src/options/name/tutorial002.py
index 10eba64c17..a2a12a77a9 100644
--- a/docs_src/options/name/tutorial002.py
+++ b/docs_src/options/name/tutorial002.py
@@ -2,7 +2,7 @@
def main(user_name: str = typer.Option(..., "--name", "-n")):
- typer.echo(f"Hello {user_name}")
+ print(f"Hello {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/options/name/tutorial003.py b/docs_src/options/name/tutorial003.py
index 5a8325aa6f..957adb0148 100644
--- a/docs_src/options/name/tutorial003.py
+++ b/docs_src/options/name/tutorial003.py
@@ -2,7 +2,7 @@
def main(user_name: str = typer.Option(..., "-n")):
- typer.echo(f"Hello {user_name}")
+ print(f"Hello {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/options/name/tutorial004.py b/docs_src/options/name/tutorial004.py
index fa31904a6f..80f64856a0 100644
--- a/docs_src/options/name/tutorial004.py
+++ b/docs_src/options/name/tutorial004.py
@@ -2,7 +2,7 @@
def main(user_name: str = typer.Option(..., "--user-name", "-n")):
- typer.echo(f"Hello {user_name}")
+ print(f"Hello {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/options/name/tutorial005.py b/docs_src/options/name/tutorial005.py
index d0a094d8c3..313c910839 100644
--- a/docs_src/options/name/tutorial005.py
+++ b/docs_src/options/name/tutorial005.py
@@ -6,9 +6,9 @@ def main(
formal: bool = typer.Option(False, "--formal", "-f"),
):
if formal:
- typer.echo(f"Good day Ms. {name}.")
+ print(f"Good day Ms. {name}.")
else:
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/password/tutorial001.py b/docs_src/options/password/tutorial001.py
index f9346b2e9d..98c58e222d 100644
--- a/docs_src/options/password/tutorial001.py
+++ b/docs_src/options/password/tutorial001.py
@@ -4,7 +4,7 @@
def main(
name: str, email: str = typer.Option(..., prompt=True, confirmation_prompt=True)
):
- typer.echo(f"Hello {name}, your email is {email}")
+ print(f"Hello {name}, your email is {email}")
if __name__ == "__main__":
diff --git a/docs_src/options/password/tutorial002.py b/docs_src/options/password/tutorial002.py
index 3eb3935375..1743e69baf 100644
--- a/docs_src/options/password/tutorial002.py
+++ b/docs_src/options/password/tutorial002.py
@@ -7,8 +7,8 @@ def main(
..., prompt=True, confirmation_prompt=True, hide_input=True
),
):
- typer.echo(f"Hello {name}. Doing something very secure with password.")
- typer.echo(f"...just kidding, here it is, very insecure: {password}")
+ print(f"Hello {name}. Doing something very secure with password.")
+ print(f"...just kidding, here it is, very insecure: {password}")
if __name__ == "__main__":
diff --git a/docs_src/options/prompt/tutorial001.py b/docs_src/options/prompt/tutorial001.py
index 2da2592455..242b557145 100644
--- a/docs_src/options/prompt/tutorial001.py
+++ b/docs_src/options/prompt/tutorial001.py
@@ -2,7 +2,7 @@
def main(name: str, lastname: str = typer.Option(..., prompt=True)):
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/options/prompt/tutorial002.py b/docs_src/options/prompt/tutorial002.py
index ec0f1c7722..b98e18a7dd 100644
--- a/docs_src/options/prompt/tutorial002.py
+++ b/docs_src/options/prompt/tutorial002.py
@@ -4,7 +4,7 @@
def main(
name: str, lastname: str = typer.Option(..., prompt="Please tell me your last name")
):
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/options/prompt/tutorial003.py b/docs_src/options/prompt/tutorial003.py
index 11471d7a64..ffe22ef6d7 100644
--- a/docs_src/options/prompt/tutorial003.py
+++ b/docs_src/options/prompt/tutorial003.py
@@ -2,7 +2,7 @@
def main(project_name: str = typer.Option(..., prompt=True, confirmation_prompt=True)):
- typer.echo(f"Deleting project {project_name}")
+ print(f"Deleting project {project_name}")
if __name__ == "__main__":
diff --git a/docs_src/options/required/tutorial001.py b/docs_src/options/required/tutorial001.py
index f62221b75b..f6f17cf992 100644
--- a/docs_src/options/required/tutorial001.py
+++ b/docs_src/options/required/tutorial001.py
@@ -2,7 +2,7 @@
def main(name: str, lastname: str = typer.Option(...)):
- typer.echo(f"Hello {name} {lastname}")
+ print(f"Hello {name} {lastname}")
if __name__ == "__main__":
diff --git a/docs_src/options/version/tutorial001.py b/docs_src/options/version/tutorial001.py
index e09ae6a657..6ca0587e36 100644
--- a/docs_src/options/version/tutorial001.py
+++ b/docs_src/options/version/tutorial001.py
@@ -7,7 +7,7 @@
def version_callback(value: bool):
if value:
- typer.echo(f"Awesome CLI Version: {__version__}")
+ print(f"Awesome CLI Version: {__version__}")
raise typer.Exit()
@@ -17,7 +17,7 @@ def main(
None, "--version", callback=version_callback
),
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/version/tutorial002.py b/docs_src/options/version/tutorial002.py
index 9b4f9dcb97..34d0a280b6 100644
--- a/docs_src/options/version/tutorial002.py
+++ b/docs_src/options/version/tutorial002.py
@@ -7,7 +7,7 @@
def version_callback(value: bool):
if value:
- typer.echo(f"Awesome CLI Version: {__version__}")
+ print(f"Awesome CLI Version: {__version__}")
raise typer.Exit()
@@ -22,7 +22,7 @@ def main(
None, "--version", callback=version_callback
),
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/version/tutorial003.py b/docs_src/options/version/tutorial003.py
index 886487dfa5..3aa5c9a08e 100644
--- a/docs_src/options/version/tutorial003.py
+++ b/docs_src/options/version/tutorial003.py
@@ -7,7 +7,7 @@
def version_callback(value: bool):
if value:
- typer.echo(f"Awesome CLI Version: {__version__}")
+ print(f"Awesome CLI Version: {__version__}")
raise typer.Exit()
@@ -23,7 +23,7 @@ def main(
None, "--version", callback=version_callback, is_eager=True
),
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/options/autocompletion/tutorial001.py b/docs_src/options_autocompletion/tutorial001.py
similarity index 62%
rename from docs_src/options/autocompletion/tutorial001.py
rename to docs_src/options_autocompletion/tutorial001.py
index a5ad1dc56d..1cfc18cc20 100644
--- a/docs_src/options/autocompletion/tutorial001.py
+++ b/docs_src/options_autocompletion/tutorial001.py
@@ -1,9 +1,12 @@
import typer
+app = typer.Typer()
+
+@app.command()
def main(name: str = typer.Option("World", help="The name to say hi to.")):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial002.py b/docs_src/options_autocompletion/tutorial002.py
similarity index 76%
rename from docs_src/options/autocompletion/tutorial002.py
rename to docs_src/options_autocompletion/tutorial002.py
index 87e17aab57..8b18760d47 100644
--- a/docs_src/options/autocompletion/tutorial002.py
+++ b/docs_src/options_autocompletion/tutorial002.py
@@ -5,13 +5,17 @@ def complete_name():
return ["Camila", "Carlos", "Sebastian"]
+app = typer.Typer()
+
+
+@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", autocompletion=complete_name
)
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial003.py b/docs_src/options_autocompletion/tutorial003.py
similarity index 84%
rename from docs_src/options/autocompletion/tutorial003.py
rename to docs_src/options_autocompletion/tutorial003.py
index 7ab6d7ad30..b2fe534028 100644
--- a/docs_src/options/autocompletion/tutorial003.py
+++ b/docs_src/options_autocompletion/tutorial003.py
@@ -11,13 +11,17 @@ def complete_name(incomplete: str):
return completion
+app = typer.Typer()
+
+
+@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", autocompletion=complete_name
)
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial004.py b/docs_src/options_autocompletion/tutorial004.py
similarity index 88%
rename from docs_src/options/autocompletion/tutorial004.py
rename to docs_src/options_autocompletion/tutorial004.py
index 9352194751..9bb08dd196 100644
--- a/docs_src/options/autocompletion/tutorial004.py
+++ b/docs_src/options_autocompletion/tutorial004.py
@@ -16,13 +16,17 @@ def complete_name(incomplete: str):
return completion
+app = typer.Typer()
+
+
+@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", autocompletion=complete_name
)
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial005.py b/docs_src/options_autocompletion/tutorial005.py
similarity index 86%
rename from docs_src/options/autocompletion/tutorial005.py
rename to docs_src/options_autocompletion/tutorial005.py
index 04d517d4f5..d6efb4fe14 100644
--- a/docs_src/options/autocompletion/tutorial005.py
+++ b/docs_src/options_autocompletion/tutorial005.py
@@ -13,13 +13,17 @@ def complete_name(incomplete: str):
yield (name, help_text)
+app = typer.Typer()
+
+
+@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", autocompletion=complete_name
)
):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial006.py b/docs_src/options_autocompletion/tutorial006.py
similarity index 68%
rename from docs_src/options/autocompletion/tutorial006.py
rename to docs_src/options_autocompletion/tutorial006.py
index ea64c676fc..4e6dba302a 100644
--- a/docs_src/options/autocompletion/tutorial006.py
+++ b/docs_src/options_autocompletion/tutorial006.py
@@ -2,11 +2,14 @@
import typer
+app = typer.Typer()
+
+@app.command()
def main(name: List[str] = typer.Option(["World"], help="The name to say hi to.")):
for each_name in name:
- typer.echo(f"Hello {each_name}")
+ print(f"Hello {each_name}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial007.py b/docs_src/options_autocompletion/tutorial007.py
similarity index 89%
rename from docs_src/options/autocompletion/tutorial007.py
rename to docs_src/options_autocompletion/tutorial007.py
index 7d7b1b46e2..c60dfe95de 100644
--- a/docs_src/options/autocompletion/tutorial007.py
+++ b/docs_src/options_autocompletion/tutorial007.py
@@ -16,14 +16,18 @@ def complete_name(ctx: typer.Context, incomplete: str):
yield (name, help_text)
+app = typer.Typer()
+
+
+@app.command()
def main(
name: List[str] = typer.Option(
["World"], help="The name to say hi to.", autocompletion=complete_name
)
):
for n in name:
- typer.echo(f"Hello {n}")
+ print(f"Hello {n}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial008.py b/docs_src/options_autocompletion/tutorial008.py
similarity index 75%
rename from docs_src/options/autocompletion/tutorial008.py
rename to docs_src/options_autocompletion/tutorial008.py
index 92daa4ea01..f90073b15f 100644
--- a/docs_src/options/autocompletion/tutorial008.py
+++ b/docs_src/options_autocompletion/tutorial008.py
@@ -1,6 +1,7 @@
from typing import List
import typer
+from rich.console import Console
valid_completion_items = [
("Camila", "The reader of books."),
@@ -8,22 +9,28 @@
("Sebastian", "The type hints guy."),
]
+err_console = Console(stderr=True)
+
def complete_name(args: List[str], incomplete: str):
- typer.echo(f"{args}", err=True)
+ err_console.print(f"{args}")
for name, help_text in valid_completion_items:
if name.startswith(incomplete):
yield (name, help_text)
+app = typer.Typer()
+
+
+@app.command()
def main(
name: List[str] = typer.Option(
["World"], help="The name to say hi to.", autocompletion=complete_name
)
):
for n in name:
- typer.echo(f"Hello {n}")
+ print(f"Hello {n}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/options/autocompletion/tutorial009.py b/docs_src/options_autocompletion/tutorial009.py
similarity index 78%
rename from docs_src/options/autocompletion/tutorial009.py
rename to docs_src/options_autocompletion/tutorial009.py
index 04b0c2df72..829f209031 100644
--- a/docs_src/options/autocompletion/tutorial009.py
+++ b/docs_src/options_autocompletion/tutorial009.py
@@ -1,6 +1,7 @@
from typing import List
import typer
+from rich.console import Console
valid_completion_items = [
("Camila", "The reader of books."),
@@ -8,23 +9,29 @@
("Sebastian", "The type hints guy."),
]
+err_console = Console(stderr=True)
+
def complete_name(ctx: typer.Context, args: List[str], incomplete: str):
- typer.echo(f"{args}", err=True)
+ err_console.print(f"{args}")
names = ctx.params.get("name") or []
for name, help_text in valid_completion_items:
if name.startswith(incomplete) and name not in names:
yield (name, help_text)
+app = typer.Typer()
+
+
+@app.command()
def main(
name: List[str] = typer.Option(
["World"], help="The name to say hi to.", autocompletion=complete_name
)
):
for n in name:
- typer.echo(f"Hello {n}")
+ print(f"Hello {n}")
if __name__ == "__main__":
- typer.run(main)
+ app()
diff --git a/docs_src/parameter_types/bool/tutorial001.py b/docs_src/parameter_types/bool/tutorial001.py
index 46f6335cee..d9b7cc1c27 100644
--- a/docs_src/parameter_types/bool/tutorial001.py
+++ b/docs_src/parameter_types/bool/tutorial001.py
@@ -3,9 +3,9 @@
def main(force: bool = typer.Option(False, "--force")):
if force:
- typer.echo("Forcing operation")
+ print("Forcing operation")
else:
- typer.echo("Not forcing")
+ print("Not forcing")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/bool/tutorial002.py b/docs_src/parameter_types/bool/tutorial002.py
index 8e88857b26..0bf116df9b 100644
--- a/docs_src/parameter_types/bool/tutorial002.py
+++ b/docs_src/parameter_types/bool/tutorial002.py
@@ -5,11 +5,11 @@
def main(accept: Optional[bool] = typer.Option(None, "--accept/--reject")):
if accept is None:
- typer.echo("I don't know what you want yet")
+ print("I don't know what you want yet")
elif accept:
- typer.echo("Accepting!")
+ print("Accepting!")
else:
- typer.echo("Rejecting!")
+ print("Rejecting!")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/bool/tutorial003.py b/docs_src/parameter_types/bool/tutorial003.py
index fda717e28c..3a5fdb4928 100644
--- a/docs_src/parameter_types/bool/tutorial003.py
+++ b/docs_src/parameter_types/bool/tutorial003.py
@@ -3,9 +3,9 @@
def main(force: bool = typer.Option(False, "--force/--no-force", "-f/-F")):
if force:
- typer.echo("Forcing operation")
+ print("Forcing operation")
else:
- typer.echo("Not forcing")
+ print("Not forcing")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/bool/tutorial004.py b/docs_src/parameter_types/bool/tutorial004.py
index 8c7f47c81b..933bec22a0 100644
--- a/docs_src/parameter_types/bool/tutorial004.py
+++ b/docs_src/parameter_types/bool/tutorial004.py
@@ -3,9 +3,9 @@
def main(in_prod: bool = typer.Option(True, " /--demo", " /-d")):
if in_prod:
- typer.echo("Running in production")
+ print("Running in production")
else:
- typer.echo("Running demo")
+ print("Running demo")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/datetime/tutorial001.py b/docs_src/parameter_types/datetime/tutorial001.py
index d32192817b..138cf0ce7c 100644
--- a/docs_src/parameter_types/datetime/tutorial001.py
+++ b/docs_src/parameter_types/datetime/tutorial001.py
@@ -4,8 +4,8 @@
def main(birth: datetime):
- typer.echo(f"Interesting day to be born: {birth}")
- typer.echo(f"Birth hour: {birth.hour}")
+ print(f"Interesting day to be born: {birth}")
+ print(f"Birth hour: {birth.hour}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/datetime/tutorial002.py b/docs_src/parameter_types/datetime/tutorial002.py
index 5cdba294d0..ad21f7772d 100644
--- a/docs_src/parameter_types/datetime/tutorial002.py
+++ b/docs_src/parameter_types/datetime/tutorial002.py
@@ -8,7 +8,7 @@ def main(
..., formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%m/%d/%Y"]
)
):
- typer.echo(f"Launch will be at: {launch_date}")
+ print(f"Launch will be at: {launch_date}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/enum/tutorial001.py b/docs_src/parameter_types/enum/tutorial001.py
index 6a31a84019..a353077e92 100644
--- a/docs_src/parameter_types/enum/tutorial001.py
+++ b/docs_src/parameter_types/enum/tutorial001.py
@@ -10,7 +10,7 @@ class NeuralNetwork(str, Enum):
def main(network: NeuralNetwork = NeuralNetwork.simple):
- typer.echo(f"Training neural network of type: {network.value}")
+ print(f"Training neural network of type: {network.value}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/enum/tutorial002.py b/docs_src/parameter_types/enum/tutorial002.py
index cacde611fd..545c579541 100644
--- a/docs_src/parameter_types/enum/tutorial002.py
+++ b/docs_src/parameter_types/enum/tutorial002.py
@@ -12,7 +12,7 @@ class NeuralNetwork(str, Enum):
def main(
network: NeuralNetwork = typer.Option(NeuralNetwork.simple, case_sensitive=False)
):
- typer.echo(f"Training neural network of type: {network.value}")
+ print(f"Training neural network of type: {network.value}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/file/tutorial001.py b/docs_src/parameter_types/file/tutorial001.py
index 68de8d9d6f..899c32394c 100644
--- a/docs_src/parameter_types/file/tutorial001.py
+++ b/docs_src/parameter_types/file/tutorial001.py
@@ -3,7 +3,7 @@
def main(config: typer.FileText = typer.Option(...)):
for line in config:
- typer.echo(f"Config line: {line}")
+ print(f"Config line: {line}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/file/tutorial002.py b/docs_src/parameter_types/file/tutorial002.py
index 183e24d022..58f59b8a7f 100644
--- a/docs_src/parameter_types/file/tutorial002.py
+++ b/docs_src/parameter_types/file/tutorial002.py
@@ -3,7 +3,7 @@
def main(config: typer.FileTextWrite = typer.Option(...)):
config.write("Some config written by the app")
- typer.echo("Config written")
+ print("Config written")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/file/tutorial003.py b/docs_src/parameter_types/file/tutorial003.py
index 092a6aadd6..375f5f57f4 100644
--- a/docs_src/parameter_types/file/tutorial003.py
+++ b/docs_src/parameter_types/file/tutorial003.py
@@ -6,7 +6,7 @@ def main(file: typer.FileBinaryRead = typer.Option(...)):
for bytes_chunk in file:
# Process the bytes in bytes_chunk
processed_total += len(bytes_chunk)
- typer.echo(f"Processed bytes total: {processed_total}")
+ print(f"Processed bytes total: {processed_total}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/file/tutorial004.py b/docs_src/parameter_types/file/tutorial004.py
index 732974f055..1fa3ae825c 100644
--- a/docs_src/parameter_types/file/tutorial004.py
+++ b/docs_src/parameter_types/file/tutorial004.py
@@ -10,7 +10,7 @@ def main(file: typer.FileBinaryWrite = typer.Option(...)):
# This is already bytes, it starts with b"
second_line = b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o"
file.write(second_line)
- typer.echo("Binary file written")
+ print("Binary file written")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/file/tutorial005.py b/docs_src/parameter_types/file/tutorial005.py
index 310b0e3060..bab6ee0969 100644
--- a/docs_src/parameter_types/file/tutorial005.py
+++ b/docs_src/parameter_types/file/tutorial005.py
@@ -3,7 +3,7 @@
def main(config: typer.FileText = typer.Option(..., mode="a")):
config.write("This is a single line\n")
- typer.echo("Config line written")
+ print("Config line written")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/index/tutorial001.py b/docs_src/parameter_types/index/tutorial001.py
index c394ecc955..8126061880 100644
--- a/docs_src/parameter_types/index/tutorial001.py
+++ b/docs_src/parameter_types/index/tutorial001.py
@@ -2,10 +2,10 @@
def main(name: str, age: int = 20, height_meters: float = 1.89, female: bool = True):
- typer.echo(f"NAME is {name}, of type: {type(name)}")
- typer.echo(f"--age is {age}, of type: {type(age)}")
- typer.echo(f"--height-meters is {height_meters}, of type: {type(height_meters)}")
- typer.echo(f"--female is {female}, of type: {type(female)}")
+ print(f"NAME is {name}, of type: {type(name)}")
+ print(f"--age is {age}, of type: {type(age)}")
+ print(f"--height-meters is {height_meters}, of type: {type(height_meters)}")
+ print(f"--female is {female}, of type: {type(female)}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/number/tutorial001.py b/docs_src/parameter_types/number/tutorial001.py
index a804db7b48..f8eb859464 100644
--- a/docs_src/parameter_types/number/tutorial001.py
+++ b/docs_src/parameter_types/number/tutorial001.py
@@ -6,9 +6,9 @@ def main(
age: int = typer.Option(20, min=18),
score: float = typer.Option(0, max=100),
):
- typer.echo(f"ID is {id}")
- typer.echo(f"--age is {age}")
- typer.echo(f"--score is {score}")
+ print(f"ID is {id}")
+ print(f"--age is {age}")
+ print(f"--score is {score}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/number/tutorial002.py b/docs_src/parameter_types/number/tutorial002.py
index 7285088529..b5f45f925f 100644
--- a/docs_src/parameter_types/number/tutorial002.py
+++ b/docs_src/parameter_types/number/tutorial002.py
@@ -6,9 +6,9 @@ def main(
rank: int = typer.Option(0, max=10, clamp=True),
score: float = typer.Option(0, min=0, max=100, clamp=True),
):
- typer.echo(f"ID is {id}")
- typer.echo(f"--rank is {rank}")
- typer.echo(f"--score is {score}")
+ print(f"ID is {id}")
+ print(f"--rank is {rank}")
+ print(f"--score is {score}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/number/tutorial003.py b/docs_src/parameter_types/number/tutorial003.py
index 95737d02c9..8fdad8ab23 100644
--- a/docs_src/parameter_types/number/tutorial003.py
+++ b/docs_src/parameter_types/number/tutorial003.py
@@ -2,7 +2,7 @@
def main(verbose: int = typer.Option(0, "--verbose", "-v", count=True)):
- typer.echo(f"Verbose level is {verbose}")
+ print(f"Verbose level is {verbose}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/path/tutorial001.py b/docs_src/parameter_types/path/tutorial001.py
index c6a938bca2..17a2e5ccc3 100644
--- a/docs_src/parameter_types/path/tutorial001.py
+++ b/docs_src/parameter_types/path/tutorial001.py
@@ -6,15 +6,15 @@
def main(config: Optional[Path] = typer.Option(None)):
if config is None:
- typer.echo("No config file")
+ print("No config file")
raise typer.Abort()
if config.is_file():
text = config.read_text()
- typer.echo(f"Config file contents: {text}")
+ print(f"Config file contents: {text}")
elif config.is_dir():
- typer.echo("Config is a directory, will use all its config files")
+ print("Config is a directory, will use all its config files")
elif not config.exists():
- typer.echo("The config doesn't exist")
+ print("The config doesn't exist")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/path/tutorial002.py b/docs_src/parameter_types/path/tutorial002.py
index 16ff0bcac0..b088655d6a 100644
--- a/docs_src/parameter_types/path/tutorial002.py
+++ b/docs_src/parameter_types/path/tutorial002.py
@@ -15,7 +15,7 @@ def main(
)
):
text = config.read_text()
- typer.echo(f"Config file contents: {text}")
+ print(f"Config file contents: {text}")
if __name__ == "__main__":
diff --git a/docs_src/parameter_types/uuid/tutorial001.py b/docs_src/parameter_types/uuid/tutorial001.py
index 72cecc5509..f97c5d9558 100644
--- a/docs_src/parameter_types/uuid/tutorial001.py
+++ b/docs_src/parameter_types/uuid/tutorial001.py
@@ -4,8 +4,8 @@
def main(user_id: UUID):
- typer.echo(f"USER_ID is {user_id}")
- typer.echo(f"UUID version is: {user_id.version}")
+ print(f"USER_ID is {user_id}")
+ print(f"UUID version is: {user_id.version}")
if __name__ == "__main__":
diff --git a/docs_src/printing/tutorial001.py b/docs_src/printing/tutorial001.py
index a9d41c9219..8c83c26cc7 100644
--- a/docs_src/printing/tutorial001.py
+++ b/docs_src/printing/tutorial001.py
@@ -1,14 +1,18 @@
import typer
+from rich import print
+data = {
+ "name": "Rick",
+ "age": 42,
+ "items": [{"name": "Portal Gun"}, {"name": "Plumbus"}],
+ "active": True,
+ "affiliation": None,
+}
-def main(good: bool = True):
- message_start = "everything is "
- if good:
- ending = typer.style("good", fg=typer.colors.GREEN, bold=True)
- else:
- ending = typer.style("bad", fg=typer.colors.WHITE, bg=typer.colors.RED)
- message = message_start + ending
- typer.echo(message)
+
+def main():
+ print("Here's the data")
+ print(data)
if __name__ == "__main__":
diff --git a/docs_src/printing/tutorial002.py b/docs_src/printing/tutorial002.py
index 6400555890..d8fe4a89b3 100644
--- a/docs_src/printing/tutorial002.py
+++ b/docs_src/printing/tutorial002.py
@@ -1,8 +1,9 @@
import typer
+from rich import print
-def main(name: str):
- typer.secho(f"Welcome here {name}", fg=typer.colors.MAGENTA)
+def main():
+ print("[bold red]Alert![/bold red] [green]Portal gun[/green] shooting! :boom:")
if __name__ == "__main__":
diff --git a/docs_src/printing/tutorial003.py b/docs_src/printing/tutorial003.py
index 220a5e1feb..1cfc51cbc8 100644
--- a/docs_src/printing/tutorial003.py
+++ b/docs_src/printing/tutorial003.py
@@ -1,8 +1,15 @@
import typer
+from rich.console import Console
+from rich.table import Table
+
+console = Console()
def main():
- typer.echo(f"Here is something written to standard error", err=True)
+ table = Table("Name", "Item")
+ table.add_row("Rick", "Portal Gun")
+ table.add_row("Morty", "Plumbus")
+ console.print(table)
if __name__ == "__main__":
diff --git a/docs_src/printing/tutorial004.py b/docs_src/printing/tutorial004.py
new file mode 100644
index 0000000000..2ecb90e7cf
--- /dev/null
+++ b/docs_src/printing/tutorial004.py
@@ -0,0 +1,12 @@
+import typer
+from rich.console import Console
+
+err_console = Console(stderr=True)
+
+
+def main():
+ err_console.print("Here is something written to standard error")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/printing/tutorial005.py b/docs_src/printing/tutorial005.py
new file mode 100644
index 0000000000..a9d41c9219
--- /dev/null
+++ b/docs_src/printing/tutorial005.py
@@ -0,0 +1,15 @@
+import typer
+
+
+def main(good: bool = True):
+ message_start = "everything is "
+ if good:
+ ending = typer.style("good", fg=typer.colors.GREEN, bold=True)
+ else:
+ ending = typer.style("bad", fg=typer.colors.WHITE, bg=typer.colors.RED)
+ message = message_start + ending
+ typer.echo(message)
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/printing/tutorial006.py b/docs_src/printing/tutorial006.py
new file mode 100644
index 0000000000..6400555890
--- /dev/null
+++ b/docs_src/printing/tutorial006.py
@@ -0,0 +1,9 @@
+import typer
+
+
+def main(name: str):
+ typer.secho(f"Welcome here {name}", fg=typer.colors.MAGENTA)
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/progressbar/tutorial001.py b/docs_src/progressbar/tutorial001.py
index b609a6e428..3e9a449212 100644
--- a/docs_src/progressbar/tutorial001.py
+++ b/docs_src/progressbar/tutorial001.py
@@ -1,16 +1,16 @@
import time
import typer
+from rich.progress import track
def main():
total = 0
- with typer.progressbar(range(100)) as progress:
- for value in progress:
- # Fake processing time
- time.sleep(0.01)
- total += 1
- typer.echo(f"Processed {total} things.")
+ for value in track(range(100), description="Processing..."):
+ # Fake processing time
+ time.sleep(0.01)
+ total += 1
+ print(f"Processed {total} things.")
if __name__ == "__main__":
diff --git a/docs_src/progressbar/tutorial002.py b/docs_src/progressbar/tutorial002.py
index 45f40d9d73..cfd4e5844d 100644
--- a/docs_src/progressbar/tutorial002.py
+++ b/docs_src/progressbar/tutorial002.py
@@ -1,22 +1,19 @@
import time
import typer
-
-
-def iterate_user_ids():
- # Let's imagine this is a web API, not a range()
- for i in range(100):
- yield i
+from rich.progress import Progress, SpinnerColumn, TextColumn
def main():
- total = 0
- with typer.progressbar(iterate_user_ids(), length=100) as progress:
- for value in progress:
- # Fake processing time
- time.sleep(0.01)
- total += 1
- typer.echo(f"Processed {total} user IDs.")
+ with Progress(
+ SpinnerColumn(),
+ TextColumn("[progress.description]{task.description}"),
+ transient=True,
+ ) as progress:
+ progress.add_task(description="Processing...", total=None)
+ progress.add_task(description="Preparing...", total=None)
+ time.sleep(5)
+ print("Done!")
if __name__ == "__main__":
diff --git a/docs_src/progressbar/tutorial003.py b/docs_src/progressbar/tutorial003.py
index 579a28db47..c020cf7692 100644
--- a/docs_src/progressbar/tutorial003.py
+++ b/docs_src/progressbar/tutorial003.py
@@ -5,12 +5,12 @@
def main():
total = 0
- with typer.progressbar(range(100), label="Processing") as progress:
+ with typer.progressbar(range(100)) as progress:
for value in progress:
# Fake processing time
time.sleep(0.01)
total += 1
- typer.echo(f"Processed {total} things.")
+ print(f"Processed {total} things.")
if __name__ == "__main__":
diff --git a/docs_src/progressbar/tutorial004.py b/docs_src/progressbar/tutorial004.py
index 3c08120739..390e41f127 100644
--- a/docs_src/progressbar/tutorial004.py
+++ b/docs_src/progressbar/tutorial004.py
@@ -3,14 +3,20 @@
import typer
+def iterate_user_ids():
+ # Let's imagine this is a web API, not a range()
+ for i in range(100):
+ yield i
+
+
def main():
- total = 1000
- with typer.progressbar(length=total) as progress:
- for batch in range(4):
+ total = 0
+ with typer.progressbar(iterate_user_ids(), length=100) as progress:
+ for value in progress:
# Fake processing time
- time.sleep(1)
- progress.update(250)
- typer.echo(f"Processed {total} things in batches.")
+ time.sleep(0.01)
+ total += 1
+ print(f"Processed {total} user IDs.")
if __name__ == "__main__":
diff --git a/docs_src/progressbar/tutorial005.py b/docs_src/progressbar/tutorial005.py
new file mode 100644
index 0000000000..a4de2f9994
--- /dev/null
+++ b/docs_src/progressbar/tutorial005.py
@@ -0,0 +1,17 @@
+import time
+
+import typer
+
+
+def main():
+ total = 0
+ with typer.progressbar(range(100), label="Processing") as progress:
+ for value in progress:
+ # Fake processing time
+ time.sleep(0.01)
+ total += 1
+ print(f"Processed {total} things.")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/progressbar/tutorial006.py b/docs_src/progressbar/tutorial006.py
new file mode 100644
index 0000000000..ac94a3ed3e
--- /dev/null
+++ b/docs_src/progressbar/tutorial006.py
@@ -0,0 +1,17 @@
+import time
+
+import typer
+
+
+def main():
+ total = 1000
+ with typer.progressbar(length=total) as progress:
+ for batch in range(4):
+ # Fake processing time
+ time.sleep(1)
+ progress.update(250)
+ print(f"Processed {total} things in batches.")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/prompt/tutorial001.py b/docs_src/prompt/tutorial001.py
index 689d43bdfc..776ac98652 100644
--- a/docs_src/prompt/tutorial001.py
+++ b/docs_src/prompt/tutorial001.py
@@ -3,7 +3,7 @@
def main():
person_name = typer.prompt("What's your name?")
- typer.echo(f"Hello {person_name}")
+ print(f"Hello {person_name}")
if __name__ == "__main__":
diff --git a/docs_src/prompt/tutorial002.py b/docs_src/prompt/tutorial002.py
index 94e3d0fa1e..08b4ff1b9b 100644
--- a/docs_src/prompt/tutorial002.py
+++ b/docs_src/prompt/tutorial002.py
@@ -4,9 +4,9 @@
def main():
delete = typer.confirm("Are you sure you want to delete it?")
if not delete:
- typer.echo("Not deleting")
+ print("Not deleting")
raise typer.Abort()
- typer.echo("Deleting it!")
+ print("Deleting it!")
if __name__ == "__main__":
diff --git a/docs_src/prompt/tutorial003.py b/docs_src/prompt/tutorial003.py
index 4e0b816547..da0016d17f 100644
--- a/docs_src/prompt/tutorial003.py
+++ b/docs_src/prompt/tutorial003.py
@@ -3,7 +3,7 @@
def main():
delete = typer.confirm("Are you sure you want to delete it?", abort=True)
- typer.echo("Deleting it!")
+ print("Deleting it!")
if __name__ == "__main__":
diff --git a/docs_src/prompt/tutorial004.py b/docs_src/prompt/tutorial004.py
new file mode 100644
index 0000000000..38f54773d2
--- /dev/null
+++ b/docs_src/prompt/tutorial004.py
@@ -0,0 +1,11 @@
+import typer
+from rich.prompt import Prompt
+
+
+def main():
+ name = Prompt.ask("Enter your name :sunglasses:")
+ print(f"Hey there {name}!")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/docs_src/subcommands/callback_override/tutorial001.py b/docs_src/subcommands/callback_override/tutorial001.py
index e9dfabd535..6442efa782 100644
--- a/docs_src/subcommands/callback_override/tutorial001.py
+++ b/docs_src/subcommands/callback_override/tutorial001.py
@@ -8,12 +8,12 @@
@users_app.callback()
def users_callback():
- typer.echo("Running a users command")
+ print("Running a users command")
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/callback_override/tutorial002.py b/docs_src/subcommands/callback_override/tutorial002.py
index d07ec7bc02..d75042f53e 100644
--- a/docs_src/subcommands/callback_override/tutorial002.py
+++ b/docs_src/subcommands/callback_override/tutorial002.py
@@ -4,7 +4,7 @@
def users_callback():
- typer.echo("Running a users command")
+ print("Running a users command")
users_app = typer.Typer(callback=users_callback)
@@ -13,7 +13,7 @@ def users_callback():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/callback_override/tutorial003.py b/docs_src/subcommands/callback_override/tutorial003.py
index 6bebbfb464..26dc5676fc 100644
--- a/docs_src/subcommands/callback_override/tutorial003.py
+++ b/docs_src/subcommands/callback_override/tutorial003.py
@@ -4,7 +4,7 @@
def default_callback():
- typer.echo("Running a users command")
+ print("Running a users command")
users_app = typer.Typer(callback=default_callback)
@@ -13,12 +13,12 @@ def default_callback():
@users_app.callback()
def user_callback():
- typer.echo("Callback override, running users command")
+ print("Callback override, running users command")
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/callback_override/tutorial004.py b/docs_src/subcommands/callback_override/tutorial004.py
index b76b3707c5..bad099fcb4 100644
--- a/docs_src/subcommands/callback_override/tutorial004.py
+++ b/docs_src/subcommands/callback_override/tutorial004.py
@@ -4,14 +4,14 @@
def default_callback():
- typer.echo("Running a users command")
+ print("Running a users command")
users_app = typer.Typer(callback=default_callback)
def callback_for_add_typer():
- typer.echo("I have the high land! Running users command")
+ print("I have the high land! Running users command")
app.add_typer(users_app, name="users", callback=callback_for_add_typer)
@@ -19,12 +19,12 @@ def callback_for_add_typer():
@users_app.callback()
def user_callback():
- typer.echo("Callback override, running users command")
+ print("Callback override, running users command")
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial001.py b/docs_src/subcommands/name_help/tutorial001.py
index 51bcd6dcf6..b49091f428 100644
--- a/docs_src/subcommands/name_help/tutorial001.py
+++ b/docs_src/subcommands/name_help/tutorial001.py
@@ -8,7 +8,7 @@
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial002.py b/docs_src/subcommands/name_help/tutorial002.py
index 42d014082c..df48e075d4 100644
--- a/docs_src/subcommands/name_help/tutorial002.py
+++ b/docs_src/subcommands/name_help/tutorial002.py
@@ -15,7 +15,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial003.py b/docs_src/subcommands/name_help/tutorial003.py
index c5af45245e..c24d0e187f 100644
--- a/docs_src/subcommands/name_help/tutorial003.py
+++ b/docs_src/subcommands/name_help/tutorial003.py
@@ -15,7 +15,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial004.py b/docs_src/subcommands/name_help/tutorial004.py
index 411b65e1e4..92fbc376aa 100644
--- a/docs_src/subcommands/name_help/tutorial004.py
+++ b/docs_src/subcommands/name_help/tutorial004.py
@@ -22,7 +22,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial005.py b/docs_src/subcommands/name_help/tutorial005.py
index 65449829cc..6cedabfef4 100644
--- a/docs_src/subcommands/name_help/tutorial005.py
+++ b/docs_src/subcommands/name_help/tutorial005.py
@@ -30,7 +30,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial006.py b/docs_src/subcommands/name_help/tutorial006.py
index 0f53a46258..6395f693cc 100644
--- a/docs_src/subcommands/name_help/tutorial006.py
+++ b/docs_src/subcommands/name_help/tutorial006.py
@@ -30,7 +30,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial007.py b/docs_src/subcommands/name_help/tutorial007.py
index 5a542e68d5..15544d89ee 100644
--- a/docs_src/subcommands/name_help/tutorial007.py
+++ b/docs_src/subcommands/name_help/tutorial007.py
@@ -30,7 +30,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/name_help/tutorial008.py b/docs_src/subcommands/name_help/tutorial008.py
index 3fd97add04..1063da2b7a 100644
--- a/docs_src/subcommands/name_help/tutorial008.py
+++ b/docs_src/subcommands/name_help/tutorial008.py
@@ -35,7 +35,7 @@ def users():
@users_app.command()
def create(name: str):
- typer.echo(f"Creating user: {name}")
+ print(f"Creating user: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial001/items.py b/docs_src/subcommands/tutorial001/items.py
index 608880bfa2..4dc2cfaba6 100644
--- a/docs_src/subcommands/tutorial001/items.py
+++ b/docs_src/subcommands/tutorial001/items.py
@@ -5,17 +5,17 @@
@app.command()
def create(item: str):
- typer.echo(f"Creating item: {item}")
+ print(f"Creating item: {item}")
@app.command()
def delete(item: str):
- typer.echo(f"Deleting item: {item}")
+ print(f"Deleting item: {item}")
@app.command()
def sell(item: str):
- typer.echo(f"Selling item: {item}")
+ print(f"Selling item: {item}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial001/users.py b/docs_src/subcommands/tutorial001/users.py
index 49315e17e8..88cc4575e1 100644
--- a/docs_src/subcommands/tutorial001/users.py
+++ b/docs_src/subcommands/tutorial001/users.py
@@ -5,12 +5,12 @@
@app.command()
def create(user_name: str):
- typer.echo(f"Creating user: {user_name}")
+ print(f"Creating user: {user_name}")
@app.command()
def delete(user_name: str):
- typer.echo(f"Deleting user: {user_name}")
+ print(f"Deleting user: {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial002/main.py b/docs_src/subcommands/tutorial002/main.py
index 8387386747..12c32214ea 100644
--- a/docs_src/subcommands/tutorial002/main.py
+++ b/docs_src/subcommands/tutorial002/main.py
@@ -9,27 +9,27 @@
@items_app.command("create")
def items_create(item: str):
- typer.echo(f"Creating item: {item}")
+ print(f"Creating item: {item}")
@items_app.command("delete")
def items_delete(item: str):
- typer.echo(f"Deleting item: {item}")
+ print(f"Deleting item: {item}")
@items_app.command("sell")
def items_sell(item: str):
- typer.echo(f"Selling item: {item}")
+ print(f"Selling item: {item}")
@users_app.command("create")
def users_create(user_name: str):
- typer.echo(f"Creating user: {user_name}")
+ print(f"Creating user: {user_name}")
@users_app.command("delete")
def users_delete(user_name: str):
- typer.echo(f"Deleting user: {user_name}")
+ print(f"Deleting user: {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial003/items.py b/docs_src/subcommands/tutorial003/items.py
index 608880bfa2..4dc2cfaba6 100644
--- a/docs_src/subcommands/tutorial003/items.py
+++ b/docs_src/subcommands/tutorial003/items.py
@@ -5,17 +5,17 @@
@app.command()
def create(item: str):
- typer.echo(f"Creating item: {item}")
+ print(f"Creating item: {item}")
@app.command()
def delete(item: str):
- typer.echo(f"Deleting item: {item}")
+ print(f"Deleting item: {item}")
@app.command()
def sell(item: str):
- typer.echo(f"Selling item: {item}")
+ print(f"Selling item: {item}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial003/reigns.py b/docs_src/subcommands/tutorial003/reigns.py
index f50e081213..5bc8d7a314 100644
--- a/docs_src/subcommands/tutorial003/reigns.py
+++ b/docs_src/subcommands/tutorial003/reigns.py
@@ -5,12 +5,12 @@
@app.command()
def conquer(name: str):
- typer.echo(f"Conquering reign: {name}")
+ print(f"Conquering reign: {name}")
@app.command()
def destroy(name: str):
- typer.echo(f"Destroying reign: {name}")
+ print(f"Destroying reign: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial003/towns.py b/docs_src/subcommands/tutorial003/towns.py
index 3b020cb944..66d01b5e9d 100644
--- a/docs_src/subcommands/tutorial003/towns.py
+++ b/docs_src/subcommands/tutorial003/towns.py
@@ -5,12 +5,12 @@
@app.command()
def found(name: str):
- typer.echo(f"Founding town: {name}")
+ print(f"Founding town: {name}")
@app.command()
def burn(name: str):
- typer.echo(f"Burning town: {name}")
+ print(f"Burning town: {name}")
if __name__ == "__main__":
diff --git a/docs_src/subcommands/tutorial003/users.py b/docs_src/subcommands/tutorial003/users.py
index 49315e17e8..88cc4575e1 100644
--- a/docs_src/subcommands/tutorial003/users.py
+++ b/docs_src/subcommands/tutorial003/users.py
@@ -5,12 +5,12 @@
@app.command()
def create(user_name: str):
- typer.echo(f"Creating user: {user_name}")
+ print(f"Creating user: {user_name}")
@app.command()
def delete(user_name: str):
- typer.echo(f"Deleting user: {user_name}")
+ print(f"Deleting user: {user_name}")
if __name__ == "__main__":
diff --git a/docs_src/terminating/tutorial001.py b/docs_src/terminating/tutorial001.py
index c77a09c999..ff6033a3dc 100644
--- a/docs_src/terminating/tutorial001.py
+++ b/docs_src/terminating/tutorial001.py
@@ -5,15 +5,15 @@
def maybe_create_user(username: str):
if username in existing_usernames:
- typer.echo("The user already exists")
+ print("The user already exists")
raise typer.Exit()
else:
- typer.echo(f"User created: {username}")
+ print(f"User created: {username}")
def send_new_user_notification(username: str):
# Somehow send a notification here for the new user, maybe an email
- typer.echo(f"Notification sent for new user: {username}")
+ print(f"Notification sent for new user: {username}")
def main(username: str):
diff --git a/docs_src/terminating/tutorial002.py b/docs_src/terminating/tutorial002.py
index f7af10175e..cb8bb5dd9d 100644
--- a/docs_src/terminating/tutorial002.py
+++ b/docs_src/terminating/tutorial002.py
@@ -3,9 +3,9 @@
def main(username: str):
if username == "root":
- typer.echo("The root user is reserved")
+ print("The root user is reserved")
raise typer.Exit(code=1)
- typer.echo(f"New user created: {username}")
+ print(f"New user created: {username}")
if __name__ == "__main__":
diff --git a/docs_src/terminating/tutorial003.py b/docs_src/terminating/tutorial003.py
index 56e14063ee..1247442296 100644
--- a/docs_src/terminating/tutorial003.py
+++ b/docs_src/terminating/tutorial003.py
@@ -3,9 +3,9 @@
def main(username: str):
if username == "root":
- typer.echo("The root user is reserved")
+ print("The root user is reserved")
raise typer.Abort()
- typer.echo(f"New user created: {username}")
+ print(f"New user created: {username}")
if __name__ == "__main__":
diff --git a/docs_src/testing/app01/main.py b/docs_src/testing/app01/main.py
index dc4743da2b..ed202734f4 100644
--- a/docs_src/testing/app01/main.py
+++ b/docs_src/testing/app01/main.py
@@ -7,9 +7,9 @@
@app.command()
def main(name: str, city: Optional[str] = None):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if city:
- typer.echo(f"Let's have a coffee in {city}")
+ print(f"Let's have a coffee in {city}")
if __name__ == "__main__":
diff --git a/docs_src/testing/app02/main.py b/docs_src/testing/app02/main.py
index 1b3d6c7c56..c3b141c626 100644
--- a/docs_src/testing/app02/main.py
+++ b/docs_src/testing/app02/main.py
@@ -5,7 +5,7 @@
@app.command()
def main(name: str, email: str = typer.Option(..., prompt=True)):
- typer.echo(f"Hello {name}, your email is: {email}")
+ print(f"Hello {name}, your email is: {email}")
if __name__ == "__main__":
diff --git a/docs_src/testing/app03/main.py b/docs_src/testing/app03/main.py
index 4dfefdeafa..97325110d2 100644
--- a/docs_src/testing/app03/main.py
+++ b/docs_src/testing/app03/main.py
@@ -2,7 +2,7 @@
def main(name: str = "World"):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/docs_src/using_click/tutorial003.py b/docs_src/using_click/tutorial003.py
index b2b2d42049..a8f99e4623 100644
--- a/docs_src/using_click/tutorial003.py
+++ b/docs_src/using_click/tutorial003.py
@@ -9,7 +9,7 @@ def top():
"""
Top level command, form Typer
"""
- typer.echo("The Typer app is at the top level")
+ print("The Typer app is at the top level")
@app.callback()
diff --git a/docs_src/using_click/tutorial004.py b/docs_src/using_click/tutorial004.py
index df43d1273c..6ac94dc5ba 100644
--- a/docs_src/using_click/tutorial004.py
+++ b/docs_src/using_click/tutorial004.py
@@ -25,7 +25,7 @@ def sub():
"""
A single-command Typer sub app
"""
- typer.echo("Typer is now below Click, the Click app is the top level")
+ print("Typer is now below Click, the Click app is the top level")
typer_click_object = typer.main.get_command(app)
diff --git a/mkdocs.yml b/mkdocs.yml
index ca6cf0d476..ed0786340e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -4,6 +4,7 @@ site_url: https://typer.tiangolo.com/
theme:
name: material
+ custom_dir: docs/overrides
palette:
primary: black
accent: teal
@@ -40,7 +41,6 @@ nav:
- CLI Option Name: tutorial/options/name.md
- CLI Option Callback and Context: tutorial/options/callback-and-context.md
- Version CLI Option, is_eager: tutorial/options/version.md
- - CLI Option autocompletion: tutorial/options/autocompletion.md
- Commands:
- Commands Intro: tutorial/commands/index.md
- Command CLI Arguments: tutorial/commands/arguments.md
@@ -50,6 +50,7 @@ nav:
- Typer Callback: tutorial/commands/callback.md
- One or Multiple Commands: tutorial/commands/one-or-multiple.md
- Using the Context: tutorial/commands/context.md
+ - CLI Option autocompletion: tutorial/options-autocompletion.md
- CLI Parameter Types:
- CLI Parameter Types Intro: tutorial/parameter-types/index.md
- Number: tutorial/parameter-types/number.md
@@ -78,6 +79,7 @@ nav:
- Testing: tutorial/testing.md
- Using Click: tutorial/using-click.md
- Building a Package: tutorial/package.md
+ - tutorial/exceptions.md
- Typer CLI - completion for small scripts: typer-cli.md
- Alternatives, Inspiration and Comparisons: alternatives.md
- Help Typer - Get Help: help-typer.md
diff --git a/pyproject.toml b/pyproject.toml
index 299f2e6ea4..acaf8f895d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,6 +24,9 @@ classifiers = [
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
"License :: OSI Approved :: MIT License"
]
requires = [
@@ -38,27 +41,32 @@ Documentation = "https://typer.tiangolo.com/"
[tool.flit.metadata.requires-extra]
test = [
"shellingham >=1.3.0,<2.0.0",
- "pytest >=4.4.0,<5.4.0",
- "pytest-cov >=2.10.0,<3.0.0",
- "coverage >=5.2,<6.0",
- "pytest-xdist >=1.32.0,<2.0.0",
+ "pytest >=4.4.0,<8.0.0",
+ "pytest-cov >=2.10.0,<5.0.0",
+ "coverage >=6.2,<7.0",
+ "pytest-xdist >=1.32.0,<4.0.0",
"pytest-sugar >=0.9.4,<0.10.0",
"mypy ==0.910",
- "black >=19.10b0,<20.0b0",
- "isort >=5.0.6,<6.0.0"
+ "black >=22.3.0,<23.0.0",
+ "isort >=5.0.6,<6.0.0",
+ "rich >=10.11.0,<13.0.0",
]
doc = [
"mkdocs >=1.1.2,<2.0.0",
"mkdocs-material >=8.1.4,<9.0.0",
"mdx-include >=1.4.1,<2.0.0",
+ "pillow >=9.3.0,<10.0.0",
+ "cairosvg >=2.5.2,<3.0.0",
]
dev = [
"autoflake >=1.3.1,<2.0.0",
"flake8 >=3.8.3,<4.0.0",
+ "pre-commit >=2.17.0,<3.0.0",
]
all = [
"colorama >=0.4.3,<0.5.0",
- "shellingham >=1.3.0,<2.0.0"
+ "shellingham >=1.3.0,<2.0.0",
+ "rich >=10.11.0,<13.0.0",
]
[tool.isort]
@@ -69,3 +77,19 @@ skip_glob = [
"docs_src/subcommands/tutorial003/lands.py",
"docs_src/subcommands/tutorial003/main.py",
]
+
+[tool.pytest.ini_options]
+addopts = [
+ "--strict-config",
+ "--strict-markers",
+]
+xfail_strict = true
+junit_family = "xunit2"
+filterwarnings = [
+ "error",
+ # TODO: until I refactor completion to use the new shell_complete
+ "ignore:'autocompletion' is renamed to 'shell_complete'. The old name is deprecated and will be removed in Click 8.1. See the docs about 'Parameter' for information about new behavior.:DeprecationWarning:typer",
+ 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette',
+ # For pytest-xdist
+ 'ignore::DeprecationWarning:xdist',
+]
diff --git a/scripts/test.sh b/scripts/test.sh
index 77dd74d738..742e51a523 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -3,6 +3,10 @@
set -e
set -x
+# For tests, a large terminal width
+export TERMINAL_WIDTH=3000
+# Force disable terminal for tests inside of pytest, takes precedence over GITHUB_ACTIONS env var
+export _TYPER_FORCE_DISABLE_TERMINAL=1
bash ./scripts/test-files.sh
-# Use xdist-pytest --forked to ensure modified sys.path to import relative modules in examples keeps working
-pytest --cov=typer --cov=tests --cov=docs_src --cov-report=term-missing --cov-report=xml -o console_output_style=progress --forked --numprocesses=auto ${@}
+# It seems xdist-pytest ensures modified sys.path to import relative modules in examples keeps working
+pytest --cov-config=.coveragerc --cov --cov-report=term-missing -o console_output_style=progress --numprocesses=auto ${@}
diff --git a/scripts/zip-docs.sh b/scripts/zip-docs.sh
index f2b7ba3be3..69315f5ddd 100644
--- a/scripts/zip-docs.sh
+++ b/scripts/zip-docs.sh
@@ -3,7 +3,9 @@
set -x
set -e
+cd ./site
+
if [ -f docs.zip ]; then
rm -rf docs.zip
fi
-zip -r docs.zip ./site
+zip -r docs.zip ./
diff --git a/tests/assets/compat_click7_8.py b/tests/assets/compat_click7_8.py
index 7ec8447f7f..74ba9e1672 100644
--- a/tests/assets/compat_click7_8.py
+++ b/tests/assets/compat_click7_8.py
@@ -22,7 +22,7 @@ def main(
"""
Say hello.
"""
- typer.echo(f"Hello {name} {lastname}, it seems you have {age}, {nickname}")
+ print(f"Hello {name} {lastname}, it seems you have {age}, {nickname}")
if __name__ == "__main__":
diff --git a/tests/assets/completion_no_types.py b/tests/assets/completion_no_types.py
index 5ec41d5673..8dc610a1b2 100644
--- a/tests/assets/completion_no_types.py
+++ b/tests/assets/completion_no_types.py
@@ -16,7 +16,7 @@ def complete(ctx, args, incomplete):
@app.command()
def main(name: str = typer.Option("World", autocompletion=complete)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/tests/assets/completion_no_types_order.py b/tests/assets/completion_no_types_order.py
index f4df229109..dbbbc77f19 100644
--- a/tests/assets/completion_no_types_order.py
+++ b/tests/assets/completion_no_types_order.py
@@ -16,7 +16,7 @@ def complete(args, incomplete, ctx):
@app.command()
def main(name: str = typer.Option("World", autocompletion=complete)):
- typer.echo(f"Hello {name}")
+ print(f"Hello {name}")
if __name__ == "__main__":
diff --git a/tests/assets/type_error_no_rich.py b/tests/assets/type_error_no_rich.py
new file mode 100644
index 0000000000..ffddd3b54b
--- /dev/null
+++ b/tests/assets/type_error_no_rich.py
@@ -0,0 +1,12 @@
+import typer
+import typer.main
+
+typer.main.rich = None
+
+
+def main(name: str = "morty"):
+ print(name + 3)
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/tests/assets/type_error_no_rich_short_disable.py b/tests/assets/type_error_no_rich_short_disable.py
new file mode 100644
index 0000000000..c14bca318f
--- /dev/null
+++ b/tests/assets/type_error_no_rich_short_disable.py
@@ -0,0 +1,16 @@
+import typer
+import typer.main
+
+typer.main.rich = None
+
+
+app = typer.Typer(pretty_exceptions_short=False)
+
+
+@app.command()
+def main(name: str = "morty"):
+ print(name + 3)
+
+
+if __name__ == "__main__":
+ app()
diff --git a/tests/assets/type_error_normal_traceback.py b/tests/assets/type_error_normal_traceback.py
new file mode 100644
index 0000000000..bcdc0edbe0
--- /dev/null
+++ b/tests/assets/type_error_normal_traceback.py
@@ -0,0 +1,22 @@
+import typer
+
+app = typer.Typer()
+
+
+@app.command()
+def main(name: str = "morty"):
+ print(name)
+
+
+broken_app = typer.Typer()
+
+
+@broken_app.command()
+def broken(name: str = "morty"):
+ print(name + 3)
+
+
+if __name__ == "__main__":
+ app(standalone_mode=False)
+
+ typer.main.get_command(broken_app)()
diff --git a/tests/test_compat/test_option_get_help.py b/tests/test_compat/test_option_get_help.py
index e8e1eac8f8..41a9d5269c 100644
--- a/tests/test_compat/test_option_get_help.py
+++ b/tests/test_compat/test_option_get_help.py
@@ -1,6 +1,8 @@
import os
import subprocess
+import sys
+import typer.core
from typer.testing import CliRunner
from tests.assets import compat_click7_8 as mod
@@ -18,6 +20,19 @@ def test_hidden_option():
assert "(dynamic)" in result.output
+def test_hidden_option_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(mod.app, ["--help"])
+ assert result.exit_code == 0
+ assert "Say hello" in result.output
+ assert "--name" not in result.output
+ assert "/lastname" in result.output
+ assert "TEST_LASTNAME" in result.output
+ assert "(dynamic)" in result.output
+ typer.core.rich = rich
+
+
def test_coverage_call():
result = runner.invoke(mod.app)
assert result.exit_code == 0
@@ -26,7 +41,7 @@ def test_coverage_call():
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_completion/test_completion.py b/tests/test_completion/test_completion.py
index d7ec6d3c6b..7f36547707 100644
--- a/tests/test_completion/test_completion.py
+++ b/tests/test_completion/test_completion.py
@@ -3,7 +3,7 @@
import sys
from pathlib import Path
-from docs_src.first_steps import tutorial001 as mod
+from docs_src.commands.index import tutorial001 as mod
def test_show_completion():
@@ -47,7 +47,7 @@ def test_install_completion():
def test_completion_invalid_instruction():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -63,7 +63,7 @@ def test_completion_invalid_instruction():
def test_completion_source_bash():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -81,7 +81,7 @@ def test_completion_source_bash():
def test_completion_source_invalid_shell():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -96,7 +96,7 @@ def test_completion_source_invalid_shell():
def test_completion_source_invalid_instruction():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -111,7 +111,7 @@ def test_completion_source_invalid_instruction():
def test_completion_source_zsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -126,7 +126,7 @@ def test_completion_source_zsh():
def test_completion_source_fish():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -141,7 +141,7 @@ def test_completion_source_fish():
def test_completion_source_powershell():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -159,7 +159,7 @@ def test_completion_source_powershell():
def test_completion_source_pwsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__],
+ [sys.executable, "-m", "coverage", "run", mod.__file__],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_completion/test_completion_complete.py b/tests/test_completion/test_completion_complete.py
index e4f8b7ebc2..e3fbb3154a 100644
--- a/tests/test_completion/test_completion_complete.py
+++ b/tests/test_completion/test_completion_complete.py
@@ -1,12 +1,13 @@
import os
import subprocess
+import sys
from docs_src.commands.help import tutorial001 as mod
def test_completion_complete_subcommand_bash():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -23,7 +24,7 @@ def test_completion_complete_subcommand_bash():
def test_completion_complete_subcommand_bash_invalid():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -40,7 +41,7 @@ def test_completion_complete_subcommand_bash_invalid():
def test_completion_complete_subcommand_zsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -59,7 +60,7 @@ def test_completion_complete_subcommand_zsh():
def test_completion_complete_subcommand_zsh_files():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -75,7 +76,7 @@ def test_completion_complete_subcommand_zsh_files():
def test_completion_complete_subcommand_fish():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -95,7 +96,7 @@ def test_completion_complete_subcommand_fish():
def test_completion_complete_subcommand_fish_should_complete():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -112,7 +113,7 @@ def test_completion_complete_subcommand_fish_should_complete():
def test_completion_complete_subcommand_fish_should_complete_no():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -129,7 +130,7 @@ def test_completion_complete_subcommand_fish_should_complete_no():
def test_completion_complete_subcommand_powershell():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -147,7 +148,7 @@ def test_completion_complete_subcommand_powershell():
def test_completion_complete_subcommand_pwsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -165,7 +166,7 @@ def test_completion_complete_subcommand_pwsh():
def test_completion_complete_subcommand_noshell():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_completion/test_completion_complete_no_help.py b/tests/test_completion/test_completion_complete_no_help.py
index c221b69987..bf5762c06d 100644
--- a/tests/test_completion/test_completion_complete_no_help.py
+++ b/tests/test_completion/test_completion_complete_no_help.py
@@ -1,12 +1,13 @@
import os
import subprocess
+import sys
from docs_src.commands.index import tutorial002 as mod
def test_completion_complete_subcommand_zsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -23,7 +24,7 @@ def test_completion_complete_subcommand_zsh():
def test_completion_complete_subcommand_fish():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -40,7 +41,7 @@ def test_completion_complete_subcommand_fish():
def test_completion_complete_subcommand_powershell():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -56,7 +57,7 @@ def test_completion_complete_subcommand_powershell():
def test_completion_complete_subcommand_pwsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_completion/test_completion_install.py b/tests/test_completion/test_completion_install.py
index f1acda1bee..0856b9b565 100644
--- a/tests/test_completion/test_completion_install.py
+++ b/tests/test_completion/test_completion_install.py
@@ -1,5 +1,6 @@
import os
import subprocess
+import sys
from pathlib import Path
from unittest import mock
@@ -7,7 +8,7 @@
import typer
from typer.testing import CliRunner
-from docs_src.first_steps import tutorial001 as mod
+from docs_src.commands.index import tutorial001 as mod
runner = CliRunner()
app = typer.Typer()
@@ -16,7 +17,7 @@
def test_completion_install_no_shell():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--install-completion"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--install-completion"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -28,8 +29,8 @@ def test_completion_install_no_shell():
)
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Option '--install-completion' requires an argument" in result.stderr
- or "Error: --install-completion option requires an argument" in result.stderr
+ "Option '--install-completion' requires an argument" in result.stderr
+ or "--install-completion option requires an argument" in result.stderr
)
@@ -39,7 +40,15 @@ def test_completion_install_bash():
if bash_completion_path.is_file():
text = bash_completion_path.read_text()
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--install-completion", "bash"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--install-completion",
+ "bash",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -74,7 +83,15 @@ def test_completion_install_zsh():
if completion_path.is_file():
text = completion_path.read_text()
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--install-completion", "zsh"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--install-completion",
+ "zsh",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -99,9 +116,19 @@ def test_completion_install_zsh():
def test_completion_install_fish():
script_path = Path(mod.__file__)
- completion_path: Path = Path.home() / f".config/fish/completions/{script_path.name}.fish"
+ completion_path: Path = (
+ Path.home() / f".config/fish/completions/{script_path.name}.fish"
+ )
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--install-completion", "fish"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--install-completion",
+ "fish",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -124,7 +151,9 @@ def test_completion_install_fish():
def test_completion_install_powershell():
- completion_path: Path = Path.home() / f".config/powershell/Microsoft.PowerShell_profile.ps1"
+ completion_path: Path = (
+ Path.home() / f".config/powershell/Microsoft.PowerShell_profile.ps1"
+ )
completion_path_bytes = f"{completion_path}\n".encode("windows-1252")
text = ""
if completion_path.is_file(): # pragma: nocover
diff --git a/tests/test_completion/test_completion_show.py b/tests/test_completion/test_completion_show.py
index 2efb6d8af9..02e70cb8ae 100644
--- a/tests/test_completion/test_completion_show.py
+++ b/tests/test_completion/test_completion_show.py
@@ -1,12 +1,13 @@
import os
import subprocess
+import sys
-from docs_src.first_steps import tutorial001 as mod
+from docs_src.commands.index import tutorial001 as mod
def test_completion_show_no_shell():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--show-completion"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--show-completion"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -18,14 +19,22 @@ def test_completion_show_no_shell():
)
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Option '--show-completion' requires an argument" in result.stderr
- or "Error: --show-completion option requires an argument" in result.stderr
+ "Option '--show-completion' requires an argument" in result.stderr
+ or "--show-completion option requires an argument" in result.stderr
)
def test_completion_show_bash():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--show-completion", "bash"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--show-completion",
+ "bash",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -43,7 +52,15 @@ def test_completion_show_bash():
def test_completion_source_zsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--show-completion", "zsh"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--show-completion",
+ "zsh",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -58,7 +75,15 @@ def test_completion_source_zsh():
def test_completion_source_fish():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--show-completion", "fish"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--show-completion",
+ "fish",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -73,7 +98,15 @@ def test_completion_source_fish():
def test_completion_source_powershell():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--show-completion", "powershell"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--show-completion",
+ "powershell",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -91,7 +124,15 @@ def test_completion_source_powershell():
def test_completion_source_pwsh():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--show-completion", "pwsh"],
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ mod.__file__,
+ "--show-completion",
+ "pwsh",
+ ],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_exit_errors.py b/tests/test_exit_errors.py
new file mode 100644
index 0000000000..598308b1d1
--- /dev/null
+++ b/tests/test_exit_errors.py
@@ -0,0 +1,45 @@
+import errno
+
+import typer
+import typer.completion
+from typer.testing import CliRunner
+
+runner = CliRunner()
+
+
+def test_eoferror():
+ # Mainly for coverage/completeness
+ app = typer.Typer()
+
+ @app.command()
+ def main():
+ raise EOFError()
+
+ result = runner.invoke(app)
+ assert result.exit_code == 1
+
+
+def test_oserror():
+ # Mainly for coverage/completeness
+ app = typer.Typer()
+
+ @app.command()
+ def main():
+ e = OSError()
+ e.errno = errno.EPIPE
+ raise e
+
+ result = runner.invoke(app)
+ assert result.exit_code == 1
+
+
+def test_oserror_no_epipe():
+ # Mainly for coverage/completeness
+ app = typer.Typer()
+
+ @app.command()
+ def main():
+ raise OSError()
+
+ result = runner.invoke(app)
+ assert result.exit_code == 1
diff --git a/tests/test_others.py b/tests/test_others.py
index 2060a86e7b..fb41bdc4f3 100644
--- a/tests/test_others.py
+++ b/tests/test_others.py
@@ -1,7 +1,7 @@
import os
import subprocess
+import sys
from pathlib import Path
-from typing import Optional
from unittest import mock
import click
@@ -16,37 +16,6 @@
runner = CliRunner()
-def test_optional():
- app = typer.Typer()
-
- @app.command()
- def opt(user: Optional[str] = None):
- if user:
- typer.echo(f"User: {user}")
- else:
- typer.echo("No user")
-
- result = runner.invoke(app)
- assert result.exit_code == 0
- assert "No user" in result.output
-
- result = runner.invoke(app, ["--user", "Camila"])
- assert result.exit_code == 0
- assert "User: Camila" in result.output
-
-
-def test_no_type():
- app = typer.Typer()
-
- @app.command()
- def no_type(user):
- typer.echo(f"User: {user}")
-
- result = runner.invoke(app, ["Camila"])
- assert result.exit_code == 0
- assert "User: Camila" in result.output
-
-
def test_help_from_info():
# Mainly for coverage/completeness
value = solve_typer_info_help(TyperInfo())
@@ -64,7 +33,7 @@ def test_install_invalid_shell():
@app.command()
def main():
- typer.echo("Hello World")
+ print("Hello World")
with mock.patch.object(
shellingham, "detect_shell", return_value=("xshell", "/usr/bin/xshell")
@@ -96,12 +65,12 @@ def test_callback_2_untyped_parameters():
app = typer.Typer()
def name_callback(ctx, value):
- typer.echo(f"info name is: {ctx.info_name}")
- typer.echo(f"value is: {value}")
+ print(f"info name is: {ctx.info_name}")
+ print(f"value is: {value}")
@app.command()
def main(name: str = typer.Option(..., callback=name_callback)):
- typer.echo("Hello World")
+ print("Hello World")
result = runner.invoke(app, ["--name", "Camila"])
assert "info name is: main" in result.stdout
@@ -112,13 +81,13 @@ def test_callback_3_untyped_parameters():
app = typer.Typer()
def name_callback(ctx, param, value):
- typer.echo(f"info name is: {ctx.info_name}")
- typer.echo(f"param name is: {param.name}")
- typer.echo(f"value is: {value}")
+ print(f"info name is: {ctx.info_name}")
+ print(f"param name is: {param.name}")
+ print(f"value is: {value}")
@app.command()
def main(name: str = typer.Option(..., callback=name_callback)):
- typer.echo("Hello World")
+ print("Hello World")
result = runner.invoke(app, ["--name", "Camila"])
assert "info name is: main" in result.stdout
@@ -129,7 +98,7 @@ def main(name: str = typer.Option(..., callback=name_callback)):
def test_completion_untyped_parameters():
file_path = Path(__file__).parent / "assets/completion_no_types.py"
result = subprocess.run(
- ["coverage", "run", str(file_path)],
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -151,7 +120,7 @@ def test_completion_untyped_parameters():
assert '"Carlos":"The writer of scripts."' in result.stdout
result = subprocess.run(
- ["coverage", "run", str(file_path)],
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -162,7 +131,7 @@ def test_completion_untyped_parameters():
def test_completion_untyped_parameters_different_order_correct_names():
file_path = Path(__file__).parent / "assets/completion_no_types_order.py"
result = subprocess.run(
- ["coverage", "run", str(file_path)],
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -184,7 +153,7 @@ def test_completion_untyped_parameters_different_order_correct_names():
assert '"Carlos":"The writer of scripts."' in result.stdout
result = subprocess.run(
- ["coverage", "run", str(file_path)],
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -212,23 +181,32 @@ def test_forward_references():
@app.command()
def main(arg1, arg2: int, arg3: "int", arg4: bool = False, arg5: "bool" = False):
- typer.echo(f"arg1: {type(arg1)} {arg1}")
- typer.echo(f"arg2: {type(arg2)} {arg2}")
- typer.echo(f"arg3: {type(arg3)} {arg3}")
- typer.echo(f"arg4: {type(arg4)} {arg4}")
- typer.echo(f"arg5: {type(arg5)} {arg5}")
+ print(f"arg1: {type(arg1)} {arg1}")
+ print(f"arg2: {type(arg2)} {arg2}")
+ print(f"arg3: {type(arg3)} {arg3}")
+ print(f"arg4: {type(arg4)} {arg4}")
+ print(f"arg5: {type(arg5)} {arg5}")
result = runner.invoke(app, ["Hello", "2", "invalid"])
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for 'ARG3': 'invalid' is not a valid integer"
- in result.stdout
- or "Error: Invalid value for 'ARG3': invalid is not a valid integer"
- in result.stdout
+ "Invalid value for 'ARG3': 'invalid' is not a valid integer" in result.stdout
+ or "Invalid value for 'ARG3': invalid is not a valid integer" in result.stdout
)
result = runner.invoke(app, ["Hello", "2", "3", "--arg4", "--arg5"])
assert (
"arg1: Hello\narg2: 2\narg3: 3\narg4: True\narg5: True\n"
in result.stdout
)
+
+
+def test_context_settings_inheritance_single_command():
+ app = typer.Typer(context_settings=dict(help_option_names=["-h", "--help"]))
+
+ @app.command()
+ def main(name: str):
+ pass # pragma: nocover
+
+ result = runner.invoke(app, ["main", "-h"])
+ assert "Show this message and exit." in result.stdout
diff --git a/tests/test_prog_name.py b/tests/test_prog_name.py
index 06dd8c83c4..58626c7ac2 100644
--- a/tests/test_prog_name.py
+++ b/tests/test_prog_name.py
@@ -1,11 +1,12 @@
import subprocess
+import sys
from pathlib import Path
def test_custom_prog_name():
file_path = Path(__file__).parent / "assets/prog_name.py"
result = subprocess.run(
- ["coverage", "run", str(file_path), "--help"],
+ [sys.executable, "-m", "coverage", "run", str(file_path), "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_rich_utils.py b/tests/test_rich_utils.py
new file mode 100644
index 0000000000..b6da8995f6
--- /dev/null
+++ b/tests/test_rich_utils.py
@@ -0,0 +1,38 @@
+import typer
+import typer.completion
+from typer.testing import CliRunner
+
+runner = CliRunner()
+
+
+def test_rich_utils_click_rewrapp():
+ app = typer.Typer(rich_markup_mode="markdown")
+
+ @app.command()
+ def main():
+ """
+ \b
+ Some text
+
+ Some unwrapped text
+ """
+ print("Hello World")
+
+ @app.command()
+ def secondary():
+ """
+ \b
+ Secondary text
+
+ Some unwrapped text
+ """
+ print("Hello Secondary World")
+
+ result = runner.invoke(app, ["--help"])
+ assert "Some text" in result.stdout
+ assert "Secondary text" in result.stdout
+ assert "\b" not in result.stdout
+ result = runner.invoke(app, ["main"])
+ assert "Hello World" in result.stdout
+ result = runner.invoke(app, ["secondary"])
+ assert "Hello Secondary World" in result.stdout
diff --git a/tests/test_tracebacks.py b/tests/test_tracebacks.py
new file mode 100644
index 0000000000..0289e9c942
--- /dev/null
+++ b/tests/test_tracebacks.py
@@ -0,0 +1,67 @@
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+
+def test_traceback_no_rich():
+ file_path = Path(__file__).parent / "assets/type_error_no_rich.py"
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "return get_command(self)(*args, **kwargs)" not in result.stderr
+
+ assert "typer.run(main)" in result.stderr
+ assert "print(name + 3)" in result.stderr
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+
+
+def test_traceback_no_rich_short_disable():
+ file_path = Path(__file__).parent / "assets/type_error_no_rich_short_disable.py"
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "return get_command(self)(*args, **kwargs)" not in result.stderr
+
+ assert "app()" in result.stderr
+ assert "print(name + 3)" in result.stderr
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+
+
+def test_unmodified_traceback():
+ file_path = Path(__file__).parent / "assets/type_error_normal_traceback.py"
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "morty" in result.stdout, "the call to the first app should work normally"
+ assert "return callback(**use_params)" in result.stderr, (
+ "calling outside of Typer should show the normal traceback, "
+ "even after the hook is installed"
+ )
+ assert "typer.main.get_command(broken_app)()" in result.stderr
+ assert "print(name + 3)" in result.stderr
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
diff --git a/tests/test_tutorial/test_arguments/test_default/test_tutorial001.py b/tests/test_tutorial/test_arguments/test_default/test_tutorial001.py
index 63e841415f..00e5734463 100644
--- a/tests/test_tutorial/test_arguments/test_default/test_tutorial001.py
+++ b/tests/test_tutorial/test_arguments/test_default/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -15,7 +16,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "[default: Wade Wilson]" in result.output
@@ -33,7 +34,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_default/test_tutorial002.py b/tests/test_tutorial/test_arguments/test_default/test_tutorial002.py
index b78d1c168c..11b096b6c2 100644
--- a/tests/test_tutorial/test_arguments/test_default/test_tutorial002.py
+++ b/tests/test_tutorial/test_arguments/test_default/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -15,7 +16,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "[default: (dynamic)]" in result.output
@@ -35,7 +36,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py b/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py
index 92ef84f0f2..1567e12565 100644
--- a/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py
+++ b/tests/test_tutorial/test_arguments/test_envvar/test_tutorial001.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.arguments.envvar import tutorial001 as mod
@@ -15,8 +17,21 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
- assert "Arguments:" in result.output
- assert "[env var: AWESOME_NAME;default: World]" in result.output
+ assert "Arguments" in result.output
+ assert "env var: AWESOME_NAME" in result.output
+ assert "default: World" in result.output
+
+
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAME]" in result.output
+ assert "Arguments" in result.output
+ assert "env var: AWESOME_NAME" in result.output
+ assert "default: World" in result.output
+ typer.core.rich = rich
def test_call_arg():
@@ -39,7 +54,7 @@ def test_call_env_var_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002.py b/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002.py
index f9e23f8da4..ce960f4c0c 100644
--- a/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002.py
+++ b/tests/test_tutorial/test_arguments/test_envvar/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -15,8 +16,9 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
- assert "Arguments:" in result.output
- assert "[env var: AWESOME_NAME, GOD_NAME;default: World]" in result.output
+ assert "Arguments" in result.output
+ assert "env var: AWESOME_NAME, GOD_NAME" in result.output
+ assert "default: World" in result.output
def test_call_arg():
@@ -39,7 +41,7 @@ def test_call_env_var2():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003.py b/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003.py
index 3a98fc5c20..396ebf23a5 100644
--- a/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003.py
+++ b/tests/test_tutorial/test_arguments/test_envvar/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -15,9 +16,9 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
- assert "Arguments:" in result.output
- assert "[env var: AWESOME_NAME;default: World]" not in result.output
- assert "[default: World]" in result.output
+ assert "Arguments" in result.output
+ assert "env var: AWESOME_NAME" not in result.output
+ assert "default: World" in result.output
def test_call_arg():
@@ -40,7 +41,7 @@ def test_call_env_var_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial001.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial001.py
index ad448cef32..1822f62a92 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial001.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial001.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.arguments.help import tutorial001 as mod
@@ -15,12 +17,25 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] NAME" in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "NAME" in result.output
assert "The name of the user to greet" in result.output
assert "[required]" in result.output
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] NAME" in result.output
+ assert "Arguments" in result.output
+ assert "NAME" in result.output
+ assert "The name of the user to greet" in result.output
+ assert "[required]" in result.output
+ typer.core.rich = rich
+
+
def test_call_arg():
result = runner.invoke(app, ["Camila"])
assert result.exit_code == 0
@@ -29,7 +44,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial002.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial002.py
index 8f2f079d08..30a183e1a2 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial002.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -16,7 +17,7 @@ def test_help():
assert result.exit_code == 0
assert "[OPTIONS] NAME" in result.output
assert "Say hi to NAME very gently, like Dirk." in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "NAME" in result.output
assert "The name of the user to greet" in result.output
assert "[required]" in result.output
@@ -30,7 +31,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial003.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial003.py
index db6ab4595e..9a1b8d1666 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial003.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -16,7 +17,7 @@ def test_help():
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
assert "Say hi to NAME very gently, like Dirk." in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "NAME" in result.output
assert "Who to greet" in result.output
assert "[default: World]" in result.output
@@ -30,7 +31,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial004.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial004.py
index 46eb0b36f1..771fbaf6dc 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial004.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -16,7 +17,7 @@ def test_help():
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
assert "Say hi to NAME very gently, like Dirk." in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "NAME" in result.output
assert "Who to greet" in result.output
assert "[default: World]" not in result.output
@@ -30,7 +31,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial005.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial005.py
index 5405b4cfa4..cf962bb4fe 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial005.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial005.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -15,7 +16,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAME]" in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "Who to greet" in result.output
assert "[default: (Deadpoolio the amazing's name)]" in result.output
@@ -28,7 +29,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial006.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial006.py
index 89acf99dbf..1b4229edb6 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial006.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial006.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -15,7 +16,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] ✨username✨" in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "✨username✨" in result.output
assert "[default: World]" in result.output
@@ -28,7 +29,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial007.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial007.py
index 3b86dc28ca..3c6eddd028 100644
--- a/tests/test_tutorial/test_arguments/test_help/test_tutorial007.py
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial007.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.arguments.help import tutorial007 as mod
@@ -14,10 +16,9 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "[OPTIONS] [NAME]" in result.output
assert "Say hi to NAME very gently, like Dirk." in result.output
- assert "Arguments:" not in result.output
- assert "[default: World]" not in result.output
+ assert "Arguments" in result.output
+ assert "Secondary Arguments" in result.output
def test_call_arg():
@@ -28,7 +29,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_help/test_tutorial008.py b/tests/test_tutorial/test_arguments/test_help/test_tutorial008.py
new file mode 100644
index 0000000000..f26c6e75ab
--- /dev/null
+++ b/tests/test_tutorial/test_arguments/test_help/test_tutorial008.py
@@ -0,0 +1,50 @@
+import subprocess
+import sys
+
+import typer
+import typer.core
+from typer.testing import CliRunner
+
+from docs_src.arguments.help import tutorial008 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAME]" in result.output
+ assert "Say hi to NAME very gently, like Dirk." in result.output
+ assert "Arguments" not in result.output
+ assert "[default: World]" not in result.output
+
+
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAME]" in result.output
+ assert "Say hi to NAME very gently, like Dirk." in result.output
+ assert "Arguments" not in result.output
+ assert "[default: World]" not in result.output
+ typer.core.rich = rich
+
+
+def test_call_arg():
+ result = runner.invoke(app, ["Camila"])
+ assert result.exit_code == 0
+ assert "Hello Camila" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py b/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py
index 0b776bd763..efcf8a11f6 100644
--- a/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py
+++ b/tests/test_tutorial/test_arguments/test_optional/test_tutorial001.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.arguments.optional import tutorial001 as mod
@@ -14,7 +16,23 @@
def test_call_no_arg():
result = runner.invoke(app)
assert result.exit_code != 0
- assert "Error: Missing argument 'NAME'." in result.output
+ assert "Missing argument 'NAME'." in result.output
+
+
+def test_call_no_arg_standalone():
+ # Mainly for coverage
+ result = runner.invoke(app, standalone_mode=False)
+ assert result.exit_code != 0
+
+
+def test_call_no_arg_no_rich():
+ # Mainly for coverage
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app)
+ assert result.exit_code != 0
+ assert "Error: Missing argument 'NAME'" in result.stdout
+ typer.core.rich = rich
def test_call_arg():
@@ -25,7 +43,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_arguments/test_optional/test_tutorial002.py b/tests/test_tutorial/test_arguments/test_optional/test_tutorial002.py
index 9a2de9575f..c616d467ee 100644
--- a/tests/test_tutorial/test_arguments/test_optional/test_tutorial002.py
+++ b/tests/test_tutorial/test_arguments/test_optional/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -31,7 +32,7 @@ def test_call_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_arguments/test_tutorial001.py b/tests/test_tutorial/test_commands/test_arguments/test_tutorial001.py
index a5615a0376..f5156699b1 100644
--- a/tests/test_tutorial/test_commands/test_arguments/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_arguments/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -35,7 +36,7 @@ def test_delete():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py b/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py
index 6d4dc1ff77..8bda783bb9 100644
--- a/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_callback/test_tutorial001.py
@@ -1,5 +1,7 @@
import subprocess
+import sys
+import typer.core
from typer.testing import CliRunner
from docs_src.commands.callback import tutorial001 as mod
@@ -13,7 +15,19 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "Manage users in the awesome CLI app." in result.output
- assert "--verbose / --no-verbose" in result.output
+ assert "--verbose" in result.output
+ assert "--no-verbose" in result.output
+
+
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "Manage users in the awesome CLI app." in result.output
+ assert "--verbose" in result.output
+ assert "--no-verbose" in result.output
+ typer.core.rich = rich
def test_create():
@@ -51,14 +65,14 @@ def test_wrong_verbose():
assert result.exit_code != 0
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: No such option: --verbose" in result.output
- or "Error: no such option: --verbose" in result.output
+ "No such option: --verbose" in result.output
+ or "no such option: --verbose" in result.output
)
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_callback/test_tutorial002.py b/tests/test_tutorial/test_commands/test_callback/test_tutorial002.py
index 4fd4e1bb61..d8478d35eb 100644
--- a/tests/test_tutorial/test_commands/test_callback/test_tutorial002.py
+++ b/tests/test_tutorial/test_commands/test_callback/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -18,7 +19,7 @@ def test_app():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_callback/test_tutorial003.py b/tests/test_tutorial/test_commands/test_callback/test_tutorial003.py
index 3f13891109..bf0e5f3c50 100644
--- a/tests/test_tutorial/test_commands/test_callback/test_tutorial003.py
+++ b/tests/test_tutorial/test_commands/test_callback/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -23,7 +24,7 @@ def test_for_coverage():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_callback/test_tutorial004.py b/tests/test_tutorial/test_commands/test_callback/test_tutorial004.py
index e2d1d2f4db..79432286ce 100644
--- a/tests/test_tutorial/test_commands/test_callback/test_tutorial004.py
+++ b/tests/test_tutorial/test_commands/test_callback/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -25,7 +26,7 @@ def test_app():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_context/test_tutorial001.py b/tests/test_tutorial/test_commands/test_context/test_tutorial001.py
index df15d7c390..2e3c7b784c 100644
--- a/tests/test_tutorial/test_commands/test_context/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_context/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -25,7 +26,7 @@ def test_delete():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_context/test_tutorial002.py b/tests/test_tutorial/test_commands/test_context/test_tutorial002.py
index 80556fcf4c..6aab364c2a 100644
--- a/tests/test_tutorial/test_commands/test_context/test_tutorial002.py
+++ b/tests/test_tutorial/test_commands/test_context/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -31,7 +32,7 @@ def test_callback():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_context/test_tutorial003.py b/tests/test_tutorial/test_commands/test_context/test_tutorial003.py
index 3b15d2739f..6d5b5c37fb 100644
--- a/tests/test_tutorial/test_commands/test_context/test_tutorial003.py
+++ b/tests/test_tutorial/test_commands/test_context/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -31,7 +32,7 @@ def test_callback():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_context/test_tutorial004.py b/tests/test_tutorial/test_commands/test_context/test_tutorial004.py
index 3546cc3ad1..8ce5997107 100644
--- a/tests/test_tutorial/test_commands/test_context/test_tutorial004.py
+++ b/tests/test_tutorial/test_commands/test_context/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -20,7 +21,7 @@ def test_1():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial001.py b/tests/test_tutorial/test_commands/test_help/test_tutorial001.py
index 232369ca80..bad756c405 100644
--- a/tests/test_tutorial/test_commands/test_help/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -35,7 +36,8 @@ def test_help_delete():
assert result.exit_code == 0
assert "delete [OPTIONS] USERNAME" in result.output
assert "Delete a user with USERNAME." in result.output
- assert "--force / --no-force" in result.output
+ assert "--force" in result.output
+ assert "--no-force" in result.output
assert "Force deletion without confirmation." in result.output
@@ -46,7 +48,8 @@ def test_help_delete_all():
assert "Delete ALL users in the database." in result.output
assert "If --force is not used, will ask for confirmation." in result.output
assert "[required]" in result.output
- assert "--force / --no-force" in result.output
+ assert "--force" in result.output
+ assert "--no-force" in result.output
assert "Force deletion without confirmation." in result.output
@@ -115,7 +118,7 @@ def test_init():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial002.py b/tests/test_tutorial/test_commands/test_help/test_tutorial002.py
index dcadf53f81..f53b5fe6f6 100644
--- a/tests/test_tutorial/test_commands/test_help/test_tutorial002.py
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -48,7 +49,7 @@ def test_delete():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial003.py b/tests/test_tutorial/test_commands/test_help/test_tutorial003.py
new file mode 100644
index 0000000000..7d5f47fa62
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial003.py
@@ -0,0 +1,45 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial003 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "create" in result.output
+ assert "Create a user." in result.output
+ assert "delete" in result.output
+ assert "(deprecated)" in result.output
+ assert "Delete a user." in result.output
+
+
+def test_help_delete():
+ result = runner.invoke(app, ["delete", "--help"])
+ assert result.exit_code == 0
+ assert "(deprecated)" in result.output
+ assert "Delete a user." in result.output
+
+
+def test_call():
+ # Mainly for coverage
+ result = runner.invoke(app, ["create", "Camila"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["delete", "Camila"])
+ assert result.exit_code == 0
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial004.py b/tests/test_tutorial/test_commands/test_help/test_tutorial004.py
new file mode 100644
index 0000000000..47b90d945f
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial004.py
@@ -0,0 +1,60 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial004 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "create" in result.output
+ assert "Create a new shinny user. ✨" in result.output
+ assert "delete" in result.output
+ assert "Delete a user with USERNAME." in result.output
+ assert "Some internal utility function to create." not in result.output
+ assert "Some internal utility function to delete." not in result.output
+
+
+def test_help_create():
+ result = runner.invoke(app, ["create", "--help"])
+ assert result.exit_code == 0
+ assert "Create a new shinny user. ✨" in result.output
+ assert "The username to be created" in result.output
+ assert "Some internal utility function to create." not in result.output
+
+
+def test_help_delete():
+ result = runner.invoke(app, ["delete", "--help"])
+ assert result.exit_code == 0
+ assert "Delete a user with USERNAME." in result.output
+ assert "The username to be deleted" in result.output
+ assert "Force the deletion 💥" in result.output
+ assert "Some internal utility function to delete." not in result.output
+
+
+def test_create():
+ result = runner.invoke(app, ["create", "Camila"])
+ assert result.exit_code == 0
+ assert "Creating user: Camila" in result.output
+
+
+def test_delete():
+ result = runner.invoke(app, ["delete", "Camila"])
+ assert result.exit_code == 0
+ assert "Deleting user: Camila" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial005.py b/tests/test_tutorial/test_commands/test_help/test_tutorial005.py
new file mode 100644
index 0000000000..4299d83197
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial005.py
@@ -0,0 +1,61 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial005 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "create" in result.output
+ assert "Create a new shinny user. ✨" in result.output
+ assert "delete" in result.output
+ assert "Delete a user with USERNAME." in result.output
+ assert "Some internal utility function to create." not in result.output
+ assert "Some internal utility function to delete." not in result.output
+
+
+def test_help_create():
+ result = runner.invoke(app, ["create", "--help"])
+ assert result.exit_code == 0
+ assert "Create a new shinny user. ✨" in result.output
+ assert "The username to be created" in result.output
+ assert "Learn more at the Typer docs website" in result.output
+ assert "Some internal utility function to create." not in result.output
+
+
+def test_help_delete():
+ result = runner.invoke(app, ["delete", "--help"])
+ assert result.exit_code == 0
+ assert "Delete a user with USERNAME." in result.output
+ assert "The username to be deleted" in result.output
+ assert "Force the deletion 💥" in result.output
+ assert "Some internal utility function to delete." not in result.output
+
+
+def test_create():
+ result = runner.invoke(app, ["create", "Camila"])
+ assert result.exit_code == 0
+ assert "Creating user: Camila" in result.output
+
+
+def test_delete():
+ result = runner.invoke(app, ["delete", "Camila"])
+ assert result.exit_code == 0
+ assert "Deleting user: Camila" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial006.py b/tests/test_tutorial/test_commands/test_help/test_tutorial006.py
new file mode 100644
index 0000000000..14f0815b3b
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial006.py
@@ -0,0 +1,52 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial006 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_main_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "create" in result.output
+ assert "Create a new user. ✨" in result.output
+ assert "delete" in result.output
+ assert "Delete a user. 🔥" in result.output
+ assert "Utils and Configs" in result.output
+ assert "config" in result.output
+ assert "Configure the system. 🔧" in result.output
+ assert "Synchronize the system or something fancy like that. ♻" in result.output
+ assert "Help and Others" in result.output
+ assert "Get help with the system. ❓" in result.output
+ assert "Report an issue. 🐛" in result.output
+
+
+def test_call():
+ # Mainly for coverage
+ result = runner.invoke(app, ["create", "Morty"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["delete", "Morty"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["config", "Morty"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["sync"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["help"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["report"])
+ assert result.exit_code == 0
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial007.py b/tests/test_tutorial/test_commands/test_help/test_tutorial007.py
new file mode 100644
index 0000000000..3a753040f8
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial007.py
@@ -0,0 +1,56 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial007 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_main_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "create" in result.output
+ assert "Create a new user. ✨" in result.output
+ assert "Utils and Configs" in result.output
+ assert "config" in result.output
+ assert "Configure the system. 🔧" in result.output
+
+
+def test_create_help():
+ result = runner.invoke(app, ["create", "--help"])
+ assert result.exit_code == 0
+ assert "username" in result.output
+ assert "The username to create" in result.output
+ assert "Secondary Arguments" in result.output
+ assert "lastname" in result.output
+ assert "The last name of the new user" in result.output
+ assert "--force" in result.output
+ assert "--no-force" in result.output
+ assert "Force the creation of the user" in result.output
+ assert "Additional Data" in result.output
+ assert "--age" in result.output
+ assert "The age of the new user" in result.output
+ assert "--favorite-color" in result.output
+ assert "The favorite color of the new user" in result.output
+
+
+def test_call():
+ # Mainly for coverage
+ result = runner.invoke(app, ["create", "Morty"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["config", "Morty"])
+ assert result.exit_code == 0
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial008.py b/tests/test_tutorial/test_commands/test_help/test_tutorial008.py
new file mode 100644
index 0000000000..b460d9c011
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial008.py
@@ -0,0 +1,33 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial008 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_main_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "Create a new user. ✨" in result.output
+ assert "Made with ❤ in Venus" in result.output
+
+
+def test_call():
+ # Mainly for coverage
+ result = runner.invoke(app, ["Morty"])
+ assert result.exit_code == 0
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_commands/test_index/test_tutorial001.py b/tests/test_tutorial/test_commands/test_index/test_tutorial001.py
index 18abc77716..fee5e76e19 100644
--- a/tests/test_tutorial/test_commands/test_index/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_index/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_no_arg():
result = runner.invoke(app)
assert result.exit_code != 0
- assert "Error: Missing argument 'NAME'." in result.output
+ assert "Missing argument 'NAME'." in result.output
def test_arg():
@@ -23,7 +24,7 @@ def test_arg():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_index/test_tutorial002.py b/tests/test_tutorial/test_commands/test_index/test_tutorial002.py
index 27421dc277..b49e38be0f 100644
--- a/tests/test_tutorial/test_commands/test_index/test_tutorial002.py
+++ b/tests/test_tutorial/test_commands/test_index/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -13,7 +14,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
@@ -32,7 +33,7 @@ def test_delete():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_name/test_tutorial001.py b/tests/test_tutorial/test_commands/test_name/test_tutorial001.py
index f7c44e3671..0dae24f2ed 100644
--- a/tests/test_tutorial/test_commands/test_name/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_name/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
@@ -31,7 +32,7 @@ def test_delete():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial001.py b/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial001.py
index e6afb1f909..5e76dd90cb 100644
--- a/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
@@ -24,7 +25,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial002.py b/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial002.py
index c4ffe59764..8af2daffb3 100644
--- a/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial002.py
+++ b/tests/test_tutorial/test_commands/test_one_or_multiple/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -14,7 +15,7 @@ def test_help():
assert result.exit_code == 0
assert "Creates a single user Hiro Hamada." in result.output
assert "In the next version it will create 5 users more." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
@@ -26,7 +27,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_commands/test_options/test_tutorial001.py b/tests/test_tutorial/test_commands/test_options/test_tutorial001.py
index 3b8b5ca90a..ce3307e3f8 100644
--- a/tests/test_tutorial/test_commands/test_options/test_tutorial001.py
+++ b/tests/test_tutorial/test_commands/test_options/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "delete-all" in result.output
@@ -88,7 +89,7 @@ def test_init():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_completion/__init__.py b/tests/test_tutorial/test_exceptions/__init__.py
similarity index 100%
rename from tests/test_tutorial/test_options/test_completion/__init__.py
rename to tests/test_tutorial/test_exceptions/__init__.py
diff --git a/tests/test_tutorial/test_exceptions/test_tutorial001.py b/tests/test_tutorial/test_exceptions/test_tutorial001.py
new file mode 100644
index 0000000000..0a4590bbf5
--- /dev/null
+++ b/tests/test_tutorial/test_exceptions/test_tutorial001.py
@@ -0,0 +1,64 @@
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+from typer.testing import CliRunner
+
+from docs_src.exceptions import tutorial001 as mod
+
+runner = CliRunner()
+
+
+def test_traceback_rich():
+ file_path = Path(mod.__file__)
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "return get_command(self)(*args, **kwargs)" not in result.stderr
+
+ assert "typer.run(main)" not in result.stderr
+ assert "print(name + 3)" in result.stderr
+
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+ assert "name = 'morty'" in result.stderr
+
+
+def test_standard_traceback_env_var():
+ file_path = Path(mod.__file__)
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": "1"},
+ )
+ assert "return get_command(self)(*args, **kwargs)" in result.stderr
+
+ assert "typer.run(main)" in result.stderr
+ assert "print(name + 3)" in result.stderr
+
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+ assert "name = 'morty'" not in result.stderr
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_exceptions/test_tutorial002.py b/tests/test_tutorial/test_exceptions/test_tutorial002.py
new file mode 100644
index 0000000000..66286e064d
--- /dev/null
+++ b/tests/test_tutorial/test_exceptions/test_tutorial002.py
@@ -0,0 +1,64 @@
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+from typer.testing import CliRunner
+
+from docs_src.exceptions import tutorial002 as mod
+
+runner = CliRunner()
+
+
+def test_traceback_rich():
+ file_path = Path(mod.__file__)
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path), "secret"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "return get_command(self)(*args, **kwargs)" not in result.stderr
+
+ assert "app()" not in result.stderr
+ assert "print(password + 3)" in result.stderr
+
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+ assert "name = 'morty'" not in result.stderr
+
+
+def test_standard_traceback_env_var():
+ file_path = Path(mod.__file__)
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path), "secret"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": "1"},
+ )
+ assert "return get_command(self)(*args, **kwargs)" in result.stderr
+
+ assert "app()" in result.stderr
+ assert "print(password + 3)" in result.stderr
+
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+ assert "name = 'morty'" not in result.stderr
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_exceptions/test_tutorial003.py b/tests/test_tutorial/test_exceptions/test_tutorial003.py
new file mode 100644
index 0000000000..51cb170eba
--- /dev/null
+++ b/tests/test_tutorial/test_exceptions/test_tutorial003.py
@@ -0,0 +1,42 @@
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+from typer.testing import CliRunner
+
+from docs_src.exceptions import tutorial003 as mod
+
+runner = CliRunner()
+
+
+def test_traceback_rich_pretty_short_disable():
+ file_path = Path(mod.__file__)
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "return get_command(self)(*args, **kwargs)" not in result.stderr
+
+ assert "app()" in result.stderr
+ assert "print(name + 3)" in result.stderr
+
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+ assert "name = 'morty'" in result.stderr
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_exceptions/test_tutorial004.py b/tests/test_tutorial/test_exceptions/test_tutorial004.py
new file mode 100644
index 0000000000..bb575e4eb2
--- /dev/null
+++ b/tests/test_tutorial/test_exceptions/test_tutorial004.py
@@ -0,0 +1,40 @@
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+from typer.testing import CliRunner
+
+from docs_src.exceptions import tutorial004 as mod
+
+runner = CliRunner()
+
+
+def test_rich_pretty_exceptions_disable():
+ file_path = Path(mod.__file__)
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", str(file_path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ env={**os.environ, "_TYPER_STANDARD_TRACEBACK": ""},
+ )
+ assert "return get_command(self)(*args, **kwargs)" in result.stderr
+
+ assert "app()" in result.stderr
+ assert "print(name + 3)" in result.stderr
+ # TODO: when deprecating Python 3.6, remove second option
+ assert (
+ 'TypeError: can only concatenate str (not "int") to str' in result.stderr
+ or "TypeError: must be str, not int" in result.stderr
+ )
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001.py
index 8c5438534e..4a465dbe41 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial001.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -17,7 +18,7 @@ def test_cli():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial002.py b/tests/test_tutorial/test_first_steps/test_tutorial002.py
index fc2026f172..7192545c65 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial002.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,7 @@
def test_1():
result = runner.invoke(app, [])
assert result.exit_code != 0
- assert "Error: Missing argument 'NAME'" in result.output
+ assert "Missing argument 'NAME'" in result.output
def test_2():
@@ -25,7 +26,7 @@ def test_2():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial003.py b/tests/test_tutorial/test_first_steps/test_tutorial003.py
index a9e57dafbb..6c39d849b5 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial003.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,7 @@
def test_1():
result = runner.invoke(app, ["Camila"])
assert result.exit_code != 0
- assert "Error: Missing argument 'LASTNAME'" in result.output
+ assert "Missing argument 'LASTNAME'" in result.output
def test_2():
@@ -25,7 +26,7 @@ def test_2():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial004.py b/tests/test_tutorial/test_first_steps/test_tutorial004.py
index 1a3bf3df28..c419bb9014 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial004.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,10 +15,13 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Arguments:" in result.output
- assert "NAME [required]" in result.output
- assert "LASTNAME [required]" in result.output
- assert "--formal / --no-formal" in result.output
+ assert "Arguments" in result.output
+ assert "NAME" in result.output
+ assert "[required]" in result.output
+ assert "LASTNAME" in result.output
+ assert "[required]" in result.output
+ assert "--formal" in result.output
+ assert "--no-formal" in result.output
def test_1():
@@ -46,7 +50,7 @@ def test_formal_3():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial005.py b/tests/test_tutorial/test_first_steps/test_tutorial005.py
index c087b01300..816ab0c413 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial005.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial005.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,10 +15,13 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Arguments:" in result.output
- assert "NAME [required]" in result.output
- assert "--lastname TEXT" in result.output
- assert "--formal / --no-formal" in result.output
+ assert "Arguments" in result.output
+ assert "NAME" in result.output
+ assert "[required]" in result.output
+ assert "--lastname" in result.output
+ assert "TEXT" in result.output
+ assert "--formal" in result.output
+ assert "--no-formal" in result.output
def test_1():
@@ -46,7 +50,7 @@ def test_formal_1():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_first_steps/test_tutorial006.py b/tests/test_tutorial/test_first_steps/test_tutorial006.py
index 560191e3b6..08cafe7ff3 100644
--- a/tests/test_tutorial/test_first_steps/test_tutorial006.py
+++ b/tests/test_tutorial/test_first_steps/test_tutorial006.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -44,7 +45,7 @@ def test_formal_1():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial001.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial001.py
index d4effbc09a..315000bd66 100644
--- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial001.py
+++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -19,7 +20,7 @@ def test_main():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py
index d97a744ea1..88e2dfe506 100644
--- a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py
+++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] [NAMES]..." in result.output
- assert "Arguments:" in result.output
+ assert "Arguments" in result.output
assert "[default: Harry, Hermione, Ron]" in result.output
@@ -32,8 +33,8 @@ def test_invalid_args():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Argument 'names' takes 3 values" in result.stdout
- or "Error: argument names takes 3 values" in result.stdout
+ "Argument 'names' takes 3 values" in result.stdout
+ or "argument names takes 3 values" in result.stdout
)
@@ -47,7 +48,7 @@ def test_valid_args():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001.py
index fd0a102d31..3c57bfd33e 100644
--- a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001.py
+++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,7 @@ def test_main():
result = runner.invoke(app)
assert result.exit_code != 0
assert "No provided users" in result.output
- assert "Aborted!" in result.output
+ assert "Aborted" in result.output
def test_1_user():
@@ -35,7 +36,7 @@ def test_3_user():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002.py
index 8c4df706a5..d30357e765 100644
--- a/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002.py
+++ b/tests/test_tutorial/test_multiple_values/test_multiple_options/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -30,7 +31,7 @@ def test_2_number():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py
index a6862f6f92..fcb216595c 100644
--- a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,7 @@ def test_main():
result = runner.invoke(app)
assert result.exit_code != 0
assert "No user provided" in result.output
- assert "Aborted!" in result.output
+ assert "Aborted" in result.output
def test_user_1():
@@ -37,14 +38,14 @@ def test_invalid_user():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Option '--user' requires 3 arguments" in result.output
- or "Error: --user option requires 3 arguments" in result.output
+ "Option '--user' requires 3 arguments" in result.output
+ or "--user option requires 3 arguments" in result.output
)
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_callback/test_tutorial001.py b/tests/test_tutorial/test_options/test_callback/test_tutorial001.py
index 71d2dc8723..f5a43d0fbb 100644
--- a/tests/test_tutorial/test_options/test_callback/test_tutorial001.py
+++ b/tests/test_tutorial/test_options/test_callback/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -20,12 +21,12 @@ def test_1():
def test_2():
result = runner.invoke(app, ["--name", "rick"])
assert result.exit_code != 0
- assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output
+ assert "Invalid value for '--name': Only Camila is allowed" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_callback/test_tutorial003.py b/tests/test_tutorial/test_options/test_callback/test_tutorial003.py
index d4dac975e3..7726997ff0 100644
--- a/tests/test_tutorial/test_options/test_callback/test_tutorial003.py
+++ b/tests/test_tutorial/test_options/test_callback/test_tutorial003.py
@@ -1,5 +1,6 @@
import os
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -22,12 +23,12 @@ def test_1():
def test_2():
result = runner.invoke(app, ["--name", "rick"])
assert result.exit_code != 0
- assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output
+ assert "Invalid value for '--name': Only Camila is allowed" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -37,7 +38,7 @@ def test_script():
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_callback/test_tutorial004.py b/tests/test_tutorial/test_options/test_callback/test_tutorial004.py
index 447c262ed6..473cd9d62f 100644
--- a/tests/test_tutorial/test_options/test_callback/test_tutorial004.py
+++ b/tests/test_tutorial/test_options/test_callback/test_tutorial004.py
@@ -1,5 +1,6 @@
import os
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -22,12 +23,12 @@ def test_1():
def test_2():
result = runner.invoke(app, ["--name", "rick"])
assert result.exit_code != 0
- assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output
+ assert "Invalid value for '--name': Only Camila is allowed" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -37,7 +38,7 @@ def test_script():
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_help/test_tutorial001.py b/tests/test_tutorial/test_options/test_help/test_tutorial001.py
index f9f7d228c3..3e89d55290 100644
--- a/tests/test_tutorial/test_options/test_help/test_tutorial001.py
+++ b/tests/test_tutorial/test_options/test_help/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -40,7 +41,7 @@ def test_formal():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_help/test_tutorial002.py b/tests/test_tutorial/test_options/test_help/test_tutorial002.py
index 501d5d2505..260d6453e8 100644
--- a/tests/test_tutorial/test_options/test_help/test_tutorial002.py
+++ b/tests/test_tutorial/test_options/test_help/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -12,21 +13,31 @@
def test_call():
- result = runner.invoke(app)
+ result = runner.invoke(app, ["World"])
assert result.exit_code == 0
- assert "Hello Wade Wilson" in result.output
+ assert "Hello World" in result.output
+
+
+def test_formal():
+ result = runner.invoke(app, ["World", "--formal"])
+ assert result.exit_code == 0
+ assert "Good day Ms. World" in result.output
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--fullname TEXT" in result.output
- assert "[default: Wade Wilson]" not in result.output
+ assert "--lastname" in result.output
+ assert "Customization and Utils" in result.output
+ assert "--formal" in result.output
+ assert "--no-formal" in result.output
+ assert "--debug" in result.output
+ assert "--no-debug" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_help/test_tutorial003.py b/tests/test_tutorial/test_options/test_help/test_tutorial003.py
new file mode 100644
index 0000000000..f6a9826e9b
--- /dev/null
+++ b/tests/test_tutorial/test_options/test_help/test_tutorial003.py
@@ -0,0 +1,36 @@
+import subprocess
+import sys
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.options.help import tutorial003 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+def test_call():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Hello Wade Wilson" in result.output
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "--fullname" in result.output
+ assert "TEXT" in result.output
+ assert "[default: Wade Wilson]" not in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial001.py b/tests/test_tutorial/test_options/test_name/test_tutorial001.py
index 4db89bc8e3..2690ff24fa 100644
--- a/tests/test_tutorial/test_options/test_name/test_tutorial001.py
+++ b/tests/test_tutorial/test_options/test_name/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,8 @@
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--name TEXT" in result.output
+ assert "--name" in result.output
+ assert "TEXT" in result.output
assert "--user-name" not in result.output
@@ -26,7 +28,7 @@ def test_call():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial002.py b/tests/test_tutorial/test_options/test_name/test_tutorial002.py
index 0426bc020d..5d50e67d1c 100644
--- a/tests/test_tutorial/test_options/test_name/test_tutorial002.py
+++ b/tests/test_tutorial/test_options/test_name/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,9 @@
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "-n, --name TEXT" in result.output
+ assert "-n" in result.output
+ assert "--name" in result.output
+ assert "TEXT" in result.output
assert "--user-name" not in result.output
@@ -32,7 +35,7 @@ def test_call_long():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial003.py b/tests/test_tutorial/test_options/test_name/test_tutorial003.py
index 98ae2537ab..729470985a 100644
--- a/tests/test_tutorial/test_options/test_name/test_tutorial003.py
+++ b/tests/test_tutorial/test_options/test_name/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,8 @@
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "-n TEXT" in result.output
+ assert "-n" in result.output
+ assert "TEXT" in result.output
assert "--user-name" not in result.output
assert "--name" not in result.output
@@ -27,7 +29,7 @@ def test_call():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial004.py b/tests/test_tutorial/test_options/test_name/test_tutorial004.py
index aa327ccebe..b0f4b5c612 100644
--- a/tests/test_tutorial/test_options/test_name/test_tutorial004.py
+++ b/tests/test_tutorial/test_options/test_name/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,9 @@
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "-n, --user-name TEXT" in result.output
+ assert "-n" in result.output
+ assert "--user-name" in result.output
+ assert "TEXT" in result.output
assert "--name" not in result.output
@@ -32,7 +35,7 @@ def test_call_long():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_name/test_tutorial005.py b/tests/test_tutorial/test_options/test_name/test_tutorial005.py
index 1a372b46d0..440c036a09 100644
--- a/tests/test_tutorial/test_options/test_name/test_tutorial005.py
+++ b/tests/test_tutorial/test_options/test_name/test_tutorial005.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,8 +15,11 @@
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "-n, --name TEXT" in result.output
- assert "-f, --formal" in result.output
+ assert "-n" in result.output
+ assert "--name" in result.output
+ assert "TEXT" in result.output
+ assert "-f" in result.output
+ assert "--formal" in result.output
def test_call():
@@ -43,7 +47,7 @@ def test_call_condensed_wrong_order():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_prompt/test_tutorial001.py b/tests/test_tutorial/test_options/test_prompt/test_tutorial001.py
index 8292942d4c..588bc97149 100644
--- a/tests/test_tutorial/test_options/test_prompt/test_tutorial001.py
+++ b/tests/test_tutorial/test_options/test_prompt/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -27,13 +28,14 @@ def test_option_lastname_prompt():
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--lastname TEXT" in result.output
+ assert "--lastname" in result.output
+ assert "TEXT" in result.output
assert "[required]" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_prompt/test_tutorial002.py b/tests/test_tutorial/test_options/test_prompt/test_tutorial002.py
index 89428e5b85..fd2048fd25 100644
--- a/tests/test_tutorial/test_options/test_prompt/test_tutorial002.py
+++ b/tests/test_tutorial/test_options/test_prompt/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -27,13 +28,14 @@ def test_option_lastname_prompt():
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--lastname TEXT" in result.output
+ assert "--lastname" in result.output
+ assert "TEXT" in result.output
assert "[required]" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py b/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py
index 7ef29de19e..ba3204adab 100644
--- a/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py
+++ b/tests/test_tutorial/test_options/test_prompt/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -41,13 +42,14 @@ def test_option():
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--project-name TEXT" in result.output
+ assert "--project-name" in result.output
+ assert "TEXT" in result.output
assert "[required]" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_required/test_tutorial002.py b/tests/test_tutorial/test_options/test_required/test_tutorial002.py
index d2e8bbce28..2e14a9f73e 100644
--- a/tests/test_tutorial/test_options/test_required/test_tutorial002.py
+++ b/tests/test_tutorial/test_options/test_required/test_tutorial002.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.options.required import tutorial001 as mod
@@ -14,7 +16,7 @@
def test_1():
result = runner.invoke(app, ["Camila"])
assert result.exit_code != 0
- assert "Error: Missing option '--lastname'." in result.output
+ assert "Missing option '--lastname'." in result.output
def test_option_lastname():
@@ -26,13 +28,25 @@ def test_option_lastname():
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--lastname TEXT" in result.output
+ assert "--lastname" in result.output
+ assert "TEXT" in result.output
assert "[required]" in result.output
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "--lastname" in result.output
+ assert "TEXT" in result.output
+ assert "[required]" in result.output
+ typer.core.rich = rich
+
+
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_version/test_tutorial003.py b/tests/test_tutorial/test_options/test_version/test_tutorial003.py
index d3f80a9ec3..a5a706077c 100644
--- a/tests/test_tutorial/test_options/test_version/test_tutorial003.py
+++ b/tests/test_tutorial/test_options/test_version/test_tutorial003.py
@@ -1,5 +1,6 @@
import os
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -21,7 +22,7 @@ def test_1():
def test_2():
result = runner.invoke(app, ["--name", "rick"])
assert result.exit_code != 0
- assert "Error: Invalid value for '--name': Only Camila is allowed" in result.output
+ assert "Invalid value for '--name': Only Camila is allowed" in result.output
def test_3():
@@ -32,7 +33,7 @@ def test_3():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -42,7 +43,7 @@ def test_script():
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options_autocompletion/__init__.py b/tests/test_tutorial/test_options_autocompletion/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/test_tutorial/test_options/test_completion/test_tutorial002.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py
similarity index 75%
rename from tests/test_tutorial/test_options/test_completion/test_tutorial002.py
rename to tests/test_tutorial/test_options_autocompletion/test_tutorial002.py
index 593f28b531..3dc81a7624 100644
--- a/tests/test_tutorial/test_options/test_completion/test_tutorial002.py
+++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial002.py
@@ -1,20 +1,17 @@
import os
import subprocess
+import sys
-import typer
from typer.testing import CliRunner
-from docs_src.options.autocompletion import tutorial002 as mod
+from docs_src.options_autocompletion import tutorial002 as mod
runner = CliRunner()
-app = typer.Typer()
-app.command()(mod.main)
-
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -31,14 +28,14 @@ def test_completion():
def test_1():
- result = runner.invoke(app, ["--name", "Camila"])
+ result = runner.invoke(mod.app, ["--name", "Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_completion/test_tutorial003.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py
similarity index 75%
rename from tests/test_tutorial/test_options/test_completion/test_tutorial003.py
rename to tests/test_tutorial/test_options_autocompletion/test_tutorial003.py
index fccb867644..e0665232ed 100644
--- a/tests/test_tutorial/test_options/test_completion/test_tutorial003.py
+++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py
@@ -1,20 +1,17 @@
import os
import subprocess
+import sys
-import typer
from typer.testing import CliRunner
-from docs_src.options.autocompletion import tutorial003 as mod
+from docs_src.options_autocompletion import tutorial003 as mod
runner = CliRunner()
-app = typer.Typer()
-app.command()(mod.main)
-
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -31,14 +28,14 @@ def test_completion():
def test_1():
- result = runner.invoke(app, ["--name", "Camila"])
+ result = runner.invoke(mod.app, ["--name", "Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_completion/test_tutorial004.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py
similarity index 71%
rename from tests/test_tutorial/test_options/test_completion/test_tutorial004.py
rename to tests/test_tutorial/test_options_autocompletion/test_tutorial004.py
index 6ae5d9a831..1aaec9d4ec 100644
--- a/tests/test_tutorial/test_options/test_completion/test_tutorial004.py
+++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial004.py
@@ -1,27 +1,24 @@
import os
import subprocess
+import sys
-import typer
from typer.testing import CliRunner
-from docs_src.options.autocompletion import tutorial004 as mod
+from docs_src.options_autocompletion import tutorial004 as mod
runner = CliRunner()
-app = typer.Typer()
-app.command()(mod.main)
-
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
env={
**os.environ,
"_TUTORIAL004.PY_COMPLETE": "complete_zsh",
- "_TYPER_COMPLETE_ARGS": "tutorial004.py --name ",
+ "_TYPER_COMPLETE_ARGS": "tutorial004_aux.py --name ",
"_TYPER_COMPLETE_TESTING": "True",
},
)
@@ -31,14 +28,14 @@ def test_completion():
def test_1():
- result = runner.invoke(app, ["--name", "Camila"])
+ result = runner.invoke(mod.app, ["--name", "Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_completion/test_tutorial007.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py
similarity index 76%
rename from tests/test_tutorial/test_options/test_completion/test_tutorial007.py
rename to tests/test_tutorial/test_options_autocompletion/test_tutorial007.py
index 097654163c..9af6681eb1 100644
--- a/tests/test_tutorial/test_options/test_completion/test_tutorial007.py
+++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial007.py
@@ -1,20 +1,17 @@
import os
import subprocess
+import sys
-import typer
from typer.testing import CliRunner
-from docs_src.options.autocompletion import tutorial007 as mod
+from docs_src.options_autocompletion import tutorial007 as mod
runner = CliRunner()
-app = typer.Typer()
-app.command()(mod.main)
-
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -31,7 +28,7 @@ def test_completion():
def test_1():
- result = runner.invoke(app, ["--name", "Camila", "--name", "Sebastian"])
+ result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
assert "Hello Sebastian" in result.output
@@ -39,7 +36,7 @@ def test_1():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_completion/test_tutorial008.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py
similarity index 78%
rename from tests/test_tutorial/test_options/test_completion/test_tutorial008.py
rename to tests/test_tutorial/test_options_autocompletion/test_tutorial008.py
index 597d8f002c..78a787a821 100644
--- a/tests/test_tutorial/test_options/test_completion/test_tutorial008.py
+++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial008.py
@@ -1,20 +1,17 @@
import os
import subprocess
+import sys
-import typer
from typer.testing import CliRunner
-from docs_src.options.autocompletion import tutorial008 as mod
+from docs_src.options_autocompletion import tutorial008 as mod
runner = CliRunner()
-app = typer.Typer()
-app.command()(mod.main)
-
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -33,7 +30,7 @@ def test_completion():
def test_1():
- result = runner.invoke(app, ["--name", "Camila", "--name", "Sebastian"])
+ result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
assert "Hello Sebastian" in result.output
@@ -41,7 +38,7 @@ def test_1():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_options/test_completion/test_tutorial009.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py
similarity index 79%
rename from tests/test_tutorial/test_options/test_completion/test_tutorial009.py
rename to tests/test_tutorial/test_options_autocompletion/test_tutorial009.py
index 8e5290806e..ee7918d5dc 100644
--- a/tests/test_tutorial/test_options/test_completion/test_tutorial009.py
+++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial009.py
@@ -1,20 +1,17 @@
import os
import subprocess
+import sys
-import typer
from typer.testing import CliRunner
-from docs_src.options.autocompletion import tutorial009 as mod
+from docs_src.options_autocompletion import tutorial009 as mod
runner = CliRunner()
-app = typer.Typer()
-app.command()(mod.main)
-
def test_completion():
result = subprocess.run(
- ["coverage", "run", mod.__file__, " "],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, " "],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
@@ -33,7 +30,7 @@ def test_completion():
def test_1():
- result = runner.invoke(app, ["--name", "Camila", "--name", "Sebastian"])
+ result = runner.invoke(mod.app, ["--name", "Camila", "--name", "Sebastian"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
assert "Hello Sebastian" in result.output
@@ -41,7 +38,7 @@ def test_1():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py
index 344d65047f..cc4b4548fe 100644
--- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -36,14 +37,14 @@ def test_invalid_no_force():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: No such option: --no-force" in result.output
- or "Error: no such option: --no-force" in result.output
+ "No such option: --no-force" in result.output
+ or "no such option: --no-force" in result.output
)
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py
index 098b196849..339ebce3b6 100644
--- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py
+++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial002.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.parameter_types.bool import tutorial002 as mod
@@ -14,10 +16,22 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--accept / --reject" in result.output
+ assert "--accept" in result.output
+ assert "--reject" in result.output
assert "--no-accept" not in result.output
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "--accept" in result.output
+ assert "--reject" in result.output
+ assert "--no-accept" not in result.output
+ typer.core.rich = rich
+
+
def test_main():
result = runner.invoke(app)
assert result.exit_code == 0
@@ -42,14 +56,14 @@ def test_invalid_no_accept():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: No such option: --no-accept" in result.output
- or "Error: no such option: --no-accept" in result.output
+ "No such option: --no-accept" in result.output
+ or "no such option: --no-accept" in result.output
)
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003.py
index 923c6e8e22..83226bd87c 100644
--- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003.py
+++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,10 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "-f, --force / -F, --no-force" in result.output
+ assert "-f" in result.output
+ assert "--force" in result.output
+ assert "-F" in result.output
+ assert "--no-force" in result.output
def test_force():
@@ -31,7 +35,7 @@ def test_no_force():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004.py
index e4e7935891..a01034d6eb 100644
--- a/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004.py
+++ b/tests/test_tutorial/test_parameter_types/test_bool/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,8 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "/ -d, --demo" in result.output
+ assert "-d" in result.output
+ assert "--demo" in result.output
def test_main():
@@ -37,7 +39,7 @@ def test_short_demo():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py
index 60b69ef7ee..f6be63e645 100644
--- a/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -27,18 +28,23 @@ def test_main():
def test_invalid():
result = runner.invoke(app, ["july-19-1989"])
assert result.exit_code != 0
+ assert (
+ "Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]':"
+ in result.stdout
+ )
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': 'july-19-1989' does not match the formats '%Y-%m-%d', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S'"
- in result.output
- or "Error: Invalid value for 'BIRTH:[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]': invalid datetime format: july-19-1989. (choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S)"
- in result.output
+ "'july-19-1989' does not match the formats" in result.output
+ or "invalid datetime format: july-19-1989. (choose from" in result.output
)
+ assert "%Y-%m-%d" in result.output
+ assert "%Y-%m-%dT%H:%M:%S" in result.output
+ assert "%Y-%m-%d %H:%M:%S" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002.py
index 38ebd3cbc5..ec8fc71cd1 100644
--- a/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002.py
+++ b/tests/test_tutorial/test_parameter_types/test_datetime/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -25,7 +26,7 @@ def test_usa_weird_date_format():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py
index 9944da5b17..584b71a0c7 100644
--- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,7 +15,8 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--network [simple|conv|lstm]" in result.output
+ assert "--network" in result.output
+ assert "[simple|conv|lstm]" in result.output
def test_main():
@@ -29,16 +31,18 @@ def test_invalid():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for '--network': 'capsule' is not one of 'simple', 'conv', 'lstm'"
- in result.output
- or "Error: Invalid value for '--network': invalid choice: capsule. (choose from simple, conv, lstm)"
+ "Invalid value for '--network': 'capsule' is not one of" in result.output
+ or "Invalid value for '--network': invalid choice: capsule. (choose from"
in result.output
)
+ assert "simple" in result.output
+ assert "conv" in result.output
+ assert "lstm" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002.py
index 51686d0ad0..293a1760bf 100644
--- a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002.py
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -25,7 +26,7 @@ def test_mix():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001.py
index 4357c9ff9c..061266abc8 100644
--- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -24,7 +25,7 @@ def test_main(tmpdir):
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002.py
index 5556ccd2ba..bbb7696634 100644
--- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002.py
+++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -26,7 +27,7 @@ def test_main(tmpdir):
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003.py
index 1387ca26b5..ae452d0ddd 100644
--- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003.py
+++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -23,7 +24,7 @@ def test_main(tmpdir):
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py
index 6ce004df3b..aa51f39e18 100644
--- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py
+++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -27,7 +28,7 @@ def test_main(tmpdir):
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005.py
index 841df52720..7f79fd42c0 100644
--- a/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005.py
+++ b/tests/test_tutorial/test_parameter_types/test_file/test_tutorial005.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -29,7 +30,7 @@ def test_main(tmpdir):
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py
index 2e1a3002da..62d16a60ab 100644
--- a/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_index/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -14,8 +15,10 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--age INTEGER" in result.output
- assert "--height-meters FLOAT" in result.output
+ assert "--age" in result.output
+ assert "INTEGER" in result.output
+ assert "--height-meters" in result.output
+ assert "FLOAT" in result.output
def test_params():
@@ -35,16 +38,14 @@ def test_invalid():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for '--age': '15.3' is not a valid integer"
- in result.output
- or "Error: Invalid value for '--age': 15.3 is not a valid integer"
- in result.output
+ "Invalid value for '--age': '15.3' is not a valid integer" in result.output
+ or "Invalid value for '--age': 15.3 is not a valid integer" in result.output
)
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py
index 64d2f2a5df..1e08f0d493 100644
--- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial001.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.parameter_types.number import tutorial001 as mod
@@ -14,8 +16,22 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "--age INTEGER RANGE" in result.output
- assert "--score FLOAT RANGE" in result.output
+ assert "--age" in result.output
+ assert "INTEGER RANGE" in result.output
+ assert "--score" in result.output
+ assert "FLOAT RANGE" in result.output
+
+
+def test_help_no_rich():
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "--age" in result.output
+ assert "INTEGER RANGE" in result.output
+ assert "--score" in result.output
+ assert "FLOAT RANGE" in result.output
+ typer.core.rich = rich
def test_params():
@@ -32,10 +48,10 @@ def test_invalid_id():
# TODO: when deprecating Click 7, remove second option
assert (
(
- "Error: Invalid value for 'ID': 1002 is not in the range 0<=x<=1000."
+ "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000."
in result.output
)
- or "Error: Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000."
+ or "Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000."
in result.output
)
@@ -46,9 +62,8 @@ def test_invalid_age():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for '--age': 15 is not in the range x>=18"
- in result.output
- or "Error: Invalid value for '--age': 15 is smaller than the minimum valid value 18."
+ "Invalid value for '--age': 15 is not in the range x>=18" in result.output
+ or "Invalid value for '--age': 15 is smaller than the minimum valid value 18."
in result.output
)
@@ -59,9 +74,9 @@ def test_invalid_score():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for '--score': 100.5 is not in the range x<=100."
+ "Invalid value for '--score': 100.5 is not in the range x<=100."
in result.output
- or "Error: Invalid value for '--score': 100.5 is bigger than the maximum valid value 100."
+ or "Invalid value for '--score': 100.5 is bigger than the maximum valid value"
in result.output
)
@@ -76,7 +91,7 @@ def test_negative_score():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py
index c0bc226459..2e33f2e7ba 100644
--- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py
+++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -17,9 +18,8 @@ def test_invalid_id():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for 'ID': 1002 is not in the range 0<=x<=1000"
- in result.output
- or "Error: Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000."
+ "Invalid value for 'ID': 1002 is not in the range 0<=x<=1000" in result.output
+ or "Invalid value for 'ID': 1002 is not in the valid range of 0 to 1000."
in result.output
)
@@ -34,7 +34,7 @@ def test_clamped():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003.py b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003.py
index 904b8b0da7..3d0a741cf7 100644
--- a/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003.py
+++ b/tests/test_tutorial/test_parameter_types/test_number/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -49,7 +50,7 @@ def test_verbose_short_3_condensed():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001.py
index 740b92fea1..a616d8f141 100644
--- a/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_path/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -17,7 +18,7 @@ def test_no_path(tmpdir):
result = runner.invoke(app)
assert result.exit_code == 1
assert "No config file" in result.output
- assert "Aborted!" in result.output
+ assert "Aborted" in result.output
def test_not_exists(tmpdir):
@@ -46,7 +47,7 @@ def test_dir():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002.py
index 6bd48ed12d..97c7b7fdb3 100644
--- a/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002.py
+++ b/tests/test_tutorial/test_parameter_types/test_path/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from pathlib import Path
import typer
@@ -18,7 +19,7 @@ def test_not_exists(tmpdir):
config_file.unlink()
result = runner.invoke(app, ["--config", f"{config_file}"])
assert result.exit_code != 0
- assert "Error: Invalid value for '--config': File" in result.output
+ assert "Invalid value for '--config': File" in result.output
assert "does not exist" in result.output
@@ -34,15 +35,12 @@ def test_exists(tmpdir):
def test_dir():
result = runner.invoke(app, ["--config", "./"])
assert result.exit_code != 0
- assert (
- "Error: Invalid value for '--config': File './' is a directory."
- in result.output
- )
+ assert "Invalid value for '--config': File './' is a directory." in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py
index dff324440f..b22ad1251c 100644
--- a/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py
+++ b/tests/test_tutorial/test_parameter_types/test_uuid/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -24,16 +25,16 @@ def test_invalid_uuid():
# TODO: when deprecating Click 7, remove second option
assert (
- "Error: Invalid value for 'USER_ID': '7479706572-72756c6573' is not a valid UUID"
+ "Invalid value for 'USER_ID': '7479706572-72756c6573' is not a valid UUID"
in result.output
- or "Error: Invalid value for 'USER_ID': 7479706572-72756c6573 is not a valid UUID value"
+ or "Invalid value for 'USER_ID': 7479706572-72756c6573 is not a valid UUID value"
in result.output
)
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_prompt/test_tutorial001.py b/tests/test_tutorial/test_prompt/test_tutorial001.py
index 42cbdfc19e..00c9061d19 100644
--- a/tests/test_tutorial/test_prompt/test_tutorial001.py
+++ b/tests/test_tutorial/test_prompt/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -20,7 +21,7 @@ def test_cli():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_prompt/test_tutorial002.py b/tests/test_tutorial/test_prompt/test_tutorial002.py
index 9ac170e5f7..1d3aa911c8 100644
--- a/tests/test_tutorial/test_prompt/test_tutorial002.py
+++ b/tests/test_tutorial/test_prompt/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -23,12 +24,12 @@ def test_no_confirm():
assert result.exit_code == 1
assert "Are you sure you want to delete it? [y/N]:" in result.output
assert "Not deleting" in result.output
- assert "Aborted!" in result.output
+ assert "Aborted" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_prompt/test_tutorial003.py b/tests/test_tutorial/test_prompt/test_tutorial003.py
index 3e66958c66..b34d82301d 100644
--- a/tests/test_tutorial/test_prompt/test_tutorial003.py
+++ b/tests/test_tutorial/test_prompt/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -22,12 +23,12 @@ def test_no_confirm():
result = runner.invoke(app, input="n\n")
assert result.exit_code == 1
assert "Are you sure you want to delete it? [y/N]:" in result.output
- assert "Aborted!" in result.output
+ assert "Aborted" in result.output
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial001.py b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial001.py
index e08e273331..f4bdf5ef96 100644
--- a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial001.py
+++ b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -18,7 +19,7 @@ def test_cli():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial002.py b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial002.py
index 6fdd5b2de9..301142d1e0 100644
--- a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial002.py
+++ b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -18,7 +19,7 @@ def test_cli():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial003.py b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial003.py
index 4f92cc84ec..7ac342db55 100644
--- a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial003.py
+++ b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -23,7 +24,7 @@ def test_for_coverage():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial004.py b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial004.py
index fb4b062f8e..e2d23d0b4d 100644
--- a/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial004.py
+++ b/tests/test_tutorial/test_subcommands/test_callback_override/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -25,7 +26,7 @@ def test_for_coverage():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial001.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial001.py
index b8cc33ec75..dfdb9f444a 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial001.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "users" in result.output
assert "Manage users in the app." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial002.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial002.py
index 244fc84748..dc81bd941d 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial002.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "users" in result.output
assert "Manage users in the app." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial003.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial003.py
index 34d27c508e..af60a9ea44 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial003.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "users" in result.output
assert "Manage users in the app." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial004.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial004.py
index 1b296042cd..75e3ce40f0 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial004.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "users" in result.output
assert "Manage users in the app." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial005.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial005.py
index ae90da7155..5b5697473e 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial005.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial005.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "new-users" in result.output
assert "I have the highland! Create some users." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial006.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial006.py
index bb4328b0f7..4b0b9166e1 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial006.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial006.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "exp-users" in result.output
assert "Explicit help." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial007.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial007.py
index 3251706a3a..faffc70083 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial007.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial007.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "call-users" in result.output
assert "Help from callback for users." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial008.py b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial008.py
index 121b4c4031..6a4d238d32 100644
--- a/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial008.py
+++ b/tests/test_tutorial/test_subcommands/test_name_help/test_tutorial008.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "cake-sith-users" in result.output
assert "Unlimited powder! Eh, users." in result.output
@@ -31,7 +32,7 @@ def test_command():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_tutorial001.py b/tests/test_tutorial/test_subcommands/test_tutorial001.py
index 5cd0bd667a..1280e22f5a 100644
--- a/tests/test_tutorial/test_subcommands/test_tutorial001.py
+++ b/tests/test_tutorial/test_subcommands/test_tutorial001.py
@@ -1,4 +1,6 @@
+import os
import subprocess
+import sys
import pytest
from typer.testing import CliRunner
@@ -26,7 +28,7 @@ def test_help(app):
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "items" in result.output
assert "users" in result.output
@@ -35,7 +37,7 @@ def test_help_items(app):
result = runner.invoke(app, ["items", "--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "sell" in result.output
@@ -63,7 +65,7 @@ def test_help_users(app):
result = runner.invoke(app, ["users", "--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "sell" not in result.output
@@ -84,11 +86,15 @@ def test_users_delete(app):
def test_scripts(mod):
from docs_src.subcommands.tutorial001 import items, users
+ env = os.environ.copy()
+ env["PYTHONPATH"] = ":".join(list(tutorial001.__path__))
+
for module in [mod, items, users]:
result = subprocess.run(
- ["coverage", "run", module.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", module.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
+ env=env,
)
assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_subcommands/test_tutorial002.py b/tests/test_tutorial/test_subcommands/test_tutorial002.py
index 600b03e754..3e1607d838 100644
--- a/tests/test_tutorial/test_subcommands/test_tutorial002.py
+++ b/tests/test_tutorial/test_subcommands/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from typer.testing import CliRunner
@@ -12,7 +13,7 @@ def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "items" in result.output
assert "users" in result.output
@@ -21,7 +22,7 @@ def test_help_items():
result = runner.invoke(app, ["items", "--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "sell" in result.output
@@ -49,7 +50,7 @@ def test_help_users():
result = runner.invoke(app, ["users", "--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "sell" not in result.output
@@ -69,7 +70,7 @@ def test_users_delete():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_subcommands/test_tutorial003.py b/tests/test_tutorial/test_subcommands/test_tutorial003.py
index 6f0be053ac..2d6149cf6a 100644
--- a/tests/test_tutorial/test_subcommands/test_tutorial003.py
+++ b/tests/test_tutorial/test_subcommands/test_tutorial003.py
@@ -1,9 +1,12 @@
+import os
import subprocess
+import sys
import pytest
from typer.testing import CliRunner
from docs_src.subcommands import tutorial003
+from docs_src.subcommands.tutorial003 import items, users
runner = CliRunner()
@@ -26,7 +29,7 @@ def test_help(app):
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "items" in result.output
assert "users" in result.output
assert "lands" in result.output
@@ -36,7 +39,7 @@ def test_help_items(app):
result = runner.invoke(app, ["items", "--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "sell" in result.output
@@ -46,25 +49,37 @@ def test_items_create(app):
result = runner.invoke(app, ["items", "create", "Wand"])
assert result.exit_code == 0
assert "Creating item: Wand" in result.output
+ # For coverage, becauses the monkeypatch above sometimes confuses coverage
+ result = runner.invoke(items.app, ["create", "Wand"])
+ assert result.exit_code == 0
+ assert "Creating item: Wand" in result.output
def test_items_sell(app):
result = runner.invoke(app, ["items", "sell", "Vase"])
assert result.exit_code == 0
assert "Selling item: Vase" in result.output
+ # For coverage, becauses the monkeypatch above sometimes confuses coverage
+ result = runner.invoke(items.app, ["sell", "Vase"])
+ assert result.exit_code == 0
+ assert "Selling item: Vase" in result.output
def test_items_delete(app):
result = runner.invoke(app, ["items", "delete", "Vase"])
assert result.exit_code == 0
assert "Deleting item: Vase" in result.output
+ # For coverage, becauses the monkeypatch above sometimes confuses coverage
+ result = runner.invoke(items.app, ["delete", "Vase"])
+ assert result.exit_code == 0
+ assert "Deleting item: Vase" in result.output
def test_help_users(app):
result = runner.invoke(app, ["users", "--help"])
assert result.exit_code == 0
assert "[OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "create" in result.output
assert "delete" in result.output
assert "sell" not in result.output
@@ -74,19 +89,27 @@ def test_users_create(app):
result = runner.invoke(app, ["users", "create", "Camila"])
assert result.exit_code == 0
assert "Creating user: Camila" in result.output
+ # For coverage, becauses the monkeypatch above sometimes confuses coverage
+ result = runner.invoke(users.app, ["create", "Camila"])
+ assert result.exit_code == 0
+ assert "Creating user: Camila" in result.output
def test_users_delete(app):
result = runner.invoke(app, ["users", "delete", "Camila"])
assert result.exit_code == 0
assert "Deleting user: Camila" in result.output
+ # For coverage, becauses the monkeypatch above sometimes confuses coverage
+ result = runner.invoke(users.app, ["delete", "Camila"])
+ assert result.exit_code == 0
+ assert "Deleting user: Camila" in result.output
def test_help_lands(app):
result = runner.invoke(app, ["lands", "--help"])
assert result.exit_code == 0
assert "lands [OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "reigns" in result.output
assert "towns" in result.output
@@ -95,7 +118,7 @@ def test_help_lands_reigns(app):
result = runner.invoke(app, ["lands", "reigns", "--help"])
assert result.exit_code == 0
assert "lands reigns [OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "conquer" in result.output
assert "destroy" in result.output
@@ -116,7 +139,7 @@ def test_help_lands_towns(app):
result = runner.invoke(app, ["lands", "towns", "--help"])
assert result.exit_code == 0
assert "lands towns [OPTIONS] COMMAND [ARGS]..." in result.output
- assert "Commands:" in result.output
+ assert "Commands" in result.output
assert "burn" in result.output
assert "found" in result.output
@@ -136,11 +159,15 @@ def test_lands_towns_burn(app):
def test_scripts(mod):
from docs_src.subcommands.tutorial003 import items, lands, reigns, towns, users
+ env = os.environ.copy()
+ env["PYTHONPATH"] = ":".join(list(tutorial003.__path__))
+
for module in [mod, items, lands, reigns, towns, users]:
result = subprocess.run(
- ["coverage", "run", module.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", module.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
+ env=env,
)
assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_terminating/test_tutorial001.py b/tests/test_tutorial/test_terminating/test_tutorial001.py
index 6df8333d3a..f6651eb057 100644
--- a/tests/test_tutorial/test_terminating/test_tutorial001.py
+++ b/tests/test_tutorial/test_terminating/test_tutorial001.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -25,9 +26,17 @@ def test_existing():
assert "Notification sent for new user" not in result.output
+def test_existing_no_standalone():
+ # Mainly for coverage
+ result = runner.invoke(app, ["rick"], standalone_mode=False)
+ assert result.exit_code == 0
+ assert "The user already exists" in result.output
+ assert "Notification sent for new user" not in result.output
+
+
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_terminating/test_tutorial002.py b/tests/test_tutorial/test_terminating/test_tutorial002.py
index dd5b5e1215..8c599fe976 100644
--- a/tests/test_tutorial/test_terminating/test_tutorial002.py
+++ b/tests/test_tutorial/test_terminating/test_tutorial002.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import typer
from typer.testing import CliRunner
@@ -25,7 +26,7 @@ def test_root():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_terminating/test_tutorial003.py b/tests/test_tutorial/test_terminating/test_tutorial003.py
index 026f630a8d..7a1909a871 100644
--- a/tests/test_tutorial/test_terminating/test_tutorial003.py
+++ b/tests/test_tutorial/test_terminating/test_tutorial003.py
@@ -1,6 +1,8 @@
import subprocess
+import sys
import typer
+import typer.core
from typer.testing import CliRunner
from docs_src.terminating import tutorial003 as mod
@@ -21,12 +23,29 @@ def test_root():
result = runner.invoke(app, ["root"])
assert result.exit_code == 1
assert "The root user is reserved" in result.output
- assert "Aborted!" in result.output
+ assert "Aborted" in result.output
+
+
+def test_root_no_standalone():
+ # Mainly for coverage
+ result = runner.invoke(app, ["root"], standalone_mode=False)
+ assert result.exit_code == 1
+
+
+def test_root_no_rich():
+ # Mainly for coverage
+ rich = typer.core.rich
+ typer.core.rich = None
+ result = runner.invoke(app, ["root"])
+ assert result.exit_code == 1
+ assert "The root user is reserved" in result.stdout
+ assert "Aborted!" in result.stdout
+ typer.core.rich = rich
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_testing/test_app01.py b/tests/test_tutorial/test_testing/test_app01.py
index 7e7b5291f0..773941a728 100644
--- a/tests/test_tutorial/test_testing/test_app01.py
+++ b/tests/test_tutorial/test_testing/test_app01.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from docs_src.testing.app01 import main as mod
from docs_src.testing.app01.test_main import test_app
@@ -10,7 +11,7 @@ def test_app01():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_testing/test_app02.py b/tests/test_tutorial/test_testing/test_app02.py
index 429f4dba89..8c29d38bfa 100644
--- a/tests/test_tutorial/test_testing/test_app02.py
+++ b/tests/test_tutorial/test_testing/test_app02.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from docs_src.testing.app02 import main as mod
from docs_src.testing.app02.test_main import test_app
@@ -10,7 +11,7 @@ def test_app02():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_testing/test_app03.py b/tests/test_tutorial/test_testing/test_app03.py
index b8cecd9949..aea281dea1 100644
--- a/tests/test_tutorial/test_testing/test_app03.py
+++ b/tests/test_tutorial/test_testing/test_app03.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from docs_src.testing.app03 import main as mod
from docs_src.testing.app03.test_main import test_app
@@ -10,7 +11,7 @@ def test_app03():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_using_click/test_tutorial003.py b/tests/test_tutorial/test_using_click/test_tutorial003.py
index eadd93ee9e..6bc9bdf22b 100644
--- a/tests/test_tutorial/test_using_click/test_tutorial003.py
+++ b/tests/test_tutorial/test_using_click/test_tutorial003.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from click.testing import CliRunner
@@ -10,7 +11,7 @@
def test_cli():
result = runner.invoke(mod.typer_click_object, [])
# TODO: when deprecating Click 7, remove second option
- assert "Error: Missing command" in result.stdout or "Usage" in result.stdout
+ assert "Missing command" in result.stdout or "Usage" in result.stdout
def test_typer():
@@ -25,7 +26,7 @@ def test_click():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_tutorial/test_using_click/test_tutorial004.py b/tests/test_tutorial/test_using_click/test_tutorial004.py
index ccec78b30c..06078d7e50 100644
--- a/tests/test_tutorial/test_using_click/test_tutorial004.py
+++ b/tests/test_tutorial/test_using_click/test_tutorial004.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
from click.testing import CliRunner
@@ -31,7 +32,7 @@ def test_click_dropdb():
def test_script():
result = subprocess.run(
- ["coverage", "run", mod.__file__, "--help"],
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
diff --git a/tests/test_type_conversion.py b/tests/test_type_conversion.py
new file mode 100644
index 0000000000..5f9f8bfc07
--- /dev/null
+++ b/tests/test_type_conversion.py
@@ -0,0 +1,92 @@
+from enum import Enum
+from pathlib import Path
+from typing import List, Optional, Tuple
+
+import pytest
+import typer
+from typer.testing import CliRunner
+
+runner = CliRunner()
+
+
+def test_optional():
+ app = typer.Typer()
+
+ @app.command()
+ def opt(user: Optional[str] = None):
+ if user:
+ print(f"User: {user}")
+ else:
+ print("No user")
+
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "No user" in result.output
+
+ result = runner.invoke(app, ["--user", "Camila"])
+ assert result.exit_code == 0
+ assert "User: Camila" in result.output
+
+
+def test_no_type():
+ app = typer.Typer()
+
+ @app.command()
+ def no_type(user):
+ print(f"User: {user}")
+
+ result = runner.invoke(app, ["Camila"])
+ assert result.exit_code == 0
+ assert "User: Camila" in result.output
+
+
+class SomeEnum(Enum):
+ ONE = "one"
+ TWO = "two"
+ THREE = "three"
+
+
+@pytest.mark.parametrize(
+ "type_annotation",
+ [List[Path], List[SomeEnum], List[str]],
+)
+def test_list_parameters_convert_to_lists(type_annotation):
+ # Lists containing objects that are converted by Click (i.e. not Path or Enum)
+ # should not be inadvertently converted to tuples
+ expected_element_type = type_annotation.__args__[0]
+ app = typer.Typer()
+
+ @app.command()
+ def list_conversion(container: type_annotation):
+ assert isinstance(container, list)
+ for element in container:
+ assert isinstance(element, expected_element_type)
+
+ result = runner.invoke(app, ["one", "two", "three"])
+ assert result.exit_code == 0
+
+
+@pytest.mark.parametrize(
+ "type_annotation",
+ [
+ Tuple[str, str],
+ Tuple[str, Path],
+ Tuple[Path, Path],
+ Tuple[str, SomeEnum],
+ Tuple[SomeEnum, SomeEnum],
+ ],
+)
+def test_tuple_parameter_elements_are_converted_recursively(type_annotation):
+ # Tuple elements that aren't converted by Click (i.e. Path or Enum)
+ # should be recursively converted by Typer
+ expected_element_types = type_annotation.__args__
+ app = typer.Typer()
+
+ @app.command()
+ def tuple_recursive_conversion(container: type_annotation):
+ assert isinstance(container, tuple)
+ for element, expected_type in zip(container, expected_element_types):
+ assert isinstance(element, expected_type)
+
+ result = runner.invoke(app, ["one", "two"])
+ assert result.exit_code == 0
diff --git a/typer/__init__.py b/typer/__init__.py
index 1d42a2f54a..26baa7acad 100644
--- a/typer/__init__.py
+++ b/typer/__init__.py
@@ -1,6 +1,8 @@
"""Typer, build great CLIs. Easy to code. Based on Python type hints."""
-__version__ = "0.4.0"
+__version__ = "0.7.0"
+
+from shutil import get_terminal_size as get_terminal_size
from click.exceptions import Abort as Abort
from click.exceptions import BadParameter as BadParameter
@@ -9,7 +11,6 @@
from click.termui import confirm as confirm
from click.termui import echo_via_pager as echo_via_pager
from click.termui import edit as edit
-from click.termui import get_terminal_size as get_terminal_size
from click.termui import getchar as getchar
from click.termui import launch as launch
from click.termui import pause as pause
diff --git a/typer/_compat_utils.py b/typer/_compat_utils.py
new file mode 100644
index 0000000000..637e8ceb0d
--- /dev/null
+++ b/typer/_compat_utils.py
@@ -0,0 +1,5 @@
+import click
+
+
+def _get_click_major() -> int:
+ return int(click.__version__.split(".")[0])
diff --git a/typer/completion.py b/typer/completion.py
index 98183c5791..2712d08da4 100644
--- a/typer/completion.py
+++ b/typer/completion.py
@@ -4,10 +4,11 @@
import click
+from ._compat_utils import _get_click_major
from ._completion_shared import Shells, get_completion_script, install
from .models import ParamMeta
from .params import Option
-from .utils import _get_click_major, get_params_from_function
+from .utils import get_params_from_function
try:
import shellingham
diff --git a/typer/core.py b/typer/core.py
index fc6ddb312e..c0776368a6 100644
--- a/typer/core.py
+++ b/typer/core.py
@@ -1,3 +1,4 @@
+import errno
import inspect
import os
import sys
@@ -10,6 +11,7 @@
List,
Optional,
Sequence,
+ TextIO,
Tuple,
Union,
cast,
@@ -20,11 +22,29 @@
import click.formatting
import click.parser
import click.types
+import click.utils
+from typer.completion import completion_init
-from .utils import _get_click_major
+from ._compat_utils import _get_click_major
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from typing_extensions import Literal
+
+try:
+ import rich
+
+ from . import rich_utils
+
+except ImportError: # pragma: nocover
+ rich = None # type: ignore
if TYPE_CHECKING: # pragma: no cover
- import click.shell_completion
+ if _get_click_major() == 7:
+ import click.shell_completion
+
+MarkupMode = Literal["markdown", "rich", None]
# TODO: when deprecating Click 7, remove this
@@ -83,6 +103,172 @@ def compat_autocompletion(
self._custom_shell_complete = compat_autocompletion
+def _get_default_string(
+ obj: Union["TyperArgument", "TyperOption"],
+ *,
+ ctx: click.Context,
+ show_default_is_str: bool,
+ default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any],
+) -> str:
+ # Extracted from click.core.Option.get_help_record() to be reused by
+ # rich_utils avoiding RegEx hacks
+ if show_default_is_str:
+ default_string = f"({obj.show_default})"
+ elif isinstance(default_value, (list, tuple)):
+ default_string = ", ".join(str(d) for d in default_value)
+ elif callable(default_value):
+ default_string = _("(dynamic)")
+ elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts:
+ # For boolean flags that have distinct True/False opts,
+ # use the opt without prefix instead of the value.
+ # Typer override, original commented
+ # default_string = click.parser.split_opt(
+ # (self.opts if self.default else self.secondary_opts)[0]
+ # )[1]
+ if obj.default:
+ if obj.opts:
+ default_string = click.parser.split_opt(obj.opts[0])[1]
+ else:
+ default_string = str(default_value)
+ else:
+ default_string = click.parser.split_opt(obj.secondary_opts[0])[1]
+ # Typer override end
+ elif (
+ isinstance(obj, TyperOption)
+ and obj.is_bool_flag
+ and not obj.secondary_opts
+ and not default_value
+ ):
+ default_string = ""
+ else:
+ default_string = str(default_value)
+ return default_string
+
+
+def _extract_default_help_str(
+ obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context
+) -> Optional[Union[Any, Callable[[], Any]]]:
+ # Extracted from click.core.Option.get_help_record() to be reused by
+ # rich_utils avoiding RegEx hacks
+ # Temporarily enable resilient parsing to avoid type casting
+ # failing for the default. Might be possible to extend this to
+ # help formatting in general.
+ resilient = ctx.resilient_parsing
+ ctx.resilient_parsing = True
+
+ try:
+ if _get_click_major() > 7:
+ default_value = obj.get_default(ctx, call=False)
+ else:
+ if inspect.isfunction(obj.default):
+ default_value = "(dynamic)"
+ else:
+ default_value = obj.default
+ finally:
+ ctx.resilient_parsing = resilient
+ return default_value
+
+
+def _main(
+ self: click.Command,
+ *,
+ args: Optional[Sequence[str]] = None,
+ prog_name: Optional[str] = None,
+ complete_var: Optional[str] = None,
+ standalone_mode: bool = True,
+ windows_expand_args: bool = True,
+ **extra: Any,
+) -> Any:
+ # Typer override, duplicated from click.main() to handle custom rich exceptions
+ # Verify that the environment is configured correctly, or reject
+ # further execution to avoid a broken script.
+ if args is None:
+ args = sys.argv[1:]
+
+ # Covered in Click tests
+ if os.name == "nt" and windows_expand_args: # pragma: no cover
+ args = click.utils._expand_args(args)
+ else:
+ args = list(args)
+
+ if prog_name is None:
+ if _get_click_major() > 7:
+ prog_name = click.utils._detect_program_name()
+ else:
+ from click.utils import make_str
+
+ prog_name = make_str(
+ os.path.basename(sys.argv[0] if sys.argv else __file__)
+ )
+
+ # Process shell completion requests and exit early.
+ if _get_click_major() > 7:
+ self._main_shell_completion(extra, prog_name, complete_var)
+ else:
+ completion_init()
+ from click.core import _bashcomplete # type: ignore
+
+ _bashcomplete(self, prog_name, complete_var)
+
+ try:
+ try:
+ with self.make_context(prog_name, args, **extra) as ctx:
+ rv = self.invoke(ctx)
+ if not standalone_mode:
+ return rv
+ # it's not safe to `ctx.exit(rv)` here!
+ # note that `rv` may actually contain data like "1" which
+ # has obvious effects
+ # more subtle case: `rv=[None, None]` can come out of
+ # chained commands which all returned `None` -- so it's not
+ # even always obvious that `rv` indicates success/failure
+ # by its truthiness/falsiness
+ ctx.exit()
+ except (EOFError, KeyboardInterrupt):
+ click.echo(file=sys.stderr)
+ raise click.Abort()
+ except click.ClickException as e:
+ if not standalone_mode:
+ raise
+ # Typer override
+ if rich:
+ rich_utils.rich_format_error(e)
+ else:
+ e.show()
+ # Typer override end
+ sys.exit(e.exit_code)
+ except OSError as e:
+ if e.errno == errno.EPIPE:
+ sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout))
+ sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr))
+ sys.exit(1)
+ else:
+ raise
+ except click.exceptions.Exit as e:
+ if standalone_mode:
+ sys.exit(e.exit_code)
+ else:
+ # in non-standalone mode, return the exit code
+ # note that this is only reached if `self.invoke` above raises
+ # an Exit explicitly -- thus bypassing the check there which
+ # would return its result
+ # the results of non-standalone execution may therefore be
+ # somewhat ambiguous: if there are codepaths which lead to
+ # `ctx.exit(1)` and to `return 1`, the caller won't be able to
+ # tell the difference between the two
+ return e.exit_code
+ except click.Abort:
+ if not standalone_mode:
+ raise
+ # Typer override
+ if rich:
+ rich_utils.rich_abort_error()
+ else:
+ click.echo(_("Aborted!"), file=sys.stderr)
+ # Typer override end
+ sys.exit(1)
+
+
class TyperArgument(click.core.Argument):
def __init__(
self,
@@ -111,12 +297,15 @@ def __init__(
show_envvar: bool = True,
help: Optional[str] = None,
hidden: bool = False,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
):
self.help = help
self.show_default = show_default
self.show_choices = show_choices
self.show_envvar = show_envvar
self.hidden = hidden
+ self.rich_help_panel = rich_help_panel
kwargs: Dict[str, Any] = {
"param_decls": param_decls,
"type": type,
@@ -139,6 +328,25 @@ def __init__(
self, autocompletion=autocompletion
)
+ def _get_default_string(
+ self,
+ *,
+ ctx: click.Context,
+ show_default_is_str: bool,
+ default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any],
+ ) -> str:
+ return _get_default_string(
+ self,
+ ctx=ctx,
+ show_default_is_str=show_default_is_str,
+ default_value=default_value,
+ )
+
+ def _extract_default_help_str(
+ self, *, ctx: click.Context
+ ) -> Optional[Union[Any, Callable[[], Any]]]:
+ return _extract_default_help_str(self, ctx=ctx)
+
def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]:
# Modified version of click.core.Option.get_help_record()
# to support Arguments
@@ -157,16 +365,27 @@ def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]:
else envvar
)
extra.append(f"env var: {var_str}")
- if self.default is not None and (self.show_default or ctx.show_default):
- if isinstance(self.show_default, str):
- default_string = f"({self.show_default})"
- elif isinstance(self.default, (list, tuple)):
- default_string = ", ".join(str(d) for d in self.default)
- elif inspect.isfunction(self.default):
- default_string = "(dynamic)"
- else:
- default_string = str(self.default)
- extra.append(f"default: {default_string}")
+
+ # Typer override:
+ # Extracted to _extract_default_help_str() to allow re-using it in rich_utils
+ default_value = self._extract_default_help_str(ctx=ctx)
+ # Typer override end
+
+ show_default_is_str = isinstance(self.show_default, str)
+
+ if show_default_is_str or (
+ default_value is not None and (self.show_default or ctx.show_default)
+ ):
+ # Typer override:
+ # Extracted to _get_default_string() to allow re-using it in rich_utils
+ default_string = self._get_default_string(
+ ctx=ctx,
+ show_default_is_str=show_default_is_str,
+ default_value=default_value,
+ )
+ # Typer override end
+ if default_string:
+ extra.append(_("default: {default}").format(default=default_string))
if self.required:
extra.append("required")
if extra:
@@ -232,6 +451,8 @@ def __init__(
hidden: bool = False,
show_choices: bool = True,
show_envvar: bool = False,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
):
# TODO: when deprecating Click 7, remove custom kwargs with prompt_required
# and call super().__init__() directly
@@ -270,6 +491,26 @@ def __init__(
_typer_param_setup_autocompletion_compat(
self, autocompletion=autocompletion
)
+ self.rich_help_panel = rich_help_panel
+
+ def _get_default_string(
+ self,
+ *,
+ ctx: click.Context,
+ show_default_is_str: bool,
+ default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any],
+ ) -> str:
+ return _get_default_string(
+ self,
+ ctx=ctx,
+ show_default_is_str=show_default_is_str,
+ default_value=default_value,
+ )
+
+ def _extract_default_help_str(
+ self, *, ctx: click.Context
+ ) -> Optional[Union[Any, Callable[[], Any]]]:
+ return _extract_default_help_str(self, ctx=ctx)
def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]:
# Click 7.x was not breaking this use case, so in that case, re-use its logic
@@ -323,48 +564,24 @@ def _write_opts(opts: Sequence[str]) -> str:
)
extra.append(_("env var: {var}").format(var=var_str))
- # Temporarily enable resilient parsing to avoid type casting
- # failing for the default. Might be possible to extend this to
- # help formatting in general.
- resilient = ctx.resilient_parsing
- ctx.resilient_parsing = True
-
- try:
- default_value = self.get_default(ctx, call=False)
- finally:
- ctx.resilient_parsing = resilient
+ # Typer override:
+ # Extracted to _extract_default() to allow re-using it in rich_utils
+ default_value = self._extract_default_help_str(ctx=ctx)
+ # Typer override end
show_default_is_str = isinstance(self.show_default, str)
if show_default_is_str or (
default_value is not None and (self.show_default or ctx.show_default)
):
- if show_default_is_str:
- default_string = f"({self.show_default})"
- elif isinstance(default_value, (list, tuple)):
- default_string = ", ".join(str(d) for d in default_value)
- elif callable(default_value):
- default_string = _("(dynamic)")
- elif self.is_bool_flag and self.secondary_opts:
- # For boolean flags that have distinct True/False opts,
- # use the opt without prefix instead of the value.
- # Typer override, original commented
- # default_string = click.parser.split_opt(
- # (self.opts if self.default else self.secondary_opts)[0]
- # )[1]
- if self.default:
- if self.opts:
- default_string = click.parser.split_opt(self.opts[0])[1]
- else:
- default_string = str(default_value)
- else:
- default_string = click.parser.split_opt(self.secondary_opts[0])[1]
- # Typer override end
- elif self.is_bool_flag and not self.secondary_opts and not default_value:
- default_string = ""
- else:
- default_string = str(default_value)
-
+ # Typer override:
+ # Extracted to _get_default_string() to allow re-using it in rich_utils
+ default_string = self._get_default_string(
+ ctx=ctx,
+ show_default_is_str=show_default_is_str,
+ default_value=default_value,
+ )
+ # Typer override end
if default_string:
extra.append(_("default: {default}").format(default=default_string))
@@ -436,6 +653,42 @@ def _typer_main_shell_completion(
class TyperCommand(click.core.Command):
+ def __init__(
+ self,
+ name: Optional[str],
+ *,
+ context_settings: Optional[Dict[str, Any]] = None,
+ callback: Optional[Callable[..., Any]] = None,
+ params: Optional[List[click.Parameter]] = None,
+ help: Optional[str] = None,
+ epilog: Optional[str] = None,
+ short_help: Optional[str] = None,
+ options_metavar: Optional[str] = "[OPTIONS]",
+ add_help_option: bool = True,
+ no_args_is_help: bool = False,
+ hidden: bool = False,
+ deprecated: bool = False,
+ # Rich settings
+ rich_markup_mode: MarkupMode = None,
+ rich_help_panel: Union[str, None] = None,
+ ) -> None:
+ super().__init__(
+ name=name,
+ context_settings=context_settings,
+ callback=callback,
+ params=params,
+ help=help,
+ epilog=epilog,
+ short_help=short_help,
+ options_metavar=options_metavar,
+ add_help_option=add_help_option,
+ no_args_is_help=no_args_is_help,
+ hidden=hidden,
+ deprecated=deprecated,
+ )
+ self.rich_markup_mode: MarkupMode = rich_markup_mode
+ self.rich_help_panel = rich_help_panel
+
def format_options(
self, ctx: click.Context, formatter: click.HelpFormatter
) -> None:
@@ -451,8 +704,52 @@ def _main_shell_completion(
self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
)
+ def main(
+ self,
+ args: Optional[Sequence[str]] = None,
+ prog_name: Optional[str] = None,
+ complete_var: Optional[str] = None,
+ standalone_mode: bool = True,
+ windows_expand_args: bool = True,
+ **extra: Any,
+ ) -> Any:
+ return _main(
+ self,
+ args=args,
+ prog_name=prog_name,
+ complete_var=complete_var,
+ standalone_mode=standalone_mode,
+ windows_expand_args=windows_expand_args,
+ **extra,
+ )
+
+ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
+ if not rich:
+ return super().format_help(ctx, formatter)
+ return rich_utils.rich_format_help(
+ obj=self,
+ ctx=ctx,
+ markup_mode=self.rich_markup_mode,
+ )
+
class TyperGroup(click.core.Group):
+ def __init__(
+ self,
+ *,
+ name: Optional[str] = None,
+ commands: Optional[
+ Union[Dict[str, click.Command], Sequence[click.Command]]
+ ] = None,
+ # Rich settings
+ rich_markup_mode: MarkupMode = None,
+ rich_help_panel: Union[str, None] = None,
+ **attrs: Any,
+ ) -> None:
+ super().__init__(name=name, commands=commands, **attrs)
+ self.rich_markup_mode: MarkupMode = rich_markup_mode
+ self.rich_help_panel = rich_help_panel
+
def format_options(
self, ctx: click.Context, formatter: click.HelpFormatter
) -> None:
@@ -468,3 +765,31 @@ def _main_shell_completion(
_typer_main_shell_completion(
self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
)
+
+ def main(
+ self,
+ args: Optional[Sequence[str]] = None,
+ prog_name: Optional[str] = None,
+ complete_var: Optional[str] = None,
+ standalone_mode: bool = True,
+ windows_expand_args: bool = True,
+ **extra: Any,
+ ) -> Any:
+ return _main(
+ self,
+ args=args,
+ prog_name=prog_name,
+ complete_var=complete_var,
+ standalone_mode=standalone_mode,
+ windows_expand_args=windows_expand_args,
+ **extra,
+ )
+
+ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
+ if not rich:
+ return super().format_help(ctx, formatter)
+ return rich_utils.rich_format_help(
+ obj=self,
+ ctx=ctx,
+ markup_mode=self.rich_markup_mode,
+ )
diff --git a/typer/main.py b/typer/main.py
index d2a45849f8..f493e544e2 100644
--- a/typer/main.py
+++ b/typer/main.py
@@ -1,15 +1,20 @@
import inspect
+import os
+import sys
+import traceback
from datetime import datetime
from enum import Enum
from functools import update_wrapper
from pathlib import Path
+from traceback import FrameSummary, StackSummary
+from types import TracebackType
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
from uuid import UUID
import click
from .completion import get_completion_inspect_parameters
-from .core import TyperArgument, TyperCommand, TyperGroup, TyperOption
+from .core import MarkupMode, TyperArgument, TyperCommand, TyperGroup, TyperOption
from .models import (
AnyType,
ArgumentInfo,
@@ -17,6 +22,7 @@
CommandInfo,
Default,
DefaultPlaceholder,
+ DeveloperExceptionConfig,
FileBinaryRead,
FileBinaryWrite,
FileText,
@@ -30,6 +36,73 @@
)
from .utils import get_params_from_function
+try:
+ import rich
+ from rich.console import Console
+ from rich.traceback import Traceback
+
+ console_stderr = Console(stderr=True)
+
+except ImportError: # pragma: nocover
+ rich = None # type: ignore
+
+_original_except_hook = sys.excepthook
+_typer_developer_exception_attr_name = "__typer_developer_exception__"
+
+
+def except_hook(
+ exc_type: Type[BaseException], exc_value: BaseException, tb: TracebackType
+) -> None:
+ exception_config: Union[DeveloperExceptionConfig, None] = getattr(
+ exc_value, _typer_developer_exception_attr_name, None
+ )
+ standard_traceback = os.getenv("_TYPER_STANDARD_TRACEBACK")
+ if (
+ standard_traceback
+ or not exception_config
+ or not exception_config.pretty_exceptions_enable
+ ):
+ _original_except_hook(exc_type, exc_value, tb)
+ return
+ typer_path = os.path.dirname(__file__)
+ click_path = os.path.dirname(click.__file__)
+ supress_internal_dir_names = [typer_path, click_path]
+ exc = exc_value
+ if rich:
+ rich_tb = Traceback.from_exception(
+ type(exc),
+ exc,
+ exc.__traceback__,
+ show_locals=exception_config.pretty_exceptions_show_locals,
+ suppress=supress_internal_dir_names,
+ )
+ console_stderr.print(rich_tb)
+ return
+ tb_exc = traceback.TracebackException.from_exception(exc)
+ stack: List[FrameSummary] = []
+ for frame in tb_exc.stack:
+ if any(
+ [frame.filename.startswith(path) for path in supress_internal_dir_names]
+ ):
+ if not exception_config.pretty_exceptions_short:
+ # Hide the line for internal libraries, Typer and Click
+ stack.append(
+ traceback.FrameSummary(
+ filename=frame.filename,
+ lineno=frame.lineno,
+ name=frame.name,
+ line="",
+ )
+ )
+ else:
+ stack.append(frame)
+ # Type ignore ref: https://github.com/python/typeshed/pull/8244
+ final_stack_summary = StackSummary.from_list(stack) # type: ignore
+ tb_exc.stack = final_stack_summary
+ for line in tb_exc.format():
+ print(line, file=sys.stderr)
+ return
+
def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]:
install_param, show_param = get_completion_inspect_parameters()
@@ -43,7 +116,7 @@ def __init__(
self,
*,
name: Optional[str] = Default(None),
- cls: Optional[Type[click.Command]] = Default(None),
+ cls: Optional[Type[TyperGroup]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
@@ -60,8 +133,19 @@ def __init__(
hidden: bool = Default(False),
deprecated: bool = Default(False),
add_completion: bool = True,
+ # Rich settings
+ rich_markup_mode: MarkupMode = None,
+ rich_help_panel: Union[str, None] = Default(None),
+ pretty_exceptions_enable: bool = True,
+ pretty_exceptions_show_locals: bool = True,
+ pretty_exceptions_short: bool = True,
):
self._add_completion = add_completion
+ self.rich_markup_mode: MarkupMode = rich_markup_mode
+ self.rich_help_panel = rich_help_panel
+ self.pretty_exceptions_enable = pretty_exceptions_enable
+ self.pretty_exceptions_show_locals = pretty_exceptions_show_locals
+ self.pretty_exceptions_short = pretty_exceptions_short
self.info = TyperInfo(
name=name,
cls=cls,
@@ -88,7 +172,7 @@ def callback(
self,
name: Optional[str] = Default(None),
*,
- cls: Optional[Type[click.Command]] = Default(None),
+ cls: Optional[Type[TyperGroup]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
@@ -103,6 +187,8 @@ def callback(
add_help_option: bool = Default(True),
hidden: bool = Default(False),
deprecated: bool = Default(False),
+ # Rich settings
+ rich_help_panel: Union[str, None] = Default(None),
) -> Callable[[CommandFunctionType], CommandFunctionType]:
def decorator(f: CommandFunctionType) -> CommandFunctionType:
self.registered_callback = TyperInfo(
@@ -122,6 +208,7 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType:
add_help_option=add_help_option,
hidden=hidden,
deprecated=deprecated,
+ rich_help_panel=rich_help_panel,
)
return f
@@ -131,7 +218,7 @@ def command(
self,
name: Optional[str] = None,
*,
- cls: Optional[Type[click.Command]] = None,
+ cls: Optional[Type[TyperCommand]] = None,
context_settings: Optional[Dict[Any, Any]] = None,
help: Optional[str] = None,
epilog: Optional[str] = None,
@@ -141,6 +228,8 @@ def command(
no_args_is_help: bool = False,
hidden: bool = False,
deprecated: bool = False,
+ # Rich settings
+ rich_help_panel: Union[str, None] = Default(None),
) -> Callable[[CommandFunctionType], CommandFunctionType]:
if cls is None:
cls = TyperCommand
@@ -160,6 +249,8 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType:
no_args_is_help=no_args_is_help,
hidden=hidden,
deprecated=deprecated,
+ # Rich settings
+ rich_help_panel=rich_help_panel,
)
)
return f
@@ -171,7 +262,7 @@ def add_typer(
typer_instance: "Typer",
*,
name: Optional[str] = Default(None),
- cls: Optional[Type[click.Command]] = Default(None),
+ cls: Optional[Type[TyperGroup]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
@@ -187,6 +278,8 @@ def add_typer(
add_help_option: bool = Default(True),
hidden: bool = Default(False),
deprecated: bool = Default(False),
+ # Rich settings
+ rich_help_panel: Union[str, None] = Default(None),
) -> None:
self.registered_groups.append(
TyperInfo(
@@ -207,15 +300,40 @@ def add_typer(
add_help_option=add_help_option,
hidden=hidden,
deprecated=deprecated,
+ rich_help_panel=rich_help_panel,
)
)
def __call__(self, *args: Any, **kwargs: Any) -> Any:
- return get_command(self)(*args, **kwargs)
+ if sys.excepthook != except_hook:
+ sys.excepthook = except_hook
+ try:
+ return get_command(self)(*args, **kwargs)
+ except Exception as e:
+ # Set a custom attribute to tell the hook to show nice exceptions for user
+ # code. An alternative/first implementation was a custom exception with
+ # raise custom_exc from e
+ # but that means the last error shown is the custom exception, not the
+ # actual error. This trick improves developer experience by showing the
+ # actual error last.
+ setattr(
+ e,
+ _typer_developer_exception_attr_name,
+ DeveloperExceptionConfig(
+ pretty_exceptions_enable=self.pretty_exceptions_enable,
+ pretty_exceptions_show_locals=self.pretty_exceptions_show_locals,
+ pretty_exceptions_short=self.pretty_exceptions_short,
+ ),
+ )
+ raise e
-def get_group(typer_instance: Typer) -> click.Command:
- group = get_group_from_info(TyperInfo(typer_instance))
+def get_group(typer_instance: Typer) -> TyperGroup:
+ group = get_group_from_info(
+ TyperInfo(typer_instance),
+ pretty_exceptions_short=typer_instance.pretty_exceptions_short,
+ rich_markup_mode=typer_instance.rich_markup_mode,
+ )
return group
@@ -229,14 +347,25 @@ def get_command(typer_instance: Typer) -> click.Command:
or len(typer_instance.registered_commands) > 1
):
# Create a Group
- click_command = get_group(typer_instance)
+ click_command: click.Command = get_group(typer_instance)
if typer_instance._add_completion:
click_command.params.append(click_install_param)
click_command.params.append(click_show_param)
return click_command
elif len(typer_instance.registered_commands) == 1:
# Create a single Command
- click_command = get_command_from_info(typer_instance.registered_commands[0])
+ single_command = typer_instance.registered_commands[0]
+
+ if not single_command.context_settings and not isinstance(
+ typer_instance.info.context_settings, DefaultPlaceholder
+ ):
+ single_command.context_settings = typer_instance.info.context_settings
+
+ click_command = get_command_from_info(
+ single_command,
+ pretty_exceptions_short=typer_instance.pretty_exceptions_short,
+ rich_markup_mode=typer_instance.rich_markup_mode,
+ )
if typer_instance._add_completion:
click_command.params.append(click_install_param)
click_command.params.append(click_show_param)
@@ -340,17 +469,30 @@ def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo:
return TyperInfo(**values)
-def get_group_from_info(group_info: TyperInfo) -> click.Command:
+def get_group_from_info(
+ group_info: TyperInfo,
+ *,
+ pretty_exceptions_short: bool,
+ rich_markup_mode: MarkupMode,
+) -> TyperGroup:
assert (
group_info.typer_instance
), "A Typer instance is needed to generate a Click Group"
commands: Dict[str, click.Command] = {}
for command_info in group_info.typer_instance.registered_commands:
- command = get_command_from_info(command_info=command_info)
+ command = get_command_from_info(
+ command_info=command_info,
+ pretty_exceptions_short=pretty_exceptions_short,
+ rich_markup_mode=rich_markup_mode,
+ )
if command.name:
commands[command.name] = command
for sub_group_info in group_info.typer_instance.registered_groups:
- sub_group = get_group_from_info(sub_group_info)
+ sub_group = get_group_from_info(
+ sub_group_info,
+ pretty_exceptions_short=pretty_exceptions_short,
+ rich_markup_mode=rich_markup_mode,
+ )
if sub_group.name:
commands[sub_group.name] = sub_group
solved_info = solve_typer_info_defaults(group_info)
@@ -360,7 +502,8 @@ def get_group_from_info(group_info: TyperInfo) -> click.Command:
context_param_name,
) = get_params_convertors_ctx_param_name_from_function(solved_info.callback)
cls = solved_info.cls or TyperGroup
- group = cls( # type: ignore
+ assert issubclass(cls, TyperGroup)
+ group = cls(
name=solved_info.name or "",
commands=commands,
invoke_without_command=solved_info.invoke_without_command,
@@ -374,8 +517,9 @@ def get_group_from_info(group_info: TyperInfo) -> click.Command:
params=params,
convertors=convertors,
context_param_name=context_param_name,
+ pretty_exceptions_short=pretty_exceptions_short,
),
- params=params, # type: ignore
+ params=params,
help=solved_info.help,
epilog=solved_info.epilog,
short_help=solved_info.short_help,
@@ -383,6 +527,9 @@ def get_group_from_info(group_info: TyperInfo) -> click.Command:
add_help_option=solved_info.add_help_option,
hidden=solved_info.hidden,
deprecated=solved_info.deprecated,
+ rich_markup_mode=rich_markup_mode,
+ # Rich settings
+ rich_help_panel=solved_info.rich_help_panel,
)
return group
@@ -410,7 +557,12 @@ def get_params_convertors_ctx_param_name_from_function(
return params, convertors, context_param_name
-def get_command_from_info(command_info: CommandInfo) -> click.Command:
+def get_command_from_info(
+ command_info: CommandInfo,
+ *,
+ pretty_exceptions_short: bool,
+ rich_markup_mode: MarkupMode,
+) -> click.Command:
assert command_info.callback, "A command must have a callback function"
name = command_info.name or get_command_name(command_info.callback.__name__)
use_help = command_info.help
@@ -432,6 +584,7 @@ def get_command_from_info(command_info: CommandInfo) -> click.Command:
params=params,
convertors=convertors,
context_param_name=context_param_name,
+ pretty_exceptions_short=pretty_exceptions_short,
),
params=params, # type: ignore
help=use_help,
@@ -442,17 +595,29 @@ def get_command_from_info(command_info: CommandInfo) -> click.Command:
no_args_is_help=command_info.no_args_is_help,
hidden=command_info.hidden,
deprecated=command_info.deprecated,
+ rich_markup_mode=rich_markup_mode,
+ # Rich settings
+ rich_help_panel=command_info.rich_help_panel,
)
return command
+def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]:
+ convertor: Optional[Callable[[Any], Any]] = None
+ if lenient_issubclass(type_, Path):
+ convertor = param_path_convertor
+ if lenient_issubclass(type_, Enum):
+ convertor = generate_enum_convertor(type_)
+ return convertor
+
+
def param_path_convertor(value: Optional[str] = None) -> Optional[Path]:
if value is not None:
return Path(value)
return None
-def generate_enum_convertor(enum: Type[Enum]) -> Callable[..., Any]:
+def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]:
lower_val_map = {str(val.value).lower(): val for val in enum}
def convertor(value: Any) -> Any:
@@ -465,9 +630,25 @@ def convertor(value: Any) -> Any:
return convertor
-def generate_iter_convertor(convertor: Callable[[Any], Any]) -> Callable[..., Any]:
- def internal_convertor(value: Any) -> List[Any]:
- return [convertor(v) for v in value]
+def generate_list_convertor(
+ convertor: Optional[Callable[[Any], Any]]
+) -> Callable[[Sequence[Any]], List[Any]]:
+ def internal_convertor(value: Sequence[Any]) -> List[Any]:
+ return [convertor(v) if convertor else v for v in value]
+
+ return internal_convertor
+
+
+def generate_tuple_convertor(
+ types: Sequence[Any],
+) -> Callable[[Tuple[Any, ...]], Tuple[Any, ...]]:
+ convertors = [determine_type_convertor(type_) for type_ in types]
+
+ def internal_convertor(param_args: Tuple[Any, ...]) -> Tuple[Any, ...]:
+ return tuple(
+ convertor(arg) if convertor else arg
+ for (convertor, arg) in zip(convertors, param_args)
+ )
return internal_convertor
@@ -478,6 +659,7 @@ def get_callback(
params: Sequence[click.Parameter] = [],
convertors: Dict[str, Callable[[str], Any]] = {},
context_param_name: Optional[str] = None,
+ pretty_exceptions_short: bool,
) -> Optional[Callable[..., Any]]:
if not callback:
return None
@@ -490,6 +672,7 @@ def get_callback(
use_params[param.name] = param.default
def wrapper(**kwargs: Any) -> Any:
+ _rich_traceback_guard = pretty_exceptions_short # noqa: F841
for k, v in kwargs.items():
if k in convertors:
use_params[k] = convertors[k](v)
@@ -624,6 +807,7 @@ def get_click_param(
annotation = str
main_type = annotation
is_list = False
+ is_tuple = False
parameter_type: Any = None
is_flag = None
origin = getattr(main_type, "__origin__", None)
@@ -655,18 +839,16 @@ def get_click_param(
get_click_type(annotation=type_, parameter_info=parameter_info)
)
parameter_type = tuple(types)
+ is_tuple = True
if parameter_type is None:
parameter_type = get_click_type(
annotation=main_type, parameter_info=parameter_info
)
- convertor = None
- if lenient_issubclass(main_type, Path):
- convertor = param_path_convertor
- if lenient_issubclass(main_type, Enum):
- convertor = generate_enum_convertor(main_type)
- if convertor and is_list:
- convertor = generate_iter_convertor(convertor)
- # TODO: handle recursive conversion for tuples
+ convertor = determine_type_convertor(main_type)
+ if is_list:
+ convertor = generate_list_convertor(convertor)
+ if is_tuple:
+ convertor = generate_tuple_convertor(main_type.__args__)
if isinstance(parameter_info, OptionInfo):
if main_type is bool and not (parameter_info.is_flag is False):
is_flag = True
@@ -716,6 +898,8 @@ def get_click_param(
envvar=parameter_info.envvar,
shell_complete=parameter_info.shell_complete,
autocompletion=get_param_completion(parameter_info.autocompletion),
+ # Rich settings
+ rich_help_panel=parameter_info.rich_help_panel,
),
convertor,
)
@@ -747,6 +931,8 @@ def get_click_param(
is_eager=parameter_info.is_eager,
envvar=parameter_info.envvar,
autocompletion=get_param_completion(parameter_info.autocompletion),
+ # Rich settings
+ rich_help_panel=parameter_info.rich_help_panel,
),
convertor,
)
@@ -858,7 +1044,7 @@ def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> A
return wrapper
-def run(function: Callable[..., Any]) -> Any:
- app = Typer()
+def run(function: Callable[..., Any]) -> None:
+ app = Typer(add_completion=False)
app.command()(function)
app()
diff --git a/typer/models.py b/typer/models.py
index e1de3a46f8..0970a3148f 100644
--- a/typer/models.py
+++ b/typer/models.py
@@ -15,10 +15,14 @@
import click
+from ._compat_utils import _get_click_major
+
if TYPE_CHECKING: # pragma: no cover
- import click.shell_completion
+ if _get_click_major() > 7:
+ import click.shell_completion
- from .main import Typer # noqa
+ from .core import TyperCommand, TyperGroup
+ from .main import Typer
NoneType = type(None)
@@ -87,7 +91,7 @@ def __init__(
self,
name: Optional[str] = None,
*,
- cls: Optional[Type[click.Command]] = None,
+ cls: Optional[Type["TyperCommand"]] = None,
context_settings: Optional[Dict[Any, Any]] = None,
callback: Optional[Callable[..., Any]] = None,
help: Optional[str] = None,
@@ -98,6 +102,8 @@ def __init__(
no_args_is_help: bool = False,
hidden: bool = False,
deprecated: bool = False,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
):
self.name = name
self.cls = cls
@@ -111,6 +117,8 @@ def __init__(
self.no_args_is_help = no_args_is_help
self.hidden = hidden
self.deprecated = deprecated
+ # Rich settings
+ self.rich_help_panel = rich_help_panel
class TyperInfo:
@@ -119,7 +127,7 @@ def __init__(
typer_instance: Optional["Typer"] = Default(None),
*,
name: Optional[str] = Default(None),
- cls: Optional[Type[click.Command]] = Default(None),
+ cls: Optional[Type["TyperGroup"]] = Default(None),
invoke_without_command: bool = Default(False),
no_args_is_help: bool = Default(False),
subcommand_metavar: Optional[str] = Default(None),
@@ -135,6 +143,8 @@ def __init__(
add_help_option: bool = Default(True),
hidden: bool = Default(False),
deprecated: bool = Default(False),
+ # Rich settings
+ rich_help_panel: Union[str, None] = Default(None),
):
self.typer_instance = typer_instance
self.name = name
@@ -153,6 +163,7 @@ def __init__(
self.add_help_option = add_help_option
self.hidden = hidden
self.deprecated = deprecated
+ self.rich_help_panel = rich_help_panel
class ParameterInfo:
@@ -186,7 +197,7 @@ def __init__(
max: Optional[Union[int, float]] = None,
clamp: bool = False,
# DateTime
- formats: Optional[Union[List[str]]] = None,
+ formats: Optional[List[str]] = None,
# File
mode: Optional[str] = None,
encoding: Optional[str] = None,
@@ -202,6 +213,8 @@ def __init__(
resolve_path: bool = False,
allow_dash: bool = False,
path_type: Union[None, Type[str], Type[bytes]] = None,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
):
self.default = default
self.param_decls = param_decls
@@ -241,6 +254,8 @@ def __init__(
self.resolve_path = resolve_path
self.allow_dash = allow_dash
self.path_type = path_type
+ # Rich settings
+ self.rich_help_panel = rich_help_panel
class OptionInfo(ParameterInfo):
@@ -283,7 +298,7 @@ def __init__(
max: Optional[Union[int, float]] = None,
clamp: bool = False,
# DateTime
- formats: Optional[Union[List[str]]] = None,
+ formats: Optional[List[str]] = None,
# File
mode: Optional[str] = None,
encoding: Optional[str] = None,
@@ -299,6 +314,8 @@ def __init__(
resolve_path: bool = False,
allow_dash: bool = False,
path_type: Union[None, Type[str], Type[bytes]] = None,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
):
super().__init__(
default=default,
@@ -339,6 +356,8 @@ def __init__(
resolve_path=resolve_path,
allow_dash=allow_dash,
path_type=path_type,
+ # Rich settings
+ rich_help_panel=rich_help_panel,
)
self.prompt = prompt
self.confirmation_prompt = confirmation_prompt
@@ -382,7 +401,7 @@ def __init__(
max: Optional[Union[int, float]] = None,
clamp: bool = False,
# DateTime
- formats: Optional[Union[List[str]]] = None,
+ formats: Optional[List[str]] = None,
# File
mode: Optional[str] = None,
encoding: Optional[str] = None,
@@ -398,6 +417,8 @@ def __init__(
resolve_path: bool = False,
allow_dash: bool = False,
path_type: Union[None, Type[str], Type[bytes]] = None,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
):
super().__init__(
default=default,
@@ -438,6 +459,8 @@ def __init__(
resolve_path=resolve_path,
allow_dash=allow_dash,
path_type=path_type,
+ # Rich settings
+ rich_help_panel=rich_help_panel,
)
@@ -454,3 +477,16 @@ def __init__(
self.name = name
self.default = default
self.annotation = annotation
+
+
+class DeveloperExceptionConfig:
+ def __init__(
+ self,
+ *,
+ pretty_exceptions_enable: bool = True,
+ pretty_exceptions_show_locals: bool = True,
+ pretty_exceptions_short: bool = True,
+ ) -> None:
+ self.pretty_exceptions_enable = pretty_exceptions_enable
+ self.pretty_exceptions_show_locals = pretty_exceptions_show_locals
+ self.pretty_exceptions_short = pretty_exceptions_short
diff --git a/typer/params.py b/typer/params.py
index 91fcd59c15..c833b552ee 100644
--- a/typer/params.py
+++ b/typer/params.py
@@ -45,7 +45,7 @@ def Option(
max: Optional[Union[int, float]] = None,
clamp: bool = False,
# DateTime
- formats: Optional[Union[List[str]]] = None,
+ formats: Optional[List[str]] = None,
# File
mode: Optional[str] = None,
encoding: Optional[str] = None,
@@ -61,6 +61,8 @@ def Option(
resolve_path: bool = False,
allow_dash: bool = False,
path_type: Union[None, Type[str], Type[bytes]] = None,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
) -> Any:
return OptionInfo(
# Parameter
@@ -110,6 +112,8 @@ def Option(
resolve_path=resolve_path,
allow_dash=allow_dash,
path_type=path_type,
+ # Rich settings
+ rich_help_panel=rich_help_panel,
)
@@ -142,7 +146,7 @@ def Argument(
max: Optional[Union[int, float]] = None,
clamp: bool = False,
# DateTime
- formats: Optional[Union[List[str]]] = None,
+ formats: Optional[List[str]] = None,
# File
mode: Optional[str] = None,
encoding: Optional[str] = None,
@@ -158,6 +162,8 @@ def Argument(
resolve_path: bool = False,
allow_dash: bool = False,
path_type: Union[None, Type[str], Type[bytes]] = None,
+ # Rich settings
+ rich_help_panel: Union[str, None] = None,
) -> Any:
return ArgumentInfo(
# Parameter
@@ -201,4 +207,6 @@ def Argument(
resolve_path=resolve_path,
allow_dash=allow_dash,
path_type=path_type,
+ # Rich settings
+ rich_help_panel=rich_help_panel,
)
diff --git a/typer/rich_utils.py b/typer/rich_utils.py
new file mode 100644
index 0000000000..3f602ee651
--- /dev/null
+++ b/typer/rich_utils.py
@@ -0,0 +1,694 @@
+# Extracted and modified from https://github.com/ewels/rich-click
+
+import inspect
+import sys
+from collections import defaultdict
+from os import getenv
+from typing import Any, DefaultDict, Dict, Iterable, List, Optional, Union
+
+import click
+from rich import box
+from rich.align import Align
+from rich.columns import Columns
+from rich.console import Console, RenderableType, group
+from rich.emoji import Emoji
+from rich.highlighter import RegexHighlighter
+from rich.markdown import Markdown
+from rich.padding import Padding
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.theme import Theme
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from typing_extensions import Literal
+
+# Default styles
+STYLE_OPTION = "bold cyan"
+STYLE_SWITCH = "bold green"
+STYLE_NEGATIVE_OPTION = "bold magenta"
+STYLE_NEGATIVE_SWITCH = "bold red"
+STYLE_METAVAR = "bold yellow"
+STYLE_METAVAR_SEPARATOR = "dim"
+STYLE_USAGE = "yellow"
+STYLE_USAGE_COMMAND = "bold"
+STYLE_DEPRECATED = "red"
+STYLE_DEPRECATED_COMMAND = "dim"
+STYLE_HELPTEXT_FIRST_LINE = ""
+STYLE_HELPTEXT = "dim"
+STYLE_OPTION_HELP = ""
+STYLE_OPTION_DEFAULT = "dim"
+STYLE_OPTION_ENVVAR = "dim yellow"
+STYLE_REQUIRED_SHORT = "red"
+STYLE_REQUIRED_LONG = "dim red"
+STYLE_OPTIONS_PANEL_BORDER = "dim"
+ALIGN_OPTIONS_PANEL: Literal["left", "center", "right"] = "left"
+STYLE_OPTIONS_TABLE_SHOW_LINES = False
+STYLE_OPTIONS_TABLE_LEADING = 0
+STYLE_OPTIONS_TABLE_PAD_EDGE = False
+STYLE_OPTIONS_TABLE_PADDING = (0, 1)
+STYLE_OPTIONS_TABLE_BOX = ""
+STYLE_OPTIONS_TABLE_ROW_STYLES = None
+STYLE_OPTIONS_TABLE_BORDER_STYLE = None
+STYLE_COMMANDS_PANEL_BORDER = "dim"
+ALIGN_COMMANDS_PANEL: Literal["left", "center", "right"] = "left"
+STYLE_COMMANDS_TABLE_SHOW_LINES = False
+STYLE_COMMANDS_TABLE_LEADING = 0
+STYLE_COMMANDS_TABLE_PAD_EDGE = False
+STYLE_COMMANDS_TABLE_PADDING = (0, 1)
+STYLE_COMMANDS_TABLE_BOX = ""
+STYLE_COMMANDS_TABLE_ROW_STYLES = None
+STYLE_COMMANDS_TABLE_BORDER_STYLE = None
+STYLE_ERRORS_PANEL_BORDER = "red"
+ALIGN_ERRORS_PANEL: Literal["left", "center", "right"] = "left"
+STYLE_ERRORS_SUGGESTION = "dim"
+STYLE_ABORTED = "red"
+_TERMINAL_WIDTH = getenv("TERMINAL_WIDTH")
+MAX_WIDTH = int(_TERMINAL_WIDTH) if _TERMINAL_WIDTH else None
+COLOR_SYSTEM: Optional[
+ Literal["auto", "standard", "256", "truecolor", "windows"]
+] = "auto" # Set to None to disable colors
+_TYPER_FORCE_DISABLE_TERMINAL = getenv("_TYPER_FORCE_DISABLE_TERMINAL")
+FORCE_TERMINAL = (
+ True
+ if getenv("GITHUB_ACTIONS") or getenv("FORCE_COLOR") or getenv("PY_COLORS")
+ else None
+)
+if _TYPER_FORCE_DISABLE_TERMINAL:
+ FORCE_TERMINAL = False
+
+# Fixed strings
+DEPRECATED_STRING = "(deprecated) "
+DEFAULT_STRING = "[default: {}]"
+ENVVAR_STRING = "[env var: {}]"
+REQUIRED_SHORT_STRING = "*"
+REQUIRED_LONG_STRING = "[required]"
+RANGE_STRING = " [{}]"
+ARGUMENTS_PANEL_TITLE = "Arguments"
+OPTIONS_PANEL_TITLE = "Options"
+COMMANDS_PANEL_TITLE = "Commands"
+ERRORS_PANEL_TITLE = "Error"
+ABORTED_TEXT = "Aborted."
+
+MARKUP_MODE_MARKDOWN = "markdown"
+MARKUP_MODE_RICH = "rich"
+_RICH_HELP_PANEL_NAME = "rich_help_panel"
+
+MarkupMode = Literal["markdown", "rich", None]
+
+
+# Rich regex highlighter
+class OptionHighlighter(RegexHighlighter):
+ """Highlights our special options."""
+
+ highlights = [
+ r"(^|\W)(?P\-\w+)(?![a-zA-Z0-9])",
+ r"(^|\W)(?P