Source code for prs_commons.odoo.rpc_client

"""
Odoo RPC Client for interacting with Odoo's external API.

This module provides a singleton client for making RPC calls to an Odoo instance.
It handles connection management, authentication, and provides common operations.
"""

import os
from typing import Any, Dict, List, Optional, TypeVar, cast

# Import odoorpc for runtime
import odoorpc
from dotenv import load_dotenv

# Type variable for generic model types
T = TypeVar("T")


[docs] class OdooRPClient: """ A singleton client for making RPC calls to an Odoo instance. This client handles connection management, authentication, and provides methods for common Odoo operations. It's implemented as a singleton to maintain a single connection pool. Environment Variables: ODOO_HOST: The hostname of the Odoo server ODOO_DB: The database name ODOO_LOGIN: The login username ODOO_PASSWORD: The login password """ _instance: Optional["OdooRPCClient"] = None def __new__(cls) -> "OdooRPCClient": """Ensure only one instance of the client exists (singleton pattern).""" if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance
[docs] def __init__(self) -> None: """Initialize the Odoo RPC client with environment configuration.""" if getattr(self, "_initialized", False): return load_dotenv() self.host = os.getenv("ODOO_HOST") self.db = os.getenv("ODOO_DB") self.login = os.getenv("ODOO_LOGIN") self.password = os.getenv("ODOO_PASSWORD") # Use Any type to avoid mypy issues with odoorpc.ODOO self.client: Any = None self._initialized = True
[docs] def ensure_connection(self) -> None: """Ensure that we have a valid connection to the Odoo server.""" if self.client is not None: return if not all([self.host, self.db, self.login, self.password]): raise ConnectionError( "Missing required Odoo connection parameters. " "Please check your environment variables." ) try: # Initialize Odoo client self.client = odoorpc.ODOO( host=str(self.host), protocol="jsonrpc+ssl", timeout=60, port=443 ) # Login to Odoo self.client.login( db=str(self.db), login=str(self.login), password=str(self.password) ) except Exception as e: self.client = None raise ConnectionError(f"Failed to connect to Odoo server: {str(e)}") from e
[docs] def execute_method( self, model: str, method: str, ids: List[int], *args: Any, **kwargs: Any ) -> Any: """ Execute a method on a specified model for given record IDs. Args: model: The Odoo model name (e.g., 'event.registration') method: The method name to call ids: List of record IDs to operate on *args: Positional arguments to pass to the method **kwargs: Keyword arguments to pass to the method Returns: The result of the method call Raises: Exception: If the method execution fails """ try: self.ensure_connection() if self.client is None: # This should never happen due to ensure_connection raise ConnectionError("Not connected to Odoo server") model_obj = self.client.env[model] return getattr(model_obj, method)(ids, *args, **kwargs) except Exception as e: self.client = None # Reset connection on error raise Exception( f"Failed to execute method {method} on {model}: {str(e)}" ) from e
[docs] def search_read( self, model: str, domain: List[tuple], fields: Optional[List[str]] = None, offset: int = 0, limit: Optional[int] = None, order: Optional[str] = None, ) -> List[Dict[str, Any]]: """ Search and read records from a model. Args: model: The Odoo model name domain: Search domain fields: List of fields to return offset: Number of records to skip limit: Maximum number of records to return order: Sort order Returns: List of dictionaries containing the requested fields for each record """ self.ensure_connection() if self.client is None: raise ConnectionError("Not connected to Odoo server") result = self.client.env[model].search_read( domain=domain or [], fields=fields or [], offset=offset, limit=limit, order=order, ) return cast(List[Dict[str, Any]], result)
[docs] def create_record(self, model: str, values: Dict[str, Any]) -> int: """ Create a new record in the specified model. Args: model: The Odoo model name values: Dictionary of field values Returns: The ID of the created record """ self.ensure_connection() if self.client is None: raise ConnectionError("Not connected to Odoo server") result = self.client.env[model].create(values) return cast(int, result)
[docs] def write_record(self, model: str, ids: List[int], values: Dict[str, Any]) -> bool: """ Update existing records. Args: model: The Odoo model name ids: List of record IDs to update values: Dictionary of field values to update Returns: True if the update was successful """ self.ensure_connection() if self.client is None: raise ConnectionError("Not connected to Odoo server") result = self.client.env[model].browse(ids).write(values) return cast(bool, result)