Skip to content

Instantly share code, notes, and snippets.

@MattOates
Created September 17, 2025 16:12
Show Gist options
  • Select an option

  • Save MattOates/382a758c5cf60aa636cc6ad704452984 to your computer and use it in GitHub Desktop.

Select an option

Save MattOates/382a758c5cf60aa636cc6ad704452984 to your computer and use it in GitHub Desktop.
A template standalone script that has some testing and can be shipped as a single file readily
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["typer", "pytest"] # keep this minimal; add libs you actually use
# ///
# -*- coding: utf-8 -*-
from pathlib import Path
import typer
def transform(xs: list[int]) -> list[int]:
"""
Example pure function with doctests.
>>> transform([1, 2, 3, 4])
[1, 4, 3, 16]
Edge cases:
>>> transform([])
[]
"""
return [x * x if x % 2 == 0 else x for x in xs]
def parse_csv_line(line: str) -> list[str]:
"""
Extremely basic CSV split (placeholder).
>>> parse_csv_line("a,b,c")
['a', 'b', 'c']
>>> parse_csv_line('a, "b", c') # doctest: +ELLIPSIS
['a', '"b"', 'c']
"""
return [p.strip() for p in line.split(",")]
app = typer.Typer(add_completion=False, no_args_is_help=True)
# CLI commands
@app.command()
def run(
input_path: Path = typer.Argument(
..., help="Input file (placeholder for real CLI)"
),
output_path: Path | None = typer.Option(None, "--out", help="Output path"),
) -> None:
"""
Example command: read first line, transform a toy payload, print result.
Replace this with your real entrypoint; keep it thin and call pure functions above.
"""
line = input_path.read_text().splitlines()[0] if input_path.exists() else ""
parts = parse_csv_line(line)
nums = [int(p) for p in parts if p.isdigit()]
result = transform(nums)
if output_path:
output_path.write_text(",".join(map(str, result)) + "\n")
else:
print(result)
# Entrypoint
if __name__ == "__main__":
app()
# Tests
# Note: this self-test command is optional; remove if you don't want it
@app.command("self-test")
def self_test(
pytest_args: list[str] = typer.Argument(None),
doctest_verbose: bool = typer.Option(False, "--doctest-verbose"),
) -> None:
"""
Run doctest first, then pytest.
If this script isn't a *.py file, create a temporary directory containing
a *.py *symlink* to this file, run pytest against that symlink path,
and then clean it up.
"""
import doctest
import sys
# 1) Run doctests on the current module
flags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
fails, _ = doctest.testmod(
sys.modules[__name__], optionflags=flags, verbose=doctest_verbose
)
# 2) If pytest isn't available, just report doctest result
try:
import pytest # type: ignore
except Exception:
raise typer.Exit(code=1 if fails else 0)
this_file = Path(__file__).resolve()
exit_code = 1 if fails else 0
# 3) Choose target for pytest (real .py file, or temp symlink)
target: Path
tmpdir = None
try:
if this_file.suffix == ".py":
target = this_file
else:
import os
import tempfile
tmpdir = tempfile.TemporaryDirectory()
link = Path(tmpdir.name) / (this_file.name + ".py")
# create symlink pointing to the current file
try:
os.symlink(this_file, link)
# on Windows, symlink may require admin rights; fall back to copy
except OSError:
import shutil
shutil.copy2(this_file, link)
target = link
# 4) Run pytest against the target
code = pytest.main([str(target), *(pytest_args or [])])
if code != 0:
exit_code = code
finally:
if tmpdir is not None:
tmpdir.cleanup()
raise typer.Exit(code=exit_code)
# Optional: bigger doctest scenarios live here if they don't read well in docstrings,
# prefer just doing pytest functions though
__test__ = {
"round_trip_example": r"""
>>> xs = [0, 1, 2]
>>> transform(xs)
[0, 1, 4]
"""
}
def test_transform_squares_evens_only():
assert transform([1, 2, 3, 4]) == [1, 4, 3, 16]
def test_parse_csv_line_basic():
assert parse_csv_line("x,y") == ["x", "y"]
def test_cli_smoke(tmp_path: Path):
from typer.testing import CliRunner
runner = CliRunner()
p = tmp_path / "in.txt"
p.write_text("1, 2, 3\n")
result = runner.invoke(app, ["run", str(p)])
assert result.exit_code == 0, result.stderr
assert result.stdout.strip() == "[1, 4, 3]"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment