Lightweight Odoo XML-RPC client with buit-in payroll novedades support
Project description
unergy-odoo
Lightweight Odoo XML-RPC client for Python. No Django required.
Installation
# From PyPI
pip install unergy-odoo
# With uv
uv add unergy-odoo
Configuration
Credentials are resolved from environment variables by default:
export ODOO_HOST="https://your-instance.odoo.com"
export ODOO_DB="your-database"
export ODOO_USERNAME="user@example.com"
export ODOO_PASSWORD="your-api-key"
Multicompany support
Use OdooCredentials to bundle credentials for a specific company and pass them
to any class. The library does not care where the values come from.
from unergy_odoo import Odoo, OdooCredentials
creds_a = OdooCredentials(
host="https://company-a.odoo.com",
db="company_a",
username="admin@a.com",
password="secret",
)
creds_b = OdooCredentials(
host="https://company-b.odoo.com",
db="company_b",
username="admin@b.com",
password="secret",
)
partners_a = Odoo("res.partner", credentials=creds_a)
partners_b = Odoo("res.partner", credentials=creds_b)
Credential resolution order for all classes:
credentialsargument (OdooCredentialsinstance)- Individual keyword arguments (
host,db,username,password) - Environment variables
Usage
Odoo — model client
from unergy_odoo import Odoo
# Query records
payslips = Odoo("hr.payslip").filter(
fields=["name", "state", "employee_id"],
filter=[["state", "=", "done"]],
limit=50,
)
# Get a single record (raises Odoo.DoesNotExist if not found)
employee = Odoo("hr.employee").get(filter=[["identification_id", "=", "1234567890"]])
# Count
total = Odoo("hr.contract").count(filter=[["state", "=", "open"]])
# Create / update / delete
record_id = Odoo("res.partner").create({"name": "Acme", "email": "acme@example.com"})
Odoo("res.partner").update([record_id], {"phone": "+57 300 000 0000"})
Odoo("res.partner").delete([record_id])
OdooExplorer — read-only introspection
Useful for discovering models, fields, and relations without risking writes.
from unergy_odoo import OdooExplorer
explorer = OdooExplorer()
# Find models by keyword
explorer.search_models("payslip")
explorer.search_models("nomina")
# Inspect fields
explorer.model_fields("hr.payslip")
explorer.model_fields("hr.payslip", field_type="selection")
# Inspect relational fields only
explorer.model_relations("hr.payslip")
# Count records (with optional domain)
explorer.count_records("hr.payslip")
explorer.count_records("hr.contract", [["state", "=", "open"]])
# Fetch sample records
explorer.sample("hr.payslip", limit=2)
explorer.sample("hr.payslip", fields=["name", "state", "employee_id"])
OdooManager — base connection
Use directly when you need raw execute_kw access.
from unergy_odoo import OdooManager
mgr = OdooManager()
uid = mgr.authenticate()
result = mgr._exec(mgr.db, uid, mgr.password, "hr.payslip", "search_count", [[]])
Novedades (payroll attachments)
High-level dataclasses for registering hr.salary.attachment records in Odoo.
TypePayment
Constants and helpers for payment period logic.
from unergy_odoo import TypePayment
from datetime import date
# Constants
TypePayment.MONTHLY # "monthly"
TypePayment.FIRST_HALF # "first_half"
TypePayment.SECOND_HALF # "second_half"
TypePayment.BOTH_FORTNIGHT # "both_fortnight"
# Determine the payment half from a date
tp = TypePayment.determine_type(date(2026, 4, 10)) # "second_half"
tp = TypePayment.determine_type(date(2026, 4, 1)) # "first_half"
# Get the actual payment date for a period
pd = TypePayment.determine_date(date(2026, 4, 10), TypePayment.SECOND_HALF) # date(2026, 4, 30)
pd = TypePayment.determine_date(date(2026, 4, 1), TypePayment.FIRST_HALF) # date(2026, 4, 15)
| Value | Quincena |
|---|---|
TypePayment.MONTHLY |
Mensual |
TypePayment.FIRST_HALF |
Primera quincena |
TypePayment.SECOND_HALF |
Segunda quincena |
TypePayment.BOTH_FORTNIGHT |
Ambas quincenas |
Auto-closing previous novedades
By default, register() closes all active (state='open') novedades for the
same employee and deduction_type before creating the new one, preventing
double payments.
To disable this behaviour, pass complete_previous=False:
bono = BonoGimnasio(
...,
complete_previous=False,
)
odoo_id = bono.register()
BonoGimnasio
from datetime import date
from unergy_odoo import BonoGimnasio, TypePayment
date_start = date(2026, 4, 1)
bono = BonoGimnasio(
identification="1234567890",
description="GIMNASIO JOHN DOE 2026-04-15 #10",
monthly_amount=80_000,
date_start=date_start,
type_payment=TypePayment.determine_type(date_start),
)
odoo_id = bono.register()
Viatico
from datetime import date
from unergy_odoo import Viatico, TypePayment
viatico = Viatico(
identification="1234567890",
description="Viático viaje Bogotá",
monthly_amount=150_000,
date_start=date(2026, 4, 1),
type_payment=TypePayment.FIRST_HALF,
attachments=["soporte.pdf"], # str, Path, or file-like object
)
odoo_id = viatico.register()
# Fixed total amount (single payment)
viatico = Viatico(
identification="1234567890",
description="Viático viaje Medellín",
monthly_amount=0,
date_start=date(2026, 4, 1),
type_payment=TypePayment.MONTHLY,
total_amount=300_000,
has_total_amount=True,
date_end=date(2026, 4, 30),
)
Deuda
from datetime import date
from unergy_odoo import Deuda, TypePayment
deuda = Deuda(
identification="1234567890",
description="Comidas 2026-04-01/2026-04-15 — D:3 A:5 C:0",
monthly_amount=45_000,
date_start=date(2026, 4, 1),
type_payment=TypePayment.FIRST_HALF,
date_end=date(2026, 4, 15), # optional
)
odoo_id = deuda.register()
Credential overrides per novedad
Pass an OdooCredentials instance to target a specific company:
from unergy_odoo import BonoGimnasio, OdooCredentials
creds = OdooCredentials(host="https://staging.odoo.com", db="staging-db",
username="test@example.com", password="staging-key")
bono = BonoGimnasio(..., odoo_credentials=creds)
Adding new novedad types
Subclass NovedadBase, set DEDUCTION_TYPE, and override _payload() for any extra fields:
from dataclasses import dataclass, field
from unergy_odoo import NovedadBase
@dataclass
class AuxilioTransporte(NovedadBase):
DEDUCTION_TYPE: str = field(init=False, default="AUX_TRANSPORTE")
def _payload(self) -> dict:
return {}
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
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 unergy_odoo-0.5.0.tar.gz.
File metadata
- Download URL: unergy_odoo-0.5.0.tar.gz
- Upload date:
- Size: 10.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
346772128ba06a9bad6104a03abf48a8bedcfa2b6bcf4eaf107fd29f713cc42f
|
|
| MD5 |
d24ad6364df31922a5af7b3a9cf3d657
|
|
| BLAKE2b-256 |
91f2ba0728bcc7c8f6772c7a792df2128057087a73920e9bd10f921d179b6ec3
|
File details
Details for the file unergy_odoo-0.5.0-py3-none-any.whl.
File metadata
- Download URL: unergy_odoo-0.5.0-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08a9059d83644d74d27b6eda1a877d3001fdbb149a43be8c58447b49ca335d18
|
|
| MD5 |
8195ed91e877809a467724864339dc10
|
|
| BLAKE2b-256 |
f64314b01761a8552e3831cada95c7c5ffbf252926dbac64a4dbd94a564d4676
|