Metadata-Version: 2.4
Name: django-graphql-api
Version: 0.0.2
Summary: Django REST Framework-inspired GraphQL API built on graphene
Author: Sezgin
License: MIT
Keywords: graphql,django,graphene,api
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: django>=4.2
Requires-Dist: graphene>=3.3
Requires-Dist: graphql-core>=3.2
Requires-Dist: graphql-relay>=3.2
Requires-Dist: django-filter>=23.5
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-django>=4.8; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: factory-boy>=3.3; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: isort>=5.13; extra == "dev"
Requires-Dist: pre-commit>=4.0; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: django-stubs>=5.0; extra == "dev"

# Django GraphQL API

DRF-inspired GraphQL API for Django, built on [graphene](https://graphene-python.org/).

> **Status:** Early alpha. APIs will change.

## Why

DRF gives you permissions, auth, pagination, filtering, throttling, validation, and CRUD out of the box. graphene-django does not. This API bridges that gap.

## Install

```bash
pip install django-graphql-api
```

Or from source:

```bash
git clone <repo>
cd django_graphql_api
pip install -e .
```

## Quick Start

### 1. Define a type

```python
from django_graphql_api import DjangoObjectType

class UserType(DjangoObjectType):
    class Meta:
        model = User
        fields = "__all__"
```

### 2. Register CRUD ops

```python
from django_graphql_api import GraphQLOpsGroup

api = GraphQLOpsGroup()
api.register(UserType)
```

### 3. Wire to a view

```python
from django_graphql_api import GraphQLView
from django.urls import path

urlpatterns = [
    path("graphql/", GraphQLView.as_view(api=api)),
]
```

That's it — `allUsers`, `user(id: ...)`, `createUser`, `updateUser`, `deleteUser` are auto-generated.

## Key Features

- **Django forms-style fields** — `clean()`, `to_python()`, `to_output()` instead of DRF's `to_internal_value` / `to_representation`
- **Wrapper types** — `ObjectType` and `InputType` sit between your code and graphene, carrying validation config
- **Auto CRUD** — `GraphQLOpsGroup.register()` generates list, retrieve, create, update, delete
- **Permissions** — `IsAuthenticated`, `IsOwner`, `IsAdmin`, `IsStaff`, `AllowAny`. Boolean composition with `&` `|` `~`
- **Auth** — Basic, Token, Session authentication
- **Pagination** — Page number, offset, cursor
- **Filtering** — Search, ordering
- **Throttling** — Per client IP, per user
- **Validation** — `Unique`, `UniqueTogether`, `NoNullChars`, custom validators
- **Custom actions** — `@action(detail=True)` / `@query_action(detail=False)` for non-CRUD ops

## Full Example

A complete, production-ready setup showing models, types, filters, permissions, validation, custom actions, and wiring.

### `models.py`

```python
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)

class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField(blank=True)
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="articles")
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name="articles")
```

### `schema.py`

```python
from django_graphql_api import (
    DjangoObjectType,
    GraphQLModelOps,
    GraphQLOpsGroup,
    Filter,
    Search,
    Ordering,
    CharField,
    BooleanField,
    IntegerField,
    IsAuthenticated,
    IsOwner,
    Unique,
    UniqueTogether,
    action,
    query_action,
)
from .models import Article, Category

# ── Types ─────────────────────────────────────────

class CategoryType(DjangoObjectType):
    class Meta:
        model = Category
        fields = ["id", "name", "slug"]

class ArticleType(DjangoObjectType):
    class Meta:
        model = Article
        fields = "__all__"
        permissions = [IsAuthenticated]
        validators = [
            Unique(Article.objects.all(), "title", message="Title already taken."),
            UniqueTogether(Article.objects.all(), ["title", "author"]),
        ]

# ── Custom input types (action-based) ─────────────

class CreateArticleInput(InputType):
    title = CharField(required=True)
    body = CharField()
    published = BooleanField()

class UpdateArticleInput(InputType):
    title = CharField()
    body = CharField()
    published = BooleanField()

class PublishInput(InputType):
    notify_subscribers = BooleanField()

# ── Filters ───────────────────────────────────────

class ArticleFilter(Filter):
    title = CharField()
    published = BooleanField()

# ── Ops (CRUD + custom actions) ─────────────────

class ArticleOps(GraphQLModelOps):
    type_class = ArticleType

    # Action-based input mapping
    action_inputs = {
        "create": CreateArticleInput,
        "update": UpdateArticleInput,
    }

    class Meta:
        permissions = [IsAuthenticated]

    @query_action(detail=False)
    def latest(self, info, limit: int = 5):
        """Return the latest published articles."""
        qs = Article.objects.filter(published=True).order_by("-created_at")
        return qs[:limit]

    @action(detail=True, input=PublishInput)
    def publish(self, info, id, input):
        """Publish a single article."""
        article = Article.objects.get(pk=id)
        article.published = True
        article.save()
        if input.get("notify_subscribers"):
            print(f"Notifying subscribers for {article.title}")
        return {"ok": True, "result": article}

# ── Build schema ──────────────────────────────────

api = GraphQLOpsGroup()
api.register(ArticleOps)

schema = api.build_schema()
```

### `urls.py`

```python
from django.urls import path
from django_graphql_api import GraphQLView
from .schema import schema

urlpatterns = [
    path("graphql", GraphQLView.as_view(schema=schema, graphiql=True)),
]
```

### `settings.py`

```python
DJANGO_GRAPHQL_API = {
    "AUTHENTICATION": ["django_graphql_api.authentication.SessionAuth"],
    "PERMISSION": ["django_graphql_api.permissions.AllowAny"],
    "PAGINATION": {"page_size": 25},
    "THROTTLE": {"anon": "100/day", "user": "1000/day"},
}
```

### GraphQL usage

```graphql
# List with filter + ordering
query {
    allArticles(title: "GraphQL", published: true, order: "-createdAt") {
        edges {
            node {
                id
                title
                published
                createdAt
            }
        }
    }
}

# Retrieve single
query {
    article(id: "QXJ0aWNsZVR5cGU6MQ==") {
        id
        title
        body
    }
}

# Custom query action
query {
    latest(limit: 3) {
        result {
            id
            title
        }
        ok
    }
}

# Create (auto-generated from GraphQLModelOps)
mutation {
    createArticle(input: {title: "Hello", body: "World", published: true}) {
        result {
            id
            title
        }
    }
}

# Custom action
mutation {
    publish(id: "QXJ0aWNsZVR5cGU6MQ==") {
        ok
        result {
            id
            title
            published
        }
    }
}
```

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements/dev.txt
pre-commit install
```

## Requirements

- Python >= 3.10
- Django >= 4.2
- graphene >= 3.3

## License

MIT
