Part 1: Getting Started with Python 3.12 - Setup, pyproject.toml, and Language Features

Introduction

I've been writing Python professionally for a while, but it wasn't until I built ansible-inspecarrow-up-right β€” an open-source compliance automation tool β€” that I really felt the upgrade from older Python habits to what Python 3.12 offers. The project ships a FastAPI server, a CLI, async job execution, Pydantic v2 models, and a Prisma ORM integration, all in a single pyproject.toml-based package. Everything in this series is grounded in that real codebase.

This first part covers getting Python 3.12 running the right way, understanding the project structure, and a tour of 3.12 language features I actually reach for.


Why Python 3.12?

When I started ansible-inspec, I had a choice of runtime target. I settled on 3.12 (while keeping β‰₯ 3.8 compatibility in the metadata for now) because of:

  • tomllib in the standard library β€” useful for reading config without extra deps

  • Better error messages β€” NameError, SyntaxError, and ImportError now tell you exactly what's wrong and why

  • typing improvements β€” override, TypeVar with defaults, @dataclass_transform β€” all reducing boilerplate in Pydantic models

  • Performance β€” CPython 3.12 is ~5% faster than 3.11 in most benchmarks; not dramatic, but free

  • f-string nesting β€” finally legal in 3.12, useful when building dynamic log lines


Installing Python 3.12

macOS

Linux (Ubuntu/Debian)

Windows

Download the official installer from python.org/downloadsarrow-up-right and tick "Add Python to PATH".


Project Setup

Virtual Environment

Always isolate dependencies from the system Python:

Modern pyproject.toml

Python packaging has moved from setup.py to pyproject.toml. This is the structure I use in ansible-inspec:

Key things to notice:

  • [project.optional-dependencies] β€” install with pip install -e ".[server,dev]" for everything, or pip install -e . for CLI-only

  • [project.scripts] β€” the ansible-inspec command maps directly to a Python function

  • [tool.ruff] / [tool.mypy] / [tool.pytest.ini_options] β€” all tooling config lives in one file

Install the project in editable mode:


Python 3.12 Language Features I Actually Use

Better Error Messages

The improved tracebacks in 3.12 point to the exact bracket or call that failed β€” a genuine daily-quality-of-life improvement.

match Statement (Structural Pattern Matching, from 3.10+)

I use this in the CLI dispatcher for ansible-inspec:

Much cleaner than a chain of if/elif.

Improved f-strings (3.12)

In Python 3.11 and earlier, you could not reuse the same quote type inside an f-string expression. 3.12 removes that limitation:

Type Alias (type keyword, 3.12)

TypeVar with Default (3.13-preview, but typing_extensions backport works on 3.12)

Not in 3.12 stdlib yet, but worth knowing. For now, the TypeVar + Generic pattern is standard:


Project Structure

The structure I landed on for ansible-inspec β€” and which I recommend for any medium-sized Python project:

The lib/ src-layout (as opposed to putting the package at root) means you can't accidentally import the un-installed package during tests β€” pip install -e . is required, which is the correct behaviour.

The Makefile is a simple task runner:


__init__.py β€” The Package Entry Point

This is what lib/ansible_inspec/__init__.py looks like:

Notice the type annotation on UPSTREAM_PROJECTS β€” using dict[str, dict[str, str]] directly (lowercase), which is valid from Python 3.9+. No from typing import Dict needed.


Summary

Topic
Takeaway

Installation

Use pyenv on macOS/Linux; official installer on Windows

Virtual env

Always use one; python3.12 -m venv .venv

Packaging

pyproject.toml is the standard β€” no more setup.py

match

Cleaner than if/elif chains for command dispatch

f-strings

3.12 removes quote nesting restrictions

type keyword

Cleaner type aliases in 3.12

Src-layout

lib/ directory prevents accidental bare imports


What's Next

Part 2 covers the data structures, type hints, and Pydantic v2 models I use throughout the ansible-inspec server β€” including how job templates and results are modelled.

Last updated