Metadata-Version: 2.4
Name: pyforge-framework
Version: 0.1.0
Summary: PyForge - an AI-first, async Python web framework. Flask simplicity, FastAPI speed, Django batteries.
Project-URL: Homepage, https://github.com/pyforge/pyforge
Project-URL: Documentation, https://github.com/pyforge/pyforge/tree/main/docs
Author-email: Aditya Bhat <contact@adityabhat.in>
License: MIT
License-File: LICENSE
Keywords: ai,asgi,async,framework,web,workflow
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: pyyaml>=6.0
Provides-Extra: ai
Requires-Dist: anthropic>=0.30; extra == 'ai'
Requires-Dist: openai>=1.0; extra == 'ai'
Provides-Extra: auth
Requires-Dist: bcrypt>=4.0; extra == 'auth'
Requires-Dist: pyjwt>=2.8; extra == 'auth'
Provides-Extra: dev
Requires-Dist: aiosqlite>=0.20; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: uvicorn>=0.30; extra == 'dev'
Provides-Extra: docs
Requires-Dist: pillow>=10.0; extra == 'docs'
Requires-Dist: pypdf>=4.0; extra == 'docs'
Provides-Extra: monitoring
Requires-Dist: psutil>=5.9; extra == 'monitoring'
Provides-Extra: orm
Requires-Dist: aiosqlite>=0.20; extra == 'orm'
Requires-Dist: alembic>=1.13; extra == 'orm'
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'orm'
Provides-Extra: server
Requires-Dist: uvicorn>=0.30; extra == 'server'
Provides-Extra: workflows
Description-Content-Type: text/markdown

# PyForge ⚒️

**An AI-first, async Python web framework.**
Flask simplicity · FastAPI performance · Django batteries — plus built-in AI,
document processing, and workflow automation (rolling out phase by phase).

```python
from pyforge import PyForge, Router

app = PyForge(title="My API")
api = Router(prefix="/api")

@api.get("/users/{user_id:int}")
async def get_user(user_id: int) -> dict:
    return {"id": user_id}

app.include_router(api)
```

```bash
pyforge new myapp
cd myapp
pyforge run
```

## Why PyForge?

| | Flask | FastAPI | Django | **PyForge** |
|---|---|---|---|---|
| Async-first ASGI | ➖ | ✅ | ➖ | ✅ |
| Zero-boilerplate routing | ✅ | ✅ | ➖ | ✅ |
| Layered config (.env + YAML + profiles + secrets) | ➖ | ➖ | ➖ | ✅ |
| Dependency injection | ➖ | ✅ | ➖ | ✅ |
| AI module (LLMs, RAG, agents) | ➖ | ➖ | ➖ | 🔜 Phase 4 |
| Document AI (OCR, PDF extraction) | ➖ | ➖ | ➖ | 🔜 Phase 5 |
| Workflow automation | ➖ | ➖ | ➖ | 🔜 Phase 6 |
| Auto admin dashboard | ➖ | ➖ | ✅ | 🔜 Phase 7 |

## Installation

```bash
pip install "pyforge-framework[server]"
# or from source:
pip install -e ".[dev]"
```

Requires **Python 3.12+**.

## Quickstart

### 1. Scaffold a project

```bash
pyforge new myapp
```

```
myapp/
├── app/
│   ├── __init__.py
│   └── main.py
├── config/
│   ├── settings.yaml
│   └── settings.production.yaml
├── tests/
│   └── test_app.py
├── conftest.py
├── .env
├── .gitignore
├── requirements.txt
└── README.md
```

### 2. Run it

```bash
cd myapp
pyforge run                 # http://127.0.0.1:8000
pyforge run --reload        # auto-reload in development
pyforge run app.main:app --host 0.0.0.0 --port 9000
```

## Core concepts (Phase 1)

### Routing

```python
from pyforge import PyForge, Router

app = PyForge()

@app.get("/")
async def index() -> dict:
    return {"hello": "world"}

# Typed path converters: str (default), int, float, uuid, slug, path
@app.get("/files/{filepath:path}")
async def download(filepath: str) -> dict:
    return {"path": filepath}

# Route groups with prefixes and group middleware
v1 = Router(prefix="/v1")

@v1.get("/items")
async def items() -> list:
    return [1, 2, 3]

api = Router(prefix="/api")
api.include_router(v1)
app.include_router(api)      # -> GET /api/v1/items
```

Handlers may return a `Response`, `dict`/`list` (JSON), `str` (plain text),
`None` (204), or a `(body, status_code)` tuple.

### Dependency injection

```python
from pyforge import Depends, PyForge, Request

app = PyForge()

class Database:
    async def fetch_user(self, user_id: int) -> dict:
        return {"id": user_id}

app.container.register(Database)            # constructor-injected singleton

async def current_user(request: Request) -> dict:
    return {"token": request.headers.get("authorization")}

@app.get("/users/{user_id:int}")
async def get_user(
    user_id: int,                            # path param (typed)
    db: Database,                            # container-resolved service
    user: dict = Depends(current_user),      # request-scoped dependency
    verbose: bool = False,                   # query param with default
) -> dict:
    return await db.fetch_user(user_id)
```

### Middleware

```python
from pyforge.middleware import BaseHTTPMiddleware, CORSMiddleware

class TimingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["x-powered-by"] = "PyForge"
        return response

app.add_middleware(CORSMiddleware, allow_origins=["https://myapp.dev"])
app.add_middleware(TimingMiddleware)

# Middleware can also be scoped to a route group:
admin = Router(prefix="/admin", middleware=[TimingMiddleware()])
```

### Configuration

Layered lookup, highest priority first:
`os.environ` → `.env` file → `config/settings.<profile>.yaml` →
`config/settings.yaml` → defaults.

```python
from pyforge.config import Config

config = Config()                       # profile from PYFORGE_ENV, default "development"
config.get("database.url")             # env override key: DATABASE__URL
config.get("app.debug", False, cast=bool)
config.require("app.name")            # raises ConfigError if missing
config.secret("db_password")          # supports Docker-style DB_PASSWORD_FILE
```

YAML values support `${ENV_VAR}` and `${ENV_VAR:default}` interpolation.

### Lifecycle & errors

```python
@app.on_startup
async def connect() -> None: ...

@app.on_shutdown
async def disconnect() -> None: ...

@app.exception_handler(KeyError)
async def missing(request, exc):
    return {"detail": "missing key"}, 400
```

### Testing

```python
from pyforge.testing import TestClient
from app.main import app

client = TestClient(app)

def test_health():
    assert client.get("/health").json() == {"status": "ok"}
```

## CLI

```
pyforge new <project>    Scaffold a new project
pyforge run [app]        Run the ASGI server (uvicorn)
pyforge version          Print the installed version
```

`generate model|crud|api`, `migrate`, `makemigrations`, `test`, and `build`
ship with Phases 2–3 (ORM & codegen).

## Roadmap

See [CONTRIBUTING.md](CONTRIBUTING.md#project-phases) for the full phase plan:
ORM → Auth → AI → Document AI → Workflows → Admin & Monitoring.

## License

MIT — see [LICENSE](LICENSE).
