# HG changeset patch
# User Vincent Hatakeyama <vincent.hatakeyama@xcg-consulting.fr>
# Date 1675673116 -3600
#      Mon Feb 06 09:45:16 2023 +0100
# Node ID 3df46e51428081d5381b784aaaa4ff7ad1e3edbd
# Parent  dabe477340734e4b65bc56940394a826a162ee8e
✨ Adapt to Odoo 16

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -174,6 +174,14 @@
   variables:
     ODOO_TYPE: odoo15
 
+import_jsonrpc_odoo16_test:
+  extends: import_jsonrpc_odoo11_test-focal
+  image:
+    name: quay.orus.io/odoo/odoo:16.0
+    entrypoint: [""]
+  variables:
+    ODOO_TYPE: odoo16
+
 docker_build_copy_test:
   needs:
     - job: build-docker-image
@@ -260,7 +268,7 @@
     - echo "running release_job"
     - export EXTRA_DESCRIPTION="test"
   release:
-    name: "Release $CI_COMMIT_TAG"
+    name: "$CI_COMMIT_TAG"
     description: "Created using the release-cli $EXTRA_DESCRIPTION"
     tag_name: "$CI_COMMIT_TAG"
     ref: "$CI_COMMIT_TAG"
diff --git a/NEWS.rst b/NEWS.rst
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -2,6 +2,11 @@
 History
 =======
 
+18.0.0
+------
+
+Add compatibility with Odoo 16.
+
 17.1.0
 ------
 
diff --git a/odoo_scripts/__init__.py b/odoo_scripts/__init__.py
--- a/odoo_scripts/__init__.py
+++ b/odoo_scripts/__init__.py
@@ -3,7 +3,7 @@
     # python 3.8+
     from importlib.metadata import PackageNotFoundError, version
 except ModuleNotFoundError:
-    from importlib_metadata import (  # type: ignore[import,no-redef]
+    from importlib_metadata import (  # type: ignore[import,no-redef,assignment]
         PackageNotFoundError,
         version,
     )
diff --git a/odoo_scripts/config.py b/odoo_scripts/config.py
--- a/odoo_scripts/config.py
+++ b/odoo_scripts/config.py
@@ -29,6 +29,8 @@
 """.. warning: This version is not handled"""
 ODOO_15 = "odoo15"
 """Value for odoo_type representing Odoo 15.0"""
+ODOO_16 = "odoo16"
+"""Value for odoo_type representing Odoo 16.0"""
 
 _logger = logging.getLogger(__name__)
 
@@ -226,7 +228,7 @@
         """Full image name used for local operations (locally built, used for test and
         running)"""
         self.odoo_type: str
-        read_expanded("odoo_type", ODOO_7)
+        read_expanded("odoo_type")
         if not hasattr(self, "odoo_type"):
             # if path is set, odoo_type might not be in the values to read
             # so no message
@@ -239,6 +241,7 @@
             ODOO_11,
             ODOO_13,
             ODOO_15,
+            ODOO_16,
         ):
             if path:
                 _logger.info(
diff --git a/odoo_scripts/docker_black.py b/odoo_scripts/docker_black.py
--- a/odoo_scripts/docker_black.py
+++ b/odoo_scripts/docker_black.py
@@ -5,15 +5,16 @@
 import os
 import sys
 from argparse import ArgumentParser
+from typing import List, Optional
 
 from .config import Config
 from .docker_build import add_build_options, build_local_image, get_build_options
 from .docker_client import DockerClient
 from .parsing import apply, basic_parser
 
-__version__ = "1.0.0"
+__version__ = "1.0.1"
 __date__ = "2022-03-03"
-__updated__ = "2022-03-03"
+__updated__ = "2023-01-20"
 
 _logger = logging.getLogger(__name__)
 _BLACK_DEST = "black"
@@ -51,7 +52,7 @@
     return parser
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     add_build_options(parser)
@@ -104,4 +105,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_build.py b/odoo_scripts/docker_build.py
--- a/odoo_scripts/docker_build.py
+++ b/odoo_scripts/docker_build.py
@@ -9,9 +9,9 @@
 import signal
 import sys
 from subprocess import call, check_output
-from typing import List
+from typing import List, Optional
 
-from .config import Config
+from .config import ODOO_7, ODOO_8, ODOO_10, ODOO_11, ODOO_13, ODOO_15, Config
 from .docker_build_clean import clean
 from .docker_build_copy import copy
 from .docker_client import DockerClient
@@ -19,9 +19,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "1.2.1"
+__version__ = "1.2.2"
 __date__ = "2018-04-04"
-__updated__ = "2022-12-16"
+__updated__ = "2023-01-20"
 
 
 def add_build_options(parser: argparse.ArgumentParser):
@@ -34,7 +34,7 @@
     )
 
 
-def get_build_options(namespace: argparse.Namespace):
+def get_build_options(namespace: argparse.Namespace) -> List[str]:
     """Return build options usable in :func:`build_local_image`."""
     result = []
     if namespace.build_arg:
@@ -44,18 +44,13 @@
     return result
 
 
-def __parser():
-    program_version = __version__
-    program_build_date = str(__updated__)
-    program_version_message = "%%(prog)s %s (%s)" % (
-        program_version,
-        program_build_date,
-    )
-    program_shortdesc = __doc__.split(".", maxsplit=1)[0]
-    program_license = """%s
+def __parser() -> argparse.ArgumentParser:
+    program_version_message = f"%(prog)s {__version__} ({__updated__})"
+    program_short_description = __doc__.split(".", maxsplit=1)[0]
+    program_license = f"""{program_short_description}
 
-      Created by Vincent Hatakeyama on %s.
-      Copyright 2018-2022 XCG Consulting. All rights reserved.
+      Created by Vincent Hatakeyama on {__date__}.
+      Copyright 2018-2023 XCG Consulting. All rights reserved.
 
       Licensed under the MIT License
 
@@ -63,19 +58,17 @@
       or conditions of any kind, either express or implied.
 
     USAGE
-    """ % (
-        program_shortdesc,
-        str(__date__),
-    )
+    """
     parser = basic_parser(program_license, program_version_message)
     return parser
 
 
-def main(argv=None):  # IGNORE:C0111
+def main(argv: Optional[List[str]] = None) -> int:
     """Parse arguments and docker build"""
     # TODO the script assume it is launched from the parent super project
     project_path = os.path.realpath(".")
     project_name = os.path.basename(project_path)
+    # TODO changez pour une vérification de la présence d’un Dockerfile
     if project_name == "odoo_scripts":
         _logger.fatal("You must run this script from the super project")
         return 1
@@ -171,9 +164,13 @@
         call(["cp", dockerfile, debug_dockerfile])
 
         with open(debug_dockerfile, "a") as myfile:
-            myfile.write("\n# Developer helpers\nUSER root\nRUN apt-get update -qq\n")
-            myfile.write("RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ")
-            if odoo_type in ("odoo11", "odoo13", "odoo15"):
+            myfile.write("\n# Developer helpers\n")
+            if odoo_type in (ODOO_7, ODOO_8, ODOO_10, ODOO_11, ODOO_13, ODOO_15):
+                myfile.write("USER root\nRUN apt-get update -qq\n")
+                myfile.write(
+                    "RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "
+                )
+            if odoo_type in (ODOO_11, ODOO_13, ODOO_15):
                 myfile.write(
                     "python3-watchdog python3-ipdb python3-pyinotify python3-wheel\n"
                 )
@@ -186,15 +183,21 @@
                 # Install pdb++
                 myfile.write("RUN python3 -m pip install pdbpp\n")
                 myfile.write("USER odoo\n")
-            elif odoo_type in ("odoo10",):
+            elif odoo_type in (ODOO_10,):
                 myfile.write("python-ipdb\n")
                 myfile.write(
                     "RUN pip install watchdog --disable-pip-version-check "
                     "--system --no-cache-dir --only-binary wheel"
                 )
-            elif odoo_type in ("odoo7", "odoo8"):
+            elif odoo_type in (ODOO_7, ODOO_8):
                 myfile.write("python-ipdb\n")
                 myfile.write("RUN pip install pyinotify")
+            else:
+                myfile.write(
+                    "RUN python3 -m pip --no-cache-dir install --user pdbpp watchdog "
+                    "ipdb pyinotify\n"
+                )
+
         dockerfile = debug_dockerfile
         # TODO remove temp image
 
@@ -240,7 +243,7 @@
     return 0
 
 
-def build_local_image(build_options, force: bool = False) -> int:
+def build_local_image(build_options: List[str], force: bool = False) -> int:
     """Build the local image if necessary
     :param force: force build if True
     :return: 0 if success, docker failed error code otherwise
@@ -263,4 +266,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_build_clean.py b/odoo_scripts/docker_build_clean.py
--- a/odoo_scripts/docker_build_clean.py
+++ b/odoo_scripts/docker_build_clean.py
@@ -5,6 +5,7 @@
 import os
 import shutil
 import sys
+from typing import List, Optional
 
 from .list_modules import MODULES_LIST_FILE
 from .parsing import apply, basic_parser
@@ -43,7 +44,7 @@
     return parser
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Clean up after a build, callable version that parses arguments"""
     parser = __parser()
     nmspc = parser.parse_args(argv)
@@ -63,4 +64,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_build_copy.py b/odoo_scripts/docker_build_copy.py
--- a/odoo_scripts/docker_build_copy.py
+++ b/odoo_scripts/docker_build_copy.py
@@ -5,6 +5,7 @@
 import os
 import sys
 from subprocess import call
+from typing import List, Optional
 
 from .config import Config
 from .list_modules import list_modules
@@ -44,7 +45,7 @@
     return parser
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     nmspc = parser.parse_args(argv)
@@ -106,4 +107,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_build_doc.py b/odoo_scripts/docker_build_doc.py
--- a/odoo_scripts/docker_build_doc.py
+++ b/odoo_scripts/docker_build_doc.py
@@ -5,7 +5,7 @@
 import logging
 import os
 import sys
-from typing import Optional
+from typing import List, Optional
 
 from .config import Config
 from .docker_build import add_build_options, build_local_image, get_build_options
@@ -95,7 +95,7 @@
     )
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     add_build_options(parser)
@@ -108,4 +108,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_client.py b/odoo_scripts/docker_client.py
--- a/odoo_scripts/docker_client.py
+++ b/odoo_scripts/docker_client.py
@@ -5,16 +5,26 @@
 import re
 import tarfile
 from multiprocessing import Process
-from typing import Any, Collection, Dict, List, Mapping, Optional, Pattern, Tuple
+from typing import Any, Dict, List, Mapping, Optional, Pattern, Tuple
 
 import docker  # type: ignore[import]
 import dockerpty  # type: ignore[import]
 from docker.models.containers import Container  # type: ignore[import]
 from docker.types import Mount  # type: ignore[import]
 
+from .config import (
+    ODOO_7,
+    ODOO_8,
+    ODOO_9,
+    ODOO_10,
+    ODOO_11,
+    ODOO_13,
+    ODOO_15,
+    ODOO_16,
+    Configuration,
+)
+
 _logger = logging.getLogger(__name__)
-if docker.__version__ < "3.4.0":
-    _logger.warning("Unexpected python docker version: %s", docker.__version__)
 
 # TODO detect that user is member of docker group
 
@@ -220,24 +230,60 @@
             cls._replace_then_print(replaces, log)
 
 
-def modules_mount(
-    modules, project_path, target_path: str
-) -> Tuple[List[Mount], Dict[str, str]]:
-    """Return a list of Mount for the given modules"""
+def pythons_used(config) -> Tuple:
+    pythons: Tuple
+    if config.odoo_type == ODOO_10:
+        pythons = ("python2.7",)
+    elif config.odoo_type == ODOO_11:
+        pythons = ("python3.5", "python3.6", "python3.8")
+    elif config.odoo_type == ODOO_13:
+        pythons = ("python3.8",)
+    elif config.odoo_type in (ODOO_15, ODOO_16):
+        pythons = (
+            "python3.9",
+            "python3.10",
+        )
+    return pythons
+
+
+def modules_mount(config: Configuration) -> Tuple[List[Mount], Dict[str, str]]:
+    """Return a list of Mount for the modules in the configuration"""
+    modules = config.modules
+    _logger.debug("addon modules: %s", ",".join(config.modules))
+    project_path = config.path
     mount_list: List[Mount] = []
     mount_dict: Dict[str, str] = {}
+    target_paths: List[str]
+    if config.odoo_type in (ODOO_7, ODOO_8, ODOO_9, ODOO_10):
+        target_paths = ["/opt/odoo/sources/odoo/addons"]
+    elif config.odoo_type in (
+        ODOO_11,
+        ODOO_13,
+        ODOO_15,
+    ):
+        target_paths = [
+            f"/usr/local/lib/{python}/dist-packages/odoo/addons"
+            for python in pythons_used(config)
+        ]
+    else:
+        target_paths = [
+            f"/opt/odoo/lib/{python}/site-packages/odoo/addons"
+            for python in pythons_used(config)
+        ]
 
-    for module in modules:
-        full_target_path = os.path.join(target_path, os.path.basename(module))
-        full_project_path = os.path.realpath(os.path.join(project_path, module))
-        mount_list.append(Mount(full_target_path, full_project_path, "bind"))
-        mount_dict[full_target_path] = full_project_path
+    for target_path in target_paths:
+        for module in modules:
+            full_target_path = os.path.join(target_path, os.path.basename(module))
+            full_project_path = os.path.realpath(os.path.join(project_path, module))
+            mount_list.append(Mount(full_target_path, full_project_path, "bind"))
+            mount_dict[full_target_path] = full_project_path
 
     return mount_list, mount_dict
 
 
-def anthem_mounts(project_path: str, pythons: Collection[str]) -> List[Mount]:
-    """Return a list of monts for all the anthem songs present in the super project."""
+def anthem_mounts(config: Configuration) -> List[Mount]:
+    """Return a list of mounts for all the anthem songs present in the super project."""
+    project_path = os.path.realpath(config.path)
     mounts: List[Mount] = []
     # also mount songs for anthem to avoid having to rebuild the image when they
     # are changed.
@@ -245,7 +291,7 @@
         for root, dirs, _files in os.walk("songs"):
             for directory in dirs:
                 # with older odoo this will not be set
-                for python in pythons:
+                for python in pythons_used(config):
                     mounts.append(
                         Mount(
                             f"/usr/local/lib/{python}/dist-packages/{root}/{directory}",
@@ -255,3 +301,49 @@
                         )
                     )
     return mounts
+
+
+def odoo_source_mounts(config: Configuration, source_path: str) -> List[Mount]:
+    """Return a list of mounts for a directory containing Odoo sources."""
+    mounts: List[Mount] = []
+    full_source_path = os.path.realpath(source_path)
+    if config.odoo_type in (ODOO_7, ODOO_8):
+        mounts.append(Mount("/opt/odoo/sources/odoo", source_path, "bind"))
+    else:
+        install_path = (
+            "/usr/local/lib/%s/dist-packages/odoo"
+            if config.odoo_type
+            in (
+                ODOO_9,
+                ODOO_10,
+                ODOO_11,
+                ODOO_13,
+                ODOO_15,
+            )
+            else "/opt/odoo/lib/%s/site-packages/odoo"
+        )
+        for python in pythons_used(config):
+            mounts.append(
+                Mount(
+                    install_path % python,
+                    os.path.join(full_source_path, "odoo"),
+                    "bind",
+                )
+            )
+            # iterate on all the modules, avoids
+            addons_path = os.path.join(full_source_path, "addons")
+            mounts.append(
+                Mount(
+                    install_path % python + "/addons",
+                    addons_path,
+                    "bind",
+                )
+            )
+            mounts.append(
+                Mount(
+                    install_path % python + "/addons/base",
+                    os.path.join(full_source_path, "odoo", "addons", "base"),
+                    "bind",
+                )
+            )
+    return mounts
diff --git a/odoo_scripts/docker_dev_start.py b/odoo_scripts/docker_dev_start.py
--- a/odoo_scripts/docker_dev_start.py
+++ b/odoo_scripts/docker_dev_start.py
@@ -13,7 +13,7 @@
 from configparser import ConfigParser
 from contextlib import closing
 from subprocess import call, check_output
-from typing import List
+from typing import Any, Dict, List, Optional
 
 import dockerpty  # type: ignore[import]
 from docker.errors import APIError  # type: ignore[import]
@@ -30,10 +30,16 @@
     ODOO_12,
     ODOO_13,
     ODOO_15,
+    ODOO_16,
     Config,
 )
 from .docker_build import add_build_options, build_local_image, get_build_options
-from .docker_client import DockerClient, anthem_mounts, modules_mount
+from .docker_client import (
+    DockerClient,
+    anthem_mounts,
+    modules_mount,
+    odoo_source_mounts,
+)
 from .docker_flake8 import apply_flake8, parser_add_flake8_group
 from .docker_isort import apply_isort, parser_add_isort_group
 from .docker_postgresql import POSTGRES_PASSWORD, docker_run_postgresql
@@ -46,9 +52,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "3.8.0"
+__version__ = "3.9.0"
 __date__ = "2017-08-11"
-__updated__ = "2022-12-22"
+__updated__ = "2023-01-20"
 
 
 def __parser(project_name: str) -> ArgumentParser:
@@ -58,7 +64,7 @@
     program_license = f"""{program_shortdesc}
 
   Created by Vincent Hatakeyama on {__date__}.
-  Copyright 2017, 2018, 2019, 2020, 2021, 2022 XCG Consulting. All rights reserved.
+  Copyright 2017-2023 XCG Consulting. All rights reserved.
 
   Licensed under the MIT License
 
@@ -406,7 +412,7 @@
             )
 
 
-def main(argv=None):  # IGNORE:C0111
+def main(argv: Optional[List[str]] = None) -> int:
     """Parse arguments and launch conversion"""
     project_path = os.path.realpath(".")
     project_name = os.path.basename(project_path)
@@ -498,7 +504,7 @@
             return failed
 
     # options is only used with subprocess call
-    options = {
+    options: Dict[str, Any] = {
         "name": project_name,
         "tty": True,
         "extra_hosts": {},
@@ -515,15 +521,11 @@
         options["ports"][exposed_port] = odoo_run_port
     mounts: List[Mount] = []
     """Mounts for Odoo docker"""
-    tested_modules = None
+    tested_modules: str = ""
     # TODO handle other version of odoo (base image need to be changed too)
     coverage = (
         nmspc.test or nmspc.test_default or nmspc.install_test
-    ) and odoo_type in (
-        ODOO_11,
-        ODOO_13,
-        ODOO_15,
-    )
+    ) and odoo_type not in (ODOO_7, ODOO_8, ODOO_9, ODOO_10)
     arg = [
         "populate"
         if populate_model or populate_size
@@ -630,10 +632,11 @@
             )
         if not local_ip:
             _logger.info("Contacting Google Public DNS to find our IP")
-            local_ip = [
-                (s.connect(("8.8.8.8", 53)), s.getsockname()[0], s.close())
-                for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]
-            ][0][1]
+            for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]:
+                s.connect(("8.8.8.8", 53))
+                local_ip = s.getsockname()[0]
+                s.close()
+                break
 
         _logger.debug("IP found %s", local_ip)
 
@@ -643,11 +646,13 @@
     password = db_password
     if os.path.isfile(local_conf_path):
         _logger.info("Local configuration file found: %s", local_conf_path)
-        odoo_conf_file = (
-            "/etc/odoo/odoo.conf"
-            if odoo_type in (ODOO_11, ODOO_12, ODOO_13, ODOO_15)
-            else "/opt/odoo/etc/odoo.conf"
-        )
+        if odoo_type in (ODOO_7, ODOO_8, ODOO_9, ODOO_10):
+            odoo_conf_file = "/opt/odoo/etc/odoo.conf"
+        else:
+            odoo_conf_file = "/etc/opt/odoo/odoo.conf"
+            # change ODOO_RC for version<ODOO_16
+            options["environment"]["ODOO_RC"] = odoo_conf_file
+
         mounts.append(
             Mount(
                 odoo_conf_file,
@@ -963,7 +968,7 @@
                 pre_sql_script_basename,
                 database,
             )
-            exit_code, output = pg.exec_run(
+            exit_code, _output = pg.exec_run(
                 [
                     "psql",
                     "-U",
@@ -1005,7 +1010,7 @@
         redner_password = nmspc.redner_password
         # share redner database by project rather than by database
         redner_database = f"redner_{project_name}"
-        connect_kwargs = {
+        connect_kwargs: Dict[str, Any] = {
             "database": "postgres",
         }
         if start_postgresql:
@@ -1100,18 +1105,12 @@
                     )
 
     # volume magic
-    modules = config.modules
-    _logger.debug("addon modules: %s", ",".join(modules))
-    target_module_directory = "/mnt/addons"
-    mount_list, _ = modules_mount(modules, project_path, target_module_directory)
+    mount_list, _ = modules_mount(config)
     if mount_list:
         mounts.extend(mount_list)
-        all_addons_dir = [target_module_directory]
-        if odoo_type in (ODOO_7, ODOO_8):
-            all_addons_dir.append("/opt/odoo/sources/odoo/addons")
-        if all_addons_dir:
-            arg.append("--addons-path")
-            arg.append(",".join(all_addons_dir))
+    if odoo_type in (ODOO_7, ODOO_8):
+        arg.append("--addons-path=/opt/odoo/sources/odoo/addons")
+    # No way to set addons path to nothing for other versions unfortunately
 
     apply_flake8(nmspc, odoo_type)
     apply_isort(nmspc, config)
@@ -1119,7 +1118,7 @@
 
     # developer mode options
     if dev:
-        if odoo_type in (ODOO_10, ODOO_11, ODOO_13, ODOO_15):
+        if odoo_type in (ODOO_10, ODOO_11, ODOO_13, ODOO_15, ODOO_16):
             # automatically add reload if not already asks for
             if not any(opt in dev_opts for opt in ("all", "reload")):
                 dev_opts.append("reload")
@@ -1158,68 +1157,25 @@
     if os.path.exists(".git"):
         project_version = check_output(["git", "describe"]).decode()
 
-    if project_version:
-        options["environment"]["SENTRY_RELEASE"] = project_version
-        options["environment"]["VERSION"] = project_version
+    options["environment"]["SENTRY_RELEASE"] = project_version
+    options["environment"]["VERSION"] = project_version
 
     if odoo_help:
         arg.append("--help")
 
-    pythons = (
-        "python2.7",
-        "python3.5",
-        "python3.6",
-        "python3.8",
-        "python3.9",
-        "python3.10",
-    )
-
     # bind Odoo sources
     if odoo_sources:
         _logger.info("Binding Odoo sources at %s", odoo_sources)
-        if odoo_type in (ODOO_10, ODOO_11, ODOO_13, ODOO_15):
-            for python in pythons:
-                mounts.append(
-                    Mount(
-                        "/usr/local/lib/%s/dist-packages/odoo" % python,
-                        os.path.join(odoo_sources, "odoo"),
-                        "bind",
-                    )
-                )
-                mounts.append(
-                    Mount(
-                        "/usr/local/lib/%s/dist-packages/odoo/addons" % python,
-                        os.path.join(odoo_sources, "addons"),
-                        "bind",
-                    )
-                )
-                mounts.append(
-                    Mount(
-                        "/usr/local/lib/%s/dist-packages/odoo/addons/base" % python,
-                        os.path.join(odoo_sources, "odoo", "addons", "base"),
-                        "bind",
-                    )
-                )
-        elif odoo_type in (ODOO_7, ODOO_8):
-            mounts.append(Mount("/opt/odoo/sources/odoo", odoo_sources, "bind"))
-        else:
-            raise Exception("Unexpected odoo_type when binding sources: %s" % odoo_type)
+        mounts.extend(odoo_source_mounts(config, odoo_sources))
 
     # avoid chowning at each run, faster but can cause trouble
     # newer odoo images does not use this environment variable anymore
     options["environment"]["ODOO_CHOWN"] = "false"
 
     if coverage:
-        if nmspc.test_default:
-            source_files = target_module_directory
-        else:
-            # TODO when testing base modules, coverage will not be shown
-            source_files = ",".join(
-                [
-                    f"{target_module_directory}/{module}"
-                    for module in tested_modules.split(",")
-                ]
-            )
+        source_files = ",".join(
+            [f"odoo.addons.{module}" for module in tested_modules.split(",")]
+        )
         options["environment"]["COVERAGE_RUN_OPTIONS"] = (
             "--source=" + source_files + " --omit=*/__manifest__.py --branch"
         )
@@ -1244,7 +1200,7 @@
                 read_only=True,
             )
         )
-        mounts.extend(anthem_mounts(project_path, pythons))
+        mounts.extend(anthem_mounts(config))
 
         options["environment"].update(
             {
@@ -1274,7 +1230,7 @@
 
     if longpolling:
         longpolling_port = find_free_port()
-        run_kwargs = {
+        run_kwargs: Dict[str, Any] = {
             "command": ["caddy", "run", "--config", "/etc/caddy/Caddyfile"],
             "detach": True,
             "name": f"{project_name}_caddy",
diff --git a/odoo_scripts/docker_flake8.py b/odoo_scripts/docker_flake8.py
--- a/odoo_scripts/docker_flake8.py
+++ b/odoo_scripts/docker_flake8.py
@@ -5,30 +5,26 @@
 import logging
 import os
 import sys
+from typing import List, Optional
 
-from .config import ODOO_15, Config
+from .config import ODOO_15, ODOO_16, Config
 from .docker_client import DockerClient
 from .parsing import apply, basic_parser
 
-__version__ = "1.2.0"
+__version__ = "1.3.0"
 __date__ = "2020-09-10"
-__updated__ = "2022-01-27"
+__updated__ = "2023-01-20"
 
 _logger = logging.getLogger(__name__)
 
 
 def __parser():
-    program_version = __version__
-    program_build_date = str(__updated__)
-    program_version_message = "%%(prog)s %s (%s)" % (
-        program_version,
-        program_build_date,
-    )
-    program_shortdesc = __doc__.split(".", maxsplit=1)[0]
-    program_license = """%s
+    program_version_message = f"%(prog)s {__version__} ({__updated__})"
+    program_short_description = __doc__.split(".", maxsplit=1)[0]
+    program_license = f"""{program_short_description}
 
-      Created by Vincent Hatakeyama on %s.
-      Copyright 2020, 2022 XCG Consulting. All rights reserved.
+      Created by Vincent Hatakeyama on {__date__}.
+      Copyright 2020, 2022, 2023 XCG Consulting. All rights reserved.
 
       Licensed under the MIT License
 
@@ -36,10 +32,7 @@
       or conditions of any kind, either express or implied.
 
     USAGE
-    """ % (
-        program_shortdesc,
-        str(__date__),
-    )
+    """
     parser = basic_parser(program_license, program_version_message)
     return parser
 
@@ -70,7 +63,7 @@
         flake8(odoo_type)
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     nmspc = parser.parse_args(argv)
@@ -80,7 +73,7 @@
     return flake8(odoo_type)
 
 
-def flake8(odoo_type: str, pull: bool = True):
+def flake8(odoo_type: str, pull: bool = True) -> int:
     """Run flake8"""
     # determine image to use based on odoo version
     repository = "quay.orus.io/odoo/odoo"
@@ -89,6 +82,9 @@
         tag = "2"
     elif odoo_type == ODOO_15:
         tag = "15.0"
+    # TODO vérifier ce qu’il faut faire en v11 et 13
+    elif odoo_type == ODOO_16:
+        tag = "16.0"
     else:
         repository = "xcgd/flake8"
         tag = "3"
diff --git a/odoo_scripts/docker_isort.py b/odoo_scripts/docker_isort.py
--- a/odoo_scripts/docker_isort.py
+++ b/odoo_scripts/docker_isort.py
@@ -5,7 +5,7 @@
 import logging
 import os
 import sys
-from typing import Optional
+from typing import List, Optional
 
 from .config import ODOO_7, ODOO_8, ODOO_9, ODOO_10, ODOO_11, ODOO_12, ODOO_13, Config
 from .docker_build import add_build_options, build_local_image, get_build_options
@@ -13,9 +13,9 @@
 from .docker_prettier import get_volumes
 from .parsing import apply, basic_parser
 
-__version__ = "3.0.1"
+__version__ = "3.0.2"
 __date__ = "2020-09-10"
-__updated__ = "2022-12-05"
+__updated__ = "2023-01-20"
 
 _logger = logging.getLogger(__name__)
 _ISORT_DEST = "isort"
@@ -41,7 +41,7 @@
     program_license = """%s
 
       Created by Vincent Hatakeyama on %s.
-      Copyright 2020, 2022 XCG Consulting. All rights reserved.
+      Copyright 2020, 2022, 2023 XCG Consulting. All rights reserved.
 
       Licensed under the MIT License
 
@@ -128,7 +128,7 @@
     return 0
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     add_build_options(parser)
@@ -171,7 +171,7 @@
 
     pwd = os.environ["PWD"]
     volumes, path = get_volumes(pwd, directory, mode == WRITE_MODE)
-    _logger.info("Running prettier in %s", path)
+    _logger.info("Running isort in %s", path)
     return DockerClient.run(
         repository,
         tag,
diff --git a/odoo_scripts/docker_postgresql.py b/odoo_scripts/docker_postgresql.py
--- a/odoo_scripts/docker_postgresql.py
+++ b/odoo_scripts/docker_postgresql.py
@@ -9,7 +9,7 @@
 import tempfile
 import time
 from functools import partial
-from typing import Callable, Optional, Tuple
+from typing import Callable, List, Optional, Tuple
 
 import docker  # type: ignore[import]
 
@@ -175,7 +175,7 @@
     return parser
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Run postgresql docker, and exec a psql into it"""
     parser = __parser()
     namespace = parser.parse_args(argv)
@@ -218,4 +218,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_prettier.py b/odoo_scripts/docker_prettier.py
--- a/odoo_scripts/docker_prettier.py
+++ b/odoo_scripts/docker_prettier.py
@@ -4,6 +4,7 @@
 import logging
 import os
 import sys
+from typing import List, Optional
 
 from .config import ODOO_7, ODOO_8, ODOO_9, ODOO_10, Config
 from .docker_build import add_build_options, build_local_image, get_build_options
@@ -56,7 +57,7 @@
     return parser
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     add_build_options(parser)
@@ -124,4 +125,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_pylint.py b/odoo_scripts/docker_pylint.py
--- a/odoo_scripts/docker_pylint.py
+++ b/odoo_scripts/docker_pylint.py
@@ -3,13 +3,12 @@
 """
 import argparse
 import logging
-import os
 import sys
-from typing import Dict, List, Tuple
+from typing import Dict, List, Optional
 
 from docker.types import Mount  # type: ignore[import]
 
-from .config import ODOO_7, ODOO_8, ODOO_10, ODOO_11, ODOO_13, ODOO_15, Config
+from .config import Config
 from .docker_build import add_build_options, build_local_image, get_build_options
 from .docker_client import DockerClient, anthem_mounts, modules_mount
 from .parsing import apply, basic_parser
@@ -24,7 +23,7 @@
 """Name of destination variable used for pylint in parsing"""
 
 
-def __parser():
+def __parser() -> argparse.ArgumentParser:
     program_version_message = f"%(prog)s {__version__} ({__updated__})"
     program_short_description = __doc__.split(".", maxsplit=1)[0]
     program_license = f"""{program_short_description}
@@ -55,14 +54,14 @@
     )
 
 
-def apply_pylint(namespace: argparse.Namespace):
+def apply_pylint(namespace: argparse.Namespace) -> int:
     """Run pylint if the option was set."""
     if _PYLINT_DEST in namespace and getattr(namespace, _PYLINT_DEST):
         return pylint(getattr(namespace, _PYLINT_DEST), get_build_options(namespace))
     return 0
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     parser_add_pylint_group(parser)
@@ -72,45 +71,13 @@
     return apply_pylint(nmspc)
 
 
-def pylint(modules, build_options):
+def pylint(modules: str, build_options: List[str]):
     """Run pylint"""
-    project_path = os.path.realpath(".")
-
-    # always use project image
     config = Config()
-
-    odoo_type = config.odoo_type
-
-    mounts: List[Mount] = []
+    mounts: List[Mount]
+    target_source_dict: Dict[str, str]
+    mounts, target_source_dict = modules_mount(config)
     environment = {}
-    target_source_dict: Dict[str, str] = {}
-
-    pythons: Tuple
-    # odoo configuration is not read so put the modules in the path
-    if odoo_type in (ODOO_10, ODOO_11, ODOO_13, ODOO_15):
-        if odoo_type == ODOO_10:
-            pythons = ("python2.7",)
-        elif odoo_type == ODOO_11:
-            pythons = ("python3.5", "python3.6", "python3.8")
-        elif odoo_type == ODOO_13:
-            pythons = ("python3.8",)
-        elif odoo_type == ODOO_15:
-            pythons = (
-                "python3.9",
-                "python3.10",
-            )
-        for python in pythons:
-            result = modules_mount(
-                config.modules,
-                project_path,
-                f"/usr/local/lib/{python}/dist-packages/odoo/addons",
-            )
-            mounts.extend(result[0])
-            target_source_dict.update(result[1])
-    elif odoo_type in (ODOO_7, ODOO_8):
-        mounts, target_source_dict = modules_mount(
-            config.modules, project_path, "/opt/odoo/sources/odoo/addons"
-        )
 
     # Add a volume to store cache
     cache_volume_name = "pylint-cache"
@@ -119,7 +86,7 @@
     mounts.append(Mount(cache_target_path, cache_volume_name))
     environment["PYLINTHOME"] = cache_target_path
 
-    mounts.extend(anthem_mounts(project_path, pythons))
+    mounts.extend(anthem_mounts(config))
 
     build_local_image(build_options)
 
@@ -142,4 +109,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/docker_rednerd.py b/odoo_scripts/docker_rednerd.py
--- a/odoo_scripts/docker_rednerd.py
+++ b/odoo_scripts/docker_rednerd.py
@@ -25,9 +25,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "1.1.1"
+__version__ = "1.1.2"
 __date__ = "2021-01-14"
-__updated__ = "2022-09-12"
+__updated__ = "2023-01-20"
 
 MIGRATE = "migrate"
 ADMIN_PASSWORD = "admin-password"
@@ -130,7 +130,7 @@
         """Store the API key"""
         self._log_process: Optional[Process] = None
 
-    def start(self):
+    def start(self) -> bool:
         """Start rednerd container"""
         if self.force_pull or not self.docker_cli.images.list(self.base_image):
             _logger.info("Pulling %s", self.base_image)
@@ -453,7 +453,7 @@
     return parser
 
 
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> int:
     """Run redner docker using command line arguments"""
     parser = __parser()
     namespace = parser.parse_args(argv)
@@ -502,4 +502,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/import_base_import.py b/odoo_scripts/import_base_import.py
--- a/odoo_scripts/import_base_import.py
+++ b/odoo_scripts/import_base_import.py
@@ -6,7 +6,7 @@
 import logging
 import sys
 from datetime import datetime
-from typing import Dict, List
+from typing import Dict, List, Optional
 
 from requests_toolbelt import MultipartEncoder  # type: ignore[import]
 
@@ -16,9 +16,9 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "1.0.4"
+__version__ = "1.0.5"
 __date__ = "2019-07-29"
-__updated__ = "2020-02-17"
+__updated__ = "2022-01-20"
 
 
 def import_with_base_import(
@@ -115,7 +115,7 @@
     return 0
 
 
-def main(argv=None):  # IGNORE:C0111
+def main(argv: Optional[List[str]] = None) -> int:
     """Parse arguments and launch conversion"""
     program_version = __version__
     program_build_date = str(__updated__)
@@ -127,7 +127,7 @@
     program_license = """%s
 
   Created by Vincent Hatakeyama on %s.
-  Copyright 2019, 2020 XCG Consulting. All rights reserved.
+  Copyright 2019, 2020, 2023 XCG Consulting. All rights reserved.
 
   Licensed under the MIT License
 
@@ -159,4 +159,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/import_jsonrpc.py b/odoo_scripts/import_jsonrpc.py
--- a/odoo_scripts/import_jsonrpc.py
+++ b/odoo_scripts/import_jsonrpc.py
@@ -55,9 +55,21 @@
     def _ref(xmlid: str, raise_if_not_found: bool = True) -> Union[bool, int]:
         if xmlid not in xmlid_cache:
             try:
-                # o.env.ref also exists but also fails in odoo 15.
-                # xmlid_to_res_id is now _xmlid_to_res_id
-                if odoo.version == "15.0":
+                if odoo.version in (
+                    "7.0",
+                    "8.0",
+                    "9.0",
+                    "10.0",
+                    "11.0",
+                    "12.0",
+                    "13.0",
+                    "14.0",
+                ):
+                    full_xmlid = xmlid if "." in xmlid else "." + xmlid
+                    record_id = imd.xmlid_to_res_id(full_xmlid, raise_if_not_found)
+                else:
+                    # o.env.ref also exists but also fails in odoo 15.
+                    # xmlid_to_res_id is now _xmlid_to_res_id in 15 or 16.
                     if "." in xmlid:
                         module, xml_id = xmlid.split(".", maxsplit=1)
                     else:
@@ -70,9 +82,6 @@
                     record_id = (
                         imd.read(imd_id, ["res_id"])[0]["res_id"] if imd_id else None
                     )
-                else:
-                    full_xmlid = xmlid if "." in xmlid else "." + xmlid
-                    record_id = imd.xmlid_to_res_id(full_xmlid, raise_if_not_found)
             except RPCError as error:
                 if allow_missing_refs:
                     _logger.warning(str(error))
@@ -319,7 +328,7 @@
     return 0
 
 
-def main(argv=None):  # IGNORE:C0111
+def main(argv: Optional[List[str]] = None) -> int:
     """Parse arguments and launch conversion"""
     program_version = __version__
     program_build_date = str(__updated__)
@@ -393,4 +402,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/import_sql.py b/odoo_scripts/import_sql.py
--- a/odoo_scripts/import_sql.py
+++ b/odoo_scripts/import_sql.py
@@ -4,7 +4,7 @@
 import csv
 import logging
 import sys
-from typing import Dict, List
+from typing import Dict, List, Optional
 
 from .importing import add_importing_file_parsing, extract_model_table_from_parsed
 from .postgres import postgres_apply, postgres_connect_parser
@@ -185,7 +185,7 @@
         _logger.info("Commited.")
 
 
-def main(argv=None):  # IGNORE:C0111
+def main(argv: Optional[List[str]] = None) -> int:
     """Parse arguments and launch conversion"""
     program_version = __version__
     program_build_date = str(__updated__)
@@ -228,4 +228,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/importing.py b/odoo_scripts/importing.py
--- a/odoo_scripts/importing.py
+++ b/odoo_scripts/importing.py
@@ -75,8 +75,8 @@
     :param expected_filenames_pattern: a re.compile pattern
     :return: list of dict with the group data in it
     """
-    model_filenames: List[Dict[str, str]] = list()
-    file_list = list()
+    model_filenames: List[Dict[str, str]] = []
+    file_list = []
     if files:
         file_list.extend(files)
     if directories:
diff --git a/odoo_scripts/list_modules.py b/odoo_scripts/list_modules.py
--- a/odoo_scripts/list_modules.py
+++ b/odoo_scripts/list_modules.py
@@ -17,7 +17,7 @@
 __updated__ = "2022-12-16"
 
 
-def main(argv: List[str]) -> int:  # IGNORE:C0111
+def main(argv: Optional[List[str]] = None) -> int:
     """Parse arguments and launch conversion"""
     program_version = __version__
     program_build_date = str(__updated__)
@@ -43,7 +43,7 @@
     )
     # Argument parsing
     parser = basic_parser(program_license, program_version_message)
-    nmspc = parser.parse_args()
+    nmspc = parser.parse_args(argv)
     apply(nmspc)
     list_modules(None)
     return 0
@@ -61,4 +61,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/odoo_conf_inject_env_var.py b/odoo_scripts/odoo_conf_inject_env_var.py
--- a/odoo_scripts/odoo_conf_inject_env_var.py
+++ b/odoo_scripts/odoo_conf_inject_env_var.py
@@ -94,4 +94,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/odoo_scripts/update_duplicate_sources.py b/odoo_scripts/update_duplicate_sources.py
--- a/odoo_scripts/update_duplicate_sources.py
+++ b/odoo_scripts/update_duplicate_sources.py
@@ -6,6 +6,7 @@
 import shutil
 import sys
 from subprocess import call
+from typing import List, Optional
 
 import hglib  # type: ignore[import]
 
@@ -46,7 +47,7 @@
     return parser
 
 
-def main(argv=None) -> int:
+def main(argv: Optional[List[str]] = None) -> int:
     """Copy modules for a build, callable version that parses arguments"""
     parser = __parser()
     nmspc = parser.parse_args(argv)
@@ -159,4 +160,4 @@
 
 
 if __name__ == "__main__":
-    sys.exit(main(sys.argv[1:]))
+    sys.exit(main())
diff --git a/run_tests b/run_tests
--- a/run_tests
+++ b/run_tests
@@ -39,7 +39,7 @@
     loglevel="--log-level=test"
 fi
 logfile=$(tempfile --suffix=.log)
-if [ "$ODOO_TYPE" = "odoo11" ] || [ "$ODOO_TYPE" = "odoo13" ] || [ "$ODOO_TYPE" = "odoo15" ];
+if [ "$ODOO_TYPE" = "odoo11" ] || [ "$ODOO_TYPE" = "odoo13" ] || [ "$ODOO_TYPE" = "odoo15" ] || [ "$ODOO_TYPE" = "odoo16" ];
 then
     export PRE_ODOO_BIN="python3-coverage run --omit=__manifest__.py --source=$project_home "
 fi
diff --git a/start b/start
--- a/start
+++ b/start
@@ -27,7 +27,7 @@
         odoo_command=openerp-server
         odoo_addons_path=${ODOO_ADDONS_PATH:-/opt/odoo/sources/odoo}
         ;;
-    odoo10 | odoo11 | odoo13 | odoo15)
+    *)
         odoo_command=odoo
         ;;
 esac