Skip to main content

Decorator-based REST API framework for Odoo modules

Project description

odoo-rest-api

A decorator-based REST API framework for Odoo. Create clean, standardized REST endpoints inside your Odoo modules with a FastAPI-like developer experience.

Features

  • Decorator-based routing@api.get(), @api.post(), @api.put(), @api.patch(), @api.delete()
  • Standardized JSON responses — Consistent {success, data, error} format
  • Automatic recordset serialization — Return env['res.partner'].search() directly, recordsets are auto-converted to dicts
  • Automatic request parsing — JSON body, query params, and path params injected via signature inspection
  • Error handling — Exception classes map to proper HTTP status codes
  • Pluggable authentication — Bring your own auth logic
  • Multi-file support — Share one API instance across partner.py, order.py, etc.
  • Odoo 16+ compatible

Installation

pip install odoo-rest-api

No Odoo module dependency needed — just a pip package.

Quick Start

Single file

# my_addon/controllers/partner_api.py
from odoo_rest_api import OdooAPI, NotFound, BadRequest

api = OdooAPI(prefix='/api/v1')

@api.get('/partners')
def list_partners(env, **params):
    limit = min(int(params.get('limit', 80)), 1000)
    offset = int(params.get('offset', 0))
    return env['res.partner'].search_read(
        [], ['name', 'email', 'phone'], limit=limit, offset=offset
    )

@api.get('/partners/{id}')
def get_partner(env, id):
    partner = env['res.partner'].browse(int(id))
    if not partner.exists():
        raise NotFound('Partner not found')
    return partner.read(['name', 'email', 'phone'])[0]

@api.post('/partners')
def create_partner(env, body):
    if not body or not body.get('name'):
        raise BadRequest("'name' is required")
    partner = env['res.partner'].create(body)
    return partner.read(['name', 'email', 'phone'])[0]

@api.put('/partners/{id}')
def update_partner(env, id, body):
    partner = env['res.partner'].browse(int(id))
    if not partner.exists():
        raise NotFound('Partner not found')
    partner.write(body)
    return partner.read(['name', 'email', 'phone'])[0]

@api.delete('/partners/{id}')
def delete_partner(env, id):
    partner = env['res.partner'].browse(int(id))
    if not partner.exists():
        raise NotFound('Partner not found')
    partner.unlink()
    return {'deleted': True}

api.register()
# my_addon/controllers/__init__.py
from . import partner_api

Multi-file (recommended)

Share one API instance across multiple files:

# controllers/app.py — shared instance
from odoo_rest_api import OdooAPI
api = OdooAPI(prefix='/api/v1')

# controllers/partner.py
from .app import api
from odoo_rest_api import NotFound

@api.get('/partners')
def list_partners(env, **params): ...

@api.get('/partners/{id}')
def get_partner(env, id): ...

# controllers/order.py
from .app import api
from odoo_rest_api import NotFound

@api.get('/orders')
def list_orders(env, **params): ...

@api.get('/orders/{id}')
def get_order(env, id): ...

# controllers/__init__.py — import routes then register
from . import partner
from . import order
from .app import api
api.register()

Test:

curl http://localhost:8069/api/v1/partners
curl http://localhost:8069/api/v1/partners/1
curl http://localhost:8069/api/v1/orders
curl -X POST -H "Content-Type: application/json" \
     -d '{"name": "John Doe", "email": "john@example.com"}' \
     http://localhost:8069/api/v1/partners

Response Format

Success

{
    "success": true,
    "data": [{"id": 1, "name": "Alice", "email": "alice@example.com"}],
    "error": null
}

Error

{
    "success": false,
    "data": null,
    "error": {
        "type": "NotFound",
        "message": "Partner not found"
    }
}

Authentication

By default, routes have no authentication (auth="none"). You add auth by providing your own handler — a function that takes request and returns a user_id.

Option 1: Inline auth handler

from odoo import SUPERUSER_ID, api as odoo_api
from odoo_rest_api import OdooAPI, Unauthorized

def my_auth(request):
    api_key = request.httprequest.headers.get('X-API-Key')
    if not api_key:
        raise Unauthorized('Missing X-API-Key header')

    env = odoo_api.Environment(request.env.cr, SUPERUSER_ID, {})
    expected = env['ir.config_parameter'].sudo().get_param('my_api.secret_key')

    if api_key != expected:
        raise Unauthorized('Invalid API key')

    return SUPERUSER_ID  # or a specific user_id

api = OdooAPI(prefix='/api/v1', auth_handler=my_auth)

Option 2: Named handler (reusable across multiple APIs)

from odoo_rest_api import register_auth_handler

register_auth_handler('my_key', my_auth)

api = OdooAPI(prefix='/api/v1', auth='my_key')

Option 3: Odoo's built-in API keys

from odoo import SUPERUSER_ID, api as odoo_api
from odoo_rest_api import OdooAPI, Unauthorized

def odoo_apikey_auth(request):
    api_key = request.httprequest.headers.get('X-API-Key')
    if not api_key:
        raise Unauthorized('Missing X-API-Key header')
    try:
        env = odoo_api.Environment(request.env.cr, SUPERUSER_ID, {})
        uid = env['res.users']._api_key_authenticate(api_key)
    except Exception:
        raise Unauthorized('Invalid API key')
    return uid

api = OdooAPI(prefix='/api/v1', auth_handler=odoo_apikey_auth)

See examples/ for a complete working addon with auth and multi-file routing.

Handler Signature

Handler arguments are injected based on parameter names:

Parameter Value
env Odoo Environment (authenticated if auth handler provided)
body Parsed JSON request body (POST/PUT/PATCH)
{name} matching path param Path parameter value (e.g. id from /partners/{id})
**params or **kwargs Remaining query string parameters
Named param matching query key Individual query parameter

Exceptions

Exception Status Code
BadRequest 400
Unauthorized 401
Forbidden 403
NotFound 404
MethodNotAllowed 405
Conflict 409
ValidationError 422
RateLimitExceeded 429

Recordset Serialization

Return recordsets directly — they're auto-converted to dicts via .read():

@api.get('/partners')
def list_partners(env):
    return env['res.partner'].search([])  # Auto-serialized to list of dicts

License

LGPL-3.0

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

odoo_rest_api-0.1.0.tar.gz (18.6 kB view details)

Uploaded Source

Built Distribution

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

odoo_rest_api-0.1.0-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file odoo_rest_api-0.1.0.tar.gz.

File metadata

  • Download URL: odoo_rest_api-0.1.0.tar.gz
  • Upload date:
  • Size: 18.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for odoo_rest_api-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a242045c226895fb095efe13b77809ff175b855a0ea97e9ad8773557aec7bc8b
MD5 760e1fca9797b9a96da686e6bdc0d792
BLAKE2b-256 c3f1f14d742416764192b33a7799fd3f02519e71bbc79043a8bb322196161b03

See more details on using hashes here.

File details

Details for the file odoo_rest_api-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: odoo_rest_api-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for odoo_rest_api-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 96aa948e893b7aa2ab12aabca6efd1ba1c3e957c6ae17810e32368a815036d8b
MD5 a35f2d7977be8adb1200c02babf15e83
BLAKE2b-256 3d6de263faa79badb50361e205a6763574bf3593715be5d1c6a04c3696b76197

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