-
-
Notifications
You must be signed in to change notification settings - Fork 807
✨ Support bytes in Options and Arguments #1190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
doubledare704
wants to merge
20
commits into
fastapi:master
Choose a base branch
from
doubledare704:feature/support-bytes-type
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+587
−0
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
bfb093a
Remove debug_process.py file
doubledare704 87bcd4b
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] 73bd728
Fix linting issues and add type annotations
doubledare704 2766fe7
Fix mypy type error in BytesParamType.convert
doubledare704 38145f5
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] c0ce2ce
make full coverage of tests
doubledare704 8a52f94
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] de03a32
avoid assert false
doubledare704 dc98576
avoid coverage in 1 line of tests
doubledare704 3f94475
Merge branch 'master' into feature/support-bytes-type
doubledare704 32520a7
Merge branch 'master' into feature/support-bytes-type
doubledare704 8eb5fcc
Merge branch 'fastapi:master' into feature/support-bytes-type
doubledare704 963b0d1
feat(bytes): support encoding/errors for bytes params\n\n- Add encodi…
doubledare704 b5111fc
fix(tests): correct stderr assertion in bytes encoding test
doubledare704 a59129f
fix(tests): correct stderr assertion in bytes encoding test
doubledare704 2e8b8ec
fix(tests): correct stderr assertion in bytes encoding test
doubledare704 996a5d7
fix(tests): correct stderr assertion in bytes encoding test
doubledare704 77c2846
set no cover for uncoverable line
doubledare704 14b60f3
update tutorial files to use Typer()
svlandeg e64aa57
Merge branch 'master' into feature/support-bytes-type
svlandeg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # Bytes | ||
|
|
||
| You can declare `bytes` for CLI arguments and options. | ||
|
|
||
| By default, `bytes` are created by encoding the input string with UTF-8 (the same as Python's default for `str.encode()`), but you can configure the encoding and error handling. | ||
|
|
||
| ## Default UTF-8 encoding | ||
|
|
||
| This example declares a `bytes` argument using the default UTF-8 encoding: | ||
|
|
||
| {* docs_src/parameter_types/bytes/tutorial001.py hl[7-9] *} | ||
|
|
||
| Try it with non-ASCII characters and you will get UTF-8 encoded bytes. | ||
|
|
||
| ## Custom encoding on Argument | ||
|
|
||
| You can set a specific encoding for a `bytes` argument: | ||
|
|
||
| {* docs_src/parameter_types/bytes/tutorial002.py hl[7] *} | ||
|
|
||
| Here the argument is configured with `encoding="latin-1"`, so the command line input will be encoded accordingly. | ||
|
|
||
| ## Custom encoding and errors on Option | ||
|
|
||
| You can also configure a `bytes` option with a specific encoding and error handling mode: | ||
|
|
||
| {* docs_src/parameter_types/bytes/tutorial003.py hl[7] *} | ||
|
|
||
| The `errors` parameter supports the same values as Python's `str.encode()` (e.g. `"strict"`, `"ignore"`, `"replace"`). | ||
|
|
||
| ## Primary use case | ||
|
|
||
| The goal of supporting `bytes` is to let you write a single function that works both: | ||
|
|
||
| - Inside Typer: when called as a CLI, Typer parses command line input and converts it to `bytes` using the configured `encoding`/`errors`. | ||
| - Outside Typer: when called as regular Python code, you can pass `bytes` directly, without any CLI parsing involved. | ||
|
|
||
| This keeps your function reusable in both contexts while giving you control over how CLI text inputs are converted to `bytes`. | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import typer | ||
|
|
||
| app = typer.Typer() | ||
|
|
||
|
|
||
| @app.command() | ||
| def main(data: bytes): | ||
| # Default encoding is UTF-8 | ||
| print(f"Bytes: {data!r}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import typer | ||
|
|
||
| app = typer.Typer() | ||
|
|
||
|
|
||
| @app.command() | ||
| def main(data: bytes = typer.Argument(..., encoding="latin-1")): | ||
| # Argument configured to use latin-1 | ||
| print(f"Bytes (latin-1): {data!r}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import typer | ||
|
|
||
| app = typer.Typer() | ||
|
|
||
|
|
||
| @app.command() | ||
| def main(token: bytes = typer.Option(..., encoding="ascii", errors="replace")): | ||
| # Option configured with ascii encoding and errors=replace | ||
| print(f"Token: {token!r}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| import base64 | ||
| import binascii | ||
|
|
||
| import typer | ||
|
|
||
| app = typer.Typer() | ||
|
|
||
|
|
||
| @app.command() | ||
| def base64_encode(text: bytes): | ||
| """Encode text to base64.""" | ||
| encoded = base64.b64encode(text) | ||
| typer.echo(f"Original: {text!r}") | ||
| typer.echo(f"Base64 encoded: {encoded.decode()}") | ||
|
|
||
|
|
||
| @app.command() | ||
| def base64_decode(encoded: str): | ||
| """Decode base64 to bytes.""" | ||
| try: | ||
| decoded = base64.b64decode(encoded) | ||
| typer.echo(f"Base64 encoded: {encoded}") | ||
| typer.echo(f"Decoded: {decoded!r}") | ||
| typer.echo(f"As string: {decoded.decode(errors='replace')}") | ||
| except Exception as e: | ||
| typer.echo(f"Error decoding base64: {e}", err=True) | ||
| raise typer.Exit(code=1) from e | ||
|
|
||
|
|
||
| @app.command() | ||
| def hex_encode(data: bytes): | ||
| """Convert bytes to hex string.""" | ||
| hex_str = binascii.hexlify(data).decode() | ||
| typer.echo(f"Original: {data!r}") | ||
| typer.echo(f"Hex encoded: {hex_str}") | ||
|
|
||
|
|
||
| @app.command() | ||
| def hex_decode(hex_str: str): | ||
| """Convert hex string to bytes.""" | ||
| try: | ||
| data = binascii.unhexlify(hex_str) | ||
| typer.echo(f"Hex encoded: {hex_str}") | ||
| typer.echo(f"Decoded: {data!r}") | ||
| typer.echo(f"As string: {data.decode(errors='replace')}") | ||
| except Exception as e: | ||
| typer.echo(f"Error decoding hex: {e}", err=True) | ||
| raise typer.Exit(code=1) from e | ||
|
|
||
|
|
||
| @app.command() | ||
| def convert( | ||
| data: bytes = typer.Argument(..., help="Data to convert"), | ||
| from_format: str = typer.Option( | ||
| "raw", "--from", "-f", help="Source format: raw, base64, or hex" | ||
| ), | ||
| to_format: str = typer.Option( | ||
| "base64", "--to", "-t", help="Target format: raw, base64, or hex" | ||
| ), | ||
| ): | ||
| """Convert between different encodings.""" | ||
| # First decode from source format to raw bytes | ||
| raw_bytes = data | ||
| if from_format == "base64": | ||
| try: | ||
| raw_bytes = base64.b64decode(data) | ||
| except Exception as e: | ||
| typer.echo(f"Error decoding base64: {e}", err=True) | ||
| raise typer.Exit(code=1) from e | ||
| elif from_format == "hex": | ||
| try: | ||
| raw_bytes = binascii.unhexlify(data) | ||
| except Exception as e: | ||
| typer.echo(f"Error decoding hex: {e}", err=True) | ||
| raise typer.Exit(code=1) from e | ||
| elif from_format != "raw": | ||
| typer.echo(f"Unknown source format: {from_format}", err=True) | ||
| raise typer.Exit(code=1) | ||
|
|
||
| # Then encode to target format | ||
| if to_format == "raw": | ||
| typer.echo(f"Raw bytes: {raw_bytes!r}") | ||
| typer.echo(f"As string: {raw_bytes.decode(errors='replace')}") | ||
| elif to_format == "base64": | ||
| encoded = base64.b64encode(raw_bytes).decode() | ||
| typer.echo(f"Base64 encoded: {encoded}") | ||
| elif to_format == "hex": | ||
| encoded = binascii.hexlify(raw_bytes).decode() | ||
| typer.echo(f"Hex encoded: {encoded}") | ||
| else: | ||
| typer.echo(f"Unknown target format: {to_format}", err=True) | ||
| raise typer.Exit(code=1) | ||
|
|
||
|
|
||
| @app.command() | ||
| def convert_latin1( | ||
| data: bytes = typer.Argument( | ||
| ..., help="Data to convert (latin-1)", encoding="latin-1" | ||
| ), | ||
| from_format: str = typer.Option( | ||
| "raw", "--from", "-f", help="Source format: raw, base64, or hex" | ||
| ), | ||
| to_format: str = typer.Option( | ||
| "base64", "--to", "-t", help="Target format: raw, base64, or hex" | ||
| ), | ||
| ): | ||
| """Convert using latin-1 input decoding for the bytes argument.""" | ||
| # Reuse the same logic as convert() | ||
| raw_bytes = data | ||
| if from_format == "base64": | ||
| try: | ||
| raw_bytes = base64.b64decode(data) | ||
| except Exception as e: | ||
| typer.echo(f"Error decoding base64: {e}", err=True) | ||
| raise typer.Exit(code=1) from e | ||
| elif from_format == "hex": | ||
| try: | ||
| raw_bytes = binascii.unhexlify(data) | ||
| except Exception as e: | ||
| typer.echo(f"Error decoding hex: {e}", err=True) | ||
| raise typer.Exit(code=1) from e | ||
| elif from_format != "raw": | ||
| typer.echo(f"Unknown source format: {from_format}", err=True) | ||
| raise typer.Exit(code=1) | ||
|
|
||
| if to_format == "raw": | ||
| typer.echo(f"Raw bytes: {raw_bytes!r}") | ||
| typer.echo(f"As string: {raw_bytes.decode(errors='replace')}") | ||
| elif to_format == "base64": | ||
| encoded = base64.b64encode(raw_bytes).decode() | ||
| typer.echo(f"Base64 encoded: {encoded}") | ||
| elif to_format == "hex": | ||
| encoded = binascii.hexlify(raw_bytes).decode() | ||
| typer.echo(f"Hex encoded: {encoded}") | ||
| else: | ||
| typer.echo(f"Unknown target format: {to_format}", err=True) | ||
| raise typer.Exit(code=1) | ||
|
|
||
|
|
||
| @app.command() | ||
| def option_ascii_replace( | ||
| payload: bytes = typer.Option( | ||
| ..., | ||
| "--payload", | ||
| help="Bytes option encoded with ascii and errors=replace", | ||
| encoding="ascii", | ||
| errors="replace", | ||
| ), | ||
| ): | ||
| """Demonstrate bytes option with ascii encoding and errors=replace.""" | ||
| typer.echo(f"Option bytes: {payload!r}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import base64 | ||
|
|
||
| import typer | ||
|
|
||
| app = typer.Typer() | ||
|
|
||
|
|
||
| @app.command() | ||
| def encode(text: bytes): | ||
| """Encode text to base64.""" | ||
| encoded = base64.b64encode(text) | ||
| typer.echo(f"Original: {text!r}") | ||
| typer.echo(f"Encoded: {encoded.decode()}") | ||
|
|
||
|
|
||
| @app.command() | ||
| def decode(encoded: str): | ||
| """Decode base64 to bytes.""" | ||
| decoded = base64.b64decode(encoded) | ||
| typer.echo(f"Encoded: {encoded}") | ||
| typer.echo(f"Decoded: {decoded!r}") | ||
|
|
||
|
|
||
| @app.command() | ||
| def echo_default( | ||
| name: bytes = typer.Argument(..., help="Name as bytes (default UTF-8)"), | ||
| ): | ||
| """Echo bytes with default UTF-8 encoding.""" | ||
| typer.echo(f"Default UTF-8 bytes: {name!r}") | ||
|
|
||
|
|
||
| @app.command() | ||
| def echo_latin1( | ||
| name: bytes = typer.Argument( | ||
| ..., encoding="latin-1", help="Name as bytes (latin-1)" | ||
| ), | ||
| ): | ||
| """Echo bytes with latin-1 encoding for the argument.""" | ||
| typer.echo(f"Latin-1 bytes: {name!r}") | ||
|
|
||
|
|
||
| @app.command() | ||
| def option_ascii_replace( | ||
| token: bytes = typer.Option( | ||
| ..., | ||
| "--token", | ||
| encoding="ascii", | ||
| errors="replace", | ||
| help="Token as bytes (ascii, errors=replace)", | ||
| ), | ||
| ): | ||
| """Option demonstrating ascii encoding with errors=replace.""" | ||
| typer.echo(f"Option bytes: {token!r}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This page should also be added to
mkdocs.yml