Core Commands

Updated

September 8, 2026

Core Commands

The toolchain in this book is small on purpose: uv run, uv python, python -c, python -m, ruff, pytest. Learn these six and you can run, format, lint, and test every later chapter.

Mental model

uv is the front door. uv run … means “in this project, with this Python, run that command.” You rarely type a bare python.

  • uv run python file.py — run a script.
  • uv run python -c "…" — run one statement, no file.
  • uv run python -m name — run a module by name (json.tool, or open_shift for open_shift.py).
  • uv python install|pin|list — manage interpreters.
  • uv run ruff format|check — format and lint.
  • uv run pytest — run tests.

ruff and pytest are ordinary packages. Add them once as dev dependencies, then uv run finds them.

Worked examples

Case 1: uv run and uv python

Save as open_shift.py.

# open_shift.py
def main():
    print("shift open")


if __name__ == "__main__":
    main()

Run:

uv run python open_shift.py

Output:

shift open

Useful uv python commands (output varies by machine):

uv python install 3.14
uv python pin 3.14
uv python list
uv run python --version

The last one should report Python 3.14.x. That is the check that matters.

Case 2: python -c (no file)

One-off prints and arithmetic do not need a file.

uv run python -c 'print("shift open")'

Output:

shift open

Keep -c for probes. Put anything you will run twice in a .py file.

Case 3: python -m runs a module by name

The same open_shift.py can be run as a module. The name is the filename without .py.

uv run python -m open_shift

Output:

shift open

-m is also how you run tools that shipped as modules. Save as ticket_dump.py:

# ticket_dump.py
import json


def main():
    print(json.dumps({"id": 7, "table": 12}))


if __name__ == "__main__":
    main()

Pretty-print its JSON with the standard library’s json.tool:

uv run python ticket_dump.py | uv run python -m json.tool

Output:

{
    "id": 7,
    "table": 12
}

Case 4: ruff format and ruff check

Add the linter once (this also creates or updates pyproject.toml / uv.lock):

uv add --dev ruff

Save as messy.py — working code, ugly spacing:

# messy.py
def main( ):
    tables=8
    print( f"tables {tables}" )


if __name__=="__main__":
    main()

Format it in place:

uv run ruff format messy.py

Output:

1 file reformatted

messy.py on disk is now:

# messy.py
def main():
    tables = 8
    print(f"tables {tables}")


if __name__ == "__main__":
    main()

Save as unused.py for a lint finding:

# unused.py
import json


def main():
    tables = 8
    print(tables)


if __name__ == "__main__":
    main()
uv run ruff check unused.py

Output:

F401 [*] `json` imported but unused
 --> unused.py:2:8
  |
1 | # unused.py
2 | import json
  |        ^^^^
help: Remove unused import: `json`

Found 1 error.
[*] 1 fixable with the `--fix` option.

F401 is “imported but unused.” Delete the import, or run uv run ruff check --fix unused.py. The next chapter treats format and lint as policy.

Case 5: pytest on a tiny test

Save as total.py:

# total.py
def line_total(qty, price):
    return qty * price


def main():
    print(line_total(3, 4))


if __name__ == "__main__":
    main()

Save as test_total.py in the same folder:

# test_total.py
from total import line_total


def test_line_total():
    assert line_total(3, 4) == 12
uv add --dev pytest
uv run pytest test_total.py -q

Output (the time on the last line will vary):

.                                                                        [100%]
1 passed in 0.01s

A test file is a complete program pytest loads. Functions named test_* are collected. assert is the check. You do not add if __name__ == "__main__" to the test file.

The trap

Installing ruff and pytest “globally” with a random pip, then wondering why uv run cannot see them. uv run uses this project’s environment. Add tools with uv add --dev.

The other trap is living in python -c and never saving a file. -c has no traceback you can open, no test, and no format.

The boring rule

  • Run scripts with uv run python file.py.
  • Probe with uv run python -c '…' only.
  • Use uv run python -m name for modules and stdlib tools.
  • Pin 3.14 with uv python pin 3.14.
  • Format and lint with uv run ruff format and uv run ruff check.
  • Test with uv run pytest.
  • Add ruff and pytest as dev dependencies, not as runtime dependencies of the desk.

Try this

  1. Change open_shift.py to print the table count. Run it both as uv run python open_shift.py and as uv run python -m open_shift.
  2. In test_total.py, add test_zero_qty that asserts line_total(0, 4) == 0. Run pytest again.
  3. Run uv run ruff check unused.py --fix, then uv run ruff check unused.py and confirm it is clean.