---
name: udaysharmadev-gravitas-python
description: "GRAVITAS extension for Python projects. Adds Python-specific recon, type checking with mypy/pyright, ruff/flake8 lint, pytest patterns, and common Python failure modes. Load in addition to the base GRAVITAS skill."
license: "MIT"
metadata:
  decimalai-skill-id: "4c788160-2e4e-4e9e-a636-301239dbeb8d"
  decimalai-version-id: "08e06c54-d549-456d-955c-df90b0ddc3fd"
  decimalai-version: "1"
  decimalai-body-sha256: "16862360e2078d2eb3836a0a5cf8b72827e2cce81024556c94ee8a9f5854d5b6"
  decimalai-name: "udaysharmadev/gravitas-python"
  decimalai-format: "1"
  decimalai-invocation: "model"
  decimalai-category: "coding"
  decimalai-source: "https://app.decimal.ai/s/udaysharmadev-gravitas-python@1/SKILL.md"
---
# GRAVITAS — Python Extension

Extends the base GRAVITAS skill with Python-specific patterns.
Load `skills/gravitas/SKILL.md` first, then this file.

---

## Python Recon Additions

```
□ pyproject.toml — [tool.mypy], [tool.ruff], [tool.pytest.ini_options]
□ setup.py / setup.cfg — legacy config
□ requirements.txt / requirements-dev.txt — dependency versions
□ conftest.py — pytest fixtures (critical for understanding test patterns)
□ __init__.py — package structure
□ Type stubs — are there py.typed markers? stub packages installed?
```

### Read pyproject.toml for type strictness
```bash
cat pyproject.toml | grep -A 10 "\[tool.mypy\]"
cat pyproject.toml | grep -A 10 "\[tool.ruff\]"
```

---

## Python Verification Chain

```bash
# Full chain — in this order
mypy . --strict                          # type correctness
ruff check .                             # lint (fast, Rust-based)
ruff format --check .                    # format check
pytest -v --tb=short                     # all tests

# With coverage
pytest --cov=src --cov-report=term-missing -v

# Type coverage
mypy . --strict 2>&1 | tail -5           # summary line shows error count
```

---

## Python-Specific Anti-Patterns

### Never use bare `except`
```python
BAD:  except:
GOOD: except ValueError as e:
GOOD: except (TypeError, ValueError) as e:
```

### Never shadow builtins
```python
BAD:  list = [1, 2, 3]     # shadows list()
BAD:  id = user.id         # shadows id()
GOOD: items = [1, 2, 3]
GOOD: user_id = user.id
```

### Type annotations required (with mypy strict)
```python
BAD:  def process(data):
GOOD: def process(data: dict[str, Any]) -> ProcessResult:
```

### Use dataclasses or Pydantic over dicts for structured data
```python
BAD:  return {"user_id": 1, "name": "Alice"}   # no type safety
GOOD: return UserResponse(user_id=1, name="Alice")
```

---

## Common Python Failure Modes

| Failure | Root Cause | Fix |
|---------|-----------|-----|
| `Optional[X] has no attribute Y` | None not handled | Add `if x is not None:` guard |
| `Incompatible return value type` | Wrong return type | Check function signature |
| `Missing return statement` | Not all paths return | Add explicit return |
| `Module has no attribute X` | Wrong import | Check module with `dir(module)` |
| `Cannot find implementation for X` | Missing stub | Install `types-X` package |
| Circular import | Import at module level | Move to function level or restructure |

---

## Test Patterns (pytest)

### Fixture pattern
```python
# conftest.py
import pytest
from myapp.db import get_db

@pytest.fixture
def db():
    conn = get_db(":memory:")
    yield conn
    conn.close()

@pytest.fixture
def client(db):
    from myapp import create_app
    app = create_app({"TESTING": True})
    with app.test_client() as c:
        yield c
```

### Standard test structure
```python
class TestUserAuth:
    def test_login_success(self, client, db):
        # Arrange
        db.execute("INSERT INTO users VALUES (1, 'alice', 'hash')")
        # Act
        response = client.post("/login", json={"username": "alice", "password": "pw"})
        # Assert
        assert response.status_code == 200
        assert response.json["token"] is not None

    def test_login_wrong_password(self, client, db):
        response = client.post("/login", json={"username": "alice", "password": "wrong"})
        assert response.status_code == 401
```

### Parametrize for edge cases
```python
@pytest.mark.parametrize("input,expected", [
    ("", ValueError),
    (None, TypeError),
    ("valid@email.com", None),
    ("not-an-email", ValueError),
])
def test_validate_email(input, expected):
    if expected:
        with pytest.raises(expected):
            validate_email(input)
    else:
        validate_email(input)  # should not raise
```

---

## Framework-Specific Notes

### FastAPI
```python
# Always test with TestClient, not raw function calls
from fastapi.testclient import TestClient
client = TestClient(app)

# Type all path/query params and bodies
@router.post("/users", response_model=UserResponse)
async def create_user(body: CreateUserRequest) -> UserResponse:
    ...
```

### Django
```python
# Use pytest-django
# settings for tests
@pytest.mark.django_db
def test_user_creation():
    user = User.objects.create_user(username="test", password="pass")
    assert user.id is not None
```

---

## Quick Reference

```bash
# Run single test file
pytest src/auth/test_session.py -v

# Run single test
pytest src/auth/test_session.py::TestSession::test_timeout -v

# Run with pdb on failure
pytest --pdb

# Show slowest tests
pytest --durations=10

# Type check single file
mypy src/auth/session.py --strict
```
