Package to abstract the Odoo API
Project description
Odoo-Py - Python Library for Odoo ERP Integration
A complete Python library for Odoo ERP integration, specifically developed for pharmaceutical automation and e-commerce operations.
📋 Table of Contents
- Installation
- Configuration
- Available Models
- Usage Guide
- Practical Examples
- API Reference
- Error Handling
🔧 Installation
pip install odoo-py
Dependencies
pip install environs xmlrpc
⚙️ Configuration
Environment Variables
Create a .env file in your project with the following variables:
ODOO_URL=https://your-odoo.com
ODOO_DB=your_database
ODOO_USERNAME=your_username
ODOO_PASSWORD=your_password
ODOO_LANGUAGE=pt_PT
Basic Configuration
from odoo import OdooIntegration
# Using environment variables
odoo = OdooIntegration()
# Or manual configuration
odoo = OdooIntegration(
odoo_url="https://your-odoo.com",
odoo_db="your_database",
odoo_username="your_username",
odoo_password="your_password",
odoo_language="pt_PT"
)
🏗️ Available Models
Core
OdooIntegration- Base class for all integrationsPartnerModel- Partner/contact managementSaleOrderModel- Sales ordersPurchaseOrderModel- Purchase ordersProductModel- ProductsStockModel- Stock and warehouse management
Support
CountryModel- Countries and statesCompanyModel- CompaniesAccountMoveModel- Accounting moves/invoicesUserModel- UsersPaymentTermModel- Payment terms
Specialized
ProductTemplateModel- Product templatesProductCategoryModel- Product categoriesPartnerCategoryModel- Partner categoriesCRMTagModel- CRM tagsAnalyticAccountModel- Analytic accountsAccountTaxModel- TaxesAccountInvoiceModel- Invoices
🎯 Usage Guide
1. Partner/Contact Management
from odoo import PartnerModel
partner_model = PartnerModel()
# Search partner by VAT/NIF
filter_partner = [["vat", "=", "123456789"], ["is_company", "=", True]]
partners = partner_model.get_and_read_partners_by_any_filter(
filter=filter_partner,
fields=["id", "name", "email"]
)
# Create new partner
partner_data = {
"name": "Central Pharmacy",
"street": "Main Street, 123",
"city": "Lisbon",
"state_id": 1,
"country_id": 181, # Portugal
"zip": "1000-001",
"vat": "123456789",
"phone": "+351912345678",
"email": "contact@centralpharmacy.pt",
"is_company": True,
"lang": "pt_PT"
}
partner_id = partner_model.create_partner_from_scratch(partner_data)
# Update partner
partner_model.update_partner(partner_id, {"phone": "+351987654321"})
2. Sales Orders
from odoo import SaleOrderModel
sale_model = SaleOrderModel()
# Create sales order
sale_order_data = {
"partner_id": 123,
"company_id": 1,
"warehouse_id": 1,
"analytic_account_id": 1,
"origin": "WEB-001",
"client_order_ref": "REF-2024-001"
}
sale_order_id = sale_model.create_sale_order(sale_order_data)
# Add line to order
sale_line_data = {
"order_id": sale_order_id,
"product_id": 456,
"product_uom_qty": 10,
"price_unit": 15.50,
"discount": 5.0
}
sale_model.create_sale_order_line(sale_line_data)
# Confirm order
sale_model.confirm_sale_order(sale_order_id)
# Certify order (if needed)
sale_model.certify_sale_order(sale_order_id)
# Create invoice
sale_model.create_invoice_from_sale_order(sale_order_id)
3. Stock Management
from odoo import StockModel
stock_model = StockModel()
# Search warehouse by name
warehouse_id = stock_model.get_warehouse_id_by_name("Main Warehouse", company_id=1)
# Create product lot
lot_id = stock_model.create_product_lot(
product_id=123,
serie_number="LOT2024001",
company_id=1,
expiration_date="2025-12-31",
removal_date="2025-11-30",
alert_date="2025-10-31",
use_date="2025-09-30"
)
# Validate picking/delivery
picking_ids = stock_model.get_picking_id_list_by_sale_order_id(sale_order_id)
for picking_id in picking_ids:
stock_model.validate_picking(picking_id)
4. Products
from odoo import ProductModel
product_model = ProductModel()
# Search product by reference
try:
product_id = product_model.get_product_id_by_reference("REF-PROD-001")
product_data = product_model.get_product_by_id(product_id)
print(f"Product found: {product_data[0]['name']}")
except ProductNotFoundError:
print("Product not found")
📚 Practical Examples
Example 1: Create Contact and Complete Order
from odoo import PartnerModel, SaleOrderModel, CompanyModel, StockModel
from odoo import CountryModel, AnalyticAccountModel, PaymentTermModel
def create_complete_order():
# Initialize models
partner_model = PartnerModel()
sale_model = SaleOrderModel()
country_model = CountryModel()
company_model = CompanyModel()
stock_model = StockModel()
analytic_model = AnalyticAccountModel()
payment_model = PaymentTermModel()
# 1. Get necessary IDs
country_id = country_model.get_country_id_by_name("Portugal")
state_id = country_model.get_country_state_id_by_name("Lisboa")
company_id = company_model.get_company_id_by_name("ADDO PHARM")
warehouse_id = stock_model.get_warehouse_id_by_name("Central Warehouse", company_id)
analytic_id = analytic_model.get_analytic_account_id_by_name("Online Sales")
payment_term_id = payment_model.get_payment_term_id_by_name("30 days")
# 2. Create contact
contact_data = {
"name": "New Pharmacy",
"street": "Liberty Avenue, 200",
"city": "Lisbon",
"state_id": state_id,
"country_id": country_id,
"zip": "1000-200",
"vat": "987654321",
"phone": "+351911223344",
"email": "contact@newpharmacy.pt",
"is_company": True,
"x_studio_tipo_de_contacto_1": "Farmácia",
"x_studio_cdigo_anf_1": "ANF001",
"x_studio_nome_da_farmcia_1": "New Pharmacy",
"lang": "pt_PT"
}
partner_id = partner_model.create_partner_from_scratch(contact_data)
# 3. Create sales order
sale_data = {
"partner_id": partner_id,
"company_id": company_id,
"warehouse_id": warehouse_id,
"analytic_account_id": analytic_id,
"payment_term_id": payment_term_id,
"origin": "WEB-2024-001",
"client_order_ref": "NEW-PHARM-001"
}
sale_order_id = sale_model.create_sale_order(sale_data)
# 4. Add products
products = [
{"product_id": 123, "qty": 5, "price": 12.50},
{"product_id": 124, "qty": 2, "price": 35.00}
]
for product in products:
line_data = {
"order_id": sale_order_id,
"product_id": product["product_id"],
"product_uom_qty": product["qty"],
"price_unit": product["price"]
}
sale_model.create_sale_order_line(line_data)
# 5. Confirm order
sale_model.confirm_sale_order(sale_order_id)
return {
"partner_id": partner_id,
"sale_order_id": sale_order_id,
"message": "Order created successfully!"
}
# Execute example
result = create_complete_order()
print(result)
Example 2: Purchase Order Processing
from odoo import PurchaseOrderModel, PartnerModel, StockModel
def process_purchase_order():
purchase_model = PurchaseOrderModel()
partner_model = PartnerModel()
stock_model = StockModel()
# Search supplier
supplier_filter = [["supplier_rank", ">", 0], ["name", "ilike", "Supplier XYZ"]]
suppliers = partner_model.get_and_read_partners_by_any_filter(
filter=supplier_filter,
fields=["id", "name"]
)
if not suppliers:
print("Supplier not found")
return
supplier_id = suppliers[0]["id"]
# Create purchase order
purchase_data = {
"partner_id": supplier_id,
"company_id": 1,
"payment_term_id": 1,
"picking_type_id": 1,
"partner_ref": "PO-2024-001"
}
purchase_id = purchase_model.create_purchase_order(purchase_data)
# Add lines
line_data = {
"order_id": purchase_id,
"product_id": 789,
"product_qty": 100,
"price_unit": 8.75
}
purchase_model.create_purchase_order_line(line_data)
return purchase_id
purchase_id = process_purchase_order()
print(f"Purchase order created: {purchase_id}")
Example 3: Invoice Management
from odoo import AccountMoveModel, SaleOrderModel
def process_invoice(sale_order_id):
sale_model = SaleOrderModel()
account_model = AccountMoveModel()
# Create invoice from order
sale_model.create_invoice_from_sale_order(sale_order_id)
# Get created invoice ID
invoice_id = sale_model.get_invoice_id_from_sale_order(sale_order_id)
# Add footer notes
footer_notes = "Thank you for your preference! Validity: 30 days"
account_model.invoice_insert_footer_notes(invoice_id, footer_notes)
# Confirm invoice
account_model.confirm_invoice_purchase_order(invoice_id)
return invoice_id
📖 API Reference
OdooIntegration (Base Class)
Main Methods
search(model, search_params)- Search recordssearch_read(model, search_params, fields, limit, offset)- Search and read recordssearch_count(model, search_params)- Count recordsread(model, ids, fields)- Read specific recordscreate(model, data)- Create new recordsupdate(model, object_id, data)- Update recordsexecute_action(model, action_type, object_id, extra_context)- Execute actions
PartnerModel
Specific Methods
get_partner_by_id(partner_id, fields=None)get_partner_id_by_name(partner_name)create_partner_from_scratch(partner_data)update_partner(partner_id, partner_data)get_and_read_all_partners(fields=None, limit=10, offset=0)get_and_read_partners_by_any_filter(filter, fields=None, limit=10, offset=0)count_partners_by_any_filter(filter=None)action_archive_partner(partner_id)
SaleOrderModel
Specific Methods
get_sale_order_by_id(sale_order_id, fields=None)get_sale_order_by_state(state)create_sale_order(sale_order_data)create_sale_order_line(sale_order_line_data)confirm_sale_order(sale_order_id)certify_sale_order(sale_order_id)create_invoice_from_sale_order(sale_order_id)get_invoice_id_from_sale_order(sale_order_id)
PurchaseOrderModel
Specific Methods
get_purchase_order_by_id(purchase_order_id, fields=None)get_purchase_order_by_state(state)create_purchase_order(purchase_order_data)create_purchase_order_line(purchase_order_line_data)create_invoice_from_purchase_order(purchase_order_id)
StockModel
Specific Methods
get_warehouse_id_by_name(warehouse_name, company_id)create_product_lot(product_id, serie_number, company_id, expiration_date, ...)get_product_lot_id_by_serie_name_and_product_id(serie_name, product_id, company_id)validate_picking(picking_id)get_picking_id_list_by_sale_order_id(sale_order_id)get_picking_id_list_by_purchase_order_id(purchase_order_id)
ProductModel
Specific Methods
get_product_by_id(product_id)get_product_id_by_reference(reference_id)
CountryModel
Specific Methods
get_country_id_by_code(country_code)get_country_id_by_name(country_name)get_country_state_id_by_name(state_name)
CompanyModel
Specific Methods
get_company_by_id(company_id)get_company_id_by_name(company_name)
AccountMoveModel
Specific Methods
get_invoice_by_id(invoice_id)create_account_move(account_move_data)update_account_move(account_move_id, data)invoice_insert_footer_notes(invoice_id, notes)confirm_invoice_purchase_order(invoice_id)
🔍 Common Filters
Filter Examples
# Search by VAT
filter_vat = [["vat", "=", "123456789"]]
# Search active companies
filter_companies = [["is_company", "=", True], ["active", "=", True]]
# Search by date
filter_date = [["create_date", ">=", "2024-01-01"]]
# Multiple conditions (AND)
filter_complex = [
["vat", "=", "123456789"],
["is_company", "=", True],
["active", "=", True]
]
# OR conditions
filter_or = ["|", ["name", "ilike", "Pharmacy"], ["name", "ilike", "Clinic"]]
# Available operators
# =, !=, >, >=, <, <=, like, ilike, in, not in
Pharmacy-Specific Filters
# Search pharmacies
filter_pharmacy = [
["x_studio_tipo_de_contacto_1", "=", "Farmácia"],
["is_company", "=", True]
]
# Search by ANF code
filter_anf = [["x_studio_cdigo_anf_1", "=", "12345"]]
# Sales orders in specific state
filter_sale_orders = [["state", "in", ["draft", "sent", "sale"]]]
# Products with low stock
filter_low_stock = [["qty_available", "<=", 10]]
⚠️ Error Handling
Custom Exceptions
from odoo.exceptions import ProductNotFoundError, LotProductNotFoundError, TooManyLotProducError
try:
product_id = product_model.get_product_id_by_reference("REF-001")
except ProductNotFoundError as e:
print(f"Product not found: {e}")
try:
lot_id = stock_model.get_product_lot_id_by_serie_name_and_product_id("LOT001", 123)
except LotProductNotFoundError as e:
print(f"Lot not found: {e}")
except TooManyLotProducError as e:
print(f"Multiple lots found: {e}")
Generic Error Handling
import xmlrpc.client
try:
partner_id = partner_model.create_partner_from_scratch(partner_data)
except xmlrpc.client.Fault as e:
print(f"Odoo error: {e.faultString}")
if "already exists" in e.faultString:
print("Duplicate record")
elif "access denied" in e.faultString:
print("Insufficient permissions")
except Exception as e:
print(f"Unexpected error: {e}")
Retry Logic Example
import time
from functools import wraps
def retry_on_error(max_retries=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}")
time.sleep(delay)
return None
return wrapper
return decorator
@retry_on_error(max_retries=3)
def create_partner_with_retry(partner_data):
partner_model = PartnerModel()
return partner_model.create_partner_from_scratch(partner_data)
🔐 Security
Best Practices
- Never commit credentials to code
- Use environment variables for sensitive configuration
- Validate data before sending to Odoo
- Implement retry logic for critical operations
- Log important operations for auditing
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_safe_partner(partner_data):
# Validate required fields
required_fields = ["name", "vat"]
for field in required_fields:
if not partner_data.get(field):
raise ValueError(f"Required field missing: {field}")
# Validate Portuguese VAT
vat = partner_data["vat"]
if not vat.isdigit() or len(vat) != 9:
raise ValueError("VAT must have 9 numeric digits")
# Sanitize data
partner_data["name"] = partner_data["name"].strip()
partner_data["email"] = partner_data.get("email", "").lower().strip()
logger.info(f"Creating partner: {partner_data['name']} - VAT: {vat}")
try:
partner_model = PartnerModel()
partner_id = partner_model.create_partner_from_scratch(partner_data)
logger.info(f"Partner created successfully. ID: {partner_id}")
return partner_id
except Exception as e:
logger.error(f"Error creating partner: {e}")
raise
🚀 Performance
Optimization Tips
# 1. Use specific fields in queries
fields = ["id", "name", "vat"] # Only necessary fields
partners = partner_model.get_and_read_partners_by_any_filter(
filter=filter_data,
fields=fields,
limit=100
)
# 2. Batch operations when possible
partner_data_list = [partner1_data, partner2_data, partner3_data]
# Instead of creating one by one, use create with list (when supported)
# 3. Cache frequently used data
country_cache = {}
def get_cached_country_id(country_name):
if country_name not in country_cache:
country_model = CountryModel()
country_cache[country_name] = country_model.get_country_id_by_name(country_name)
return country_cache[country_name]
# 4. Use limit and offset for pagination
offset = 0
limit = 100
while True:
partners = partner_model.get_and_read_all_partners(
fields=["id", "name"],
limit=limit,
offset=offset
)
if not partners:
break
# Process partners
for partner in partners:
process_partner(partner)
offset += limit
🧪 Testing
Unit Test Example
import unittest
from unittest.mock import Mock, patch
from odoo import PartnerModel
class TestPartnerModel(unittest.TestCase):
def setUp(self):
self.partner_model = PartnerModel()
@patch('odoo._integration.xmlrpc.client.ServerProxy')
def test_create_partner_success(self, mock_proxy):
# Mock Odoo response
mock_proxy.return_value.execute_kw.return_value = [123]
partner_data = {
"name": "Test Pharmacy",
"vat": "123456789"
}
result = self.partner_model.create_partner_from_scratch(partner_data)
self.assertEqual(result, [123])
@patch('odoo._integration.xmlrpc.client.ServerProxy')
def test_get_partner_not_found(self, mock_proxy):
# Mock empty response
mock_proxy.return_value.execute_kw.return_value = []
with self.assertRaises(Exception) as context:
self.partner_model.get_partner_id_by_name("Does Not Exist")
self.assertIn("not found", str(context.exception))
if __name__ == '__main__':
unittest.main()
📦 Deployment
Example Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
ENV PYTHONPATH="/app"
CMD ["python", "main.py"]
requirements.txt
odoo-py
environs
pymssql
🤝 Contributing
- Fork the project
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🆘 Support
For support, open an issue on GitHub or contact the development team.
Developed with ❤️ for pharmaceutical automation
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 odoo_py-0.0.17.tar.gz.
File metadata
- Download URL: odoo_py-0.0.17.tar.gz
- Upload date:
- Size: 22.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9262db70049999a376216f9a619a40d185a4b7417678d5edb44e6415765c1acb
|
|
| MD5 |
307b8aa52809b3ae334bc0f4071e7311
|
|
| BLAKE2b-256 |
4793a2e6d9c6cbe7ca107f295619263a14cb6c92b19182239e269156f6e051ab
|
File details
Details for the file odoo_py-0.0.17-py3-none-any.whl.
File metadata
- Download URL: odoo_py-0.0.17-py3-none-any.whl
- Upload date:
- Size: 24.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e7b5349ef3b973acff39e268427229e31ecdaf2a89f63a1d8494e0b7410987d
|
|
| MD5 |
9d18c11b566e8ef23b7632290fe2bace
|
|
| BLAKE2b-256 |
051cc433cdd2f85049e06db1cf5194c3d83f79211e251e9aa4db60815a4cfa21
|