Skip to content

Commit 866a0b0

Browse files
committed
[ADD] base_registry_cache_custom
1 parent 6b21bbb commit 866a0b0

File tree

13 files changed

+1051
-0
lines changed

13 files changed

+1051
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
=====================
2+
Registry Custom Cache
3+
=====================
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:6c8e306992a72a18f30d78f12456cec5b1802ae1fde1c9179f122297a61c4562
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Beta
16+
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
18+
:alt: License: LGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
20+
:target: https://github.com/OCA/server-tools/tree/17.0/base_registry_cache_custom
21+
:alt: OCA/server-tools
22+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
23+
:target: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-base_registry_cache_custom
24+
:alt: Translate me on Weblate
25+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=17.0
27+
:alt: Try me on Runboat
28+
29+
|badge1| |badge2| |badge3| |badge4| |badge5|
30+
31+
This module allows adding custom caches to the DBs' registries.
32+
33+
**Table of contents**
34+
35+
.. contents::
36+
:local:
37+
38+
Configuration
39+
=============
40+
41+
Make sure you run Odoo with
42+
``--load=base,web,base_registry_cache_custom`` or using the Odoo
43+
configuration file:
44+
45+
.. code:: ini
46+
47+
[options]
48+
(...)
49+
server_wide_modules = base,web,base_registry_cache_custom
50+
51+
Usage
52+
=====
53+
54+
If you need to create a custom cache, create a new module and:
55+
56+
- add this module as its dependency
57+
- add a ``post_load`` hook like this:
58+
59+
.. code:: python
60+
61+
from odoo.addons.base_registry_cache_custom.registry import add_custom_cache
62+
63+
64+
def post_load():
65+
add_custom_cache(name="my_cache", count=256)
66+
67+
If you make use of multiple caches, and some of them should be
68+
invalidated when another one gets invalidated itself, use the
69+
``depends_on_caches`` argument:
70+
71+
.. code:: python
72+
73+
from odoo.addons.base_registry_cache_custom.registry import add_custom_cache
74+
75+
76+
def post_load():
77+
add_custom_cache(name="my_cache_1", count=256)
78+
add_custom_cache(name="my_cache_2", count=128, depends_on_caches=["my_cache"])
79+
80+
You can also add sub-caches, which can be declared using dotted names,
81+
and will be invalidated only when one of their dependency caches are
82+
invalidated:
83+
84+
.. code:: python
85+
86+
from odoo.addons.base_registry_cache_custom.registry import add_custom_cache
87+
88+
89+
def post_load():
90+
add_custom_cache(name="my_cache", count=256)
91+
add_custom_cache(name="my_cache.subcache", count=128, depends_on_caches=["my_cache"], allows_direct_invalidation=False)
92+
93+
Bug Tracker
94+
===========
95+
96+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
97+
In case of trouble, please check there if your issue has already been reported.
98+
If you spotted it first, help us to smash it by providing a detailed and welcomed
99+
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20base_registry_cache_custom%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
100+
101+
Do not contact contributors directly about support or help with technical issues.
102+
103+
Credits
104+
=======
105+
106+
Authors
107+
-------
108+
109+
* Camptocamp
110+
111+
Contributors
112+
------------
113+
114+
- Silvio Gregorini <[email protected]>
115+
116+
Maintainers
117+
-----------
118+
119+
This module is maintained by the OCA.
120+
121+
.. image:: https://odoo-community.org/logo.png
122+
:alt: Odoo Community Association
123+
:target: https://odoo-community.org
124+
125+
OCA, or the Odoo Community Association, is a nonprofit organization whose
126+
mission is to support the collaborative development of Odoo features and
127+
promote its widespread use.
128+
129+
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/17.0/base_registry_cache_custom>`_ project on GitHub.
130+
131+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

base_registry_cache_custom/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright 2025 Camptocamp SA
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
3+
4+
{
5+
"name": "Registry Custom Cache",
6+
"version": "17.0.1.0.0",
7+
"category": "Tools",
8+
"summary": "Add custom caches to Odoo registries",
9+
"author": "Camptocamp,Odoo Community Association (OCA)",
10+
"license": "LGPL-3",
11+
"website": "https://github.com/OCA/server-tools",
12+
"depends": ["base"],
13+
"installable": True,
14+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2025 Camptocamp SA
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
3+
4+
5+
class CacheError(Exception):
6+
"""Base error for invalid cache configuration"""
7+
8+
9+
class CacheAlreadyExistsError(CacheError):
10+
"""Cache cannot be added to the registry because it already exists"""
11+
12+
13+
class CacheInvalidConfigError(CacheError):
14+
"""Invalid cache configuration"""
15+
16+
17+
class CacheInvalidDependencyError(CacheInvalidConfigError):
18+
"""Invalid cache dependency"""
19+
20+
21+
class CacheInvalidNameError(CacheInvalidConfigError):
22+
"""Invalid cache name"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["whool"]
3+
build-backend = "whool.buildapi"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Make sure you run Odoo with ``--load=base,web,base_registry_cache_custom`` or using the Odoo configuration file:
2+
3+
``` ini
4+
[options]
5+
(...)
6+
server_wide_modules = base,web,base_registry_cache_custom
7+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Silvio Gregorini \<<[email protected]>\>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This module allows adding custom caches to the DBs' registries.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
If you need to create a custom cache, create a new module and:
2+
3+
- add this module as its dependency
4+
- add a `post_load` hook like this:
5+
6+
```python
7+
from odoo.addons.base_registry_cache_custom.registry import add_custom_cache
8+
9+
10+
def post_load():
11+
add_custom_cache(name="my_cache", count=256)
12+
```
13+
14+
If you make use of multiple caches, and some of them should be invalidated when another
15+
one gets invalidated itself, use the `depends_on_caches` argument:
16+
17+
```python
18+
from odoo.addons.base_registry_cache_custom.registry import add_custom_cache
19+
20+
21+
def post_load():
22+
add_custom_cache(name="my_cache_1", count=256)
23+
add_custom_cache(name="my_cache_2", count=128, depends_on_caches=["my_cache"])
24+
```
25+
26+
You can also add sub-caches, which can be declared using dotted names, and will be
27+
invalidated only when one of their dependency caches are invalidated:
28+
29+
```python
30+
from odoo.addons.base_registry_cache_custom.registry import add_custom_cache
31+
32+
33+
def post_load():
34+
add_custom_cache(name="my_cache", count=256)
35+
add_custom_cache(name="my_cache.subcache", count=128, depends_on_caches=["my_cache"], allows_direct_invalidation=False)
36+
```
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2025 Camptocamp SA
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
3+
4+
import logging
5+
import typing
6+
7+
from odoo.modules import registry
8+
from odoo.tools.lru import LRU
9+
from odoo.tools.misc import OrderedSet
10+
11+
from .exceptions import (
12+
CacheAlreadyExistsError,
13+
CacheInvalidConfigError,
14+
CacheInvalidDependencyError,
15+
CacheInvalidNameError,
16+
)
17+
18+
_logger = logging.getLogger(__name__)
19+
20+
21+
def add_custom_cache(
22+
name: str,
23+
count: int,
24+
depends_on_caches: typing.Iterable[str] = None,
25+
allows_direct_invalidation: bool = True,
26+
ignore_exceptions: typing.Iterable[type] = (),
27+
):
28+
"""Adds a custom cache into the Odoo registry
29+
30+
:param name: name of the custom cache to add
31+
:param count: max capability of the custom cache; set as 1 if a lower value is given
32+
:param allows_direct_invalidation: if ``True``, a DB sequence is assigned to the
33+
cache is assigned any sequence and (therefore dotted names for such caches are
34+
not allowed) and method ``Registry.clear_cache()`` can be called directly for it
35+
:param depends_on_caches: iterable of other cache names: if set, the current cache
36+
will be listed as dependent on those caches, and invalidating one of them will
37+
invalidate the custom cache as well
38+
:param ignore_exceptions: iterable of Exception types: if set, no error is raised,
39+
but the cache is not added to the registries anyway
40+
"""
41+
_logger.info(f"Adding cache '{name}' to registries...")
42+
43+
# Backup ``registry`` module attributes that needs restoring if something goes wrong
44+
registry_caches_backup = dict(registry._REGISTRY_CACHES)
45+
caches_by_key_backup = dict(registry._CACHES_BY_KEY)
46+
47+
try:
48+
# ``registry._REGISTRY_CACHES`` is used by ``registry.Registry.init()`` to
49+
# initialize registries' caches (attr ``__cache``)
50+
if name in registry._REGISTRY_CACHES:
51+
raise CacheAlreadyExistsError(f"Cache '{name}' already exists")
52+
normalized_count = max(count, 1)
53+
registry._REGISTRY_CACHES[name] = normalized_count
54+
55+
# ``registry._CACHES_BY_KEY`` is used by a variety of ``registry.Registry``
56+
# methods to handle caches dependencies and DB signaling (main reason why a
57+
# cache that allows direct invalidation cannot have a dotted name)
58+
if allows_direct_invalidation:
59+
if "." in name:
60+
raise CacheInvalidNameError(f"Invalid cache name '{name}'")
61+
registry._CACHES_BY_KEY[name] = (name,)
62+
elif not depends_on_caches:
63+
raise CacheInvalidConfigError(
64+
f"Cache '{name}' should either allow direct invalidation"
65+
f" or depend on another cache for indirect invalidation"
66+
)
67+
68+
# Setup invalidation dependencies
69+
# NB: use an ``OrderedSet`` to avoid duplicates while keeping the dependency
70+
# order, then convert to tuple for consistency w/ the standard
71+
# ``registry._CACHES_BY_KEY`` structure
72+
for parent in depends_on_caches or ():
73+
if parent not in registry._CACHES_BY_KEY:
74+
raise CacheInvalidDependencyError(
75+
f"Cache '{name}' cannot depend on cache '{parent}':"
76+
f" '{parent}' doesn't exist or doesn't allow direct invalidation"
77+
)
78+
deps = OrderedSet(registry._CACHES_BY_KEY[parent])
79+
deps.add(name)
80+
registry._CACHES_BY_KEY[parent] = tuple(deps)
81+
82+
# Update existing registries by:
83+
# - adding the custom cache to the registry (name-mangle: no AttributeError)
84+
# - setting up the proper signaling workflow
85+
# NB: ``registry.Registry.registries`` is a class attribute that returns an
86+
# ``odoo.tools.lru.LRU`` object that maps DB names to ``registry.Registry``
87+
# objects through variable ``d`` (which is a ``collections.OrderedDict`` object)
88+
for db_name, db_registry in registry.Registry.registries.d.items():
89+
_logger.info(f"Adding cache '{name}' to '{db_name}' registry")
90+
db_registry._Registry__caches[name] = LRU(normalized_count)
91+
if allows_direct_invalidation:
92+
db_registry.setup_signaling()
93+
94+
except Exception as exc:
95+
_logger.error(f"Could not add custom cache '{name}': {exc}")
96+
registry._REGISTRY_CACHES = registry_caches_backup
97+
registry._CACHES_BY_KEY = caches_by_key_backup
98+
ignore_exceptions = tuple(ignore_exceptions or ())
99+
if not (ignore_exceptions and isinstance(exc, ignore_exceptions)):
100+
raise

0 commit comments

Comments
 (0)