Skip to content

add native support for django (only DRF and drf-spectacular) #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,43 @@ print(f"Deployed FastAPI app: {result.id}")
```bash
pip install fastapi
```

## Django Support

You can deploy Django REST Framework applications directly using drf-spectacular:

```python
import os
import tadata_sdk

# Set up Django environment
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
import django
django.setup()

# Your Django settings should include:
# INSTALLED_APPS = [
# # ... your apps
# 'rest_framework',
# 'drf_spectacular',
# ]
# REST_FRAMEWORK = {
# 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
# }

# Deploy using Django schema extraction
result = tadata_sdk.deploy(
use_django=True, # Extract schema from configured Django application
api_key="TADATA_API_KEY",
base_url="https://api.myservice.com",
name="My Django Deployment"
)

print(f"Deployed Django app: {result.id}")
```

**Note:** Django, Django REST Framework, and drf-spectacular are not required dependencies. If you want to use Django support, install them separately:

```bash
pip install django djangorestframework drf-spectacular
```
153 changes: 153 additions & 0 deletions examples/03_django_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""
Example: Deploying a Django REST Framework application as an MCP server.

This example shows how to deploy a Django REST Framework application
using the Tadata SDK. This is a complete working example that sets up
a minimal Django configuration with drf-spectacular.
"""

import os
import django
from django.conf import settings
from tadata_sdk import deploy


def setup_django():
"""Set up a minimal Django configuration for demonstration."""
if not settings.configured:
settings.configure(
DEBUG=True,
SECRET_KEY="demo-secret-key-not-for-production",
INSTALLED_APPS=[
"django.contrib.contenttypes",
"django.contrib.auth",
"rest_framework",
"drf_spectacular",
],
REST_FRAMEWORK={
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
},
SPECTACULAR_SETTINGS={
"TITLE": "Demo Django API",
"DESCRIPTION": "A demonstration Django REST Framework API",
"VERSION": "1.0.0",
},
ROOT_URLCONF=__name__, # Use this module as the URL config
USE_TZ=True,
)
django.setup()


def create_demo_api():
"""Create a simple Django REST API for demonstration."""
from django.urls import path, include
from rest_framework import serializers, viewsets, routers
from rest_framework.decorators import api_view
from rest_framework.response import Response

# Simple serializer
class ItemSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField(max_length=100)
description = serializers.CharField(max_length=500, required=False)

# Simple viewset
class ItemViewSet(viewsets.ViewSet):
"""A simple ViewSet for managing items."""

def list(self, request):
"""List all items."""
items = [
{"id": 1, "name": "Item 1", "description": "First item"},
{"id": 2, "name": "Item 2", "description": "Second item"},
]
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)

def retrieve(self, request, pk=None):
"""Retrieve a specific item."""
item = {"id": int(pk), "name": f"Item {pk}", "description": f"Item number {pk}"}
serializer = ItemSerializer(item)
return Response(serializer.data)

# Simple API view
@api_view(["GET"])
def hello_world(request):
"""A simple hello world endpoint."""
return Response({"message": "Hello from Django!"})

# Setup URL routing
router = routers.DefaultRouter()
router.register(r"items", ItemViewSet, basename="item")

# URL patterns (this module serves as ROOT_URLCONF)
global urlpatterns
urlpatterns = [
path("api/", include(router.urls)),
path("hello/", hello_world, name="hello"),
]


def main():
"""Deploy Django REST Framework application."""
print("Setting up Django configuration...")
setup_django()

print("Creating demo API...")
create_demo_api()

print("Django setup complete! Now deploying to Tadata...")

# Get API key (you would set this in your environment)
api_key = os.getenv("TADATA_API_KEY")
if not api_key:
print("⚠️ TADATA_API_KEY environment variable not set.")
print(" For a real deployment, you would need to set this.")
print(" For this demo, we'll show what the call would look like:")
print()
print(" result = deploy(")
print(" use_django=True,")
print(" api_key='your-api-key-here',")
print(" name='my-django-api',")
print(" base_url='https://api.example.com'")
print(" )")
print()
print("Let's test the Django schema extraction instead...")

# Test the schema extraction without actually deploying
from tadata_sdk.openapi.source import OpenAPISpec

try:
spec = OpenAPISpec.from_django()
print("✅ Django schema extraction successful!")
print(f" API Title: {spec.info.title}")
print(f" API Version: {spec.info.version}")
print(f" Available paths: {list(spec.paths.keys())}")
print()
print("This OpenAPI specification would be deployed to Tadata as an MCP server.")
except Exception as e:
print(f"❌ Schema extraction failed: {e}")
return

# Deploy using Django schema extraction
try:
result = deploy(
use_django=True, # Extract schema from configured Django application
api_key=api_key,
base_url="https://api.example.com", # Your Django API base URL
)

print("✅ Deployment successful!")
print(f" MCP Server ID: {result.id}")
print(f" Created at: {result.created_at}")
if result.updated:
print(" Status: New deployment created")
else:
print(" Status: No changes detected, deployment skipped")

except Exception as e:
print(f"❌ Deployment failed: {e}")


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ dev = [
"types-pyyaml>=6.0.12.20250516",
"pip>=25.1.1",
"fastapi>=0.115.12",
"django>=5.2.1",
"drf-spectacular>=0.28.0",
"djangorestframework>=3.16.0",
"django-stubs>=5.2.0",
"djangorestframework-stubs>=3.16.0",
]

[project.urls]
Expand Down
29 changes: 25 additions & 4 deletions tadata_sdk/core/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ def deploy(
) -> DeploymentResult: ...


@overload
def deploy(
*,
use_django: bool,
api_key: str,
base_url: Optional[str] = None,
name: Optional[str] = None,
auth_config: Optional[AuthConfig] = None,
api_version: Literal["05-2025", "latest"] = "latest",
timeout: int = 30,
) -> DeploymentResult: ...


def deploy(
*,
openapi_spec_path: Annotated[Optional[str], Doc("Path to an OpenAPI specification file (JSON or YAML)")] = None,
Expand All @@ -106,6 +119,7 @@ def deploy(
Optional[Union[Dict[str, Any], OpenAPISpec]], Doc("OpenAPI specification as a dictionary or OpenAPISpec object")
] = None,
fastapi_app: Annotated[Optional[FastAPI], Doc("FastAPI application instance")] = None,
use_django: Annotated[Optional[bool], Doc("Extract OpenAPI spec from Django application")] = None,
base_url: Annotated[
Optional[str],
Doc("Base URL of the API to proxy requests to. If not provided, will try to extract from the OpenAPI spec"),
Expand All @@ -120,7 +134,7 @@ def deploy(
) -> DeploymentResult:
"""Deploy a Model Context Protocol (MCP) server from an OpenAPI specification.

You must provide exactly one of: openapi_spec_path, openapi_spec_url, openapi_spec, or fastapi_app.
You must provide exactly one of: openapi_spec_path, openapi_spec_url, openapi_spec, fastapi_app, or use_django=True.

Returns:
A DeploymentResult object containing details of the deployment.
Expand All @@ -135,12 +149,16 @@ def deploy(
logger.info("Deploying MCP server from OpenAPI spec")

# Validate input - must have exactly one openapi_spec source
source_count = sum(1 for x in [openapi_spec_path, openapi_spec_url, openapi_spec, fastapi_app] if x is not None)
source_count = sum(
1 for x in [openapi_spec_path, openapi_spec_url, openapi_spec, fastapi_app, use_django] if x is not None
)
if source_count == 0:
raise ValueError("One of openapi_spec_path, openapi_spec_url, openapi_spec, or fastapi_app must be provided")
raise ValueError(
"One of openapi_spec_path, openapi_spec_url, openapi_spec, fastapi_app, or use_django=True must be provided"
)
if source_count > 1:
raise ValueError(
"Only one of openapi_spec_path, openapi_spec_url, openapi_spec, or fastapi_app should be provided"
"Only one of openapi_spec_path, openapi_spec_url, openapi_spec, fastapi_app, or use_django=True should be provided"
)

# Process OpenAPI spec from the provided source
Expand Down Expand Up @@ -182,6 +200,9 @@ def deploy(
elif fastapi_app is not None:
logger.info("Loading OpenAPI spec from FastAPI app")
spec = OpenAPISpec.from_fastapi(fastapi_app)
elif use_django:
logger.info("Loading OpenAPI spec from Django app")
spec = OpenAPISpec.from_django()
elif isinstance(openapi_spec, dict):
logger.info("Using provided OpenAPI spec dictionary")
spec = OpenAPISpec.from_dict(openapi_spec)
Expand Down
86 changes: 86 additions & 0 deletions tadata_sdk/openapi/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,89 @@ def from_fastapi(cls, app: "FastAPI") -> "OpenAPISpec":
details={"app_title": getattr(app, "title", "Unknown")},
cause=e,
)

@classmethod
def from_django(cls) -> "OpenAPISpec":
"""Create an OpenAPISpec instance from a Django application.

This method requires Django REST Framework and drf-spectacular to be installed
and properly configured in the Django application.

Returns:
An OpenAPISpec instance.

Raises:
SpecInvalidError: If Django, DRF, or drf-spectacular are not installed,
or the OpenAPI specification cannot be extracted.
"""
try:
import django
from django.conf import settings
except ImportError as e:
raise SpecInvalidError(
"Django is not installed. Please install it with: pip install django",
details={"missing_package": "django"},
cause=e,
)

try:
# Check if Django REST Framework is installed
import rest_framework # noqa: F401
except ImportError as e:
raise SpecInvalidError(
"Django REST Framework is not installed. Please install it with: pip install djangorestframework",
details={"missing_package": "djangorestframework"},
cause=e,
)

try:
# Check if drf-spectacular is installed
import drf_spectacular # noqa: F401
from drf_spectacular.openapi import AutoSchema # noqa: F401
except ImportError as e:
raise SpecInvalidError(
"drf-spectacular is not installed. Please install it with: pip install drf-spectacular",
details={"missing_package": "drf-spectacular"},
cause=e,
)

try:
# Ensure Django is configured
if not settings.configured:
raise SpecInvalidError(
"Django settings are not configured. Please ensure Django is properly set up.",
details={"django_configured": False},
)

# Check if drf-spectacular is in INSTALLED_APPS
if "drf_spectacular" not in settings.INSTALLED_APPS:
raise SpecInvalidError(
"drf-spectacular is not in INSTALLED_APPS. Please add 'drf_spectacular' to your INSTALLED_APPS setting.",
details={"missing_app": "drf_spectacular"},
)

# Check if the schema class is configured
rest_framework_settings = getattr(settings, "REST_FRAMEWORK", {})
schema_class = rest_framework_settings.get("DEFAULT_SCHEMA_CLASS")

if schema_class != "drf_spectacular.openapi.AutoSchema":
raise SpecInvalidError(
"drf-spectacular AutoSchema is not configured. Please set REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'drf_spectacular.openapi.AutoSchema'",
details={"current_schema_class": schema_class},
)

# Generate the OpenAPI schema using drf-spectacular
# Use the generator directly instead of SpectacularAPIView
from drf_spectacular.generators import SchemaGenerator

generator = SchemaGenerator()
schema_dict = generator.get_schema(request=None, public=True)

return cls.from_dict(schema_dict)

except Exception as e:
raise SpecInvalidError(
f"Failed to extract OpenAPI specification from Django app: {str(e)}",
details={"django_version": getattr(django, "VERSION", "Unknown")},
cause=e,
)
Loading