OdooRPC API
Project description
odoorpc-api
A modern FastAPI-based wrapper for Odoo RPC integration, designed to decouple integration logic from the core Odoo server and provide maximum flexibility for third-party ecosystems.
Why odoorpc-api?
This package is intentionally built to keep your integration layer separate from the Odoo server. By decoupling these environments, you gain the freedom to integrate Odoo with various third-party platforms without affecting the core Odoo performance or stability.
Project Roadmap 🚀
This project is currently in an early alpha stage and under active (but gradual) development. While the core foundation is functional, many features are still experimental. Our current roadmap includes several "work-in-progress" ideas that will be added over time:
- Advanced Authentication: Enhanced OAuth2 and JWT support.
- Session Management: Secure and persistent session handling.
- Redis Caching: High-performance data caching to reduce Odoo RPC overhead.
- Third-Party Logging: Integration with Sentry, BetterStack, or ELK.
- CLI Scaffolding: Generate routes, schemas, and API structures automatically with a single command.
- AI Integration: Seamless connection with LLMs (OpenAI, LangChain) for smart data querying.
- And more... to make Odoo integration smoother than ever.
Contributing
We believe in the power of the community! If you are familiar with Odoo's internal workings or FastAPI best practices, we would love to have your contribution.
Feel free to open issues, submit PRs, or suggest new features to help this package reach its full potential.
License
Distributed under the MIT License.
Note
To use Raw Query features, you must manually install the md_odoorpc_api module on your Odoo server. https://github.com/alwy95/md_odoorpc_api
Example Usage
Project Setup
# Create project
mkdir -p myproject
cd myproject
# Setup virtual environment
python3.10 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install the package
pip install odoorpc-api
# Create structure project
mkdir -p app app/routes/purchase app/routes/supplier
touch .env app/main.py
touch app/routes/purchase/__init__.py app/routes/purchase/api.py app/routes/purchase/schema.py
touch app/routes/supplier/__init__.py app/routes/supplier/api.py app/routes/supplier/schema.py
Structure Project
myproject/ <--- Root Project
├── .env
├── .gitignore
├── venv/
├── logs/ <--- AUTOMATICALLY CREATED HERE LOG_FILE=True
│ └── odooapi.log
└── app/
├── main.py
└── routes/
├── purchase/
│ ├── __init__.py
│ ├── api.py
│ └── schema.py
└── supplier/
├── __init__.py
├── api.py
└── schema.py
Configuration Management
.env.default (Default values)
# --- Application Settings ---
TITLE=OdooRPC API
VERSION=1.0
DEBUG=True
LOGFILE=True
# --- Server Configuration ---
HOST=127.0.0.1
PORT=9000
# --- Documentation & Swagger ---
API_URL=/docs
SAVE_SESSION_SWAGGER=False
# --- Odoo Backend Connection ---
ODOO_URL=http://127.0.0.1:8069
ODOO_DB=mydb
.env (Local overrides)
PORT=9000
HOST=127.0.0.1
ODOO_URL=http://127.0.0.1:8069
ODOO_DB=mydb
Entry Point
main.py
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from odoorpc_api.settings import env
from odoorpc_api.exceptions import handling_exception
from odoorpc_api.routes import handling_router
from odoorpc_api.logging_config import LOGGING_CONFIG
from odoorpc_api.middleware import LoggerMiddleware
app = FastAPI(
title=env.TITLE,
version=env.VERSION,
docs_url=env.API_URL,
swagger_ui_parameters={
"persistAuthorization": env.SAVE_SESSION_SWAGGER,
},
)
handling_exception(app)
handling_router(app)
app.add_middleware(LoggerMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
if __name__ == '__main__':
uvicorn.run(
'main:app',
host=env.HOST,
port=env.PORT,
reload=env.DEBUG,
server_header=False,
log_config=LOGGING_CONFIG
)
Route Implementation
routes/purchase/api.py
from typing import Annotated
from fastapi import APIRouter, Depends, status
from odoorpc_api.jsonrpc import OdooEnv
from odoorpc_api.params import PageParams
from odoorpc_api.responses import PaginationResponse, ListResponse, SingleResponse, BaseResponse
from odoorpc_api.types import ResponseMessage
from .schema import PurchaseList, PurchaseUpdate, model, path
router = APIRouter(tags=["Purchase"])
@router.get(path, status_code=status.HTTP_200_OK, response_model=PaginationResponse[PurchaseList])
def get(q: Annotated[PageParams, Depends()], odoo = OdooEnv):
data = odoo.search_read(
model,
q.domain,
fields=PurchaseList.fields(),
limit=q.limit,
offset=q.offset,
)
total = odoo.search_count(model, q.domain)
# PaginationResponse
return {
'message': ResponseMessage.GET,
'offset': q.offset,
'limit': q.limit,
'total': total,
'data': data,
}
# ListResponse
# return {
# 'message': ResponseMessage.GET,
# 'data': data
# }
# SingleResponse
# return {
# 'message': ResponseMessage.GET,
# 'data': data[0] if data else []
# }
# BaseResponse
# return {
# 'message': ResponseMessage.GET,
# }
@router.post(path, status_code=status.HTTP_201_CREATED, response_model=ListResponse)
def post(payload: list[PurchaseUpdate] | PurchaseUpdate, odoo = OdooEnv):
data = odoo.create(model, payload)
return {
'message': ResponseMessage.POST,
'data': data,
}
@router.put(path+'/{id}', status_code=status.HTTP_200_OK, response_model=BaseResponse)
def put(id: int, payload: PurchaseUpdate, odoo = OdooEnv):
odoo.write(model, id, payload)
return {
'message': ResponseMessage.PUT,
}
@router.delete(path+'/id', status_code=status.HTTP_204_NO_CONTENT)
def delete(id: int, odoo = OdooEnv):
odoo.button_cancel(model, id)
odoo.unlink(model, id)
routes/supplier/api.py
from typing import Annotated
from fastapi import APIRouter, Depends, status
from odoorpc_api.jsonrpc import OdooEnv
from odoorpc_api.params import PageParams
from odoorpc_api.responses import BaseResponse, PaginationResponse
from odoorpc_api.types import ResponseMessage
from odoorpc_api.utils import to_dict
from .schema import SupplierCreate, SupplierList, path
router = APIRouter(tags=["Supplier"])
@router.get(path, status_code=status.HTTP_200_OK, response_model=PaginationResponse[SupplierList])
def get(q: Annotated[PageParams, Depends()], odoo = OdooEnv):
query = """
select
count(*) over() as total_count,
rp.name as partner,
rp.type as address_type,
rp.function as position,
rp.phone as phonex,
rp.email as email,
rcs.name as state
from res_partner rp
left join res_country_state rcs on rcs.id = rp.state_id
where rp.create_date between %(create_date_from)s and %(create_date_to)s
and supplier_rank > 0
limit %(limit)s offset %(offset)s
"""
data = odoo.fetch('odoorpc.api', query, q.param_dict)
total_data = data[0].get('total_count', 0) if data else 0
data = [{k: v for k, v in item.items() if k != 'total_count'} for item in data]
return {
'message': ResponseMessage.GET,
'offset': q.offset,
'limit': q.limit,
'total': total_data,
'data': data,
}
Data Schemas
routes/purchase/schema.py
from odoorpc_api.schemas import OdooBaseModel
path = '/purchase'
model = 'purchase.order'
class PurchaseList(OdooBaseModel):
name: str
partner_id: int
class PurchaseUpdate(OdooBaseModel):
partner_id: int
routes/supplier/schema.py
from odoorpc_api.schemas import OdooBaseModel
path = '/supplier'
model = 'res.partner'
class SupplierList(OdooBaseModel):
name: str
address_type: str
phone: str
position: str
email: str
state: str
class SupplierCreate(OdooBaseModel):
name: str
address_type: str
Query Parameters Handling
The framework provides a default PageParams which includes
- offset
- limit
- create_date_from (odoo field create_date)
- create_date_to (odoo field create_date)
@router.get(path, status_code=status.HTTP_200_OK, response_model=PaginationResponse[PurchaseList])
def get(q: Annotated[PageParams, Depends()], odoo = OdooEnv):
Scenario A: Removing Parameters If you don't need pagination or filtering, simply omit the parameter from the function:
@router.get(path, status_code=status.HTTP_200_OK, response_model=PaginationResponse[PurchaseList])
def get(odoo = OdooEnv):
Scenario B: Custom Parameters Create a custom Pydantic model for specific filtering requirements:
routes/purchase/schema.py or new file routes/purchase/params.py
from datetime import date
from pydantic import BaseModel
class CustomPurchaseParam(BaseModel):
number: str
date: date
routes/purchase/api.py
# from .schema import CustomPurchaseParam # from schema.py
from .params import CustomPurchaseParam
@router.get(path, status_code=status.HTTP_200_OK, response_model=PaginationResponse[PurchaseList])
def get(q: Annotated[CustomPurchaseParam, Depends()], odoo = OdooEnv):
Execution
python main.py
# Open: http://127.0.0.1/9000/docs
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odoorpc_api-0.1.2.tar.gz.
File metadata
- Download URL: odoorpc_api-0.1.2.tar.gz
- Upload date:
- Size: 15.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
562778e8f6ac0d489b4f89b52d9c630aaaebbd94167a6bc6b3035adad49fb185
|
|
| MD5 |
3980484b6b7496ba139ef3b11d7d2312
|
|
| BLAKE2b-256 |
e071c131620c87c9e925a483b01bfcd30f7b489df29482f52d08693063bdbb86
|
Provenance
The following attestation bundles were made for odoorpc_api-0.1.2.tar.gz:
Publisher:
pypi-publish.yaml on alwy95/odoorpc-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odoorpc_api-0.1.2.tar.gz -
Subject digest:
562778e8f6ac0d489b4f89b52d9c630aaaebbd94167a6bc6b3035adad49fb185 - Sigstore transparency entry: 992691575
- Sigstore integration time:
-
Permalink:
alwy95/odoorpc-api@ed831e9b8f56da07b7a8e8e4022074139289d0b6 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/alwy95
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yaml@ed831e9b8f56da07b7a8e8e4022074139289d0b6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file odoorpc_api-0.1.2-py3-none-any.whl.
File metadata
- Download URL: odoorpc_api-0.1.2-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da4d6e3afaf1542df45cc47a68ecd5f724b7bcd798b1cc0f6b8637838c5a1904
|
|
| MD5 |
2fb883a8a20389b3cb08497600efeff2
|
|
| BLAKE2b-256 |
f28d6457ac59a12b874e8266887dedfbb1fef7d4aafb6f4b1aae0ac22ea34eb9
|
Provenance
The following attestation bundles were made for odoorpc_api-0.1.2-py3-none-any.whl:
Publisher:
pypi-publish.yaml on alwy95/odoorpc-api
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odoorpc_api-0.1.2-py3-none-any.whl -
Subject digest:
da4d6e3afaf1542df45cc47a68ecd5f724b7bcd798b1cc0f6b8637838c5a1904 - Sigstore transparency entry: 992691579
- Sigstore integration time:
-
Permalink:
alwy95/odoorpc-api@ed831e9b8f56da07b7a8e8e4022074139289d0b6 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/alwy95
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yaml@ed831e9b8f56da07b7a8e8e4022074139289d0b6 -
Trigger Event:
push
-
Statement type: