Skip to main content

File and Path Inputs

Handle file system interactions by using pathlib.Path for path-based logic or specialized File* types for direct file-like object interaction.

Validate File System Paths

To receive a path and validate its properties (like existence or permissions) before your command runs, use the standard pathlib.Path type hint combined with typer.Option or typer.Argument validation parameters.

from pathlib import Path
from typing import Annotated
import typer

app = typer.Typer()

@app.command()
def main(
config: Annotated[
Path,
typer.Option(
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
resolve_path=True,
),
],
):
text = config.read_text()
print(f"Config file contents: {text}")

if __name__ == "__main__":
app()

When you use these parameters, Typer internally uses the TyperPath class (a subclass of click.Path) to perform the following checks:

  • exists: Ensures the path exists on the file system.
  • file_okay: Allows files as valid inputs.
  • dir_okay: Allows directories as valid inputs.
  • writable / readable: Checks for specific file permissions.
  • resolve_path: Automatically converts the input to an absolute path.

Read and Write Text Files

If you need to interact with the file contents directly, use FileText or FileTextWrite. These types provide an open file-like object (io.TextIOWrapper) that yields str data.

Reading Text

Use FileText to open a file for reading. The default mode is "r".

from typing import Annotated
import typer

app = typer.Typer()

@app.command()
def main(config: Annotated[typer.FileText, typer.Option()]):
for line in config:
print(f"Config line: {line}")

if __name__ == "__main__":
app()

Writing Text

Use FileTextWrite to open a file for writing. The default mode is "w".

from typing import Annotated
import typer

app = typer.Typer()

@app.command()
def main(output: Annotated[typer.FileTextWrite, typer.Option()]):
output.write("Some text written by the app\n")
print("Content written to file")

if __name__ == "__main__":
app()

Handle Binary Files

For non-text data like images or compressed files, use FileBinaryRead and FileBinaryWrite. These provide io.BufferedReader or io.BufferedWriter objects that handle bytes.

Reading Binary Data

FileBinaryRead uses mode "rb" by default.

from typing import Annotated
import typer

app = typer.Typer()

@app.command()
def main(file: Annotated[typer.FileBinaryRead, typer.Option()]):
processed_total = 0
for bytes_chunk in file:
processed_total += len(bytes_chunk)
print(f"Processed bytes total: {processed_total}")

if __name__ == "__main__":
app()

Writing Binary Data

FileBinaryWrite uses mode "wb" by default. You must encode strings to bytes before writing.

from typing import Annotated
import typer

app = typer.Typer()

@app.command()
def main(file: Annotated[typer.FileBinaryWrite, typer.Option()]):
# Encode strings to bytes
first_line = "header\n".encode("utf-8")
file.write(first_line)

# Write raw bytes directly
raw_data = b"\x00\x01\x02\x03"
file.write(raw_data)
print("Binary file written")

if __name__ == "__main__":
app()

Advanced File Configuration

You can further customize how files are opened by passing additional parameters to typer.Option or typer.Argument. These parameters are passed directly to the underlying click.File implementation.

  • mode: Override the default (e.g., use mode="a" for appending).
  • encoding: Specify the text encoding (e.g., encoding="utf-16").
  • lazy: If True (default), the file is opened only when accessed. If False, it is opened immediately.
  • allow_dash: If True, allows - as an input to represent stdin or stdout.
@app.command()
def main(
log: Annotated[
typer.FileText,
typer.Option(mode="a", encoding="utf-8", lazy=False)
]
):
log.write("New log entry\n")

Troubleshooting

Manual Encoding in Binary Mode

When using FileBinaryWrite, attempting to write a str directly will result in a TypeError. Always use .encode() or provide a bytes object:

# Correct
file.write("data".encode("utf-8"))

# Incorrect - will raise TypeError
# file.write("data")

File Handles and Context Managers

Typer manages the lifecycle of these file objects. If lazy=True (the default), the file is opened when your command function starts and is automatically closed when the function finishes. You do not need to use a with statement inside your command for files passed as parameters.

TyperPath and Shell Completion

The TyperPath class used for pathlib.Path validation overrides Click's default shell_complete to return an empty list. This is intentional; it allows Typer's internal completion system to handle path suggestions more accurately across different shells.