Skip to content

Commit 026c0c4

Browse files
author
Callum Dickinson
committed
Add Odoo 18 support, remove Odoo 13 support
* Add support for Odoo 18 (and hopefully 15, 16 and 17 with it). * Remove support for Odoo 13.
1 parent e80c7d5 commit 026c0c4

13 files changed

Lines changed: 793 additions & 618 deletions

File tree

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ jobs:
2222
git config --global user.name "${GITHUB_ACTOR}"
2323
git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
2424
- name: Setup Python
25-
uses: actions/setup-python@v6
25+
uses: actions/setup-python@v7
2626
with:
2727
python-version: "3.14"
2828
cache: "pip"
2929
- name: Setup uv
30-
uses: astral-sh/setup-uv@v7
30+
uses: astral-sh/setup-uv@v8.3.2
3131
with:
32-
version: "0.9.17"
32+
version: "0.12.1"
3333
- name: Create virtual environment
3434
run: uv sync --only-dev
3535
- name: Publish the docs to GitHub Pages

.github/workflows/tag.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ jobs:
1212
runs-on: ubuntu-24.04
1313
steps:
1414
- name: Clone full tree, and checkout tag
15-
uses: actions/checkout@v5
15+
uses: actions/checkout@v7
1616
with:
1717
fetch-depth: 0
1818
- name: Setup Python
19-
uses: actions/setup-python@v6
19+
uses: actions/setup-python@v7
2020
with:
2121
python-version: "3.14"
2222
cache: "pip"
2323
- name: Setup uv
24-
uses: astral-sh/setup-uv@v7
24+
uses: astral-sh/setup-uv@v8.3.2
2525
with:
26-
version: "0.9.17"
26+
version: "0.12.1"
2727
- name: Build source dist and wheels
2828
run: uv build
2929
- name: Upload source dist and wheels to artifacts
30-
uses: actions/upload-artifact@v4
30+
uses: actions/upload-artifact@v7
3131
with:
3232
name: dist
3333
path: dist/
@@ -42,7 +42,7 @@ jobs:
4242
id-token: write
4343
steps:
4444
- name: Clone full tree, and checkout tag
45-
uses: actions/checkout@v5
45+
uses: actions/checkout@v7
4646
with:
4747
fetch-depth: 0
4848
- name: Download source dist and wheels from artifacts
@@ -51,14 +51,14 @@ jobs:
5151
name: dist
5252
path: dist/
5353
- name: Setup Python
54-
uses: actions/setup-python@v6
54+
uses: actions/setup-python@v7
5555
with:
5656
python-version: "3.14"
5757
cache: "pip"
5858
- name: Setup uv
59-
uses: astral-sh/setup-uv@v7
59+
uses: astral-sh/setup-uv@v8.3.2
6060
with:
61-
version: "0.9.17"
61+
version: "0.12.1"
6262
- name: Publish source dist and wheels to PyPI
6363
run: uv publish
6464

@@ -69,7 +69,7 @@ jobs:
6969
contents: write
7070
steps:
7171
- name: Clone and checkout tag
72-
uses: actions/checkout@v5
72+
uses: actions/checkout@v7
7373
- name: Download source dist and wheels from artifacts
7474
uses: actions/download-artifact@v4
7575
with:
@@ -94,22 +94,22 @@ jobs:
9494
contents: write
9595
steps:
9696
- name: Clone full tree, and checkout branch
97-
uses: actions/checkout@v4
97+
uses: actions/checkout@v7
9898
with:
9999
fetch-depth: 0
100100
- name: Configure git user
101101
run: |
102102
git config --global user.name "${GITHUB_ACTOR}"
103103
git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
104104
- name: Setup Python
105-
uses: actions/setup-python@v6
105+
uses: actions/setup-python@v7
106106
with:
107107
python-version: "3.14"
108108
cache: "pip"
109109
- name: Setup uv
110-
uses: astral-sh/setup-uv@v7
110+
uses: astral-sh/setup-uv@v8.3.2
111111
with:
112-
version: "0.9.17"
112+
version: "0.12.1"
113113
- name: Create virtual environment
114114
run: uv sync --only-dev
115115
- name: Publish the docs to GitHub Pages

.github/workflows/test.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ jobs:
2121
- "3.14"
2222
steps:
2323
- name: Clone and checkout branch
24-
uses: actions/checkout@v5
24+
uses: actions/checkout@v7
2525
- name: Setup Python
26-
uses: actions/setup-python@v6
26+
uses: actions/setup-python@v7
2727
with:
2828
python-version: "${{ matrix.python_version }}"
2929
cache: "pip"
3030
- name: Run pre-commit hooks
31-
uses: pre-commit/action@v3.0.1
31+
uses: j178/prek-action@v2
3232

3333
test:
3434
needs: pre-commit
@@ -43,18 +43,18 @@ jobs:
4343
- "3.14"
4444
steps:
4545
- name: Clone full tree, and checkout branch
46-
uses: actions/checkout@v5
46+
uses: actions/checkout@v7
4747
with:
4848
fetch-depth: 0
4949
- name: Setup Python
50-
uses: actions/setup-python@v6
50+
uses: actions/setup-python@v7
5151
with:
5252
python-version: "${{ matrix.python_version }}"
5353
cache: "pip"
5454
- name: Setup uv
55-
uses: astral-sh/setup-uv@v7
55+
uses: astral-sh/setup-uv@v8.3.2
5656
with:
57-
version: "0.9.17"
57+
version: "0.12.1"
5858
- name: Run tests
5959
run: uv run poe test
6060

@@ -63,22 +63,22 @@ jobs:
6363
runs-on: ubuntu-24.04
6464
steps:
6565
- name: Clone full tree, and checkout branch
66-
uses: actions/checkout@v5
66+
uses: actions/checkout@v7
6767
with:
6868
fetch-depth: 0
6969
- name: Setup Python
70-
uses: actions/setup-python@v6
70+
uses: actions/setup-python@v7
7171
with:
7272
python-version: "3.14"
7373
cache: "pip"
7474
- name: Setup uv
75-
uses: astral-sh/setup-uv@v7
75+
uses: astral-sh/setup-uv@v8.3.2
7676
with:
77-
version: "0.9.17"
77+
version: "0.12.1"
7878
- name: Build source dist and wheels
7979
run: uv build
8080
- name: Upload source dist and wheels to artifacts
81-
uses: actions/upload-artifact@v4
81+
uses: actions/upload-artifact@v7
8282
with:
8383
name: dist
8484
path: dist/

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ repos:
1515
- id: check-added-large-files
1616
- id: check-merge-conflict
1717
- repo: https://github.com/crate-ci/typos
18-
rev: "v1.40.0"
18+
rev: "v1.48.0"
1919
hooks:
2020
- id: typos
2121
- repo: https://github.com/astral-sh/uv-pre-commit
22-
rev: "0.9.17"
22+
rev: "0.12.1"
2323
hooks:
2424
- id: uv-lock
2525
- repo: https://github.com/astral-sh/ruff-pre-commit
26-
rev: "v0.14.9"
26+
rev: "v0.16.1"
2727
hooks:
2828
- id: ruff-check
2929
- id: ruff-format
3030
- repo: https://github.com/pre-commit/mirrors-mypy
31-
rev: "v1.19.0"
31+
rev: "v2.3.0"
3232
hooks:
3333
- id: mypy
3434
additional_dependencies:
3535
- OdooRPC>=0.9.0
3636
- packaging
37-
- typing-extensions>=4.15.0
37+
- typing-extensions>=4.12.2

docs/managers/custom.md

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -562,59 +562,112 @@ from .parent import Parent # noqa: E402
562562

563563
### Odoo Version Compatibility
564564

565-
Major releases of Odoo may change the database models to introduce
566-
new functionality.
565+
*Changed in version 0.3.0: Replaced `_field_mapping` with the
566+
`VersionMapping` annotation.*
567567

568-
The way models usually change in a backwards-incompatible way is
569-
that fields are renamed so that they are referenced using another name,
570-
without providing an alias for the old one.
568+
Major releases of Odoo may change aspects of the API and database schema
569+
to introduce new functionality, fix bugs and so on. These changes can be
570+
difficult to manage when upgrading as generally the provisions for backwards
571+
compatibility are limited.
571572

572-
In the OpenStack Odoo Client library, this is handled by defining
573-
the `_field_mapping` attribute on the record class.
573+
The OpenStack Odoo Client library for Python provides features that allow
574+
record types and managers to be configured such that applications using the
575+
library do not need to be aware of differences between Odoo versions, which
576+
hopefully makes upgrading major Odoo versions easier.
574577

575-
```python
576-
_field_mapping: dict[str | None, dict[str, str]]
577-
```
578+
#### Version Mappings
579+
580+
One way models can change in a backwards-incompatible way is that
581+
fields are renamed so that they are referenced using another name,
582+
without providing an alias for the old one.
578583

579-
The `_field_mapping` attribute is a nested dictionary structure used
580-
to define local-to-remote field name mappings.
584+
These differences can be handled using the `VersionMapping` annotation.
585+
This annotation allows you to configure Odoo version differences for specific
586+
fields on a model, as shown in the example below.
581587

582588
```python
583589
from __future__ import annotations
584590

585-
from openstack_odooclient import RecordBase
591+
from typing import Annotated
592+
593+
from openstack_odooclient import RecordBase, VersionMapping
586594

587595
class CustomRecord(RecordBase["CustomRecordManager"]):
588-
custom_field: str
596+
custom_field: Annotated[str, VersionMapping("<18.0", "old_custom_field")]
589597
"""Description of the field."""
590598

591599
custom_field_2: int
592600
"""Description of the second field."""
593601

594602
custom_field_3: float
595603
"""Description of the third field."""
604+
```
596605

597-
_field_mapping = {
598-
# The Odoo version for which to generate the mapping.
599-
"13.0": {
600-
# Key is local field name. Value is the field name in Odoo 13.
601-
"custom_field": "old_custom_field",
602-
}
603-
# Use None to provide a mapping to use for all Odoo versions.
604-
None: {
605-
"custom_field_2": "old_custom_field_2",
606-
},
607-
# custom_field_3 is not defined here.
608-
# The field name will be used as-is on all Odoo versions.
609-
}
606+
By default `custom_field` will be used as the field name to query from Odoo,
607+
but if the connected Odoo server version satisfies the given version
608+
constraint (in this case it would match for Odoo 17 and earlier),
609+
`old_custom_field` is used instead.
610+
611+
Clients only need to reference the field name defined on the record object;
612+
the Python OpenStack Odoo Client library will transparently convert between
613+
the local and the correct remote field names, depending on the server's
614+
version.
615+
616+
When defining the annotation, set the first argument to the version specifier
617+
that defines the set of versions to match, and set the second argument to the
618+
name of the field to use.
619+
620+
```python
621+
from typing import Annotated
622+
623+
from openstack_odooclient import RecordBase, User, VersionMapping
624+
625+
class CustomRecord(RecordBase["CustomRecordManager"]):
626+
name: Annotated[str, VersionMapping("<18.0", "old_name")]
610627
```
611628

612-
Mappings can be added for specific Odoo versions, or by using `None`,
613-
mappings that apply to all Odoo versions can be defined.
629+
For [model refs](#model-refs), the version mapping applies to the model ref
630+
field specified in the `ModelRef` annotation. The version mapping only needs
631+
to be defined on **one** of the defined model ref fields (the same version
632+
mapping will be used for all of them). It is recommended to add it to the
633+
field representing the record ID (or list of record IDs), as shown below.
614634

615-
When the Odoo Client library interfaces with Odoo, it will automatically find
616-
and use the correct field name to present based on the server version
617-
and the record class's field mapping.
635+
```python
636+
from typing import Annotated
637+
638+
from openstack_odooclient import (
639+
ModelRef,
640+
RecordBase,
641+
User,
642+
VersionMapping,
643+
)
644+
645+
class CustomRecord(RecordBase["CustomRecordManager"]):
646+
user_id: Annotated[
647+
int,
648+
ModelRef("user_id", User),
649+
VersionMapping("<18.0", "old_user_id"),
650+
]
651+
user_name: Annotated[str, ModelRef("user_id", User)]
652+
user: Annotated[User, ModelRef("user_id", User)]
653+
```
654+
655+
Multiple version mappings can be defined for a single field.
656+
Version mappings are evaluated in order, and the first one that
657+
matches is used.
658+
659+
```python
660+
from typing import Annotated
661+
662+
from openstack_odooclient import RecordBase, User, VersionMapping
663+
664+
class CustomRecord(RecordBase["CustomRecordManager"]):
665+
name: Annotated[
666+
str,
667+
VersionMapping("<14.0", "old_name1"),
668+
VersionMapping(">=14.0,<18.0", "old_name2"),
669+
]
670+
```
618671

619672
### Record Methods
620673

@@ -1093,13 +1146,14 @@ class CustomClient(Client):
10931146
custom_users: CustomUserManager
10941147
```
10951148

1096-
Due to the Odoo Client library using type hints to determine what record classes to use,
1097-
and the type hints being physically defined in code to allow type analysis tools such as Mypy
1098-
and Pyright to properly evaluate the source, *existing* references on *existing* record classes
1099-
cannot be automatically updated to use the custom versions.
1149+
Due to the Odoo Client library using type hints to determine what record
1150+
classes to use, and the type hints being physically defined in code to allow
1151+
static analysis tools such as Mypy to properly evaluate the source, *existing*
1152+
references on *existing* record classes cannot be automatically updated to use
1153+
the custom versions.
11001154

1101-
However, it is possible to **cast** a record object of the base type into the custom type
1102-
using the record class's `from_record_obj` class method.
1155+
However, it is possible to **cast** a record object of the base type into the
1156+
custom type using the record class's `from_record_obj` class method.
11031157

11041158
```python
11051159
>>> odoo_client = CustomClient(...)
@@ -1113,5 +1167,5 @@ CustomUser(record={'id': 1234, 'custom_field': 'Hello, world!', ...}, fields=Non
11131167
'Hello, world!'
11141168
```
11151169

1116-
This should cover the majority of use cases where custom add-ons add new functionality
1117-
to existing models.
1170+
This should cover the majority of use cases where custom add-ons add new
1171+
functionality to existing models.

0 commit comments

Comments
 (0)