Skip to main content

A modern, high-performance ASGI-based Python web framework.

Project description

NeutronAPI

Async Python web framework for building APIs with first-class commands, models, migrations, background tasks, and ASGI support.

Source: github.com/aaronkazah/neutronapi

Install

Use the package

pip install neutronapi
python -m neutronapi --help

Work on the framework

python3.12 -m venv venv
source venv/bin/activate
python -m pip install -e .
python -m neutronapi --help

Quick Start

neutronapi startproject blog
cd blog
python manage.py check
python manage.py start --no-reload

Create an app:

python manage.py startapp posts

Add your API in apps/posts/api.py:

from neutronapi.base import API, endpoint


class PostAPI(API):
    resource = "/posts"
    name = "posts"

    @endpoint("/", methods=["GET"], name="list")
    async def list_posts(self, scope, receive, send, **kwargs):
        return await self.response(
            [
                {"id": "post_1", "title": "Hello"},
            ]
        )

Register it in apps/entry.py:

from neutronapi.application import Application
from apps.posts.api import PostAPI


app = Application(
    apis=[
        PostAPI(),
    ],
)

Then verify and run:

python manage.py check
python manage.py test
python manage.py start --no-reload

Project Layout

myproject/
├── manage.py
└── apps/
    ├── __init__.py
    ├── settings.py
    ├── entry.py
    └── posts/
        ├── __init__.py
        ├── api.py
        ├── models.py
        ├── commands/
        ├── migrations/
        └── tests/

Core Commands

python -m neutronapi --help
neutronapi startproject blog
python manage.py check
python manage.py start
python manage.py startapp posts
python manage.py makemigrations
python manage.py migrate
python manage.py test

Test database selection:

python manage.py test
python manage.py test --database sqlite
python manage.py test --database postgres

auto is the default:

  • in the NeutronAPI source tree it uses SQLite
  • in a real project it uses DATABASES["default"]

Settings

Minimal apps/settings.py:

import os
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent.parent
ENTRY = "apps.entry:app"

DATABASES = {
    "default": {
        "ENGINE": "aiosqlite",
        "NAME": ":memory:" if os.getenv("TESTING") == "1" else BASE_DIR / "db.sqlite3",
    }
}

SECRET_KEY = os.getenv("SECRET_KEY", "dev-key-change-me")
DEBUG = os.getenv("DEBUG", "true").lower() == "true"
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]

Endpoints

Use the decorator aliases directly:

from neutronapi.base import API, endpoint, websocket


class HelloAPI(API):
    resource = "/hello"
    name = "hello"

    @endpoint("/", methods=["GET"], name="home")
    async def home(self, scope, receive, send, **kwargs):
        return await self.response({"message": "Hello from NeutronAPI"})

    @websocket("/stream")
    async def stream(self, scope, receive, send, **kwargs):
        await send({"type": "websocket.accept"})
        await send({"type": "websocket.send", "text": "connected"})
        await send({"type": "websocket.close", "code": 1000})

Models and Migrations

from neutronapi.db.fields import CharField, TextField
from neutronapi.db.models import Model


class Post(Model):
    title = CharField(max_length=255)
    body = TextField(null=True)

Generate and apply migrations:

python manage.py makemigrations
python manage.py migrate

Logging and Request IDs

NeutronAPI logs under the neutronapi.* namespace.

from neutronapi.logging import configure_logging, get_logger


configure_logging(level="INFO", fmt="json")

logger = get_logger(__name__)
logger.info("application booted")

You can also configure logging from apps/settings.py:

LOGGING = {
    "level": "INFO",
    "format": "json",
}

Every HTTP response gets X-Request-Id.

Events

from neutronapi.event_bus import events


@events.on("request.completed")
async def on_request(event):
    print(event.request_id, event.path, event.status)

Throttling

from neutronapi.base import API, endpoint
from neutronapi.throttling import BaseThrottle


class RateThrottle(BaseThrottle):
    async def allow_request(self, scope: dict) -> bool:
        return True

    async def wait(self) -> int | None:
        return None

    async def get_headers(self) -> dict[str, str]:
        return {
            "X-RateLimit-Limit": "100",
            "X-RateLimit-Remaining": "99",
            "X-RateLimit-Reset": "1717200000",
        }


class ItemAPI(API):
    resource = "/items"
    name = "items"

    @endpoint("/", methods=["POST"], throttle_classes=[RateThrottle])
    async def create_item(self, scope, receive, send, **kwargs):
        return await self.response({"ok": True})

Throttle headers are included on normal responses and 429 responses. Throttled responses also include Retry-After.

Idempotency

from neutronapi.application import Application
from neutronapi.idempotency import IdempotencyMiddleware, InMemoryIdempotencyStore


app = Application(
    apis=[PostAPI()],
    middlewares=[
        IdempotencyMiddleware(store=InMemoryIdempotencyStore(), ttl=86400),
    ],
)

Replay responses include:

  • Idempotency-Key
  • Idempotent-Replayed: true

Geo Middleware

from neutronapi.application import Application
from neutronapi.middleware import CloudflareGeoMiddleware, MaxMindGeoMiddleware


app = Application(
    apis=[PostAPI()],
    middlewares=[
        MaxMindGeoMiddleware(),
        CloudflareGeoMiddleware(),
    ],
)

Middleware order is the fallback chain. The first middleware that sets scope["_neutronapi_geo"] wins, so custom geo middleware can run ahead of the built-ins.

MaxMindGeoMiddleware uses GEOIP_DATABASE_PATH from apps/settings.py and enriches the scope with country_code, region, city, latitude, and longitude. CloudflareGeoMiddleware is a lightweight fallback that only fills country_code from cf-ipcountry.

Background Tasks

from neutronapi.application import Application
from neutronapi.background import Task, TaskFrequency


class CleanupTask(Task):
    name = "cleanup"
    frequency = TaskFrequency.HOURLY

    async def run(self, **kwargs):
        return None


app = Application(
    apis=[PostAPI()],
    tasks={"cleanup": CleanupTask()},
)

Custom Commands

Create apps/posts/commands/greet.py:

from typing import List


class Command:
    def __init__(self):
        self.help = "Greet a user"

    async def handle(self, args: List[str]) -> None:
        name = args[0] if args else "World"
        print(f"Hello, {name}!")

Run it:

python manage.py greet Alice
python manage.py greet --help

Development

Run the framework test suite from the repo root:

source venv/bin/activate
python manage.py test
python manage.py test --database sqlite
python manage.py test --database postgres

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

neutronapi-0.6.8.tar.gz (133.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

neutronapi-0.6.8-py3-none-any.whl (156.0 kB view details)

Uploaded Python 3

File details

Details for the file neutronapi-0.6.8.tar.gz.

File metadata

  • Download URL: neutronapi-0.6.8.tar.gz
  • Upload date:
  • Size: 133.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for neutronapi-0.6.8.tar.gz
Algorithm Hash digest
SHA256 4b5569b5fbf62dcb364899d95d85dd3221b20819955d0629fa1c799fe94b7d7a
MD5 18331af9a6c220dc29e3cbe1630da4e7
BLAKE2b-256 f2c023f83dd46fb60a8172e29bf463f8477cafff6473e637b4e9f60395b3849f

See more details on using hashes here.

File details

Details for the file neutronapi-0.6.8-py3-none-any.whl.

File metadata

  • Download URL: neutronapi-0.6.8-py3-none-any.whl
  • Upload date:
  • Size: 156.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for neutronapi-0.6.8-py3-none-any.whl
Algorithm Hash digest
SHA256 4b0e14ed5a45608bbb24b6cf7445bce36d79fa5dea627c743c18d32688f4fca8
MD5 f8cf5be033b898a592248991462e9a99
BLAKE2b-256 d562e8da3583b60a0aebda1069394fe389a6d24774ada37b38d9771bc36debbf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page