odoo-easy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo_easy-0.1.0/LICENSE +21 -0
- odoo_easy-0.1.0/PKG-INFO +154 -0
- odoo_easy-0.1.0/README.md +131 -0
- odoo_easy-0.1.0/odoo_connect/__init__.py +5 -0
- odoo_easy-0.1.0/odoo_connect/client.py +231 -0
- odoo_easy-0.1.0/odoo_connect/exceptions.py +13 -0
- odoo_easy-0.1.0/odoo_easy.egg-info/PKG-INFO +154 -0
- odoo_easy-0.1.0/odoo_easy.egg-info/SOURCES.txt +10 -0
- odoo_easy-0.1.0/odoo_easy.egg-info/dependency_links.txt +1 -0
- odoo_easy-0.1.0/odoo_easy.egg-info/top_level.txt +1 -0
- odoo_easy-0.1.0/pyproject.toml +28 -0
- odoo_easy-0.1.0/setup.cfg +4 -0
odoo_easy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antonio Maminiaina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
odoo_easy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: odoo-easy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python wrapper for the Odoo XML-RPC API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/atovoman/odoo-easy
|
|
7
|
+
Project-URL: Repository, https://github.com/atovoman/odoo-easy
|
|
8
|
+
Project-URL: Issues, https://github.com/atovoman/odoo-easy/issues
|
|
9
|
+
Keywords: odoo,xmlrpc,erp,api,wrapper
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# odoo-easy
|
|
25
|
+
|
|
26
|
+
A lightweight Python wrapper for the Odoo XML-RPC API. Stop writing boilerplate — connect and query Odoo in 3 lines.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install odoo-easy
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from odoo_connect import OdooClient
|
|
38
|
+
|
|
39
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
40
|
+
odoo.login("admin@example.com", "your-password")
|
|
41
|
+
|
|
42
|
+
# List products
|
|
43
|
+
products = odoo.search_read("product.template", fields=["name", "list_price"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Connect
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from odoo_connect import OdooClient
|
|
52
|
+
|
|
53
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
54
|
+
odoo.login("admin@example.com", "your-password")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Search records
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# Get all IDs
|
|
61
|
+
ids = odoo.search("product.template")
|
|
62
|
+
|
|
63
|
+
# With filters
|
|
64
|
+
ids = odoo.search("product.template", filters=[["type", "=", "product"]])
|
|
65
|
+
|
|
66
|
+
# With limit
|
|
67
|
+
ids = odoo.search("product.template", limit=10)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Search and read in one call
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
products = odoo.search_read(
|
|
74
|
+
"product.template",
|
|
75
|
+
filters=[["sale_ok", "=", True]],
|
|
76
|
+
fields=["name", "list_price", "categ_id"],
|
|
77
|
+
limit=50
|
|
78
|
+
)
|
|
79
|
+
# Returns: [{"id": 1, "name": "My Product", "list_price": 99.0, ...}, ...]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Read by IDs
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
records = odoo.read("res.partner", ids=[1, 2, 3], fields=["name", "email"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Create a record
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
new_id = odoo.create("product.template", {
|
|
92
|
+
"name": "New Product",
|
|
93
|
+
"list_price": 49.99,
|
|
94
|
+
"type": "product",
|
|
95
|
+
})
|
|
96
|
+
print(f"Created product with ID: {new_id}")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Update records
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
odoo.write("product.template", ids=[1, 2, 3], values={
|
|
103
|
+
"list_price": 59.99
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Delete records
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
odoo.unlink("product.template", ids=[99])
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Count records
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
total = odoo.count("product.template", filters=[["active", "=", True]])
|
|
117
|
+
print(f"Total active products: {total}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Inspect model fields
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
fields = odoo.get_fields("product.template", attributes=["string", "type"])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Get server version
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
info = odoo.version()
|
|
130
|
+
print(info) # {'server_version': '18.0', ...}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Error Handling
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from odoo_connect import OdooClient, OdooAuthError, OdooError
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
140
|
+
odoo.login("wrong@example.com", "badpassword")
|
|
141
|
+
except OdooAuthError as e:
|
|
142
|
+
print(f"Authentication failed: {e}")
|
|
143
|
+
except OdooError as e:
|
|
144
|
+
print(f"Odoo error: {e}")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Compatibility
|
|
148
|
+
|
|
149
|
+
- Python 3.8+
|
|
150
|
+
- Odoo 14, 15, 16, 17, 18
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# odoo-easy
|
|
2
|
+
|
|
3
|
+
A lightweight Python wrapper for the Odoo XML-RPC API. Stop writing boilerplate — connect and query Odoo in 3 lines.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install odoo-easy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from odoo_connect import OdooClient
|
|
15
|
+
|
|
16
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
17
|
+
odoo.login("admin@example.com", "your-password")
|
|
18
|
+
|
|
19
|
+
# List products
|
|
20
|
+
products = odoo.search_read("product.template", fields=["name", "list_price"])
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Connect
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from odoo_connect import OdooClient
|
|
29
|
+
|
|
30
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
31
|
+
odoo.login("admin@example.com", "your-password")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Search records
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Get all IDs
|
|
38
|
+
ids = odoo.search("product.template")
|
|
39
|
+
|
|
40
|
+
# With filters
|
|
41
|
+
ids = odoo.search("product.template", filters=[["type", "=", "product"]])
|
|
42
|
+
|
|
43
|
+
# With limit
|
|
44
|
+
ids = odoo.search("product.template", limit=10)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Search and read in one call
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
products = odoo.search_read(
|
|
51
|
+
"product.template",
|
|
52
|
+
filters=[["sale_ok", "=", True]],
|
|
53
|
+
fields=["name", "list_price", "categ_id"],
|
|
54
|
+
limit=50
|
|
55
|
+
)
|
|
56
|
+
# Returns: [{"id": 1, "name": "My Product", "list_price": 99.0, ...}, ...]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Read by IDs
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
records = odoo.read("res.partner", ids=[1, 2, 3], fields=["name", "email"])
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Create a record
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
new_id = odoo.create("product.template", {
|
|
69
|
+
"name": "New Product",
|
|
70
|
+
"list_price": 49.99,
|
|
71
|
+
"type": "product",
|
|
72
|
+
})
|
|
73
|
+
print(f"Created product with ID: {new_id}")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Update records
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
odoo.write("product.template", ids=[1, 2, 3], values={
|
|
80
|
+
"list_price": 59.99
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Delete records
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
odoo.unlink("product.template", ids=[99])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Count records
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
total = odoo.count("product.template", filters=[["active", "=", True]])
|
|
94
|
+
print(f"Total active products: {total}")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Inspect model fields
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
fields = odoo.get_fields("product.template", attributes=["string", "type"])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Get server version
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
info = odoo.version()
|
|
107
|
+
print(info) # {'server_version': '18.0', ...}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Error Handling
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from odoo_connect import OdooClient, OdooAuthError, OdooError
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
117
|
+
odoo.login("wrong@example.com", "badpassword")
|
|
118
|
+
except OdooAuthError as e:
|
|
119
|
+
print(f"Authentication failed: {e}")
|
|
120
|
+
except OdooError as e:
|
|
121
|
+
print(f"Odoo error: {e}")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Compatibility
|
|
125
|
+
|
|
126
|
+
- Python 3.8+
|
|
127
|
+
- Odoo 14, 15, 16, 17, 18
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import xmlrpc.client
|
|
2
|
+
from .exceptions import OdooAuthError, OdooRPCError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OdooClient:
|
|
6
|
+
"""
|
|
7
|
+
A lightweight Python wrapper for the Odoo XML-RPC API.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
from odoo_connect import OdooClient
|
|
11
|
+
|
|
12
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
13
|
+
odoo.login("admin@example.com", "password")
|
|
14
|
+
|
|
15
|
+
products = odoo.search_read("product.template", fields=["name", "list_price"])
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, url: str, db: str):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the Odoo client.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
url: The base URL of your Odoo instance (e.g. "https://mycompany.odoo.com")
|
|
24
|
+
db: The database name
|
|
25
|
+
"""
|
|
26
|
+
self.url = url.rstrip("/")
|
|
27
|
+
self.db = db
|
|
28
|
+
self.uid = None
|
|
29
|
+
self._password = None
|
|
30
|
+
|
|
31
|
+
self._common = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/common")
|
|
32
|
+
self._models = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/object")
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
# Auth
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
def login(self, username: str, password: str) -> int:
|
|
39
|
+
"""
|
|
40
|
+
Authenticate against the Odoo instance.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
username: Your Odoo login (usually an email)
|
|
44
|
+
password: Your Odoo password or API key
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The user ID (uid)
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
OdooAuthError: If authentication fails
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
uid = self._common.authenticate(self.db, username, password, {})
|
|
54
|
+
except Exception as e:
|
|
55
|
+
raise OdooRPCError(f"Connection error: {e}") from e
|
|
56
|
+
|
|
57
|
+
if not uid:
|
|
58
|
+
raise OdooAuthError(
|
|
59
|
+
f"Login failed for user '{username}' on database '{self.db}'. "
|
|
60
|
+
"Check your credentials."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.uid = uid
|
|
64
|
+
self._password = password
|
|
65
|
+
return uid
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Internal helper
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def _execute(self, model: str, method: str, *args, **kwargs):
|
|
72
|
+
"""Low-level execute_kw wrapper."""
|
|
73
|
+
if not self.uid:
|
|
74
|
+
raise OdooAuthError("Not logged in. Call login() first.")
|
|
75
|
+
try:
|
|
76
|
+
return self._models.execute_kw(
|
|
77
|
+
self.db, self.uid, self._password,
|
|
78
|
+
model, method, args, kwargs
|
|
79
|
+
)
|
|
80
|
+
except xmlrpc.client.Fault as e:
|
|
81
|
+
raise OdooRPCError(f"Odoo error on {model}.{method}: {e.faultString}") from e
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
# CRUD operations
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def search(self, model: str, filters: list = None, limit: int = None, offset: int = 0) -> list:
|
|
88
|
+
"""
|
|
89
|
+
Search for record IDs matching the given filters.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
model: Odoo model name (e.g. "product.template")
|
|
93
|
+
filters: Domain filter list (e.g. [["active", "=", True]])
|
|
94
|
+
limit: Max number of results (None = no limit)
|
|
95
|
+
offset: Number of records to skip
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of matching record IDs
|
|
99
|
+
"""
|
|
100
|
+
domain = filters or []
|
|
101
|
+
kwargs = {"offset": offset}
|
|
102
|
+
if limit is not None:
|
|
103
|
+
kwargs["limit"] = limit
|
|
104
|
+
return self._execute(model, "search", domain, **kwargs)
|
|
105
|
+
|
|
106
|
+
def search_read(self, model: str, filters: list = None, fields: list = None,
|
|
107
|
+
limit: int = None, offset: int = 0) -> list:
|
|
108
|
+
"""
|
|
109
|
+
Search and return records with their field values.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
model: Odoo model name
|
|
113
|
+
filters: Domain filter list
|
|
114
|
+
fields: List of field names to return (None = all fields)
|
|
115
|
+
limit: Max number of results
|
|
116
|
+
offset: Number of records to skip
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of dicts with the requested field values
|
|
120
|
+
"""
|
|
121
|
+
domain = filters or []
|
|
122
|
+
kwargs = {"offset": offset}
|
|
123
|
+
if fields:
|
|
124
|
+
kwargs["fields"] = fields
|
|
125
|
+
if limit is not None:
|
|
126
|
+
kwargs["limit"] = limit
|
|
127
|
+
return self._execute(model, "search_read", domain, **kwargs)
|
|
128
|
+
|
|
129
|
+
def read(self, model: str, ids: list, fields: list = None) -> list:
|
|
130
|
+
"""
|
|
131
|
+
Read specific records by their IDs.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
model: Odoo model name
|
|
135
|
+
ids: List of record IDs to read
|
|
136
|
+
fields: List of field names to return (None = all fields)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of dicts with the requested field values
|
|
140
|
+
"""
|
|
141
|
+
kwargs = {}
|
|
142
|
+
if fields:
|
|
143
|
+
kwargs["fields"] = fields
|
|
144
|
+
return self._execute(model, "read", ids, **kwargs)
|
|
145
|
+
|
|
146
|
+
def create(self, model: str, values: dict) -> int:
|
|
147
|
+
"""
|
|
148
|
+
Create a new record.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
model: Odoo model name
|
|
152
|
+
values: Dict of field values for the new record
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
ID of the newly created record
|
|
156
|
+
"""
|
|
157
|
+
return self._execute(model, "create", values)
|
|
158
|
+
|
|
159
|
+
def write(self, model: str, ids: list, values: dict) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
Update existing records.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
model: Odoo model name
|
|
165
|
+
ids: List of record IDs to update
|
|
166
|
+
values: Dict of field values to update
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
True if successful
|
|
170
|
+
"""
|
|
171
|
+
return self._execute(model, "write", ids, values)
|
|
172
|
+
|
|
173
|
+
def unlink(self, model: str, ids: list) -> bool:
|
|
174
|
+
"""
|
|
175
|
+
Delete records.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
model: Odoo model name
|
|
179
|
+
ids: List of record IDs to delete
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
True if successful
|
|
183
|
+
"""
|
|
184
|
+
return self._execute(model, "unlink", ids)
|
|
185
|
+
|
|
186
|
+
def count(self, model: str, filters: list = None) -> int:
|
|
187
|
+
"""
|
|
188
|
+
Count records matching the given filters.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
model: Odoo model name
|
|
192
|
+
filters: Domain filter list
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Number of matching records
|
|
196
|
+
"""
|
|
197
|
+
domain = filters or []
|
|
198
|
+
return self._execute(model, "search_count", domain)
|
|
199
|
+
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
# Extras
|
|
202
|
+
# ------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
def get_fields(self, model: str, attributes: list = None) -> dict:
|
|
205
|
+
"""
|
|
206
|
+
Get the field definitions for a model.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
model: Odoo model name
|
|
210
|
+
attributes: List of attributes to return (e.g. ["string", "type"])
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dict of field definitions
|
|
214
|
+
"""
|
|
215
|
+
kwargs = {}
|
|
216
|
+
if attributes:
|
|
217
|
+
kwargs["attributes"] = attributes
|
|
218
|
+
return self._execute(model, "fields_get", [], **kwargs)
|
|
219
|
+
|
|
220
|
+
def version(self) -> dict:
|
|
221
|
+
"""
|
|
222
|
+
Get the Odoo server version info.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Dict with version information
|
|
226
|
+
"""
|
|
227
|
+
return self._common.version()
|
|
228
|
+
|
|
229
|
+
def __repr__(self):
|
|
230
|
+
status = f"uid={self.uid}" if self.uid else "not logged in"
|
|
231
|
+
return f"OdooClient(url='{self.url}', db='{self.db}', {status})"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: odoo-easy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python wrapper for the Odoo XML-RPC API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/atovoman/odoo-easy
|
|
7
|
+
Project-URL: Repository, https://github.com/atovoman/odoo-easy
|
|
8
|
+
Project-URL: Issues, https://github.com/atovoman/odoo-easy/issues
|
|
9
|
+
Keywords: odoo,xmlrpc,erp,api,wrapper
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# odoo-easy
|
|
25
|
+
|
|
26
|
+
A lightweight Python wrapper for the Odoo XML-RPC API. Stop writing boilerplate — connect and query Odoo in 3 lines.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install odoo-easy
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from odoo_connect import OdooClient
|
|
38
|
+
|
|
39
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
40
|
+
odoo.login("admin@example.com", "your-password")
|
|
41
|
+
|
|
42
|
+
# List products
|
|
43
|
+
products = odoo.search_read("product.template", fields=["name", "list_price"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Connect
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from odoo_connect import OdooClient
|
|
52
|
+
|
|
53
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
54
|
+
odoo.login("admin@example.com", "your-password")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Search records
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# Get all IDs
|
|
61
|
+
ids = odoo.search("product.template")
|
|
62
|
+
|
|
63
|
+
# With filters
|
|
64
|
+
ids = odoo.search("product.template", filters=[["type", "=", "product"]])
|
|
65
|
+
|
|
66
|
+
# With limit
|
|
67
|
+
ids = odoo.search("product.template", limit=10)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Search and read in one call
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
products = odoo.search_read(
|
|
74
|
+
"product.template",
|
|
75
|
+
filters=[["sale_ok", "=", True]],
|
|
76
|
+
fields=["name", "list_price", "categ_id"],
|
|
77
|
+
limit=50
|
|
78
|
+
)
|
|
79
|
+
# Returns: [{"id": 1, "name": "My Product", "list_price": 99.0, ...}, ...]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Read by IDs
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
records = odoo.read("res.partner", ids=[1, 2, 3], fields=["name", "email"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Create a record
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
new_id = odoo.create("product.template", {
|
|
92
|
+
"name": "New Product",
|
|
93
|
+
"list_price": 49.99,
|
|
94
|
+
"type": "product",
|
|
95
|
+
})
|
|
96
|
+
print(f"Created product with ID: {new_id}")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Update records
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
odoo.write("product.template", ids=[1, 2, 3], values={
|
|
103
|
+
"list_price": 59.99
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Delete records
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
odoo.unlink("product.template", ids=[99])
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Count records
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
total = odoo.count("product.template", filters=[["active", "=", True]])
|
|
117
|
+
print(f"Total active products: {total}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Inspect model fields
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
fields = odoo.get_fields("product.template", attributes=["string", "type"])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Get server version
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
info = odoo.version()
|
|
130
|
+
print(info) # {'server_version': '18.0', ...}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Error Handling
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from odoo_connect import OdooClient, OdooAuthError, OdooError
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
odoo = OdooClient("https://mycompany.odoo.com", "my-database")
|
|
140
|
+
odoo.login("wrong@example.com", "badpassword")
|
|
141
|
+
except OdooAuthError as e:
|
|
142
|
+
print(f"Authentication failed: {e}")
|
|
143
|
+
except OdooError as e:
|
|
144
|
+
print(f"Odoo error: {e}")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Compatibility
|
|
148
|
+
|
|
149
|
+
- Python 3.8+
|
|
150
|
+
- Odoo 14, 15, 16, 17, 18
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
odoo_connect
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "odoo-easy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lightweight Python wrapper for the Odoo XML-RPC API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
keywords = ["odoo", "xmlrpc", "erp", "api", "wrapper"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.8",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/atovoman/odoo-easy"
|
|
27
|
+
Repository = "https://github.com/atovoman/odoo-easy"
|
|
28
|
+
Issues = "https://github.com/atovoman/odoo-easy/issues"
|