<p align="center">
<img src="./docs/assets/logo.png" alt="FraiseQL Logo" width="200" />
</p>
<p align="center">
<strong>A GraphQL-to-PostgreSQL translator with a CQRS architecture</strong><br>
<em>Views for queries. Functions for mutations. GraphQL for developers.</em>
</p>
[](https://github.com/fraiseql/fraiseql/actions/workflows/ci.yml)
[](https://github.com/fraiseql/fraiseql/actions/workflows/test.yml)
[](https://github.com/fraiseql/fraiseql/actions/workflows/security.yml)
[](https://github.com/fraiseql/fraiseql/actions/workflows/docs.yml)
[](https://codecov.io/gh/fraiseql/fraiseql)
[](https://www.python.org/downloads/)
[](https://badge.fury.io/py/fraiseql)
[](https://opensource.org/licenses/MIT)
[](https://github.com/pre-commit/pre-commit)
**FraiseQL** is a Python framework that translates GraphQL queries directly into PostgreSQL queries, embracing a CQRS (Command Query Responsibility Segregation) architecture where database views handle queries and PostgreSQL functions handle mutations.
## ⚡ Quick Links
| Getting Started | Reference | Learn More |
|----------------|-----------|------------|
| 🚀 [Getting Started Guide](docs/GETTING_STARTED.md) | 📖 [API Reference](docs/API_REFERENCE.md) | 🏗️ [Architecture](docs/ARCHITECTURE.md) |
| 📚 [Query Patterns](docs/QUERY_PATTERNS.md) | 🔧 [Common Patterns](docs/COMMON_PATTERNS.md) | 🔄 [Migration Guide](docs/MIGRATION_TO_JSONB_PATTERN.md) |
| 🔍 [Filtering Patterns](docs/FILTERING_PATTERNS.md) | 💡 [Examples](examples/) | 📝 [Contributing](CONTRIBUTING.md) |
| 🔍 [WHERE Types Guide](docs/WHERE_TYPES.md) | 🆕 [Partial Instantiation](docs/PARTIAL_INSTANTIATION.md) | |
| ❓ [Troubleshooting](docs/TROUBLESHOOTING.md) | | |
## 🎯 Core Concepts
FraiseQL has four fundamental patterns:
### 1. Types are Python Classes
```python
@fraise_type
class User:
id: UUID
name: str
email: str
```
### 2. Queries are Functions (Not Resolvers!)
```python
@fraiseql.query
async def get_user(info, id: UUID) -> User:
# 'info' is ALWAYS first parameter
db = info.context["db"]
return await db.find_one("user_view", id=id)
```
### 3. All Data in JSONB Column (v0.1.0a14+)
```sql
CREATE VIEW user_view AS
SELECT
id, -- For filtering
tenant_id, -- For access control
jsonb_build_object(
'id', id,
'name', name,
'email', email
) as data -- REQUIRED: All object data here
FROM users;
```
### 4. Repository Handles Database
```python
# No manual connection management needed
db = info.context["db"] # FraiseQLRepository
user = await db.find_one("user_view", id=user_id)
```
## ✨ Key Features
| Feature | Description |
|---------|-------------|
| 🚀 **Simple Patterns** | Decorator-based API with no resolver classes |
| 📊 **JSONB-First** | All data flows through JSONB columns for consistency |
| 🔐 **Type-Safe** | Full type hints with Python 3.11+ |
| 🛡️ **SQL Injection Safe** | Parameterized queries throughout |
| 🔌 **Pluggable Auth** | Built-in Auth0, easy to add others |
| ⚡ **FastAPI Integration** | Production-ready ASGI application |
| 🏎️ **High Performance** | Direct SQL queries, no N+1 problems |
| 🎯 **CQRS Architecture** | Views for queries, functions for mutations |
## 📦 Installation
```bash
pip install fraiseql
# With optional features:
pip install "fraiseql[auth0]" # Auth0 authentication
pip install "fraiseql[tracing]" # OpenTelemetry tracing
pip install "fraiseql[dev]" # Development dependencies
```
> ⚠️ **Breaking Changes**:
> - **v0.1.0a14**: All database views must now return data in a JSONB `data` column. See the [Migration Guide](docs/MIGRATION_TO_JSONB_PATTERN.md)
> - **v0.1.0a18**: Partial object instantiation is now supported in development mode, allowing nested queries to request only specific fields
## 🚀 Quick Start
### 1. Hello World (No Database)
```python
import fraiseql
from datetime import datetime
from uuid import UUID, uuid4
# Define a type
@fraise_type
class Book:
id: UUID
title: str
author: str
published: datetime
# Create a query (NOT a resolver!)
@fraiseql.query
async def books(info) -> list[Book]:
"""Get all books."""
# 'info' is ALWAYS the first parameter
return [
Book(
id=uuid4(),
title="The Great Gatsby",
author="F. Scott Fitzgerald",
published=datetime(1925, 4, 10)
)
]
# Create the app
app = fraiseql.create_fraiseql_app(
types=[Book],
production=False # Enables GraphQL Playground
)
# Run with: uvicorn app:app --reload
# Visit: http://localhost:8000/graphql
```
### 2. With Database (The Right Way)
```python
# First, create your database view with JSONB data column:
"""
CREATE VIEW book_view AS
SELECT
id, -- For filtering
author, -- For author queries
published, -- For date filtering
jsonb_build_object(
'id', id,
'title', title,
'author', author,
'published', published
) as data -- REQUIRED: All object data here!
FROM books;
"""
# Then create your query:
@fraiseql.query
async def books(info, author: str | None = None) -> list[Book]:
"""Get books, optionally filtered by author."""
db = info.context["db"] # FraiseQLRepository
if author:
return await db.find("book_view", author=author)
return await db.find("book_view")
# Create app with database
app = fraiseql.create_fraiseql_app(
database_url="postgresql://localhost/mydb",
types=[Book],
production=False
)
```
### 3. Common Mistakes to Avoid
```python
# ❌ WRONG: Don't use resolver classes
class Query:
async def resolve_users(self, info):
pass
# ✅ CORRECT: Use @fraiseql.query decorator
@fraiseql.query
async def users(info) -> list[User]:
db = info.context["db"]
return await db.find("user_view")
# ❌ WRONG: Don't forget the data column
CREATE VIEW bad_view AS
SELECT id, name, email FROM users;
# ✅ CORRECT: Always include JSONB data column
CREATE VIEW good_view AS
SELECT id, jsonb_build_object(
'id', id, 'name', name, 'email', email
) as data FROM users;
```
👉 **See the [Getting Started Guide](docs/GETTING_STARTED.md) for a complete walkthrough**
## 🆕 New in v0.1.0a18: Partial Object Instantiation
FraiseQL now supports partial object instantiation for nested queries in development mode. This means you can request only the fields you need from nested objects without errors:
```graphql
query GetUsers {
users {
id
name
profile {
avatar # Only request avatar, not all profile fields
}
}
}
```
```python
@fraise_type
class Profile:
id: UUID
avatar: str
email: str # Required but not requested - no error!
bio: str # Required but not requested - no error!
```
This brings FraiseQL closer to GraphQL's promise of "ask for what you need, get exactly that". See the [Partial Instantiation Guide](docs/PARTIAL_INSTANTIATION.md) for details.
### Complex Nested Query Example
With partial instantiation, you can now build efficient queries that traverse multiple levels of relationships:
```graphql
query BlogDashboard {
posts(where: { published_at: { neq: null } }) {
id
title
published_at
author {
name
profile {
avatar # Only need avatar for display
}
}
comments {
id
content
author {
name # Only need commenter's name
}
}
}
}
```
All nested objects will be properly instantiated with only the requested fields, avoiding errors from missing required fields in the type definitions.
## 🎯 Why FraiseQL?
### The Problem
Traditional GraphQL servers require complex resolver hierarchies, N+1 query problems, and lots of boilerplate.
### The FraiseQL Solution
- **Direct SQL queries** from GraphQL queries
- **JSONB pattern** for consistent data access
- **No resolver classes** - just functions
- **Type-safe** with full IDE support
- **Production-ready** with auth, caching, and monitoring
- **Partial field selection** (v0.1.0a18+) for optimal queries
## 📚 Learn More
| Topic | Description |
|-------|-------------|
| [Query Patterns](docs/QUERY_PATTERNS.md) | How to write queries the FraiseQL way |
| [JSONB Pattern](docs/ARCHITECTURE.md#the-jsonb-data-column-pattern) | Why all data goes in a JSONB column |
| [Multi-Tenancy](docs/COMMON_PATTERNS.md#multi-tenant-applications) | Building SaaS applications |
| [Authentication](docs/COMMON_PATTERNS.md#authentication--authorization) | Adding auth to your API |
| [Testing](docs/testing/unified-container-testing.md) | Our unified container approach |
## Real-World Example
Here's how you might structure a blog application:
```python
@fraise_type
class Post:
id: UUID
title: str
content: str
author: User
comments: list['Comment']
tags: list[str]
published_at: datetime | None
@fraise_type
class Comment:
id: UUID
content: str
author: User
created_at: datetime
```
With a corresponding view:
```sql
CREATE VIEW post_details AS
SELECT
p.id, -- For filtering
p.author_id, -- For joins
p.published_at, -- For filtering by date
jsonb_build_object(
'id', p.id,
'title', p.title,
'content', p.content,
'author', (
SELECT data FROM user_profile
WHERE id = p.author_id -- Use id column for filtering
),
'comments', (
SELECT jsonb_agg(
jsonb_build_object(
'id', c.id,
'content', c.content,
'author', (
SELECT data FROM user_profile
WHERE id = c.author_id -- Use id column for filtering
),
'created_at', c.created_at
)
ORDER BY c.created_at
)
FROM comments c
WHERE c.post_id = p.id
),
'tags', p.tags,
'published_at', p.published_at
) as data -- All object data in 'data' column
FROM posts p;
```
## Authentication
FraiseQL includes a pluggable authentication system:
```python
from fraiseql.auth.decorators import requires_auth
from fraiseql.auth.auth0 import Auth0Config
@fraise_type
class Query:
@requires_auth
async def me(self, info) -> User:
# info.context["user"] contains authenticated user info
user_id = info.context["user"].user_id
# Fetch and return user...
app = create_fraiseql_app(
database_url="postgresql://localhost/mydb",
auth=Auth0Config(
domain="your-domain.auth0.com",
api_identifier="https://api.example.com"
),
types=[User, Query],
)
```
## LLM-Friendly Architecture
FraiseQL's design makes it exceptionally well-suited for AI-assisted development:
### Clear Contracts
- **Explicit type definitions** with decorators (`@fraise_type`, `@fraise_input`)
- **Structured mutations** with success/failure unions
- **Well-defined boundaries** between queries (views) and mutations (functions)
- **No hidden magic** - what you define is what you get
### Simple, Common Languages
- **Just Python and SQL** - no proprietary DSLs or complex configurations
- **Standard PostgreSQL** - 40+ years of documentation and examples
- **Familiar patterns** - decorators and dataclasses that LLMs understand well
### Predictable Code Generation
When you ask an LLM to generate a FraiseQL API, it can reliably produce:
```python
# LLMs can easily generate this pattern
@fraise_type
class Product:
id: UUID
name: str
price: Decimal
in_stock: bool
# And the corresponding SQL view
"""
CREATE VIEW product_catalog AS
SELECT
id, -- For filtering
category_id, -- For joins
jsonb_build_object(
'id', id,
'name', name,
'price', price,
'in_stock', quantity > 0
) as data -- All product data in 'data' column
FROM products;
"""
```
This simplicity means:
- **Lower token costs** - concise, standard patterns
- **Higher accuracy** - LLMs trained on Python/SQL perform better
- **Faster iteration** - generate, test, and refine quickly
- **Maintainable output** - generated code looks like human-written code
## Development
### Prerequisites
- Python 3.11+
- PostgreSQL 13+
- Podman or Docker (optional, for integration tests)
### Setting Up
```bash
# Clone the repo
git clone https://github.com/fraiseql/fraiseql.git
cd fraiseql
# Create virtual environment
python -m venv .venv
source .venv/bin/activate
# Install in development mode
pip install -e ".[dev]"
# Run tests (uses unified container for performance)
pytest # Automatically detects available container runtime
# Or explicitly with Podman (recommended for socket performance)
TESTCONTAINERS_PODMAN=true pytest
# Skip container-based tests if no runtime available
pytest -m "not docker"
```
### Container Runtime
FraiseQL uses a **unified container approach** for testing - a single PostgreSQL container runs for the entire test session with socket-based communication, providing 5-10x faster test execution.
- **Podman** (recommended): Rootless, daemonless, uses Unix domain sockets
- **Docker**: Traditional container runtime
Tests requiring containers are automatically skipped if neither is available. See [docs/testing/unified-container-testing.md](docs/testing/unified-container-testing.md) for architecture details.
### Code Quality
```bash
# Linting
ruff check src/
# Type checking
pyright
# Format code
ruff format src/ tests/
```
## Contributing
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## Why "FraiseQL"?
"Fraise" is French for strawberry. This project was heavily inspired by the excellent [Strawberry GraphQL](https://strawberry.rocks/) library, whose elegant API design showed us how delightful Python GraphQL development could be. While we take a different architectural approach, we aim to preserve that same developer-friendly experience.
## Current Status
FraiseQL is in active development. We're working on:
- Performance benchmarks and optimization
- Additional authentication providers
- Enhanced query compilation for production
- More comprehensive documentation
- Real-world example applications
## License
MIT License - see [LICENSE](LICENSE) for details.
---
**FraiseQL**: Where GraphQL meets PostgreSQL. 🍓
Raw data
{
"_id": null,
"home_page": null,
"name": "fraiseql",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "api, async, database, fastapi, graphql, jsonb, orm, postgresql",
"author": null,
"author_email": "Lionel Hamayon <lionel.hamayon@evolution-digitale.fr>",
"download_url": "https://files.pythonhosted.org/packages/ef/8c/3b7b4a05f29a81cf9135428134601454c07dc4f4ef199e97b51246cba9c9/fraiseql-0.1.0b7.tar.gz",
"platform": null,
"description": "<p align=\"center\">\n <img src=\"./docs/assets/logo.png\" alt=\"FraiseQL Logo\" width=\"200\" />\n</p>\n\n<p align=\"center\">\n <strong>A GraphQL-to-PostgreSQL translator with a CQRS architecture</strong><br>\n <em>Views for queries. Functions for mutations. GraphQL for developers.</em>\n</p>\n\n[](https://github.com/fraiseql/fraiseql/actions/workflows/ci.yml)\n[](https://github.com/fraiseql/fraiseql/actions/workflows/test.yml)\n[](https://github.com/fraiseql/fraiseql/actions/workflows/security.yml)\n[](https://github.com/fraiseql/fraiseql/actions/workflows/docs.yml)\n[](https://codecov.io/gh/fraiseql/fraiseql)\n[](https://www.python.org/downloads/)\n[](https://badge.fury.io/py/fraiseql)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/pre-commit/pre-commit)\n\n**FraiseQL** is a Python framework that translates GraphQL queries directly into PostgreSQL queries, embracing a CQRS (Command Query Responsibility Segregation) architecture where database views handle queries and PostgreSQL functions handle mutations.\n\n## \u26a1 Quick Links\n\n| Getting Started | Reference | Learn More |\n|----------------|-----------|------------|\n| \ud83d\ude80 [Getting Started Guide](docs/GETTING_STARTED.md) | \ud83d\udcd6 [API Reference](docs/API_REFERENCE.md) | \ud83c\udfd7\ufe0f [Architecture](docs/ARCHITECTURE.md) |\n| \ud83d\udcda [Query Patterns](docs/QUERY_PATTERNS.md) | \ud83d\udd27 [Common Patterns](docs/COMMON_PATTERNS.md) | \ud83d\udd04 [Migration Guide](docs/MIGRATION_TO_JSONB_PATTERN.md) |\n| \ud83d\udd0d [Filtering Patterns](docs/FILTERING_PATTERNS.md) | \ud83d\udca1 [Examples](examples/) | \ud83d\udcdd [Contributing](CONTRIBUTING.md) |\n| \ud83d\udd0d [WHERE Types Guide](docs/WHERE_TYPES.md) | \ud83c\udd95 [Partial Instantiation](docs/PARTIAL_INSTANTIATION.md) | |\n| \u2753 [Troubleshooting](docs/TROUBLESHOOTING.md) | | |\n\n## \ud83c\udfaf Core Concepts\n\nFraiseQL has four fundamental patterns:\n\n### 1. Types are Python Classes\n```python\n@fraise_type\nclass User:\n id: UUID\n name: str\n email: str\n```\n\n### 2. Queries are Functions (Not Resolvers!)\n```python\n@fraiseql.query\nasync def get_user(info, id: UUID) -> User:\n # 'info' is ALWAYS first parameter\n db = info.context[\"db\"]\n return await db.find_one(\"user_view\", id=id)\n```\n\n### 3. All Data in JSONB Column (v0.1.0a14+)\n```sql\nCREATE VIEW user_view AS\nSELECT \n id, -- For filtering\n tenant_id, -- For access control\n jsonb_build_object(\n 'id', id,\n 'name', name,\n 'email', email\n ) as data -- REQUIRED: All object data here\nFROM users;\n```\n\n### 4. Repository Handles Database\n```python\n# No manual connection management needed\ndb = info.context[\"db\"] # FraiseQLRepository\nuser = await db.find_one(\"user_view\", id=user_id)\n```\n\n## \u2728 Key Features\n\n| Feature | Description |\n|---------|-------------|\n| \ud83d\ude80 **Simple Patterns** | Decorator-based API with no resolver classes |\n| \ud83d\udcca **JSONB-First** | All data flows through JSONB columns for consistency |\n| \ud83d\udd10 **Type-Safe** | Full type hints with Python 3.11+ |\n| \ud83d\udee1\ufe0f **SQL Injection Safe** | Parameterized queries throughout |\n| \ud83d\udd0c **Pluggable Auth** | Built-in Auth0, easy to add others |\n| \u26a1 **FastAPI Integration** | Production-ready ASGI application |\n| \ud83c\udfce\ufe0f **High Performance** | Direct SQL queries, no N+1 problems |\n| \ud83c\udfaf **CQRS Architecture** | Views for queries, functions for mutations |\n\n## \ud83d\udce6 Installation\n\n```bash\npip install fraiseql\n\n# With optional features:\npip install \"fraiseql[auth0]\" # Auth0 authentication\npip install \"fraiseql[tracing]\" # OpenTelemetry tracing\npip install \"fraiseql[dev]\" # Development dependencies\n```\n\n> \u26a0\ufe0f **Breaking Changes**: \n> - **v0.1.0a14**: All database views must now return data in a JSONB `data` column. See the [Migration Guide](docs/MIGRATION_TO_JSONB_PATTERN.md)\n> - **v0.1.0a18**: Partial object instantiation is now supported in development mode, allowing nested queries to request only specific fields\n\n## \ud83d\ude80 Quick Start\n\n### 1. Hello World (No Database)\n\n```python\nimport fraiseql\nfrom datetime import datetime\nfrom uuid import UUID, uuid4\n\n# Define a type\n@fraise_type\nclass Book:\n id: UUID\n title: str\n author: str\n published: datetime\n\n# Create a query (NOT a resolver!)\n@fraiseql.query\nasync def books(info) -> list[Book]:\n \"\"\"Get all books.\"\"\"\n # 'info' is ALWAYS the first parameter\n return [\n Book(\n id=uuid4(),\n title=\"The Great Gatsby\",\n author=\"F. Scott Fitzgerald\",\n published=datetime(1925, 4, 10)\n )\n ]\n\n# Create the app\napp = fraiseql.create_fraiseql_app(\n types=[Book],\n production=False # Enables GraphQL Playground\n)\n\n# Run with: uvicorn app:app --reload\n# Visit: http://localhost:8000/graphql\n```\n\n### 2. With Database (The Right Way)\n\n```python\n# First, create your database view with JSONB data column:\n\"\"\"\nCREATE VIEW book_view AS\nSELECT \n id, -- For filtering\n author, -- For author queries\n published, -- For date filtering\n jsonb_build_object(\n 'id', id,\n 'title', title,\n 'author', author,\n 'published', published\n ) as data -- REQUIRED: All object data here!\nFROM books;\n\"\"\"\n\n# Then create your query:\n@fraiseql.query\nasync def books(info, author: str | None = None) -> list[Book]:\n \"\"\"Get books, optionally filtered by author.\"\"\"\n db = info.context[\"db\"] # FraiseQLRepository\n \n if author:\n return await db.find(\"book_view\", author=author)\n return await db.find(\"book_view\")\n\n# Create app with database\napp = fraiseql.create_fraiseql_app(\n database_url=\"postgresql://localhost/mydb\",\n types=[Book],\n production=False\n)\n```\n\n### 3. Common Mistakes to Avoid\n\n```python\n# \u274c WRONG: Don't use resolver classes\nclass Query:\n async def resolve_users(self, info):\n pass\n\n# \u2705 CORRECT: Use @fraiseql.query decorator\n@fraiseql.query\nasync def users(info) -> list[User]:\n db = info.context[\"db\"]\n return await db.find(\"user_view\")\n\n# \u274c WRONG: Don't forget the data column\nCREATE VIEW bad_view AS\nSELECT id, name, email FROM users;\n\n# \u2705 CORRECT: Always include JSONB data column\nCREATE VIEW good_view AS\nSELECT id, jsonb_build_object(\n 'id', id, 'name', name, 'email', email\n) as data FROM users;\n```\n\n\ud83d\udc49 **See the [Getting Started Guide](docs/GETTING_STARTED.md) for a complete walkthrough**\n\n## \ud83c\udd95 New in v0.1.0a18: Partial Object Instantiation\n\nFraiseQL now supports partial object instantiation for nested queries in development mode. This means you can request only the fields you need from nested objects without errors:\n\n```graphql\nquery GetUsers {\n users {\n id\n name\n profile {\n avatar # Only request avatar, not all profile fields\n }\n }\n}\n```\n\n```python\n@fraise_type\nclass Profile:\n id: UUID\n avatar: str\n email: str # Required but not requested - no error!\n bio: str # Required but not requested - no error!\n```\n\nThis brings FraiseQL closer to GraphQL's promise of \"ask for what you need, get exactly that\". See the [Partial Instantiation Guide](docs/PARTIAL_INSTANTIATION.md) for details.\n\n### Complex Nested Query Example\n\nWith partial instantiation, you can now build efficient queries that traverse multiple levels of relationships:\n\n```graphql\nquery BlogDashboard {\n posts(where: { published_at: { neq: null } }) {\n id\n title\n published_at\n author {\n name\n profile {\n avatar # Only need avatar for display\n }\n }\n comments {\n id\n content\n author {\n name # Only need commenter's name\n }\n }\n }\n}\n```\n\nAll nested objects will be properly instantiated with only the requested fields, avoiding errors from missing required fields in the type definitions.\n\n## \ud83c\udfaf Why FraiseQL?\n\n### The Problem\nTraditional GraphQL servers require complex resolver hierarchies, N+1 query problems, and lots of boilerplate.\n\n### The FraiseQL Solution\n- **Direct SQL queries** from GraphQL queries\n- **JSONB pattern** for consistent data access\n- **No resolver classes** - just functions\n- **Type-safe** with full IDE support\n- **Production-ready** with auth, caching, and monitoring\n- **Partial field selection** (v0.1.0a18+) for optimal queries\n\n## \ud83d\udcda Learn More\n\n| Topic | Description |\n|-------|-------------|\n| [Query Patterns](docs/QUERY_PATTERNS.md) | How to write queries the FraiseQL way |\n| [JSONB Pattern](docs/ARCHITECTURE.md#the-jsonb-data-column-pattern) | Why all data goes in a JSONB column |\n| [Multi-Tenancy](docs/COMMON_PATTERNS.md#multi-tenant-applications) | Building SaaS applications |\n| [Authentication](docs/COMMON_PATTERNS.md#authentication--authorization) | Adding auth to your API |\n| [Testing](docs/testing/unified-container-testing.md) | Our unified container approach |\n\n## Real-World Example\n\nHere's how you might structure a blog application:\n\n```python\n@fraise_type\nclass Post:\n id: UUID\n title: str\n content: str\n author: User\n comments: list['Comment']\n tags: list[str]\n published_at: datetime | None\n\n@fraise_type\nclass Comment:\n id: UUID\n content: str\n author: User\n created_at: datetime\n```\n\nWith a corresponding view:\n\n```sql\nCREATE VIEW post_details AS\nSELECT\n p.id, -- For filtering\n p.author_id, -- For joins\n p.published_at, -- For filtering by date\n jsonb_build_object(\n 'id', p.id,\n 'title', p.title,\n 'content', p.content,\n 'author', (\n SELECT data FROM user_profile\n WHERE id = p.author_id -- Use id column for filtering\n ),\n 'comments', (\n SELECT jsonb_agg(\n jsonb_build_object(\n 'id', c.id,\n 'content', c.content,\n 'author', (\n SELECT data FROM user_profile\n WHERE id = c.author_id -- Use id column for filtering\n ),\n 'created_at', c.created_at\n )\n ORDER BY c.created_at\n )\n FROM comments c\n WHERE c.post_id = p.id\n ),\n 'tags', p.tags,\n 'published_at', p.published_at\n ) as data -- All object data in 'data' column\nFROM posts p;\n```\n\n## Authentication\n\nFraiseQL includes a pluggable authentication system:\n\n```python\nfrom fraiseql.auth.decorators import requires_auth\nfrom fraiseql.auth.auth0 import Auth0Config\n\n@fraise_type\nclass Query:\n @requires_auth\n async def me(self, info) -> User:\n # info.context[\"user\"] contains authenticated user info\n user_id = info.context[\"user\"].user_id\n # Fetch and return user...\n\napp = create_fraiseql_app(\n database_url=\"postgresql://localhost/mydb\",\n auth=Auth0Config(\n domain=\"your-domain.auth0.com\",\n api_identifier=\"https://api.example.com\"\n ),\n types=[User, Query],\n)\n```\n\n## LLM-Friendly Architecture\n\nFraiseQL's design makes it exceptionally well-suited for AI-assisted development:\n\n### Clear Contracts\n- **Explicit type definitions** with decorators (`@fraise_type`, `@fraise_input`)\n- **Structured mutations** with success/failure unions\n- **Well-defined boundaries** between queries (views) and mutations (functions)\n- **No hidden magic** - what you define is what you get\n\n### Simple, Common Languages\n- **Just Python and SQL** - no proprietary DSLs or complex configurations\n- **Standard PostgreSQL** - 40+ years of documentation and examples\n- **Familiar patterns** - decorators and dataclasses that LLMs understand well\n\n### Predictable Code Generation\nWhen you ask an LLM to generate a FraiseQL API, it can reliably produce:\n\n```python\n# LLMs can easily generate this pattern\n@fraise_type\nclass Product:\n id: UUID\n name: str\n price: Decimal\n in_stock: bool\n\n# And the corresponding SQL view\n\"\"\"\nCREATE VIEW product_catalog AS\nSELECT \n id, -- For filtering\n category_id, -- For joins\n jsonb_build_object(\n 'id', id,\n 'name', name,\n 'price', price,\n 'in_stock', quantity > 0\n ) as data -- All product data in 'data' column\nFROM products;\n\"\"\"\n```\n\nThis simplicity means:\n- **Lower token costs** - concise, standard patterns\n- **Higher accuracy** - LLMs trained on Python/SQL perform better\n- **Faster iteration** - generate, test, and refine quickly\n- **Maintainable output** - generated code looks like human-written code\n\n## Development\n\n### Prerequisites\n\n- Python 3.11+\n- PostgreSQL 13+\n- Podman or Docker (optional, for integration tests)\n\n### Setting Up\n\n```bash\n# Clone the repo\ngit clone https://github.com/fraiseql/fraiseql.git\ncd fraiseql\n\n# Create virtual environment\npython -m venv .venv\nsource .venv/bin/activate\n\n# Install in development mode\npip install -e \".[dev]\"\n\n# Run tests (uses unified container for performance)\npytest # Automatically detects available container runtime\n\n# Or explicitly with Podman (recommended for socket performance)\nTESTCONTAINERS_PODMAN=true pytest\n\n# Skip container-based tests if no runtime available\npytest -m \"not docker\"\n```\n\n### Container Runtime\n\nFraiseQL uses a **unified container approach** for testing - a single PostgreSQL container runs for the entire test session with socket-based communication, providing 5-10x faster test execution.\n\n- **Podman** (recommended): Rootless, daemonless, uses Unix domain sockets\n- **Docker**: Traditional container runtime\n\nTests requiring containers are automatically skipped if neither is available. See [docs/testing/unified-container-testing.md](docs/testing/unified-container-testing.md) for architecture details.\n\n### Code Quality\n\n```bash\n# Linting\nruff check src/\n\n# Type checking\npyright\n\n# Format code\nruff format src/ tests/\n```\n\n## Contributing\n\nWe welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n## Why \"FraiseQL\"?\n\n\"Fraise\" is French for strawberry. This project was heavily inspired by the excellent [Strawberry GraphQL](https://strawberry.rocks/) library, whose elegant API design showed us how delightful Python GraphQL development could be. While we take a different architectural approach, we aim to preserve that same developer-friendly experience.\n\n## Current Status\n\nFraiseQL is in active development. We're working on:\n\n- Performance benchmarks and optimization\n- Additional authentication providers\n- Enhanced query compilation for production\n- More comprehensive documentation\n- Real-world example applications\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n---\n\n**FraiseQL**: Where GraphQL meets PostgreSQL. \ud83c\udf53\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Lightweight GraphQL-to-PostgreSQL query builder using jsonb",
"version": "0.1.0b7",
"project_urls": {
"Changelog": "https://github.com/fraiseql/fraiseql/blob/main/CHANGELOG.md",
"Documentation": "https://fraiseql.readthedocs.io",
"Homepage": "https://github.com/fraiseql/fraiseql",
"Issues": "https://github.com/fraiseql/fraiseql/issues",
"Repository": "https://github.com/fraiseql/fraiseql"
},
"split_keywords": [
"api",
" async",
" database",
" fastapi",
" graphql",
" jsonb",
" orm",
" postgresql"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "4e808e465cfa6b033f475bfde831e45462a5f9ccca2a3389e47277b0d2324435",
"md5": "0d0a25d964de5659dbf2538de842389d",
"sha256": "86c63c7bbb5f6d4860638f484bb493784bd5deee6c703648e6083badeb999856"
},
"downloads": -1,
"filename": "fraiseql-0.1.0b7-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0d0a25d964de5659dbf2538de842389d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 268230,
"upload_time": "2025-07-08T20:46:53",
"upload_time_iso_8601": "2025-07-08T20:46:53.474125Z",
"url": "https://files.pythonhosted.org/packages/4e/80/8e465cfa6b033f475bfde831e45462a5f9ccca2a3389e47277b0d2324435/fraiseql-0.1.0b7-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "ef8c3b7b4a05f29a81cf9135428134601454c07dc4f4ef199e97b51246cba9c9",
"md5": "ad8e7c3dc79e7b46c98d9f6b328fe4bf",
"sha256": "fe986b8f7936e25aa0ee0062ec094f5d4b81228a2e8759be8762dc2703ece433"
},
"downloads": -1,
"filename": "fraiseql-0.1.0b7.tar.gz",
"has_sig": false,
"md5_digest": "ad8e7c3dc79e7b46c98d9f6b328fe4bf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 200443,
"upload_time": "2025-07-08T20:46:55",
"upload_time_iso_8601": "2025-07-08T20:46:55.280515Z",
"url": "https://files.pythonhosted.org/packages/ef/8c/3b7b4a05f29a81cf9135428134601454c07dc4f4ef199e97b51246cba9c9/fraiseql-0.1.0b7.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-08 20:46:55",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "fraiseql",
"github_project": "fraiseql",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "fraiseql"
}