diff --git a/endpoint_cache_preheat/README.rst b/endpoint_cache_preheat/README.rst new file mode 100644 index 00000000..204cda4b --- /dev/null +++ b/endpoint_cache_preheat/README.rst @@ -0,0 +1,99 @@ +======================= +Endpoint cache pre-heat +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:18f1a3985866b5d6564ce0c27a8c10a1cfd4e95a74fbc02eeee5789e2be63503 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb--api-lightgray.png?logo=github + :target: https://github.com/OCA/web-api/tree/18.0/endpoint_cache_preheat + :alt: OCA/web-api +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-api-18-0/web-api-18-0-endpoint_cache_preheat + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/web-api&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Delay cache pre-heat for expensive endpoints. + +If an endpoint takes to long to get cached, it will probably make your +call fail or any cron job that calls it. + +This module allows pre-heat the cache and delay its generation. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Go to an endpoint form and tick Automatic cache pre-heat option. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Camptocamp + +Contributors +------------ + +- Simone Orsi + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-simahawk| image:: https://github.com/simahawk.png?size=40px + :target: https://github.com/simahawk + :alt: simahawk + +Current `maintainer `__: + +|maintainer-simahawk| + +This module is part of the `OCA/web-api `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/endpoint_cache_preheat/__init__.py b/endpoint_cache_preheat/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/endpoint_cache_preheat/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/endpoint_cache_preheat/__manifest__.py b/endpoint_cache_preheat/__manifest__.py new file mode 100644 index 00000000..29e4cf10 --- /dev/null +++ b/endpoint_cache_preheat/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2025 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Endpoint cache pre-heat", + "summary": """Provide basic pre-caching features for endpoints""", + "version": "18.0.1.0.0", + "license": "LGPL-3", + "development_status": "Alpha", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["simahawk"], + "website": "https://github.com/OCA/web-api", + "depends": ["endpoint_cache", "queue_job"], + "data": [ + "views/endpoint_view.xml", + "data/cron.xml", + "data/job_channel.xml", + "data/job_function.xml", + ], +} diff --git a/endpoint_cache_preheat/data/cron.xml b/endpoint_cache_preheat/data/cron.xml new file mode 100644 index 00000000..c86d0288 --- /dev/null +++ b/endpoint_cache_preheat/data/cron.xml @@ -0,0 +1,17 @@ + + + + Endpoint cache pre-heat + + + 1 + days + + + code + model.cron_endpoint_cache_preheat() + + diff --git a/endpoint_cache_preheat/data/job_channel.xml b/endpoint_cache_preheat/data/job_channel.xml new file mode 100644 index 00000000..727bedee --- /dev/null +++ b/endpoint_cache_preheat/data/job_channel.xml @@ -0,0 +1,6 @@ + + + endpoint_cache + + + diff --git a/endpoint_cache_preheat/data/job_function.xml b/endpoint_cache_preheat/data/job_function.xml new file mode 100644 index 00000000..86a3460c --- /dev/null +++ b/endpoint_cache_preheat/data/job_function.xml @@ -0,0 +1,7 @@ + + + + _cron_endpoint_cache_preheat + + + diff --git a/endpoint_cache_preheat/i18n/endpoint_cache_preheat.pot b/endpoint_cache_preheat/i18n/endpoint_cache_preheat.pot new file mode 100644 index 00000000..b4825215 --- /dev/null +++ b/endpoint_cache_preheat/i18n/endpoint_cache_preheat.pot @@ -0,0 +1,73 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * endpoint_cache_preheat +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: endpoint_cache_preheat +#: model_terms:ir.ui.view,arch_db:endpoint_cache_preheat.endpoint_mixin_form_view +msgid "" +"A job will be created to pre-heat the cache. You can check the status of the" +" job in the Jobs menu." +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_endpoint__cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__cache_preheat +msgid "Automatic cache pre-heat" +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__display_name +msgid "Display Name" +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.actions.server,name:endpoint_cache_preheat.cron_endpoint_cache_preheat_ir_actions_server +#: model:ir.cron,cron_name:endpoint_cache_preheat.cron_endpoint_cache_preheat +#: model:ir.cron,name:endpoint_cache_preheat.cron_endpoint_cache_preheat +msgid "Endpoint cache pre-heat" +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model,name:endpoint_cache_preheat.model_endpoint_mixin +msgid "Endpoint mixin" +msgstr "" + +#. module: endpoint_cache_preheat +#: model_terms:ir.ui.view,arch_db:endpoint_cache_preheat.endpoint_mixin_form_view +msgid "Force cache pre-heat (async)" +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__id +msgid "ID" +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,help:endpoint_cache_preheat.field_endpoint_endpoint__cache_preheat +#: model:ir.model.fields,help:endpoint_cache_preheat.field_endpoint_mixin__cache_preheat +msgid "" +"If checked, cache will be pre-heated by a cron according to the selected " +"policy. Cache generation will be done in a queue job." +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin____last_update +msgid "Last Modified on" +msgstr "" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_endpoint__cache_preheat_ts +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__cache_preheat_ts +msgid "Last pre-heat on" +msgstr "" diff --git a/endpoint_cache_preheat/i18n/it.po b/endpoint_cache_preheat/i18n/it.po new file mode 100644 index 00000000..4520b5ce --- /dev/null +++ b/endpoint_cache_preheat/i18n/it.po @@ -0,0 +1,81 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * endpoint_cache_preheat +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-06-09 14:26+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: endpoint_cache_preheat +#: model_terms:ir.ui.view,arch_db:endpoint_cache_preheat.endpoint_mixin_form_view +msgid "" +"A job will be created to pre-heat the cache. You can check the status of the" +" job in the Jobs menu." +msgstr "" +"Verrà creato un lavoro per preriscaldare la cache. Puoi controllare lo stato " +"del lavoro nel menu Lavoro." + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_endpoint__cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__cache_preheat +msgid "Automatic cache pre-heat" +msgstr "Preriscaldamento cache automatico" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: endpoint_cache_preheat +#: model:ir.actions.server,name:endpoint_cache_preheat.cron_endpoint_cache_preheat_ir_actions_server +#: model:ir.cron,cron_name:endpoint_cache_preheat.cron_endpoint_cache_preheat +#: model:ir.cron,name:endpoint_cache_preheat.cron_endpoint_cache_preheat +msgid "Endpoint cache pre-heat" +msgstr "Preriscaldamento cache endpoint" + +#. module: endpoint_cache_preheat +#: model:ir.model,name:endpoint_cache_preheat.model_endpoint_mixin +msgid "Endpoint mixin" +msgstr "Mixin endpoint" + +#. module: endpoint_cache_preheat +#: model_terms:ir.ui.view,arch_db:endpoint_cache_preheat.endpoint_mixin_form_view +msgid "Force cache pre-heat (async)" +msgstr "Forza preriscaldamento cache (asincrono)" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__id +msgid "ID" +msgstr "ID" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,help:endpoint_cache_preheat.field_endpoint_endpoint__cache_preheat +#: model:ir.model.fields,help:endpoint_cache_preheat.field_endpoint_mixin__cache_preheat +msgid "" +"If checked, cache will be pre-heated by a cron according to the selected " +"policy. Cache generation will be done in a queue job." +msgstr "" +"Se selezionata, la cache verrà preriscaldata da un cron in conformità alla " +"apolitica selezionata. La generazione della cache verrà eseguita in un " +"lavoro in coda." + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: endpoint_cache_preheat +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_endpoint__cache_preheat_ts +#: model:ir.model.fields,field_description:endpoint_cache_preheat.field_endpoint_mixin__cache_preheat_ts +msgid "Last pre-heat on" +msgstr "Ultimo preriscaldamento su" diff --git a/endpoint_cache_preheat/models/__init__.py b/endpoint_cache_preheat/models/__init__.py new file mode 100644 index 00000000..95c62dfe --- /dev/null +++ b/endpoint_cache_preheat/models/__init__.py @@ -0,0 +1 @@ +from . import endpoint_mixin diff --git a/endpoint_cache_preheat/models/endpoint_mixin.py b/endpoint_cache_preheat/models/endpoint_mixin.py new file mode 100644 index 00000000..d7a06352 --- /dev/null +++ b/endpoint_cache_preheat/models/endpoint_mixin.py @@ -0,0 +1,68 @@ +# Copyright 2025 Camptocamp SA +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class EndpointMixin(models.AbstractModel): + _inherit = "endpoint.mixin" + + cache_preheat = fields.Boolean( + string="Automatic cache pre-heat", + help="If checked, cache will be pre-heated by a cron " + "according to the selected policy. " + "Cache generation will be done in a queue job.", + ) + cache_preheat_ts = fields.Datetime(readonly=True, string="Last pre-heat on") + + @api.model + def cron_endpoint_cache_preheat(self): + """Cron job to preheat cache""" + now = fields.Datetime.now() + _logger.info("cron_endpoint_cache_preheat started") + base_domain = [("cache_policy", "!=", False), ("cache_preheat", "=", True)] + delta = { + "day": fields.Datetime.subtract(now, days=1), + "week": fields.Datetime.subtract(now, weeks=1), + "month": fields.Datetime.subtract(now, months=1), + } + for policy in ("day", "week", "month"): + domain = base_domain + [ + ("cache_policy", "=", policy), + "|", + ( + "cache_preheat_ts", + "<=", + delta[policy].replace(hour=23, minute=59, second=59), + ), + ("cache_preheat_ts", "=", False), + ] + for rec in self.search(domain): + rec.with_delay( + description=f"Pre-heat cache for endpoint {rec.route}" + )._cron_endpoint_cache_preheat() + _logger.info("cron_endpoint_cache_preheat preheated rec=%s", rec.id) + _logger.info("cron_endpoint_cache_preheat finished") + return True + + def _cron_endpoint_cache_preheat(self): + """Preheat cache for cron""" + self._endpoint_cache_preheat() + self.cache_preheat_ts = fields.Datetime.now() + + def _endpoint_cache_wipe(self, domain): + # pylint: disable=missing-return + super()._endpoint_cache_wipe(domain) + self.cache_preheat_ts = False + + def action_preheat_cache_async(self): + self.action_purge_cache_attachments() + self.with_delay( + description=f"Pre-heat cache for endpoint {self.route}" + )._cron_endpoint_cache_preheat() + return {"type": "ir.actions.act_window_close"} diff --git a/endpoint_cache_preheat/pyproject.toml b/endpoint_cache_preheat/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/endpoint_cache_preheat/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/endpoint_cache_preheat/readme/CONFIGURE.md b/endpoint_cache_preheat/readme/CONFIGURE.md new file mode 100644 index 00000000..1a3dc377 --- /dev/null +++ b/endpoint_cache_preheat/readme/CONFIGURE.md @@ -0,0 +1 @@ +Go to an endpoint form and tick Automatic cache pre-heat option. diff --git a/endpoint_cache_preheat/readme/CONTRIBUTORS.md b/endpoint_cache_preheat/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..2b66303a --- /dev/null +++ b/endpoint_cache_preheat/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Simone Orsi \<\> diff --git a/endpoint_cache_preheat/readme/DESCRIPTION.md b/endpoint_cache_preheat/readme/DESCRIPTION.md new file mode 100644 index 00000000..eced22f3 --- /dev/null +++ b/endpoint_cache_preheat/readme/DESCRIPTION.md @@ -0,0 +1,6 @@ +Delay cache pre-heat for expensive endpoints. + +If an endpoint takes to long to get cached, it will probably make your +call fail or any cron job that calls it. + +This module allows pre-heat the cache and delay its generation. diff --git a/endpoint_cache_preheat/static/description/icon.png b/endpoint_cache_preheat/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/endpoint_cache_preheat/static/description/icon.png differ diff --git a/endpoint_cache_preheat/static/description/index.html b/endpoint_cache_preheat/static/description/index.html new file mode 100644 index 00000000..68594c32 --- /dev/null +++ b/endpoint_cache_preheat/static/description/index.html @@ -0,0 +1,439 @@ + + + + + +Endpoint cache pre-heat + + + +
+

Endpoint cache pre-heat

+ + +

Alpha License: LGPL-3 OCA/web-api Translate me on Weblate Try me on Runboat

+

Delay cache pre-heat for expensive endpoints.

+

If an endpoint takes to long to get cached, it will probably make your +call fail or any cron job that calls it.

+

This module allows pre-heat the cache and delay its generation.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

Go to an endpoint form and tick Automatic cache pre-heat option.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

simahawk

+

This module is part of the OCA/web-api project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/endpoint_cache_preheat/tests/__init__.py b/endpoint_cache_preheat/tests/__init__.py new file mode 100644 index 00000000..c3f07a7d --- /dev/null +++ b/endpoint_cache_preheat/tests/__init__.py @@ -0,0 +1 @@ +from . import test_endpoint diff --git a/endpoint_cache_preheat/tests/test_endpoint.py b/endpoint_cache_preheat/tests/test_endpoint.py new file mode 100644 index 00000000..3bdd1a1c --- /dev/null +++ b/endpoint_cache_preheat/tests/test_endpoint.py @@ -0,0 +1,226 @@ +# Copyright 2025 Camptocamp SA +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import textwrap +from datetime import datetime + +from dateutil.relativedelta import relativedelta +from freezegun import freeze_time + +from odoo import fields + +from odoo.addons.endpoint.tests.common import CommonEndpoint +from odoo.addons.queue_job.tests.common import trap_jobs +from odoo.addons.website.tools import MockRequest + + +class TestEndpoint(CommonEndpoint): + @classmethod + def _setup_records(cls): + # pylint: disable=missing-return + super()._setup_records() + cls.endpoint1 = cls.env.ref("endpoint.endpoint_demo_1") + cls.cron = cls.env.ref("endpoint_cache_preheat.cron_endpoint_cache_preheat") + + def _run_cron(self): + with MockRequest(self.env): + self.cron.ir_actions_server_id.run() + + def test_cron_preheat_cache(self): + self.assertFalse(self.endpoint1.cache_preheat) + self.assertFalse(self.endpoint1.cache_preheat_ts) + now = datetime.now().replace(microsecond=0) + ep_daily = self.endpoint1.copy({"route": "/daily"}) + ep_daily.cache_preheat = True + ep_daily.cache_preheat_ts = now + ep_daily.code_snippet = textwrap.dedent( + """ + cache_name = endpoint._endpoint_cache_make_name("json") + cached = endpoint._endpoint_cache_get(cache_name) + if cached: + result = cached + else: + result = json.dumps({"foo": "bar"}) + endpoint._endpoint_cache_store(cache_name, result) + resp = Response(result, content_type="application/json", status=200) + result = dict(response=resp) + """ + ) + ep_weekly = ep_daily.copy({"route": "/weekly", "cache_policy": "week"}) + ep_monthly = ep_daily.copy({"route": "/monthly", "cache_policy": "month"}) + + # 1 day later + future_date_1 = now + relativedelta(days=1) + with trap_jobs() as trap, freeze_time(future_date_1), MockRequest(self.env): + self._run_cron() + trap.assert_jobs_count(1) + trap.assert_enqueued_job(ep_daily._cron_endpoint_cache_preheat) + trap.perform_enqueued_jobs() + self.assertEqual(ep_daily.cache_preheat_ts, future_date_1) + self.assertEqual(ep_weekly.cache_preheat_ts, now) + self.assertEqual(ep_monthly.cache_preheat_ts, now) + + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_daily._endpoint_view_cache_domain() + ), + 1, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_weekly._endpoint_view_cache_domain() + ), + 0, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_monthly._endpoint_view_cache_domain() + ), + 0, + ) + + # 2 days later + future_date_2 = now + relativedelta(days=2) + with trap_jobs() as trap, freeze_time(future_date_2), MockRequest(self.env): + self._run_cron() + trap.assert_jobs_count(1) + trap.assert_enqueued_job(ep_daily._cron_endpoint_cache_preheat) + trap.perform_enqueued_jobs() + self.assertEqual(ep_daily.cache_preheat_ts, future_date_2) + self.assertEqual(ep_weekly.cache_preheat_ts, now) + self.assertEqual(ep_monthly.cache_preheat_ts, now) + + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_daily._endpoint_view_cache_domain() + ), + 2, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_weekly._endpoint_view_cache_domain() + ), + 0, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_monthly._endpoint_view_cache_domain() + ), + 0, + ) + + # 1 week later + future_date_3 = now + relativedelta(weeks=1) + with trap_jobs() as trap, freeze_time(future_date_3), MockRequest(self.env): + self._run_cron() + trap.assert_jobs_count(2) + trap.assert_enqueued_job(ep_daily._cron_endpoint_cache_preheat) + trap.perform_enqueued_jobs() + self.assertEqual(ep_daily.cache_preheat_ts, future_date_3) + self.assertEqual(ep_weekly.cache_preheat_ts, future_date_3) + self.assertEqual(ep_monthly.cache_preheat_ts, now) + + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_daily._endpoint_view_cache_domain() + ), + 3, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_weekly._endpoint_view_cache_domain() + ), + 1, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_monthly._endpoint_view_cache_domain() + ), + 0, + ) + + # 1 month later + future_date_4 = now + relativedelta(months=1) + with trap_jobs() as trap, freeze_time(future_date_4), MockRequest(self.env): + self._run_cron() + trap.assert_jobs_count(3) + trap.assert_enqueued_job(ep_daily._cron_endpoint_cache_preheat) + trap.perform_enqueued_jobs() + self.assertEqual(ep_daily.cache_preheat_ts, future_date_4) + self.assertEqual(ep_weekly.cache_preheat_ts, future_date_4) + self.assertEqual(ep_monthly.cache_preheat_ts, future_date_4) + + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_daily._endpoint_view_cache_domain() + ), + 4, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_weekly._endpoint_view_cache_domain() + ), + 2, + ) + self.assertEqual( + self.env["ir.attachment"].search_count( + ep_monthly._endpoint_view_cache_domain() + ), + 1, + ) + + def test_cron_preheat_cache_disabled(self): + now = fields.Datetime.from_string("2025-02-19 23:00:00") + ep_daily = self.endpoint1.copy({"route": "/daily"}) + ep_daily.cache_preheat = False + ep_daily.cache_preheat_ts = now + ep_daily.code_snippet = textwrap.dedent( + """ + cache_name = endpoint._endpoint_cache_make_name("json") + cached = endpoint._endpoint_cache_get(cache_name) + if cached: + result = cached + else: + result = json.dumps({"foo": "bar"}) + endpoint._endpoint_cache_store(cache_name, result) + resp = Response(result, content_type="application/json", status=200) + result = dict(response=resp) + """ + ) + # 1 day later + future_date = fields.Datetime.from_string("2025-02-20 20:00:00") + with trap_jobs() as trap, freeze_time(future_date), MockRequest(self.env): + self._run_cron() + trap.assert_jobs_count(0) + self.assertEqual(ep_daily.cache_preheat_ts, now) + + def test_action_preheat_cache_async(self): + ep_daily = self.endpoint1.copy({"route": "/daily"}) + ep_daily.cache_preheat = True + ep_daily.code_snippet = textwrap.dedent( + """ + cache_name = endpoint._endpoint_cache_make_name("json") + cached = endpoint._endpoint_cache_get(cache_name) + if cached: + result = cached + else: + result = json.dumps({"foo": "bar"}) + endpoint._endpoint_cache_store(cache_name, result) + resp = Response(result, content_type="application/json", status=200) + result = dict(response=resp) + """ + ) + now = fields.Datetime.from_string("2025-02-19 23:00:00") + with trap_jobs() as trap, freeze_time(now), MockRequest(self.env): + ep_daily.action_preheat_cache_async() + trap.assert_jobs_count(1) + trap.assert_enqueued_job(ep_daily._cron_endpoint_cache_preheat) + trap.perform_enqueued_jobs() + self.assertEqual(ep_daily.cache_preheat_ts, now) + + def test_action_cache_purge(self): + # purging cache should reset the TS + self.endpoint1.cache_preheat_ts = fields.Datetime.now() + self.endpoint1.action_purge_cache_attachments() + self.assertFalse(self.endpoint1.cache_preheat_ts) diff --git a/endpoint_cache_preheat/views/endpoint_view.xml b/endpoint_cache_preheat/views/endpoint_view.xml new file mode 100644 index 00000000..df10480a --- /dev/null +++ b/endpoint_cache_preheat/views/endpoint_view.xml @@ -0,0 +1,26 @@ + + + + + endpoint.mixin + + + + + + + + +