diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index aede7ea1a7..8e589cd6ab 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,10 +4,10 @@ contact_links: about: Please report security vulnerabilities to security@tiangolo.com - name: Question or Problem about: Ask a question or ask about a problem in GitHub Discussions. - url: https://github.com/tiangolo/typer/discussions/categories/questions + url: https://github.com/fastapi/typer/discussions/categories/questions - name: Feature Request about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already. - url: https://github.com/tiangolo/typer/discussions/categories/questions + url: https://github.com/fastapi/typer/discussions/categories/questions - name: Show and tell about: Show what you built with Typer or to be used with Typer. - url: https://github.com/tiangolo/typer/discussions/categories/show-and-tell + url: https://github.com/fastapi/typer/discussions/categories/show-and-tell diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml index f8acd76f2e..8037408426 100644 --- a/.github/ISSUE_TEMPLATE/privileged.yml +++ b/.github/ISSUE_TEMPLATE/privileged.yml @@ -6,7 +6,7 @@ body: value: | Thanks for your interest in Typer! π - If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/typer/discussions/categories/questions) instead. + If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/fastapi/typer/discussions/categories/questions) instead. - type: checkboxes id: privileged attributes: diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..4290a118b9 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,24 @@ +docs: + - all: + - changed-files: + - any-glob-to-any-file: + - docs/** + - docs_src/** + - all-globs-to-all-files: + - '!typer/**' + - '!pyproject.toml' + +internal: + - all: + - changed-files: + - any-glob-to-any-file: + - .github/** + - scripts/** + - .gitignore + - .pre-commit-config.yaml + - pdm_build.py + - requirements*.txt + - all-globs-to-all-files: + - '!docs/**' + - '!typer/**' + - '!pyproject.toml' diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml new file mode 100644 index 0000000000..dccea83f35 --- /dev/null +++ b/.github/workflows/add-to-project.yml @@ -0,0 +1,18 @@ +name: Add to Project + +on: + pull_request_target: + issues: + types: + - opened + - reopened + +jobs: + add-to-project: + name: Add to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/fastapi/projects/2 + github-token: ${{ secrets.PROJECTS_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000000..c3bb83f9a5 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,31 @@ +name: Labels +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + # For label-checker + - labeled + - unlabeled + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + # Run this after labeler applied labels + check-labels: + needs: + - labeler + permissions: + pull-requests: read + runs-on: ubuntu-latest + steps: + - uses: docker://agilepathway/pull-request-label-checker:latest + with: + one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal + repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CITATION.cff b/CITATION.cff index 43da1f3c89..5fe4aa2d34 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -12,7 +12,7 @@ authors: family-names: RamΓrez email: tiangolo@gmail.com identifiers: -repository-code: 'https://github.com/tiangolo/typer' +repository-code: 'https://github.com/fastapi/typer' url: 'https://typer.tiangolo.com' abstract: >- Typer, build great CLIs. Easy to code. Based on Python type hints. diff --git a/README.md b/README.md index 844ca8a1e1..3be56f4031 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ Typer, build great CLIs. Easy to code. Based on Python type hints.
- - + + - - + + - - + + @@ -23,7 +23,7 @@ **Documentation**: https://typer.tiangolo.com -**Source Code**: https://github.com/tiangolo/typer +**Source Code**: https://github.com/fastapi/typer --- diff --git a/data/members.yml b/data/members.yml new file mode 100644 index 0000000000..2c30d19420 --- /dev/null +++ b/data/members.yml @@ -0,0 +1,3 @@ +members: +- login: tiangolo +- login: svlandeg diff --git a/docs/about/index.md b/docs/about/index.md new file mode 100644 index 0000000000..1688bf193d --- /dev/null +++ b/docs/about/index.md @@ -0,0 +1,3 @@ +# About + +About **Typer**, its design, inspiration, and more. π€ diff --git a/docs/css/custom.css b/docs/css/custom.css index 65265a5389..200ac45cd6 100644 --- a/docs/css/custom.css +++ b/docs/css/custom.css @@ -29,3 +29,43 @@ a.internal-link::after { .shadow { box-shadow: 5px 5px 10px #999; } + +.user-list { + display: flex; + flex-wrap: wrap; + margin-bottom: 2rem; +} + +.user-list-center { + justify-content: space-evenly; +} + +.user { + margin: 1em; + min-width: 7em; +} + +.user .avatar-wrapper { + width: 80px; + height: 80px; + margin: 10px auto; + overflow: hidden; + border-radius: 50%; + position: relative; +} + +.user .avatar-wrapper img { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.user .title { + text-align: center; +} + +.user .count { + font-size: 80%; + text-align: center; +} diff --git a/docs/help-typer.md b/docs/help-typer.md index 11596d7024..26c5cd6474 100644 --- a/docs/help-typer.md +++ b/docs/help-typer.md @@ -22,13 +22,13 @@ You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/news ## Star **Typer** in GitHub -You can "star" Typer in GitHub (clicking the star button at the top right): https://github.com/tiangolo/typer. +You can "star" Typer in GitHub (clicking the star button at the top right): https://github.com/fastapi/typer. By adding a star, other users will be able to find it more easily and see that it has been already useful for others. ## Watch the GitHub repository for releases -You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/typer. +You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/fastapi/typer. There you can select "Releases only". @@ -54,7 +54,7 @@ You can: ## Tweet about **Typer** -Tweet about **Typer** and let me and others know why you like it. +Tweet about **Typer** and let me and others know why you like it. I love to hear about how **Typer** is being used, what have you liked in it, in which project/company you are using it, etc. @@ -62,8 +62,8 @@ I love to hear about how **Typer** is being used, what have you liked in it, in You can try and help others with their questions in: -* GitHub Discussions -* GitHub Issues +* GitHub Discussions +* GitHub Issues In many cases you might already know the answer for those questions. π€ @@ -112,7 +112,7 @@ If they reply, there's a high chance you would have solved their problem, congra ## Watch the GitHub repository -You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/typer. +You can "watch" Typer in GitHub (clicking the "watch" button at the top right): https://github.com/fastapi/typer. If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc. @@ -120,7 +120,7 @@ Then you can try and help them solve those questions. ## Ask Questions -You can create a new question in the GitHub repository, for example to: +You can create a new question in the GitHub repository, for example to: * Ask a **question** or ask about a **problem**. * Suggest a new **feature**. @@ -214,7 +214,7 @@ Join the π₯ GitHub Discussions, there's a much better chance you will receive help there. +For questions, ask them in GitHub Discussions, there's a much better chance you will receive help there. Use the chat only for other general conversations. diff --git a/docs/index.md b/docs/index.md index 8a84cd0ba6..b54e02426f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,14 +12,14 @@ Typer, build great CLIs. Easy to code. Based on Python type hints.
- - + + - - + + - - + + @@ -29,7 +29,7 @@ **Documentation**: https://typer.tiangolo.com -**Source Code**: https://github.com/tiangolo/typer +**Source Code**: https://github.com/fastapi/typer --- diff --git a/docs/management-tasks.md b/docs/management-tasks.md new file mode 100644 index 0000000000..879e0a0c3f --- /dev/null +++ b/docs/management-tasks.md @@ -0,0 +1,115 @@ +# Repository Management Tasks + +These are the tasks that can be performed to manage the Typer repository by [team members](./management.md#team){.internal-link target=_blank}. + +/// tip + +This section is useful only to a handful of people, team members with permissions to manage the repository. You can probably skip it. π + +/// + +...so, you are a [team member of Typer](./management.md#team){.internal-link target=_blank}? Wow, you are so cool! π + +You can help with everything on [Help Typer - Get Help](./help-typer.md){.internal-link target=_blank} the same ways as external contributors. But additionally, there are some tasks that only you (as part of the team) can perform. + +Here are the general instructions for the tasks you can perform. + +Thanks a lot for your help. π + +## Be Nice + +First of all, be nice. π + +You probably are super nice if you were added to the team, but it's worth mentioning it. π€ + +### When Things are Difficult + +When things are great, everything is easier, so that doesn't need much instructions. But when things are difficult, here are some guidelines. + +Try to find the good side. In general, if people are not being unfriendly, try to thank their effort and interest, even if you disagree with the main subject (discussion, PR), just thank them for being interested in the project, or for having dedicated some time to try to do something. + +It's difficult to convey emotion in text, use emojis to help. π + +In discussions and PRs, in many cases, people bring their frustration and show it without filter, in many cases exaggerating, complaining, being entitled, etc. That's really not nice, and when it happens, it lowers our priority to solve their problems. But still, try to breath, and be gentle with your answers. + +Try to avoid using bitter sarcasm or potentially passive-aggressive comments. If something is wrong, it's better to be direct (try to be gentle) than sarcastic. + +Try to be as specific and objective as possible, avoid generalizations. + +For conversations that are more difficult, for example to reject a PR, you can ask me (@tiangolo) to handle it directly. + +## Edit PR Titles + +* Edit the PR title to start with an emoji from gitmoji. + * Use the emoji character, not the GitHub code. So, use `π` instead of `:bug:`. This is so that it shows up correctly outside of GitHub, for example in the release notes. +* Start the title with a verb. For example `Add`, `Refactor`, `Fix`, etc. This way the title will say the action that the PR does. Like `Add support for teleporting`, instead of `Teleporting wasn't working, so this PR fixes it`. +* Edit the text of the PR title to start in "imperative", like giving an order. So, instead of `Adding support for teleporting` use `Add support for teleporting`. +* Try to make the title descriptive about what it achieves. If it's a feature, try to describe it, for example `Add support for teleporting` instead of `Create TeleportAdapter class`. +* Do not finish the title with a period (`.`). + +Once the PR is merged, a GitHub Action (latest-changes) will use the PR title to update the latest changes automatically. + +So, having a nice PR title will not only look nice in GitHub, but also in the release notes. π + +## Add Labels to PRs + +The same GitHub Action latest-changes uses one label in the PR to decide the section in the release notes to put this PR in. + +Make sure you use a supported label from the latest-changes list of labels: + +* `breaking`: Breaking Changes + * Existing code will break if they update the version without changing their code. This rarely happens, so this label is not frequently used. +* `security`: Security Fixes + * This is for security fixes, like vulnerabilities. It would almost never be used. +* `feature`: Features + * New features, adding support for things that didn't exist before. +* `bug`: Fixes + * Something that was supported didn't work, and this fixes it. There are many PRs that claim to be bug fixes because the user is doing something in an unexpected way that is not supported, but they considered it what should be supported by default. Many of these are actually features or refactors. But in some cases there's an actual bug. +* `refactor`: Refactors + * This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc. +* `upgrade`: Upgrades + * This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`. +* `docs`: Docs + * Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations. + * You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`. +* `internal`: Internal + * Use this for changes that only affect how the repo is managed. For example upgrades to internal dependencies, changes in GitHub Actions or scripts, etc. + +/// tip + +Some tools like Dependabot, will add some labels, like `dependencies`, but have in mind that this label is not used by the `latest-changes` GitHub Action, so it won't be used in the release notes. Please make sure one of the labels above is added. + +/// + +## Review PRs + +If a PR doesn't explain what it does or why, ask for more information. + +A PR should have a specific use case that it is solving. + +* If the PR is for a feature, it should have docs. + * Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use. +* The docs should include a source example file, not write Python directly in Markdown. +* If the source example(s) file can have different syntax for Python 3.8, 3.9, 3.10, there should be different versions of the file, and they should be shown in tabs in the docs. +* There should be tests testing the source example. +* Before the PR is applied, the new tests should fail. +* After applying the PR, the new tests should pass. +* Coverage should stay at 100%. +* If you see the PR makes sense, or we discussed it and considered it should be accepted, you can add commits on top of the PR to tweak it, to add docs, tests, format, refactor, remove extra files, etc. +* Feel free to comment in the PR to ask for more information, to suggest changes, etc. +* Once you think the PR is ready, move it in the internal GitHub project for me to review it. + +## Dependabot PRs + +Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others. + +* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. π± Let me check it first. There's a good chance that some additional tweaks or updates are needed. +* If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. π + +## Mark GitHub Discussions Answers + +When a question in GitHub Discussions has been answered, mark the answer by clicking "Mark as answer". + +Many of the current Discussion Questions were migrated from old issues. Many have the label `answered`, that means they were answered when they were issues, but now in GitHub Discussions, it's not known what is the actual response from the messages. + +You can filter discussions by `Questions` that are `Unanswered`. diff --git a/docs/management.md b/docs/management.md new file mode 100644 index 0000000000..5c1f66e90c --- /dev/null +++ b/docs/management.md @@ -0,0 +1,45 @@ +# Repository Management + +Here's a short description of how the Typer repository is managed and maintained. + +## Owner + +I, @tiangolo, am the creator and owner of the Typer repository. π€ + +I normally give the final review to each PR before merging them. I make the final decisions on the project, I'm the BDFL. π + +## Team + +There's a team of people that help manage and maintain the project. π + +They have different levels of permissions and [specific instructions](./management-tasks.md){.internal-link target=_blank}. + +Some of the tasks they can perform include: + +* Adding labels to PRs. +* Editing PR titles. +* Adding commits on top of PRs to tweak them. +* Mark answers in GitHub Discussions questions, etc. +* Merge some specific types of PRs. + +Joining the team is by invitation only, and I could update or remove permissions, instructions, or membership. + +### Team Members + +This is the current list of team members. π + +
- - + + - - + + - - + + @@ -22,7 +22,7 @@ **Documentation**: https://typer.tiangolo.com/tutorial/typer-command/ -**Source Code**: https://github.com/tiangolo/typer +**Source Code**: https://github.com/fastapi/typer --- diff --git a/typer/__init__.py b/typer/__init__.py index d4ac56d0ba..dd2531263d 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -1,6 +1,6 @@ """Typer, build great CLIs. Easy to code. Based on Python type hints.""" -__version__ = "0.12.3" +__version__ = "0.12.4" from shutil import get_terminal_size as get_terminal_size diff --git a/typer/_completion_shared.py b/typer/_completion_shared.py index 10de54420d..a5ff7b196a 100644 --- a/typer/_completion_shared.py +++ b/typer/_completion_shared.py @@ -128,16 +128,16 @@ def install_zsh(*, prog_name: str, complete_var: str, shell: str) -> Path: zshrc_content = "" if zshrc_path.is_file(): zshrc_content = zshrc_path.read_text() - completion_init_lines = [ - "autoload -Uz compinit", - "compinit", - "zstyle ':completion:*' menu select", - "fpath+=~/.zfunc", - ] - for line in completion_init_lines: - if line not in zshrc_content: # pragma: no cover - zshrc_content += f"\n{line}" - zshrc_content += "\n" + completion_line = "fpath+=~/.zfunc; autoload -Uz compinit; compinit" + if completion_line not in zshrc_content: + zshrc_content += f"\n{completion_line}\n" + style_line = "zstyle ':completion:*' menu select" + # TODO: consider setting the style only for the current program + # style_line = f"zstyle ':completion:*:*:{prog_name}:*' menu select" + # Install zstyle completion config only if the user doesn't have a customization + if "zstyle" not in zshrc_content: + zshrc_content += f"\n{style_line}\n" + zshrc_content = f"{zshrc_content.strip()}\n" zshrc_path.write_text(zshrc_content) # Install completion under ~/.zfunc/ path_obj = Path.home() / f".zfunc/_{prog_name}" diff --git a/typer/main.py b/typer/main.py index 17294eea6d..33da580b7f 100644 --- a/typer/main.py +++ b/typer/main.py @@ -14,6 +14,7 @@ import click from typing_extensions import Annotated, TypeAlias, get_args, get_origin +from ._typing import get_args, get_origin, is_union from .completion import get_completion_inspect_parameters from .core import MarkupMode, TyperArgument, TyperCommand, TyperGroup, TyperOption from .models import ( @@ -871,29 +872,30 @@ def get_click_param( is_tuple = False parameter_type: Any = None is_flag = None - origin = getattr(main_type, "__origin__", None) + origin = get_origin(main_type) callback = parameter_info.callback if origin is not None: - # Handle Optional[SomeType] - if origin is Union: + # Handle SomeType | None and Optional[SomeType] + if is_union(origin): types = [] - for type_ in main_type.__args__: + for type_ in get_args(main_type): if type_ is NoneType: continue types.append(type_) assert len(types) == 1, "Typer Currently doesn't support Union types" main_type = types[0] - origin = getattr(main_type, "__origin__", None) + origin = get_origin(main_type) # Handle Tuples and Lists if lenient_issubclass(origin, List): - main_type = main_type.__args__[0] + main_type = get_args(main_type)[0] assert not is_complex_subtype( main_type ), "List types with complex sub-types are not currently supported" is_list = True elif lenient_issubclass(origin, Tuple): # type: ignore types = [] - for type_ in main_type.__args__: + + for type_ in get_args(main_type): assert not is_complex_subtype( type_ ), "Tuple types with complex sub-types are not currently supported" @@ -912,7 +914,7 @@ def get_click_param( convertor=convertor, default_value=default_value ) if is_tuple: - convertor = generate_tuple_convertor(main_type.__args__) + convertor = generate_tuple_convertor(get_args(main_type)) if isinstance(parameter_info, OptionInfo): if main_type is bool and parameter_info.is_flag is not False: is_flag = True @@ -990,6 +992,7 @@ def get_click_param( expose_value=parameter_info.expose_value, is_eager=parameter_info.is_eager, envvar=parameter_info.envvar, + shell_complete=parameter_info.shell_complete, autocompletion=get_param_completion(parameter_info.autocompletion), # Rich settings rich_help_panel=parameter_info.rich_help_panel, @@ -1062,7 +1065,7 @@ def get_param_completion( incomplete_name = None unassigned_params = list(parameters.values()) for param_sig in unassigned_params[:]: - origin = getattr(param_sig.annotation, "__origin__", None) + origin = get_origin(param_sig.annotation) if lenient_issubclass(param_sig.annotation, click.Context): ctx_name = param_sig.name unassigned_params.remove(param_sig) diff --git a/typer/utils.py b/typer/utils.py index 2ba7bace45..5c0e967a64 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -1,9 +1,9 @@ import inspect import sys from copy import copy -from typing import Any, Callable, Dict, List, Tuple, Type, cast, get_type_hints +from typing import Any, Callable, Dict, List, Tuple, Type, cast -from typing_extensions import Annotated +from typing_extensions import Annotated, get_type_hints from ._typing import get_args, get_origin from .models import ArgumentInfo, OptionInfo, ParameterInfo, ParamMeta