axon-api


Nameaxon-api JSON
Version 0.9.0 PyPI version JSON
download
home_pagehttps://github.com/b-is-for-build/axon-api
SummaryZero-dependency WSGI framework with request batching, multipart streaming, and HTTP range support. Built for applications that require high performance without the bloat.
upload_time2025-08-19 10:18:56
maintainerNone
docs_urlNone
authorB LLC
requires_python>=3.10
licenseMIT
keywords wsgi framework streaming multipart http-range zero-dependency
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Axon API

Zero-dependency WSGI framework with request batching, multipart streaming, and HTTP range support. Built for applications that require high performance without the bloat.

## Features

- **Zero Dependencies** - Pure Python standard library implementation
- **Multipart Streaming** - Stream multiple files in a single response with boundary separation
- **HTTP Range Support** - Partial content delivery for efficient media streaming
- **Request Sanitization** - Built-in security through input validation and sanitization
- **Structured Logging** - Thread-safe JSON logging with contextual metadata
- **WSGI Compliant** - Works with any WSGI server (Gunicorn, uWSGI, Waitress)

## Installation

```bash
pip install axon-api
```

## Quick Start

### Project Structure

```
your_project/
├── main.py              # WSGI application entry point
├── routes.py            # Route definitions
├── dev_server.py        # Development server
└── axon_api/            # Framework package
    ├── core/
    └── services/
```

### Basic Setup

```python
# main.py
from axon_api.core.application import ApplicationEngine
from routes import my_routes


def application(environ, start_response):
    """WSGI application entry point."""
    return ApplicationEngine(environ, start_response, router=my_routes)
```

```python
# routes.py
import json
from urllib.parse import parse_qsl


def my_routes(environ, request_handler):
    # Setup request context
    method = environ['method']
    path = [part for part in request_handler.path.split('/') if part]
    # Log Route
    request_handler.logger.info(f"Route: {method}, {path}")
    # Handle response
    response = request_handler.response

    match (method, path):

        case ('GET', ['hello-world']):
            # Return HTML
            return response.file("examples/hello-world.html")

        case ('GET', ['multipart', 'stream']):
            # Stream files
            files = [
                'examples/files/file1.txt',
                'examples/files/file2.txt',
                'examples/files/file3.txt'
            ]
            return response.stream(files)

        case ('GET', ['api', 'query']):
            raw_query_string = environ['query_string']
            return response.json({"raw_query_string": raw_query_string})

        case ('POST', ['api', 'json']):
            try:
                body_bytes = environ['wsgi_input'].read(environ['content_length'])
                raw_json_body = body_bytes.decode('utf-8')
                raw_parsed_data = json.loads(raw_json_body)
            except (IOError, UnicodeDecodeError, json.JSONDecodeError):
                raise
            return response.json({"raw_parsed_data": raw_parsed_data})

        case ('GET', ['api', 'health']):
            # Return JSON
            return response.json({"status": "available"})

        case _:
            # Return message
            return response.message("Not Found", status_code=404)
```

```python
# dev_server.py
from main import application

if __name__ == "__main__":
    try:
        from wsgiref.simple_server import make_server

        httpd = make_server('', 9000, application)
        print('Starting server..')
        httpd.serve_forever()
    except KeyboardInterrupt:
        print('Exiting server..')
```

### Running the Application

```bash
# Development (using included server)
python dev_server.py
# Server runs on http://localhost:9000

# Production with Gunicorn
gunicorn -w 4 main:application

# Production with uWSGI
uwsgi --http :8000 --wsgi-file main.py --callable application
```

## Core Components

### ApplicationEngine

Central request processor with automatic error handling:

```python
def application(environ, start_response):
    """WSGI application entry point."""
    return ApplicationEngine(environ, start_response, router=my_routes)
```

### Response Methods

The response object provides multiple content delivery methods:

```python
def my_routes(environ, request_handler):
    # Setup request context
    method = environ['method']
    path = [part for part in request_handler.path.split('/') if part]
    # Log Route
    request_handler.logger.info(f"Route: {method}, {path}")
    # Handle response
    response = request_handler.response
    
    match (method, path):
        
        case ('GET', ['hello-world']):
            # Return HTML
            return response.file("examples/hello-world.html")

        case ('GET', ['api', 'health']):
            # Return JSON
            return response.json({"status": "available"})

        case _:
            # Return message
            return response.message("Not Found", status_code=404)
```

### Multipart Streaming

Stream multiple files in a single HTTP response:

```python
case ('GET', ['multipart', 'stream']):
    # Stream files
    files = [
        'examples/app.js',
        'examples/style.css',
        'examples/logo.png'
    ]
    return response.stream(files)
```

Browser receives multipart response with boundaries:

```
--boundary_abc123
Content-Type: text/javascript
Content-Length: 1024
Content-Disposition: inline; filename="app.js"

[file content]
--boundary_abc123
Content-Type: text/css
Content-Length: 512
Content-Disposition: inline; filename="styles.css"

[file content]
--boundary_abc123--
```

### Dynamic File Streaming

Dynamic file selection and streaming via query parameters:

```python
case ('GET', ['api', 'stream-files']):
    # Return batched response for batched request
    raw_query_string = environ['query_string']

    if not raw_query_string:
        return response.message("No files specified in query parameters", status_code=400)

    # Parse query parameters to get file paths
    query_params = dict(parse_qsl(raw_query_string))

    # Extract all file paths from query parameters
    files = list(query_params.values())

    if not files:
        return response.message("No valid file parameters found", status_code=400)

    # Stream the requested files
    return response.stream(files)
```

### Request Sanitization

All requests are automatically sanitized with configurable limits:

```python
# Sanitizer enforces:
# - Max path length: 2048 chars
# - Max header length: 8192 chars  
# - Max content length: 10MB
# - Allowed methods: GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH
# - Control character removal
# - Input stream wrapping with size limits
```

### Error Handling

Centralized error mapping with automatic logging:

```python
# Automatic error responses:
# FileNotFoundError → 404 Not Found
# PermissionError → 403 Forbidden
# SecurityError → 400 Bad Request
# ValueError (method) → 405 Method Not Allowed
# Exception → 500 Internal Server Error
```

## Advanced Usage

### Pattern Matching Routes

Leverage Python 3.10+ pattern matching:

```python
def my_routes(environ, request_handler):
    # Setup request context
    method = environ['method']
    path = [part for part in request_handler.path.split('/') if part]
    # Log Route
    request_handler.logger.info(f"Route: {method}, {path}")
    # Handle response
    response = request_handler.response

    match (method, path):

        case ('GET', ['hello-world']):
            # Return HTML
            return response.file("examples/hello-world.html")
            
        case ('GET', ['static', *filepath]):
            file_path = '/'.join(filepath)
            return response.file(f"static/{file_path}")

        case ('GET', ['api', 'health']):
            # Return JSON
            return response.json({"status": "available"})

        case _:
            # Return message
            return response.message("Not Found", status_code=404)
```

### Processing Request Data

```python
    # JSON body
    import json
    body = environ['wsgi_input'].read(environ['content_length'])
    data = json.loads(body.decode('utf-8'))
```
```python
    # Form data
    from urllib.parse import parse_qsl
    form_data = parse_qsl(body.decode('utf-8'))
```
```python
    # Query parameters
    from urllib.parse import parse_qsl
    params = dict(parse_qsl(environ['query_string']))
```

### Structured Logging

Thread-safe logging with JSON metadata:

```python
request_handler.logger.info("Request processed", 
    status_code=200, 
    path="/api/users",
    user_id=123,
    response_time=0.045
)

# Output: [2024-01-15 10:30:45] [INFO] Request processed | {"status_code":200,"path":"/api/users","user_id":123,"response_time":0.045}
```

## Architecture

```
axon_api/
├── core/
│   ├── application.py   # WSGI application engine
│   ├── response.py      # Response handling
│   ├── sanitizer.py     # Input validation and sanitization
│   ├── streaming.py     # File streaming with range support
│   ├── headers.py       # HTTP header utilities
│   ├── mimetype.py      # MIME type detection
│   └── errors.py        # Error handling and mapping
└── services/
    └── logger.py        # Thread-safe structured logging
```

## Production Deployment

### Gunicorn

```bash
gunicorn -w 4 -b 0.0.0.0:8000 \
  --access-logfile logs/access.log \
  --error-logfile logs/error.log \
  --worker-class sync \
  app:application
```

### uWSGI

```ini
[uwsgi]
module = app:application
master = true
processes = 4
socket = /tmp/axon.sock
chmod-socket = 666
vacuum = true
die-on-term = true
```

### Nginx Configuration

```nginx
upstream axon_backend {
    server 127.0.0.1:8000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;
    
    client_max_body_size 10M;
    
    location / {
        proxy_pass http://axon_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # Let Nginx handle static files directly
    location /static/ {
        alias /path/to/static/;
        expires 30d;
    }
}
```

## Performance Considerations

### Multipart Streaming Benefits

Traditional approach (10 separate requests):
```
Browser → 10 HTTP requests → Server → 10 responses
```

Axon multipart streaming (1 batched request):
```
Browser → 1 HTTP request → Server → 1 multipart response
```

Result: 90% reduction in HTTP overhead, lower latency, better connection utilization.

### Memory Efficiency

- Streaming uses generators with 64KB chunks
- No full file loading into memory
- Efficient for large file transfers
- LimitedReader enforces content-length limits

## Security Features

- **Input Sanitization**: Automatic removal of control characters
- **Size Limits**: Configurable limits for paths, headers, and content
- **Method Validation**: Only allowed HTTP methods accepted
- **Content Length Enforcement**: Prevents resource exhaustion
- **Error Masking**: Generic error messages prevent information leakage

## Limitations

- No built-in authentication/authorization
- No built-in body sanitization
- No session management
- No template engine
- No ORM/database integration
- WSGI only (no ASGI/async support)

## Requirements

- Python 3.10+ (uses structural pattern matching)
- No external dependencies

## Contributing

Contributions must:
- Maintain zero-dependency philosophy
- Use only Python standard library
- Include tests for new features
- Follow existing code patterns
- Pass security review

## License

MIT License

## Support

- Security: Report vulnerabilities to b-is-for-build@bellone.com

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/b-is-for-build/axon-api",
    "name": "axon-api",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "wsgi framework streaming multipart http-range zero-dependency",
    "author": "B LLC",
    "author_email": "b-is-for-build@bellone.com",
    "download_url": "https://files.pythonhosted.org/packages/ea/a0/be84075191e1a0f898b3529dfcdab39167b7aeecec71a898c4a2b0ded9f2/axon_api-0.9.0.tar.gz",
    "platform": null,
    "description": "# Axon API\r\n\r\nZero-dependency WSGI framework with request batching, multipart streaming, and HTTP range support. Built for applications that require high performance without the bloat.\r\n\r\n## Features\r\n\r\n- **Zero Dependencies** - Pure Python standard library implementation\r\n- **Multipart Streaming** - Stream multiple files in a single response with boundary separation\r\n- **HTTP Range Support** - Partial content delivery for efficient media streaming\r\n- **Request Sanitization** - Built-in security through input validation and sanitization\r\n- **Structured Logging** - Thread-safe JSON logging with contextual metadata\r\n- **WSGI Compliant** - Works with any WSGI server (Gunicorn, uWSGI, Waitress)\r\n\r\n## Installation\r\n\r\n```bash\r\npip install axon-api\r\n```\r\n\r\n## Quick Start\r\n\r\n### Project Structure\r\n\r\n```\r\nyour_project/\r\n\u251c\u2500\u2500 main.py              # WSGI application entry point\r\n\u251c\u2500\u2500 routes.py            # Route definitions\r\n\u251c\u2500\u2500 dev_server.py        # Development server\r\n\u2514\u2500\u2500 axon_api/            # Framework package\r\n    \u251c\u2500\u2500 core/\r\n    \u2514\u2500\u2500 services/\r\n```\r\n\r\n### Basic Setup\r\n\r\n```python\r\n# main.py\r\nfrom axon_api.core.application import ApplicationEngine\r\nfrom routes import my_routes\r\n\r\n\r\ndef application(environ, start_response):\r\n    \"\"\"WSGI application entry point.\"\"\"\r\n    return ApplicationEngine(environ, start_response, router=my_routes)\r\n```\r\n\r\n```python\r\n# routes.py\r\nimport json\r\nfrom urllib.parse import parse_qsl\r\n\r\n\r\ndef my_routes(environ, request_handler):\r\n    # Setup request context\r\n    method = environ['method']\r\n    path = [part for part in request_handler.path.split('/') if part]\r\n    # Log Route\r\n    request_handler.logger.info(f\"Route: {method}, {path}\")\r\n    # Handle response\r\n    response = request_handler.response\r\n\r\n    match (method, path):\r\n\r\n        case ('GET', ['hello-world']):\r\n            # Return HTML\r\n            return response.file(\"examples/hello-world.html\")\r\n\r\n        case ('GET', ['multipart', 'stream']):\r\n            # Stream files\r\n            files = [\r\n                'examples/files/file1.txt',\r\n                'examples/files/file2.txt',\r\n                'examples/files/file3.txt'\r\n            ]\r\n            return response.stream(files)\r\n\r\n        case ('GET', ['api', 'query']):\r\n            raw_query_string = environ['query_string']\r\n            return response.json({\"raw_query_string\": raw_query_string})\r\n\r\n        case ('POST', ['api', 'json']):\r\n            try:\r\n                body_bytes = environ['wsgi_input'].read(environ['content_length'])\r\n                raw_json_body = body_bytes.decode('utf-8')\r\n                raw_parsed_data = json.loads(raw_json_body)\r\n            except (IOError, UnicodeDecodeError, json.JSONDecodeError):\r\n                raise\r\n            return response.json({\"raw_parsed_data\": raw_parsed_data})\r\n\r\n        case ('GET', ['api', 'health']):\r\n            # Return JSON\r\n            return response.json({\"status\": \"available\"})\r\n\r\n        case _:\r\n            # Return message\r\n            return response.message(\"Not Found\", status_code=404)\r\n```\r\n\r\n```python\r\n# dev_server.py\r\nfrom main import application\r\n\r\nif __name__ == \"__main__\":\r\n    try:\r\n        from wsgiref.simple_server import make_server\r\n\r\n        httpd = make_server('', 9000, application)\r\n        print('Starting server..')\r\n        httpd.serve_forever()\r\n    except KeyboardInterrupt:\r\n        print('Exiting server..')\r\n```\r\n\r\n### Running the Application\r\n\r\n```bash\r\n# Development (using included server)\r\npython dev_server.py\r\n# Server runs on http://localhost:9000\r\n\r\n# Production with Gunicorn\r\ngunicorn -w 4 main:application\r\n\r\n# Production with uWSGI\r\nuwsgi --http :8000 --wsgi-file main.py --callable application\r\n```\r\n\r\n## Core Components\r\n\r\n### ApplicationEngine\r\n\r\nCentral request processor with automatic error handling:\r\n\r\n```python\r\ndef application(environ, start_response):\r\n    \"\"\"WSGI application entry point.\"\"\"\r\n    return ApplicationEngine(environ, start_response, router=my_routes)\r\n```\r\n\r\n### Response Methods\r\n\r\nThe response object provides multiple content delivery methods:\r\n\r\n```python\r\ndef my_routes(environ, request_handler):\r\n    # Setup request context\r\n    method = environ['method']\r\n    path = [part for part in request_handler.path.split('/') if part]\r\n    # Log Route\r\n    request_handler.logger.info(f\"Route: {method}, {path}\")\r\n    # Handle response\r\n    response = request_handler.response\r\n    \r\n    match (method, path):\r\n        \r\n        case ('GET', ['hello-world']):\r\n            # Return HTML\r\n            return response.file(\"examples/hello-world.html\")\r\n\r\n        case ('GET', ['api', 'health']):\r\n            # Return JSON\r\n            return response.json({\"status\": \"available\"})\r\n\r\n        case _:\r\n            # Return message\r\n            return response.message(\"Not Found\", status_code=404)\r\n```\r\n\r\n### Multipart Streaming\r\n\r\nStream multiple files in a single HTTP response:\r\n\r\n```python\r\ncase ('GET', ['multipart', 'stream']):\r\n    # Stream files\r\n    files = [\r\n        'examples/app.js',\r\n        'examples/style.css',\r\n        'examples/logo.png'\r\n    ]\r\n    return response.stream(files)\r\n```\r\n\r\nBrowser receives multipart response with boundaries:\r\n\r\n```\r\n--boundary_abc123\r\nContent-Type: text/javascript\r\nContent-Length: 1024\r\nContent-Disposition: inline; filename=\"app.js\"\r\n\r\n[file content]\r\n--boundary_abc123\r\nContent-Type: text/css\r\nContent-Length: 512\r\nContent-Disposition: inline; filename=\"styles.css\"\r\n\r\n[file content]\r\n--boundary_abc123--\r\n```\r\n\r\n### Dynamic File Streaming\r\n\r\nDynamic file selection and streaming via query parameters:\r\n\r\n```python\r\ncase ('GET', ['api', 'stream-files']):\r\n    # Return batched response for batched request\r\n    raw_query_string = environ['query_string']\r\n\r\n    if not raw_query_string:\r\n        return response.message(\"No files specified in query parameters\", status_code=400)\r\n\r\n    # Parse query parameters to get file paths\r\n    query_params = dict(parse_qsl(raw_query_string))\r\n\r\n    # Extract all file paths from query parameters\r\n    files = list(query_params.values())\r\n\r\n    if not files:\r\n        return response.message(\"No valid file parameters found\", status_code=400)\r\n\r\n    # Stream the requested files\r\n    return response.stream(files)\r\n```\r\n\r\n### Request Sanitization\r\n\r\nAll requests are automatically sanitized with configurable limits:\r\n\r\n```python\r\n# Sanitizer enforces:\r\n# - Max path length: 2048 chars\r\n# - Max header length: 8192 chars  \r\n# - Max content length: 10MB\r\n# - Allowed methods: GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH\r\n# - Control character removal\r\n# - Input stream wrapping with size limits\r\n```\r\n\r\n### Error Handling\r\n\r\nCentralized error mapping with automatic logging:\r\n\r\n```python\r\n# Automatic error responses:\r\n# FileNotFoundError \u2192 404 Not Found\r\n# PermissionError \u2192 403 Forbidden\r\n# SecurityError \u2192 400 Bad Request\r\n# ValueError (method) \u2192 405 Method Not Allowed\r\n# Exception \u2192 500 Internal Server Error\r\n```\r\n\r\n## Advanced Usage\r\n\r\n### Pattern Matching Routes\r\n\r\nLeverage Python 3.10+ pattern matching:\r\n\r\n```python\r\ndef my_routes(environ, request_handler):\r\n    # Setup request context\r\n    method = environ['method']\r\n    path = [part for part in request_handler.path.split('/') if part]\r\n    # Log Route\r\n    request_handler.logger.info(f\"Route: {method}, {path}\")\r\n    # Handle response\r\n    response = request_handler.response\r\n\r\n    match (method, path):\r\n\r\n        case ('GET', ['hello-world']):\r\n            # Return HTML\r\n            return response.file(\"examples/hello-world.html\")\r\n            \r\n        case ('GET', ['static', *filepath]):\r\n            file_path = '/'.join(filepath)\r\n            return response.file(f\"static/{file_path}\")\r\n\r\n        case ('GET', ['api', 'health']):\r\n            # Return JSON\r\n            return response.json({\"status\": \"available\"})\r\n\r\n        case _:\r\n            # Return message\r\n            return response.message(\"Not Found\", status_code=404)\r\n```\r\n\r\n### Processing Request Data\r\n\r\n```python\r\n    # JSON body\r\n    import json\r\n    body = environ['wsgi_input'].read(environ['content_length'])\r\n    data = json.loads(body.decode('utf-8'))\r\n```\r\n```python\r\n    # Form data\r\n    from urllib.parse import parse_qsl\r\n    form_data = parse_qsl(body.decode('utf-8'))\r\n```\r\n```python\r\n    # Query parameters\r\n    from urllib.parse import parse_qsl\r\n    params = dict(parse_qsl(environ['query_string']))\r\n```\r\n\r\n### Structured Logging\r\n\r\nThread-safe logging with JSON metadata:\r\n\r\n```python\r\nrequest_handler.logger.info(\"Request processed\", \r\n    status_code=200, \r\n    path=\"/api/users\",\r\n    user_id=123,\r\n    response_time=0.045\r\n)\r\n\r\n# Output: [2024-01-15 10:30:45] [INFO] Request processed | {\"status_code\":200,\"path\":\"/api/users\",\"user_id\":123,\"response_time\":0.045}\r\n```\r\n\r\n## Architecture\r\n\r\n```\r\naxon_api/\r\n\u251c\u2500\u2500 core/\r\n\u2502   \u251c\u2500\u2500 application.py   # WSGI application engine\r\n\u2502   \u251c\u2500\u2500 response.py      # Response handling\r\n\u2502   \u251c\u2500\u2500 sanitizer.py     # Input validation and sanitization\r\n\u2502   \u251c\u2500\u2500 streaming.py     # File streaming with range support\r\n\u2502   \u251c\u2500\u2500 headers.py       # HTTP header utilities\r\n\u2502   \u251c\u2500\u2500 mimetype.py      # MIME type detection\r\n\u2502   \u2514\u2500\u2500 errors.py        # Error handling and mapping\r\n\u2514\u2500\u2500 services/\r\n    \u2514\u2500\u2500 logger.py        # Thread-safe structured logging\r\n```\r\n\r\n## Production Deployment\r\n\r\n### Gunicorn\r\n\r\n```bash\r\ngunicorn -w 4 -b 0.0.0.0:8000 \\\r\n  --access-logfile logs/access.log \\\r\n  --error-logfile logs/error.log \\\r\n  --worker-class sync \\\r\n  app:application\r\n```\r\n\r\n### uWSGI\r\n\r\n```ini\r\n[uwsgi]\r\nmodule = app:application\r\nmaster = true\r\nprocesses = 4\r\nsocket = /tmp/axon.sock\r\nchmod-socket = 666\r\nvacuum = true\r\ndie-on-term = true\r\n```\r\n\r\n### Nginx Configuration\r\n\r\n```nginx\r\nupstream axon_backend {\r\n    server 127.0.0.1:8000;\r\n    keepalive 32;\r\n}\r\n\r\nserver {\r\n    listen 80;\r\n    server_name example.com;\r\n    \r\n    client_max_body_size 10M;\r\n    \r\n    location / {\r\n        proxy_pass http://axon_backend;\r\n        proxy_http_version 1.1;\r\n        proxy_set_header Connection \"\";\r\n        proxy_set_header Host $host;\r\n        proxy_set_header X-Real-IP $remote_addr;\r\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\r\n    }\r\n    \r\n    # Let Nginx handle static files directly\r\n    location /static/ {\r\n        alias /path/to/static/;\r\n        expires 30d;\r\n    }\r\n}\r\n```\r\n\r\n## Performance Considerations\r\n\r\n### Multipart Streaming Benefits\r\n\r\nTraditional approach (10 separate requests):\r\n```\r\nBrowser \u2192 10 HTTP requests \u2192 Server \u2192 10 responses\r\n```\r\n\r\nAxon multipart streaming (1 batched request):\r\n```\r\nBrowser \u2192 1 HTTP request \u2192 Server \u2192 1 multipart response\r\n```\r\n\r\nResult: 90% reduction in HTTP overhead, lower latency, better connection utilization.\r\n\r\n### Memory Efficiency\r\n\r\n- Streaming uses generators with 64KB chunks\r\n- No full file loading into memory\r\n- Efficient for large file transfers\r\n- LimitedReader enforces content-length limits\r\n\r\n## Security Features\r\n\r\n- **Input Sanitization**: Automatic removal of control characters\r\n- **Size Limits**: Configurable limits for paths, headers, and content\r\n- **Method Validation**: Only allowed HTTP methods accepted\r\n- **Content Length Enforcement**: Prevents resource exhaustion\r\n- **Error Masking**: Generic error messages prevent information leakage\r\n\r\n## Limitations\r\n\r\n- No built-in authentication/authorization\r\n- No built-in body sanitization\r\n- No session management\r\n- No template engine\r\n- No ORM/database integration\r\n- WSGI only (no ASGI/async support)\r\n\r\n## Requirements\r\n\r\n- Python 3.10+ (uses structural pattern matching)\r\n- No external dependencies\r\n\r\n## Contributing\r\n\r\nContributions must:\r\n- Maintain zero-dependency philosophy\r\n- Use only Python standard library\r\n- Include tests for new features\r\n- Follow existing code patterns\r\n- Pass security review\r\n\r\n## License\r\n\r\nMIT License\r\n\r\n## Support\r\n\r\n- Security: Report vulnerabilities to b-is-for-build@bellone.com\r\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Zero-dependency WSGI framework with request batching, multipart streaming, and HTTP range support. Built for applications that require high performance without the bloat.",
    "version": "0.9.0",
    "project_urls": {
        "Bug Reports": "https://github.com/b-is-for-build/axon-api/issues",
        "Documentation": "https://github.com/b-is-for-build/axon-api#readme",
        "Homepage": "https://github.com/b-is-for-build/axon-api",
        "Source": "https://github.com/b-is-for-build/axon-api"
    },
    "split_keywords": [
        "wsgi",
        "framework",
        "streaming",
        "multipart",
        "http-range",
        "zero-dependency"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0761e870e418b6ab38b8e3376d3c080214ba955dab2eb5c06c1a523fc41ead69",
                "md5": "c253af7d1f2f379538a257f29ab617c9",
                "sha256": "82d40be95fa79eff69ae7927c2b156d42d0d8000f69782cdc9f8dbe1fcdbdec4"
            },
            "downloads": -1,
            "filename": "axon_api-0.9.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c253af7d1f2f379538a257f29ab617c9",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 16998,
            "upload_time": "2025-08-19T10:18:56",
            "upload_time_iso_8601": "2025-08-19T10:18:56.099187Z",
            "url": "https://files.pythonhosted.org/packages/07/61/e870e418b6ab38b8e3376d3c080214ba955dab2eb5c06c1a523fc41ead69/axon_api-0.9.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "eaa0be84075191e1a0f898b3529dfcdab39167b7aeecec71a898c4a2b0ded9f2",
                "md5": "bbfff9712a39b62697cd2c88bb5a0f6d",
                "sha256": "e1c49d58a1d631dadc5dfab99f67ab69bf048bcde0e5cb14cfa7cd4c6541b264"
            },
            "downloads": -1,
            "filename": "axon_api-0.9.0.tar.gz",
            "has_sig": false,
            "md5_digest": "bbfff9712a39b62697cd2c88bb5a0f6d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 17945,
            "upload_time": "2025-08-19T10:18:56",
            "upload_time_iso_8601": "2025-08-19T10:18:56.887681Z",
            "url": "https://files.pythonhosted.org/packages/ea/a0/be84075191e1a0f898b3529dfcdab39167b7aeecec71a898c4a2b0ded9f2/axon_api-0.9.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-19 10:18:56",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "b-is-for-build",
    "github_project": "axon-api",
    "github_not_found": true,
    "lcname": "axon-api"
}
        
Elapsed time: 1.37289s