Vucense

Python 3.12 Getting Started Guide 2026: venv, pip & uv

🟢Beginner

Python 3.12 setup for sovereign developers on Ubuntu 24.04: install, virtual environments with venv, package management with pip and uv, project structure, and essential stdlib modules.

Python 3.12 Getting Started Guide 2026: venv, pip & uv
Article Roadmap

Key Takeaways

  • Virtual env before everything: python3 -m venv .venvsource .venv/bin/activate → install packages. Never install globally.
  • uv replaces pip + venv + pyenv: One tool for environments, packages, Python version management. pip install uv once, then uv for everything.
  • externally-managed-environment error: Ubuntu 24.04 blocks system-wide pip installs. Use venv or add --break-system-packages only for CLI tools.
  • pyproject.toml is the standard: Replace requirements.txt with [project] dependencies = [...] in pyproject.toml.

Introduction

Direct Answer: How do I set up Python 3.12 with virtual environments and package management on Ubuntu 24.04 in 2026?

Python 3.12 is pre-installed on Ubuntu 24.04. Create a project directory and virtual environment: mkdir myproject && cd myproject && python3 -m venv .venv && source .venv/bin/activate. Your shell prompt changes to (.venv) confirming activation. Install packages: pip install requests fastapi uvicorn. Save dependencies: pip freeze > requirements.txt. Deactivate: deactivate. For the modern approach, use uv: pip install uv --break-system-packages && uv init myproject && cd myproject && uv add fastapi uvicorn && uv run python main.py. The uv tool creates the venv, manages Python versions, and installs packages 10–100× faster than pip with lockfile support.


Part 1: Python Installation

# Python 3.12 is pre-installed on Ubuntu 24.04
python3 --version
python3 -m pip --version
which python3

Expected output:

Python 3.12.3
pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)
/usr/bin/python3
# Install additional Python tools
sudo apt-get install -y python3-venv python3-dev python3-pip

# Install uv (the modern pip replacement — written in Rust, 10-100x faster)
pip install uv --break-system-packages
uv --version

Expected output: uv 0.5.4


Part 2: Virtual Environments

# Create a project with venv
mkdir ~/myproject && cd ~/myproject
python3 -m venv .venv

# Activate (Linux/macOS)
source .venv/bin/activate
# Your prompt changes to: (.venv) ubuntu@server:~/myproject$

# Install packages (into the venv, NOT system-wide)
pip install fastapi uvicorn pydantic httpx

# Check what's installed
pip list

# Save exact versions
pip freeze > requirements.txt
cat requirements.txt | head -5

# Deactivate when done
deactivate

Expected output:

Package    Version
---------- -------
fastapi    0.115.6
uvicorn    0.32.1
pydantic   2.10.0
httpx      0.28.0

Part 3: Modern Project Setup with uv

# uv init creates a complete project structure
uv init myapp
cd myapp
ls -la

Expected output:

.python-version     ← Python version pin (3.12)
pyproject.toml      ← Project config + dependencies
README.md
src/
  myapp/
    __init__.py
    main.py
# Add dependencies (creates virtual env automatically)
uv add fastapi "uvicorn[standard]" pydantic sqlalchemy

# Run the app
uv run python src/myapp/main.py

# Add a dev dependency (not included in production installs)
uv add --dev pytest pytest-asyncio httpx

# Sync environment from lockfile (reproducible installs)
uv sync              # Installs exact versions from uv.lock

# Update all packages
uv lock --upgrade

Part 4: pyproject.toml

# pyproject.toml — the standard project file in 2026
[project]
name = "myapp"
version = "0.1.0"
description = "A sovereign web application"
requires-python = ">=3.12"

dependencies = [
    "fastapi>=0.115",
    "uvicorn[standard]>=0.32",
    "pydantic>=2.10",
]

[dependency-groups]
dev = [
    "pytest>=8.0",
    "httpx>=0.28",   # For FastAPI test client
    "ruff>=0.8",     # Linter + formatter
]

[tool.ruff]
line-length = 100
select = ["E", "F", "I"]   # Errors, pyflakes, isort

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

Part 5: Essential Python Patterns

# Type hints (standard in 2026 Python)
def greet(name: str, count: int = 1) -> list[str]:
    return [f"Hello, {name}!" for _ in range(count)]

# Dataclasses (built-in structured data)
from dataclasses import dataclass, field

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8000
    debug: bool = False
    tags: list[str] = field(default_factory=list)

cfg = Config(host="0.0.0.0", debug=True)
print(cfg)   # Config(host='0.0.0.0', port=8000, debug=True, tags=[])

# pathlib for file operations (never use os.path in 2026)
from pathlib import Path
config_path = Path.home() / ".config" / "myapp" / "config.json"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('{"version": "1.0"}')
print(config_path.read_text())

# f-strings (clear formatting)
name, score = "Alice", 98.5
print(f"{name}: {score:.1f}% ({score:.0f}/100)")  # Alice: 98.5% (99/100)

Conclusion

Python 3.12 on Ubuntu 24.04 is ready: virtual environments prevent global pollution, uv replaces pip with 10–100× faster installs and lockfile reproducibility, and pyproject.toml is the unified project configuration format. These foundations underpin every Python guide in the Dev Corner — FastAPI, automation, AI integrations, and testing.


People Also Ask

Should I use venv or uv for Python virtual environments?

Both create isolated Python environments, but uv is the better choice for new projects in 2026. uv manages Python versions (no pyenv needed), creates environments automatically when you run uv add, produces a lockfile (uv.lock) for reproducible installs, and is 10–100× faster than pip for installing packages. Use venv only if uv isn’t available or if you’re working with legacy projects that predate uv. The resulting virtual environment is identical — uv just creates and manages it more conveniently.


Part 6: Project Structure and Package Layout

A clean Python project layout is the foundation of maintainable code. In 2026, the recommended structure looks like this:

myapp/
  .venv/
  src/
    myapp/
      __init__.py
      main.py
      config.py
  tests/
    test_main.py
  pyproject.toml
  README.md
  LICENSE
  .gitignore
  uv.lock

Put application code under src/ to avoid accidental imports from the repository root. Keep tests in a parallel tests/ folder and use pytest naming conventions such as test_*.py.

A minimal src/myapp/main.py might look like:

from pathlib import Path
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root() -> dict[str, str]:
    return {"message": "Hello, sovereign Python 2026"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

This separation keeps the codebase tidy and makes packaging easier when you publish wheels or deploy containers.


Part 7: Dependency Management and Reproducible Installs

uv is now the standard dependency manager for Python projects in 2026. It simplifies the workflow by combining virtual environment creation, package installation, and lockfile management.

Initialize a new project with uv init:

uv init myapp
cd myapp
uv add fastapi "uvicorn[standard]" pydantic
uv add --dev pytest ruff
uv sync

uv creates uv.lock, which pins exact package versions for reproducible installs. Store pyproject.toml and uv.lock in Git, but do not store the virtual environment itself.

If you still use pip, use pip install --requirement requirements.txt and pip freeze > requirements.txt only inside a venv or uv environment. Never run sudo pip install on Ubuntu 24.04 unless you understand --break-system-packages and the risks.

When you need a specific Python version, uv can manage that too:

uv env use 3.12
uv env list

This ensures every developer and CI runner uses the same interpreter version.


Part 8: Linters, Formatters, and Static Analysis

Clean code is secure code. In 2026, use a standard toolchain of ruff, black, mypy or pyright, and isort.

Add these tools to your pyproject.toml:

[tool.ruff]
line-length = 100
select = ["E", "F", "I", "B", "C"]
exclude = ["tests"]

[tool.isort]
profile = "black"

[tool.pyright]
pythonVersion = "3.12"

Use uv run to execute them inside the project environment:

uv run ruff check src tests
uv run ruff format src tests
uv run python -m pytest

Static analysis catches common bugs before they reach production. In particular, mypy and ruff can catch type mismatches, unused imports, and formatting issues that frequently lead to runtime problems.


Part 9: Testing and Continuous Verification

A sovereign Python project needs a test strategy that is fast, reliable, and automated.

Create tests with pytest and httpx for web services:

from httpx import AsyncClient
from myapp.main import app

async def test_read_root() -> None:
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/")
        assert response.status_code == 200
        assert response.json() == {"message": "Hello, sovereign Python 2026"}

Run tests with coverage in CI:

uv run pytest --cov=src/myapp --cov-report=term-missing

Use pre-commit if you want local guards before commits, but uv makes the core dependency story easier.

Add test utilities in tests/conftest.py for shared fixtures and isolated environments, and keep the suite fast enough to run on every push.


Part 10: Packaging, Builds, and Distribution

When your Python app is ready for deployment, use pyproject.toml to build wheels and source distributions.

uv run python -m build

This creates dist/myapp-0.1.0-py3-none-any.whl and dist/myapp-0.1.0.tar.gz.

Prefer wheels for deployment because they install faster and are reproducible. For local deployment or container builds, copy the wheel into the image and install it with pip install dist/myapp-0.1.0-py3-none-any.whl.

If you publish to a private package index, use a local Artifactory, Nexus, or private PyPI-compatible registry rather than public PyPI. Keep the package index and authentication under your sovereign control.


Part 11: Security and Environment Isolation

A safe Python environment is one that is isolated, pinned, and audited.

  • Never install dependencies globally on Ubuntu 24.04 unless the package is a one-off CLI tool and you understand the system package policy.
  • Use uv or venv for every project.
  • Use pip-audit or python -m pip audit to scan dependencies for vulnerabilities.
  • Pin exact versions in uv.lock, and review lockfile changes in code review.

For CLI utilities or worker scripts, prefer separate environments per service. This avoids dependency conflict drift and keeps each service reproducible.


Part 12: Python 3.12 Features for Sovereign Developers

Python 3.12 brings improvements that matter for modern applications:

  • typing improvements like Self and TypeAlias for cleaner type definitions.
  • Faster startup and smaller memory usage in many workloads.
  • Better error messages and exception notes.
  • tomllib for reading TOML configuration directly from the standard library.

Example using tomllib:

from pathlib import Path
import tomllib

config_path = Path("pyproject.toml")
config = tomllib.loads(config_path.read_text())
print(config["project"]["dependencies"])

These language improvements make Python code easier to read and diagnose, especially in a sovereign environment where clarity is essential.


Part 13: Performance and Concurrency

Python 3.12 is a strong choice for I/O-bound services and automation. Use asynchronous frameworks like FastAPI and asyncio for local APIs, and prefer worker pools for CPU-bound tasks.

Example asynchronous file scanning:

import asyncio
from pathlib import Path

async def read_file(path: Path) -> str:
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, path.read_text)

async def main() -> None:
    files = list(Path(".").glob("**/*.py"))
    contents = await asyncio.gather(*(read_file(path) for path in files))
    print(f"Read {len(contents)} files")

asyncio.run(main())

For compute-heavy tasks, use process-based concurrency:

from concurrent.futures import ProcessPoolExecutor

with ProcessPoolExecutor() as executor:
    results = executor.map(compute_heavy_function, data)

This keeps CPU-bound work off the event loop and avoids the Global Interpreter Lock limitations.


Part 14: Local AI and Data Workflows

Python remains the dominant language for local AI and data tooling. In 2026, use Python to integrate with local-model runtimes such as Ollama, OpenAI-compatible APIs, or embedded inference libraries.

Example local AI call:

from httpx import AsyncClient

async def generate(prompt: str) -> str:
    async with AsyncClient(base_url="http://127.0.0.1:11434") as client:
        response = await client.post("/v1/completions", json={
            "model": "llama-3-13b",
            "prompt": prompt,
        })
        return response.json()["choices"][0]["text"]

For data processing, prefer built-in libraries such as csv, json, sqlite3, and pathlib before reaching for heavier frameworks. This keeps your applications light, portable, and easier to audit.


Part 15: Deployment Patterns for Python 2026

Deploy Python services in containers or virtual machines with a fixed environment. A simple container workflow looks like this:

FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install build
RUN pip install .
COPY src ./src
CMD ["uvicorn", "myapp.main:app", "--host", "0.0.0.0", "--port", "8000"]

Use uv or pip inside the container to install pinned dependencies. Store the built image in a private registry and sign it with Cosign if you are using a sovereign supply chain model.

A local VM deployment can use uv run in a systemd service:

[Unit]
Description=MyApp service
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/srv/myapp
ExecStart=/srv/myapp/.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000
Restart=on-failure

[Install]
WantedBy=multi-user.target

This pattern keeps the Python app isolated and manageable on Ubuntu 24.04.

Part 16: Command Line Scripts and Automation

Python excels at local automation. Build CLI tools with argparse, typer, or click, and keep them inside your src package.

Example typer command:

import typer
from pathlib import Path

app = typer.Typer()

@app.command()
def init(path: Path = Path('.')) -> None:
    """Create a project scaffold."""
    (path / 'src').mkdir(parents=True, exist_ok=True)
    typer.echo(f"Project initialized at {path}")

if __name__ == '__main__':
    app()

Package CLI entry points in pyproject.toml:

[project.scripts]
myapp = "myapp.main:app"

This makes your scripts importable and testable, which is superior to ad hoc shell scripts.


Part 17: Documentation and README Standards

A sovereign Python project should document the setup, environment, testing, and deployment workflows clearly.

Your README.md should include:

  • Python version and install instructions
  • virtual environment activation steps
  • package installation commands
  • how to run tests
  • how to start the application
  • where to find configuration files

Example section:

## Local Development

1. `uv init myapp`
2. `uv add fastapi uvicorn`
3. `uv run python -m uvicorn src.myapp.main:app --reload`

Also include a CONTRIBUTING.md for coding standards and a SECURITY.md for responsible disclosure processes. Documentation is part of the sovereign codebase and should be version-controlled.


Part 18: Caching and Offline Dependency Installation

In an air-gapped or local-first environment, cached dependencies are invaluable.

Use uv sync with a local cache directory, and mirror packages when possible.

uv config set cache-dir /var/cache/uv
uv add fastapi
uv sync

For full offline installs, pre-populate the cache on a connected machine, then transfer the cache bundle into the air-gapped environment. This lets you install packages without direct network access while preserving exact versions.


Part 19: Packaging Wheels for Local Deployment

Wheels are the preferred binary artifact for Python deployment. Build them locally and store them in a private registry or artifact repository.

uv run python -m build

Then install from the wheel:

pip install dist/myapp-0.1.0-py3-none-any.whl

Avoid deploying via pip install . in production unless you have a controlled build pipeline. Wheels provide a reproducible, audited package artifact that can be signed and verified.


Part 20: Local AI Prototyping with Python

Python is the lingua franca of local AI development. Use minimal dependencies and local runtimes to keep models sovereign.

Example using ONNX Runtime or a local LLM inference API:

from pathlib import Path

MODEL_PATH = Path('/opt/models/llama-3-13b.onnx')
if MODEL_PATH.exists():
    print('Model ready for inference')

When integrating with local AI, prefer stable APIs and do not hard-code remote endpoints. That ensures your codebase remains portable even if the model runtime moves between hosts.


Part 21: Data Processing and File IO

For local data workflows, use standard libraries instead of heavy dependencies when possible.

from pathlib import Path
import csv

csv_path = Path('data.csv')
with csv_path.open(newline='') as file:
    reader = csv.DictReader(file)
    for row in reader:
        print(row['id'], row['name'])

Use sqlite3 for small local datasets and json or tomllib for configuration. These built-in tools are easier to audit and maintain than complex external stacks.


Part 22: Secure Python Dependencies

Python dependencies carry risk. Use pip-audit or uv audit if available to scan packages before installation.

uv run python -m pip audit

Review dependency graphs and avoid unneeded transitive packages. A smaller dependency footprint reduces your attack surface.

When a dependency is vulnerable, patch it immediately in your lockfile and rebuild the environment. Do not ignore security warnings in pip-audit or similar scans.


Part 23: Production Readiness and Process Supervision

For services deployed on Ubuntu 24.04, use systemd to supervise Python applications. A typical unit file looks like this:

[Unit]
Description=MyApp service
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/srv/myapp
ExecStart=/srv/myapp/.venv/bin/uvicorn myapp.main:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

This keeps your Python service managed by the OS and restarts it if it crashes. Combine it with log rotation and health checks for a resilient deployment.


Part 24: Local Testing with Docker

If you use containers for local testing, build a small image and mount source code for rapid iteration.

FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install build
RUN pip install .
COPY src ./src
CMD ["uvicorn", "myapp.main:app", "--host", "0.0.0.0", "--port", "8000"]

Use docker build -t myapp:local . and docker run -p 8000:8000 myapp:local. Keep the image minimal and use the same dependency pinning as your non-containerized workflows.


Part 25: Long-Term Maintenance and Upgrade Strategy

A sovereign Python codebase should have an upgrade plan. Track Python release notes and schedule upgrades at least once per year.

Keep tests green, update pyproject.toml with supported Python versions, and use uv to create new environments for the upgraded interpreter. Do not let the codebase drift onto unsupported Python versions.

Part 26: CI/CD for Python Projects

Your Python setup should integrate with CI. Use a simple pipeline that validates the environment on every push.

A typical GitHub Actions workflow:

name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: snok/install-uv@v1
      - run: uv sync
      - run: uv run ruff check src tests
      - run: uv run python -m pytest

The CI job should use the same uv.lock file as local development, ensuring parity between developer machines and automated builds.


Part 27: VS Code and Devcontainer Configuration

If your team uses VS Code, provide a devcontainer.json for a reproducible development environment.

Example devcontainer.json:

{
  "name": "Python 3.12",
  "image": "mcr.microsoft.com/devcontainers/python:3.12",
  "postCreateCommand": "uv sync",
  "customizations": {
    "vscode": {
      "extensions": ["ms-python.python", "charliermarsh.ruff"]
    }
  }
}

This gives every developer the same tools, Python version, and dependency setup. It also makes onboarding faster and more sovereign because the configuration is owned by your repo.


Part 28: Profiling and Performance Investigation

Python performance matters for production services. Use built-in profiling tools such as cProfile, yappi, or scalene.

Example cProfile usage:

uv run python -m cProfile -o profile.out src/myapp/main.py
uv run python -m pstats profile.out

Monitor CPU-bound functions, I/O wait, and event loop latency. A sovereign codebase should include perf guidance in the developer documentation so operators know where to look when a service slows down.


Part 29: Python SBOM and Dependency Evidence

Generate an SBOM for your Python project to support supply chain visibility.

Use syft or pip-licenses to capture dependency metadata.

syft dir:. -o spdx-json > python-sbom.json

Store the SBOM with your release artifacts. This makes it easier to verify exactly what packages were installed when a particular version was deployed.


Part 30: Data Security in Python Applications

Protect sensitive data in Python apps by default. Use environment-based configuration and avoid logging secrets.

A secure config loader might look like this:

from pydantic import BaseSettings

class Settings(BaseSettings):
    database_url: str
    secret_key: str

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

settings = Settings()

Do not commit .env files to Git. Use role-based access on the host file system and, when possible, load secrets from a local vault or encrypted store.


Part 31: Building Maintainable Python Libraries

If your project is a library rather than an application, follow library best practices:

  • use src/ layout
  • provide a README with examples
  • include a changelog
  • write public API docs
  • version using semantic versioning

A library package should also include a pyproject.toml with project.urls for documentation and source repository.


Part 32: Local Python Security Practices

Python security is an ongoing process. In addition to dependency audits, add these practices:

  • run uv run ruff check before commits
  • avoid eval() and insecure deserialization
  • escape user input before using it in shell commands
  • validate all HTTP request payloads in web services
  • use httpx with TLS verification enabled

These practices reduce the risk of application-layer vulnerabilities in local Python services.

Part 33: Local Dev Environment Sanity Checks

A good Python development environment includes sanity checks on every startup. Add a small script that verifies the active Python version, checks for a virtual environment, and confirms the lockfile is in sync.

For example:

import sys
from pathlib import Path

if sys.version_info[:2] != (3, 12):
    raise SystemExit("Python 3.12 is required")

if Path('.venv').exists():
    print('Virtual environment detected')
else:
    raise SystemExit('Activate .venv before running this project')

This lightweight guard keeps developers aligned and prevents accidental sudo pip install mistakes.

Part 34: Local Developer Onboarding Checklist

Every Python project benefits from an onboarding checklist. Include steps for:

  • cloning the repository
  • installing uv
  • creating and activating the environment
  • syncing dependencies
  • running linting and tests
  • starting the application locally

A checklist reduces friction for new contributors and ensures everyone follows the same sovereign setup path.

Part 35: Release Readiness and Post-Release Review

A sovereign Python project should include a release checklist that validates the application before it goes live. Key steps include:

  • running unit and integration tests
  • verifying dependencies with uv lock
  • checking formatting and linting
  • building a wheel and confirming it installs cleanly
  • generating documentation or README artifacts

After release, review deployment telemetry and test logs to confirm behavior. A post-release review turns each deploy into an opportunity for continuous improvement.

Further Reading

Tested on: Ubuntu 24.04 LTS. Python 3.12.3, uv 0.5.4. Last verified: April 29, 2026.

Anju Kushwaha

About the Author

Founder & Editorial Director

B-Tech Electronics & Communication Engineering | Founder of Vucense | Technical Operations & Editorial Strategy

Anju Kushwaha is the founder and editorial director of Vucense, driving the publication's mission to provide independent, expert analysis of sovereign technology and AI. With a background in electronics engineering and years of experience in tech strategy and operations, Anju curates Vucense's editorial calendar, collaborates with subject-matter experts to validate technical accuracy, and oversees quality standards across all content. Her role combines editorial leadership (ensuring author expertise matches topics, fact-checking and source verification, coordinating with specialist contributors) with strategic direction (choosing which emerging tech trends deserve in-depth coverage). Anju works directly with experts like Noah Choi (infrastructure), Elena Volkov (cryptography), and Siddharth Rao (AI policy) to ensure each article meets E-E-A-T standards and serves Vucense's readers with authoritative guidance. At Vucense, Anju also writes curated analysis pieces, trend summaries, and editorial perspectives on the state of sovereign tech infrastructure.

View Profile

Further Reading

All Dev Corner

Comments