fraiseql


Namefraiseql JSON
Version 0.1.0b7 PyPI version JSON
download
home_pageNone
SummaryLightweight GraphQL-to-PostgreSQL query builder using jsonb
upload_time2025-07-08 20:46:55
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseMIT
keywords api async database fastapi graphql jsonb orm postgresql
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <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>

[![CI](https://github.com/fraiseql/fraiseql/actions/workflows/ci.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/ci.yml)
[![Test Suite](https://github.com/fraiseql/fraiseql/actions/workflows/test.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/test.yml)
[![Security](https://github.com/fraiseql/fraiseql/actions/workflows/security.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/security.yml)
[![Documentation](https://github.com/fraiseql/fraiseql/actions/workflows/docs.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/docs.yml)
[![codecov](https://codecov.io/gh/fraiseql/fraiseql/branch/main/graph/badge.svg)](https://codecov.io/gh/fraiseql/fraiseql)
[![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![PyPI version](https://img.shields.io/badge/pypi-v0.1.0b1-blue.svg)](https://badge.fury.io/py/fraiseql)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](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[![CI](https://github.com/fraiseql/fraiseql/actions/workflows/ci.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/ci.yml)\n[![Test Suite](https://github.com/fraiseql/fraiseql/actions/workflows/test.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/test.yml)\n[![Security](https://github.com/fraiseql/fraiseql/actions/workflows/security.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/security.yml)\n[![Documentation](https://github.com/fraiseql/fraiseql/actions/workflows/docs.yml/badge.svg)](https://github.com/fraiseql/fraiseql/actions/workflows/docs.yml)\n[![codecov](https://codecov.io/gh/fraiseql/fraiseql/branch/main/graph/badge.svg)](https://codecov.io/gh/fraiseql/fraiseql)\n[![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)\n[![PyPI version](https://img.shields.io/badge/pypi-v0.1.0b1-blue.svg)](https://badge.fury.io/py/fraiseql)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](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"
}
        
Elapsed time: 2.13053s