Migration Guide: Upgrading to APIFromAnything 1.0.0

This guide will help you migrate your application from APIFromAnything 0.1.0 to 1.0.0. Version 1.0.0 includes significant improvements, including full async/await support, enhanced security features, and improved performance.

Overview of Changes

Major Changes

  • Full Async Support: All components now support async/await syntax

  • Improved Middleware System: Enhanced middleware architecture with better async support

  • Enhanced Security Features: Improved security middleware and decorators

  • Performance Optimizations: Better caching, connection pooling, and request coalescing

  • Comprehensive Documentation: Detailed documentation for all features

  • Improved Testing Framework: Better support for testing async functions

Breaking Changes

  1. Middleware Interface: The middleware interface has changed to better support async functions

  2. Security Decorators: Security decorators now handle async functions differently

  3. Error Handling: Error handling has been improved and may require changes to your code

  4. Configuration Options: Some configuration options have been renamed or changed

  5. Testing Utilities: Testing utilities have been updated to support async functions

Step-by-Step Migration Guide

1. Update Your Dependencies

Update your requirements.txt file to use the latest version:

- apifrom==0.1.0
+ apifrom==1.0.0

Or install the latest version directly:

pip install --upgrade apifrom

2. Update Middleware Implementation

If you’ve created custom middleware, you’ll need to update it to use the new async interface:

from apifrom.middleware import Middleware
from apifrom.core.request import Request
from apifrom.core.response import Response

class CustomMiddleware(Middleware):
-    def dispatch(self, request, call_next):
+    async def dispatch(self, request, call_next):
        # Process request
        print(f"Request: {request.method} {request.url.path}")
        
-        response = call_next(request)
+        response = await call_next(request)
        
        # Process response
        print(f"Response: {response.status_code}")
        
        return response

3. Update API Endpoints for Async Support

If you want to take advantage of async support, update your API endpoints:

from apifrom import API, api

app = API()

@api(route="/users", method="GET")
- def get_users():
+ async def get_users():
    # Your code here
    return {"users": [...]}

4. Update Database Operations

If you’re using database operations, update them to use async libraries:

- import sqlite3
+ import aiosqlite

@api(route="/users", method="GET")
- def get_users():
+ async def get_users():
-    conn = sqlite3.connect("database.db")
-    cursor = conn.cursor()
-    cursor.execute("SELECT * FROM users")
-    users = cursor.fetchall()
-    conn.close()
+    async with aiosqlite.connect("database.db") as db:
+        async with db.execute("SELECT * FROM users") as cursor:
+            users = await cursor.fetchall()
    
    return {"users": users}

5. Update HTTP Client Code

If you’re making HTTP requests, update them to use async HTTP clients:

- import requests
+ import aiohttp

@api(route="/proxy", method="GET")
- def proxy(url: str):
+ async def proxy(url: str):
-    response = requests.get(url)
-    data = response.json()
+    async with aiohttp.ClientSession() as session:
+        async with session.get(url) as response:
+            data = await response.json()
    
    return {"data": data}

6. Update Security Decorators

If you’re using security decorators, update them to handle async functions:

from apifrom import API, api
from apifrom.security import jwt_required

app = API()

@api(route="/protected", method="GET")
@jwt_required(secret="your-secret-key")
- def protected_endpoint(request):
+ async def protected_endpoint(request):
    user_id = request.state.jwt_payload.get("sub")
    return {"message": f"Hello, user {user_id}!"}

7. Update Error Handling

If you’re using custom error handling, update it to handle async functions:

from apifrom import API, api
from apifrom.middleware import ErrorHandlingMiddleware
from apifrom.exceptions import BadRequestError

app = API()

app.add_middleware(
    ErrorHandlingMiddleware(
        debug=True,
-        on_error=lambda request, exc, traceback: print(f"Error: {exc}")
+        on_error=async_error_handler
    )
)

+ async def async_error_handler(request, exc, traceback):
+    print(f"Error: {exc}")
+    # You can now perform async operations here
+    return {"error": str(exc)}, 500

@api(route="/users/{user_id}", method="GET")
- def get_user(user_id: int):
+ async def get_user(user_id: int):
    if user_id <= 0:
        raise BadRequestError(message="User ID must be positive")
    
    # Your code here
    return {"id": user_id, "name": "John Doe"}

8. Update Testing Code

If you’re using the testing utilities, update them to support async functions:

- from apifrom.testing import TestClient, MiddlewareTester
+ from apifrom.testing import TestClient, AsyncMiddlewareTester
import pytest

- def test_api():
+ @pytest.mark.asyncio
+ async def test_api():
    client = TestClient(app)
    
-    response = client.get("/users")
+    response = await client.get("/users")
    
    assert response.status_code == 200
    assert "users" in response.json()

- def test_middleware():
+ @pytest.mark.asyncio
+ async def test_middleware():
-    tester = MiddlewareTester(CustomMiddleware())
+    tester = AsyncMiddlewareTester(CustomMiddleware())
    
    request = Request(method="GET", url="/test")
    mock_response = Response(content={"message": "Test"}, status_code=200)
    
-    response = tester.test_dispatch(request=request, mock_response=mock_response)
+    response = await tester.test_dispatch(request=request, mock_response=mock_response)
    
    assert response.status_code == 200

9. Update Configuration Options

Some configuration options have been renamed or changed:

app = API(
    title="My API",
    version="1.0.0",
-    enable_docs=True,
+    docs_url="/docs",
-    enable_openapi=True,
+    openapi_url="/openapi.json",
-    debug_mode=True,
+    debug=True,
)

10. Update Performance Optimization Code

If you’re using performance optimization features, update them to support async functions:

from apifrom import API, api
from apifrom.performance import cache, coalesce_requests, batch_process

app = API()

@api(route="/expensive-operation", method="GET")
@cache(ttl=60)
- def expensive_operation():
+ async def expensive_operation():
    # Your code here
    return {"result": "expensive computation"}

@api(route="/popular-data", method="GET")
@coalesce_requests(ttl=30, max_wait_time=0.05)
- def get_popular_data():
+ async def get_popular_data():
    # Your code here
    return {"data": "popular data"}

@api(route="/users", method="POST")
@batch_process(max_batch_size=100, max_wait_time=0.1)
- def create_users(user_data_batch):
+ async def create_users(user_data_batch):
    # Your code here
    return {"status": "success"}

Common Migration Issues and Solutions

Issue 1: Coroutine Was Never Awaited Warning

Problem: You see a warning like RuntimeWarning: coroutine 'function_name' was never awaited

Solution: Ensure you’re properly awaiting all async functions:

# Incorrect
@api(route="/users", method="GET")
async def get_users():
    result = fetch_users()  # Missing await
    return result

# Correct
@api(route="/users", method="GET")
async def get_users():
    result = await fetch_users()  # Properly awaited
    return result

Issue 2: Event Loop Already Running Error

Problem: You get an error like RuntimeError: This event loop is already running

Solution: Use asyncio.create_task() instead of asyncio.run() when inside an async function:

# Incorrect - inside an async function
async def outer_function():
    # This will fail with "event loop already running"
    result = asyncio.run(inner_function())
    return result

# Correct - inside an async function
async def outer_function():
    # Create a task instead
    task = asyncio.create_task(inner_function())
    result = await task
    return result

Issue 3: Middleware Not Handling Async Functions

Problem: Your middleware is not properly handling async functions

Solution: Ensure your middleware is using await when calling the next middleware or endpoint:

# Incorrect middleware
class IncorrectMiddleware(Middleware):
    def dispatch(self, request, call_next):
        # Missing await
        response = call_next(request)
        return response

# Correct middleware
class CorrectMiddleware(Middleware):
    async def dispatch(self, request, call_next):
        # Properly awaited
        response = await call_next(request)
        return response

Issue 4: Testing Async Functions

Problem: You’re having trouble testing async functions

Solution: Use pytest.mark.asyncio to test async functions:

import pytest

# Mark the test as async
@pytest.mark.asyncio
async def test_async_function():
    # Test async function
    result = await async_function()
    assert result == "expected result"

Issue 5: Mixing Sync and Async Code

Problem: You’re trying to call an async function from a synchronous function

Solution: Use asyncio.run() to call async functions from synchronous code:

import asyncio

# Synchronous function calling an async function
def sync_function():
    # Use asyncio.run() to call async functions from sync code
    result = asyncio.run(async_function())
    return result

async def async_function():
    await asyncio.sleep(1)
    return "result"

Conclusion

Migrating to APIFromAnything 1.0.0 requires some changes to your code, but the benefits of full async support, improved security, and better performance are worth the effort. If you encounter any issues during migration, please refer to the troubleshooting guide or open an issue on the GitHub repository.

For more information about the new features in version 1.0.0, see the changelog.