Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:49:58 +08:00
commit 5007abf04b
89 changed files with 44129 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
# Multi-stage Docker build with uv
# Optimized for production deployments with minimal image size
# Stage 1: Builder
FROM python:3.12-slim AS builder
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Set working directory
WORKDIR /app
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Create virtual environment and install dependencies
# Using --frozen ensures we use exact versions from lockfile
# Using --no-dev excludes development dependencies
RUN uv sync --frozen --no-dev
# Stage 2: Runtime
FROM python:3.12-slim
# Set working directory
WORKDIR /app
# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv
# Copy application code
COPY . .
# Set environment variables
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Create non-root user for security
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import sys; sys.exit(0)"
# Expose port
EXPOSE 8000
# Run application
CMD ["python", "-m", "myapp"]

View File

@@ -0,0 +1,29 @@
# Simple Docker build with uv
# Single-stage build for development or simpler deployments
FROM python:3.12-slim
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Set working directory
WORKDIR /app
# Copy all files
COPY . .
# Create virtual environment and install dependencies
RUN uv venv /opt/venv && \
. /opt/venv/bin/activate && \
uv sync --frozen --no-dev
# Set environment to use virtual environment
ENV VIRTUAL_ENV=/opt/venv \
PATH="/opt/venv/bin:$PATH" \
PYTHONUNBUFFERED=1
# Expose port
EXPOSE 8000
# Run application
CMD ["python", "-m", "myapp"]

View File

@@ -0,0 +1,120 @@
# Comprehensive CI workflow with uv
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
UV_SYSTEM_PYTHON: 1 # Optional: use system Python
jobs:
# Linting and formatting
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "0.9.5"
enable-cache: true
- name: Set up Python
run: uv python install 3.11
- name: Install dependencies
run: uv sync --frozen --all-extras
- name: Run ruff (linter)
run: uv run ruff check .
- name: Run ruff (formatter)
run: uv run ruff format --check .
- name: Run mypy (type checker)
run: uv run mypy src/
# Test matrix across multiple Python versions
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --frozen --all-extras
- name: Run tests
run: uv run pytest --cov --cov-report=xml
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
# Build and verify package
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
run: uv python install 3.11
- name: Build package
run: uv build
- name: Check package metadata
run: uv run twine check dist/*
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
# Optional: Publish to PyPI (on release tags)
publish:
if: startsWith(github.ref, 'refs/tags/v')
needs: [build]
runs-on: ubuntu-latest
permissions:
id-token: write # Required for trusted publishing
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/

View File

@@ -0,0 +1,286 @@
# Advanced Python Project Configuration
# Full-featured configuration with custom indexes, workspace, and advanced settings
[project]
name = "advanced-project"
version = "1.0.0"
description = "Advanced Python project with custom configuration"
readme = "README.md"
requires-python = ">=3.11"
authors = [{name = "Your Name", email = "your.email@example.com"}]
license = {text = "MIT"}
dependencies = [
"fastapi>=0.115.0",
"torch", # Will be sourced from custom index
"pydantic>=2.5.0",
]
# Development dependencies using PEP 735 dependency groups
[dependency-groups]
dev = [
"pytest>=8.4.2",
"pytest-cov>=6.0.0",
"pytest-mock>=3.15.1",
"ruff>=0.13.3",
"pyright>=1.1.406",
"mypy>=1.18.2",
"pre-commit>=4.3.0",
]
test = [
"pytest-cov>=6.0.0",
"pytest-asyncio>=0.21.0",
"hypothesis>=6.0.0",
]
docs = [
"sphinx>=7.0",
"sphinx-rtd-theme>=1.3.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
# Core settings
managed = true
package = true
default-groups = ["dev"]
# Resolution strategy
resolution = "highest" # highest/lowest/lowest-direct
fork-strategy = "requires-python"
index-strategy = "first-index"
# Environment restrictions (only resolve for these platforms)
environments = [
"sys_platform == 'darwin'",
"sys_platform == 'linux'",
]
# Require support for specific platforms
required-environments = [
"sys_platform == 'linux' and platform_machine == 'x86_64'",
]
# Dependency version management
constraint-dependencies = [
"grpcio<1.65", # Constraint: don't allow grpcio >= 1.65
]
override-dependencies = [
"werkzeug==2.3.0", # Override: always use this exact version
]
build-constraint-dependencies = [
"setuptools==60.0.0", # Constraint for build dependencies
]
# Build configuration
compile-bytecode = true
no-build-isolation-package = ["flash-attn", "deepspeed"]
# Cache configuration
cache-dir = "./.uv_cache"
cache-keys = [
{ file = "pyproject.toml" },
{ file = "requirements.txt" },
{ git = { commit = true } },
]
# Network configuration
concurrent-downloads = 20
concurrent-builds = 8
# Python management
python-preference = "managed"
python-downloads = "automatic"
# Security
keyring-provider = "subprocess"
allow-insecure-host = []
# Preview features
preview = false
# Extra build dependencies (for packages without proper metadata)
[tool.uv.extra-build-dependencies]
flash-attn = ["torch", "setuptools", "ninja"]
deepspeed = [{ requirement = "torch", match-runtime = true }]
# Build environment variables
[tool.uv.extra-build-variables]
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
opencv-python = { CMAKE_ARGS = "-DWITH_CUDA=ON" }
# Custom package sources
[tool.uv.sources]
# PyTorch from custom index
torch = { index = "pytorch-cu121" }
# Internal workspace dependency
# internal-lib = { workspace = true }
# Git source
# httpx = { git = "https://github.com/encode/httpx", tag = "0.27.0" }
# Local path (development)
# local-pkg = { path = "../local-pkg", editable = true }
# Conditional sources (platform-specific)
# torch = [
# { index = "pytorch-cu118", marker = "sys_platform == 'darwin'" },
# { index = "pytorch-cu121", marker = "sys_platform == 'linux'" },
# ]
# Custom package indexes
[[tool.uv.index]]
name = "pytorch-cu121"
url = "https://download.pytorch.org/whl/cu121"
explicit = true # Only use for explicitly pinned packages
default = false # Don't replace PyPI as default
[[tool.uv.index]]
name = "private-registry"
url = "https://packages.example.com/simple"
explicit = true
authenticate = "always" # Always send authentication
# Workspace configuration (for monorepos)
[tool.uv.workspace]
members = ["packages/*", "apps/*"]
exclude = ["packages/deprecated"]
# Conflict resolution (mutually exclusive extras)
[[tool.uv.conflicts]]
extra = ["cuda", "rocm"]
[[tool.uv.conflicts]]
group = ["prod", "dev"]
# pip-specific settings (only for uv pip commands)
[tool.uv.pip]
compile-bytecode = true
strict = true
generate-hashes = false
annotation-style = "line"
extra = ["dev"]
universal = false
no-strip-markers = false
# Ruff configuration
[tool.ruff]
target-version = "py311"
line-length = 120
fix = true
preview = true
[tool.ruff.format]
docstring-code-format = true
quote-style = "double"
line-ending = "lf"
skip-magic-trailing-comma = true
preview = true
[tool.ruff.lint]
extend-select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"YTT", # flake8-2020
"S", # flake8-bandit
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"T10", # flake8-debugger
"SIM", # flake8-simplify
"C90", # mccabe
"PGH", # pygrep-hooks
"RUF", # ruff-specific
"TRY", # tryceratops
"DOC", # pydocstyle
"D", # pydocstyle
]
ignore = [
"COM812", # Missing trailing comma
"COM819", # Missing trailing comma
"D107", # Missing docstring in __init__
"D415", # First line should end with a period
"E111", # Indentation is not a multiple of four
"E117", # Over-indented for visual indent
"E203", # whitespace before ':'
"E402", # Module level import not at top
"E501", # Line length exceeds maximum limit
"ISC001", # isort configuration is missing
"ISC002", # isort configuration is missing
"Q000", # Remove bad quotes
"Q001", # Remove bad quotes
"Q002", # Remove bad quotes
"Q003", # Remove bad quotes
"TRY003", # Exception message should not be too long
"S404", # module is possibly insecure
"S603", # subprocess-without-shell-equals-true
"S606", # start-process-with-no-shell
"DOC501", # Missing raises section
]
unfixable = ["F401", "S404", "S603", "S606", "DOC501"]
[tool.ruff.lint.pycodestyle]
max-line-length = 120
[tool.ruff.lint.isort]
combine-as-imports = true
split-on-trailing-comma = false
force-single-line = false
force-wrap-aliases = false
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.ruff.lint.per-file-ignores]
"**/tests/*" = ["S101", "S603", "S607", "D102", "D200", "D100"]
"**/test_*.py" = ["S101", "S603", "S607", "D102", "D200", "D100"]
# Mypy configuration
[tool.mypy]
python_version = "3.11"
strict = true
extra_checks = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
ignore_missing_imports = true
show_error_codes = true
pretty = true
disable_error_code = "call-arg,arg-type"
# Pytest configuration
[tool.pytest.ini_options]
addopts = [
"--cov=advanced_project",
"--cov-report=term-missing",
"-v",
]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"slow: tests that take significant time to run",
"integration: integration tests",
]
[tool.coverage.run]
omit = ["*/tests/*"]
[tool.coverage.report]
show_missing = true
fail_under = 70

View File

@@ -0,0 +1,184 @@
# Basic Python Project Configuration
# Use this template for simple Python projects
[project]
name = "my-project"
version = "0.1.0"
description = "A simple Python project"
readme = "README.md"
requires-python = ">=3.11"
authors = [
{name = "Your Name", email = "your.email@example.com"}
]
license = {text = "MIT"}
keywords = ["example", "project"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
# Core dependencies
dependencies = [
"requests>=2.31.0",
]
# Development dependencies using PEP 735 dependency groups
[dependency-groups]
dev = [
"pytest>=8.4.2",
"pytest-cov>=6.0.0",
"pytest-mock>=3.15.1",
"ruff>=0.13.3",
"pyright>=1.1.406",
"mypy>=1.18.2",
"pre-commit>=4.3.0",
]
# Entry points for CLI tools
[project.scripts]
my-tool = "my_project.cli:main"
# Project URLs
[project.urls]
Homepage = "https://github.com/yourusername/my-project"
Documentation = "https://my-project.readthedocs.io"
Repository = "https://github.com/yourusername/my-project.git"
Issues = "https://github.com/yourusername/my-project/issues"
# Build system
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# uv configuration
[tool.uv]
managed = true
package = true
default-groups = ["dev"]
# Development server sources (optional)
[tool.uv.sources]
# Example: Install from git during development
# requests = { git = "https://github.com/psf/requests", branch = "main" }
# Ruff configuration
[tool.ruff]
target-version = "py311"
line-length = 120
fix = true
preview = true
[tool.ruff.format]
docstring-code-format = true
quote-style = "double"
line-ending = "lf"
skip-magic-trailing-comma = true
preview = true
[tool.ruff.lint]
extend-select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"YTT", # flake8-2020
"S", # flake8-bandit
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"T10", # flake8-debugger
"SIM", # flake8-simplify
"C90", # mccabe
"PGH", # pygrep-hooks
"RUF", # ruff-specific
"TRY", # tryceratops
"DOC", # pydocstyle
"D", # pydocstyle
]
ignore = [
"COM812", # Missing trailing comma
"COM819", # Missing trailing comma
"D107", # Missing docstring in __init__
"D415", # First line should end with a period
"E111", # Indentation is not a multiple of four
"E117", # Over-indented for visual indent
"E203", # whitespace before ':'
"E402", # Module level import not at top
"E501", # Line length exceeds maximum limit
"ISC001", # isort configuration is missing
"ISC002", # isort configuration is missing
"Q000", # Remove bad quotes
"Q001", # Remove bad quotes
"Q002", # Remove bad quotes
"Q003", # Remove bad quotes
"TRY003", # Exception message should not be too long
"S404", # module is possibly insecure
"S603", # subprocess-without-shell-equals-true
"S606", # start-process-with-no-shell
"DOC501", # Missing raises section
]
unfixable = ["F401", "S404", "S603", "S606", "DOC501"]
[tool.ruff.lint.pycodestyle]
max-line-length = 120
[tool.ruff.lint.isort]
combine-as-imports = true
split-on-trailing-comma = false
force-single-line = false
force-wrap-aliases = false
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.ruff.lint.per-file-ignores]
"**/tests/*" = ["S101", "S603", "S607", "D102", "D200", "D100"]
"**/test_*.py" = ["S101", "S603", "S607", "D102", "D200", "D100"]
# Mypy configuration
[tool.mypy]
python_version = "3.11"
strict = true
extra_checks = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
ignore_missing_imports = true
show_error_codes = true
pretty = true
disable_error_code = "call-arg,arg-type"
# Pytest configuration
[tool.pytest.ini_options]
addopts = [
"--cov=my_project",
"--cov-report=term-missing",
"-v",
]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"slow: tests that take significant time to run",
"integration: integration tests",
]
[tool.coverage.run]
omit = ["*/tests/*"]
[tool.coverage.report]
show_missing = true
fail_under = 70

View File

@@ -0,0 +1,199 @@
# GitLab Package Registry Configuration
# Template matching user's GitLab-based workflow
[project]
name = "my-project"
version = "0.1.0"
description = "Project using GitLab package registry"
readme = "README.md"
requires-python = ">=3.11"
authors = [{ name = "Your Name", email = "your.email@example.com" }]
license = { text = "MIT" }
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"typer>=0.19.2",
"pydantic>=2.12.2",
"python-dotenv>=1.1.1",
]
[project.scripts]
my-tool = "my_project.cli:app"
[project.urls]
Homepage = "https://sourcery.assaabloy.net/group/project"
Documentation = "https://sourcery.assaabloy.net/group/project/-/blob/main/README.md"
Repository = "https://sourcery.assaabloy.net/group/project"
"Bug Tracker" = "https://sourcery.assaabloy.net/group/project/-/issues"
# Build system with hatchling and vcs versioning
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
# Use dependency-groups instead of optional-dependencies
[dependency-groups]
dev = [
"pytest>=8.4.2",
"pytest-cov>=6.0.0",
"pytest-mock>=3.15.1",
"ruff>=0.13.3",
"pyright>=1.1.406",
"mypy>=1.18.2",
"pre-commit>=4.3.0",
]
# Ruff configuration following user's pattern
[tool.ruff]
target-version = "py311"
line-length = 120
fix = true
preview = true
[tool.ruff.format]
docstring-code-format = true
quote-style = "double"
line-ending = "lf"
skip-magic-trailing-comma = true
preview = true
[tool.ruff.lint]
extend-select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"YTT", # flake8-2020
"S", # flake8-bandit
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"T10", # flake8-debugger
"SIM", # flake8-simplify
"C90", # mccabe
"PGH", # pygrep-hooks
"RUF", # ruff-specific
"TRY", # tryceratops
"DOC", # pydocstyle
"D", # pydocstyle
]
ignore = [
"COM812", # Missing trailing comma
"COM819", # Missing trailing comma
"D107", # Missing docstring in __init__
"D415", # First line should end with a period
"E111", # Indentation is not a multiple of four
"E117", # Over-indented for visual indent
"E203", # whitespace before ':'
"E402", # Module level import not at top
"E501", # Line length exceeds maximum limit
"ISC001", # isort configuration is missing
"ISC002", # isort configuration is missing
"Q000", # Remove bad quotes
"Q001", # Remove bad quotes
"Q002", # Remove bad quotes
"Q003", # Remove bad quotes
"TRY003", # Exception message should not be too long
"S404", # module is possibly insecure
"S603", # subprocess-without-shell-equals-true
"S606", # start-process-with-no-shell
"DOC501", # Missing raises section
]
unfixable = ["F401", "S404", "S603", "S606", "DOC501"]
[tool.ruff.lint.pycodestyle]
max-line-length = 120
[tool.ruff.lint.isort]
combine-as-imports = true
split-on-trailing-comma = false
force-single-line = false
force-wrap-aliases = false
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.ruff.lint.per-file-ignores]
"**/tests/*" = ["S101", "S603", "S607", "D102", "D200", "D100"]
"**/test_*.py" = ["S101", "S603", "S607", "D102", "D200", "D100"]
# Mypy configuration
[tool.mypy]
python_version = "3.11"
strict = true
extra_checks = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
ignore_missing_imports = true
show_error_codes = true
pretty = true
disable_error_code = "call-arg,arg-type"
# Pytest configuration
[tool.pytest.ini_options]
addopts = [
"--cov=my_project",
"--cov-report=term-missing",
"-v",
]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"slow: tests that take significant time to run",
"integration: integration tests",
]
[tool.coverage.run]
omit = ["*/tests/*"]
[tool.coverage.report]
show_missing = true
fail_under = 70
# Hatchling configuration with vcs versioning
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.targets.sdist]
include = [
"my_project",
"README.md",
"pyproject.toml",
]
[tool.hatch.build.targets.wheel]
include = ["my_project"]
# uv configuration for GitLab package registry
[tool.uv]
# Publish to GitLab package registry
publish-url = "https://sourcery.assaabloy.net/api/v4/projects/PROJECT_ID/packages/pypi"
# Use GitLab as default index
[[tool.uv.index]]
name = "gitlab"
url = "https://sourcery.assaabloy.net/api/v4/projects/PROJECT_ID/packages/pypi/simple"
default = true
authenticate = "always"
# Authentication via environment variables:
# export UV_INDEX_GITLAB_USERNAME="__token__"
# export UV_INDEX_GITLAB_PASSWORD="your-gitlab-token"

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env -S uv --quiet run --active --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "pandas>=2.0.0",
# "matplotlib>=3.7.0",
# "rich>=13.0.0",
# ]
# [tool.uv]
# exclude-newer = "2025-10-24T00:00:00Z" # Time-based reproducibility
# ///
"""Data Analysis Script Example
This script demonstrates PEP 723 inline script metadata with uv.
It analyzes CSV data and creates visualizations.
Usage:
# Make executable and run
chmod +x data_analysis.py
./data_analysis.py data.csv
# Or run directly with uv
uv run data_analysis.py data.csv
# With additional dependencies
uv run --with seaborn data_analysis.py data.csv
"""
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from rich.console import Console
from rich.table import Table
console = Console()
def analyze_data(csv_path: str) -> None:
"""Analyze CSV data and display summary statistics."""
# Load data
df = pd.read_csv(csv_path)
console.print(f"[bold green]Analyzing {csv_path}[/bold green]")
console.print(f"Shape: {df.shape[0]} rows x {df.shape[1]} columns\n")
# Display summary statistics in rich table
table = Table(title="Summary Statistics")
table.add_column("Column", style="cyan")
table.add_column("Mean", style="magenta")
table.add_column("Std", style="green")
table.add_column("Min", style="yellow")
table.add_column("Max", style="red")
for col in df.select_dtypes(include=["number"]).columns:
table.add_row(
col, f"{df[col].mean():.2f}", f"{df[col].std():.2f}", f"{df[col].min():.2f}", f"{df[col].max():.2f}"
)
console.print(table)
# Create visualization
_fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Plot 1: Distribution of first numeric column
numeric_cols = df.select_dtypes(include=["number"]).columns
if len(numeric_cols) > 0:
df[numeric_cols[0]].hist(ax=axes[0], bins=30)
axes[0].set_title(f"Distribution of {numeric_cols[0]}")
axes[0].set_xlabel(numeric_cols[0])
axes[0].set_ylabel("Frequency")
# Plot 2: Correlation heatmap
if len(numeric_cols) > 1:
corr = df.loc[:, numeric_cols].corr()
im = axes[1].imshow(corr, cmap="coolwarm", aspect="auto")
axes[1].set_xticks(range(len(numeric_cols)))
axes[1].set_yticks(range(len(numeric_cols)))
axes[1].set_xticklabels(numeric_cols, rotation=45)
axes[1].set_yticklabels(numeric_cols)
axes[1].set_title("Correlation Matrix")
plt.colorbar(im, ax=axes[1])
plt.tight_layout()
output_path = Path(csv_path).stem + "_analysis.png"
plt.savefig(output_path, dpi=150)
console.print(f"\n[bold green]✓[/bold green] Saved visualization to {output_path}")
def main() -> None:
"""Main entry point."""
if len(sys.argv) < 2:
console.print("[bold red]Error:[/bold red] Please provide CSV file path")
console.print("\nUsage: uv run data_analysis.py <csv_file>")
sys.exit(1)
csv_path = sys.argv[1]
if not Path(csv_path).exists():
console.print(f"[bold red]Error:[/bold red] File '{csv_path}' not found")
sys.exit(1)
try:
analyze_data(csv_path)
except Exception as e:
console.print(f"[bold red]Error:[/bold red] {e}")
sys.exit(1)
if __name__ == "__main__":
main()