diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..bfd7ac53df9f103f6dc8853738c63fd364445fde
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# Configuration for known file extensions
+[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{json,yml,yaml,rst,md}]
+indent_size = 2
+
+# Do not configure editor for libs and autogenerated content
+[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
+charset = unset
+end_of_line = unset
+indent_size = unset
+indent_style = unset
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
index 75bb2042901930f807a588241612a29dd75e34d5..109270f27c2a80740fab3c0fbc084b9d938f964e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,89 @@
-.*
-*.pyc
-!.gitignore
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+/.venv
+/.pytest_cache
+/.ruff_cache
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+*.eggs
+.copier-answers.yml
+
+# Windows installers
+*.msi
+
+# Debian packages
+*.deb
+
+# Redhat packages
+*.rpm
+
+# MacOS packages
+*.dmg
+*.pkg
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+
+# Pycharm
+.idea
+
+# Eclipse
+.settings
+
+# Visual Studio cache/options directory
+.vs/
+.vscode
+
+# OSX Files
+.DS_Store
+
+# Django stuff:
+*.log
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
+
+# Sphinx documentation
+docs/_build/
+
+# Backup files
+*~
+*.swp
+
+# OCA rules
+!static/lib/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..207e4c9037d15863a1efcc9bd98b6199792df472
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,123 @@
+exclude: |
+  (?x)
+  # NOT INSTALLABLE ADDONS
+  # END NOT INSTALLABLE ADDONS
+  # Files and folders generated by bots, to avoid loops
+  ^setup/|/static/description/index\.html$|
+  # We don't want to mess with tool-generated files
+  .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|^eslint.config.cjs|^prettier.config.cjs|
+  # Maybe reactivate this when all README files include prettier ignore tags?
+  ^README\.md$|
+  # Library files can have extraneous formatting (even minimized)
+  /static/(src/)?lib/|
+  # Repos using Sphinx to generate docs don't need prettying
+  ^docs/_templates/.*\.html$|
+  # Don't bother non-technical authors with formatting issues in docs
+  readme/.*\.(rst|md)$|
+  # Ignore build and dist directories in addons
+  /build/|/dist/|
+  # Ignore test files in addons
+  /tests/samples/.*|
+  # You don't usually want a bot to modify your legal texts
+  (LICENSE.*|COPYING.*)
+default_language_version:
+  python: python3
+  node: "22.9.0"
+repos:
+  - repo: local
+    hooks:
+      # These files are most likely copier diff rejection junks; if found,
+      # review them manually, fix the problem (if needed) and remove them
+      - id: forbidden-files
+        name: forbidden files
+        entry: found forbidden files; remove them
+        language: fail
+        files: "\\.rej$"
+      - id: en-po-files
+        name: en.po files cannot exist
+        entry: found a en.po file
+        language: fail
+        files: '[a-zA-Z0-9_]*/i18n/en\.po$'
+  - repo: https://github.com/oca/maintainer-tools
+    rev: bf9ecb9938b6a5deca0ff3d870fbd3f33341fded
+    hooks:
+      # update the NOT INSTALLABLE ADDONS section above
+      - id: oca-update-pre-commit-excluded-addons
+      - id: oca-fix-manifest-website
+        args: ["https://le-filament.com"]
+  - repo: https://github.com/OCA/odoo-pre-commit-hooks
+    rev: v0.0.33
+    hooks:
+      - id: oca-checks-odoo-module
+      - id: oca-checks-po
+        args:
+          - --disable=po-pretty-format
+  - repo: local
+    hooks:
+      - id: prettier
+        name: prettier (with plugin-xml)
+        entry: prettier
+        args:
+          - --write
+          - --list-different
+          - --ignore-unknown
+        types: [text]
+        files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
+        language: node
+        additional_dependencies:
+          - "prettier@3.3.3"
+          - "@prettier/plugin-xml@3.4.1"
+  - repo: local
+    hooks:
+      - id: eslint
+        name: eslint
+        entry: eslint
+        args:
+          - --color
+          - --fix
+        verbose: true
+        types: [javascript]
+        language: node
+        additional_dependencies:
+          - "eslint@9.12.0"
+          - "eslint-plugin-jsdoc@50.3.1"
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.6.0
+    hooks:
+      - id: trailing-whitespace
+        # exclude autogenerated files
+        exclude: /README\.rst$|\.pot?$
+      - id: end-of-file-fixer
+        # exclude autogenerated files
+        exclude: /README\.rst$|\.pot?$
+      - id: debug-statements
+      - id: fix-encoding-pragma
+        args: ["--remove"]
+      - id: check-case-conflict
+      - id: check-docstring-first
+      - id: check-executables-have-shebangs
+      - id: check-merge-conflict
+        # exclude files where underlines are not distinguishable from merge conflicts
+        exclude: /README\.rst$|^docs/.*\.rst$
+      - id: check-symlinks
+      - id: check-xml
+      - id: mixed-line-ending
+        args: ["--fix=lf"]
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.6.8
+    hooks:
+      - id: ruff
+        args: [--fix, --exit-non-zero-on-fix]
+      - id: ruff-format
+  - repo: https://github.com/OCA/pylint-odoo
+    rev: v9.1.3
+    hooks:
+      - id: pylint_odoo
+        name: pylint with optional checks
+        args:
+          - --rcfile=.pylintrc
+          - --exit-zero
+        verbose: true
+      - id: pylint_odoo
+        args:
+          - --rcfile=.pylintrc-mandatory
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000000000000000000000000000000000000..b855a9270efdb66248c84038041d1a589961ee44
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,123 @@
+
+
+[MASTER]
+load-plugins=pylint_odoo
+score=n
+
+[ODOOLINT]
+readme-template-url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
+manifest-required-authors=Le Filament
+manifest-required-keys=license
+manifest-deprecated-keys=description,active
+license-allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
+valid-odoo-versions=18.0
+
+[MESSAGES CONTROL]
+disable=all
+
+# This .pylintrc contains optional AND mandatory checks and is meant to be
+# loaded in an IDE to have it check everything, in the hope this will make
+# optional checks more visible to contributors who otherwise never look at a
+# green travis to see optional checks that failed.
+# .pylintrc-mandatory containing only mandatory checks is used the pre-commit
+# config as a blocking check.
+
+enable=anomalous-backslash-in-string,
+    api-one-deprecated,
+    api-one-multi-together,
+    assignment-from-none,
+    attribute-deprecated,
+    class-camelcase,
+    dangerous-default-value,
+    dangerous-view-replace-wo-priority,
+    development-status-allowed,
+    duplicate-id-csv,
+    duplicate-key,
+    duplicate-xml-fields,
+    duplicate-xml-record-id,
+    eval-referenced,
+    eval-used,
+    incoherent-interpreter-exec-perm,
+    license-allowed,
+    manifest-author-string,
+    manifest-deprecated-key,
+    manifest-required-author,
+    manifest-required-key,
+    manifest-version-format,
+    method-compute,
+    method-inverse,
+    method-required-super,
+    method-search,
+    openerp-exception-warning,
+    pointless-statement,
+    pointless-string-statement,
+    print-used,
+    redundant-keyword-arg,
+    redundant-modulename-xml,
+    reimported,
+    relative-import,
+    return-in-init,
+    rst-syntax-error,
+    sql-injection,
+    too-few-format-args,
+    translation-field,
+    translation-required,
+    unreachable,
+    use-vim-comment,
+    wrong-tabs-instead-of-spaces,
+    xml-syntax-error,
+    attribute-string-redundant,
+    character-not-valid-in-resource-link,
+    consider-merging-classes-inherited,
+    context-overridden,
+    create-user-wo-reset-password,
+    dangerous-filter-wo-user,
+    dangerous-qweb-replace-wo-priority,
+    deprecated-data-xml-node,
+    deprecated-openerp-xml-node,
+    duplicate-po-message-definition,
+    except-pass,
+    file-not-used,
+    invalid-commit,
+    manifest-maintainers-list,
+    missing-newline-extrafiles,
+    missing-readme,
+    missing-return,
+    odoo-addons-relative-import,
+    old-api7-method-defined,
+    po-msgstr-variables,
+    po-syntax-error,
+    renamed-field-parameter,
+    resource-not-exist,
+    str-format-used,
+    test-folder-imported,
+    translation-contains-variable,
+    translation-positional-used,
+    unnecessary-utf8-coding-comment,
+    website-manifest-key-not-valid-uri,
+    xml-attribute-translatable,
+    xml-deprecated-qweb-directive,
+    xml-deprecated-tree-attribute,
+    external-request-timeout,
+    # messages that do not cause the lint step to fail
+    consider-merging-classes-inherited,
+    create-user-wo-reset-password,
+    dangerous-filter-wo-user,
+    deprecated-module,
+    file-not-used,
+    invalid-commit,
+    missing-manifest-dependency,
+    missing-newline-extrafiles,
+    missing-readme,
+    no-utf8-coding-comment,
+    odoo-addons-relative-import,
+    old-api7-method-defined,
+    redefined-builtin,
+    too-complex,
+    unnecessary-utf8-coding-comment
+
+
+[REPORTS]
+msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
+output-format=colorized
+reports=no
diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory
new file mode 100644
index 0000000000000000000000000000000000000000..f9450512a1b688db0b671c400b492ac9e4e2e425
--- /dev/null
+++ b/.pylintrc-mandatory
@@ -0,0 +1,98 @@
+
+[MASTER]
+load-plugins=pylint_odoo
+score=n
+
+[ODOOLINT]
+readme-template-url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
+manifest-required-authors=Le Filament
+manifest-required-keys=license
+manifest-deprecated-keys=description,active
+license-allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
+valid-odoo-versions=18.0
+
+[MESSAGES CONTROL]
+disable=all
+
+enable=anomalous-backslash-in-string,
+    api-one-deprecated,
+    api-one-multi-together,
+    assignment-from-none,
+    attribute-deprecated,
+    class-camelcase,
+    dangerous-default-value,
+    dangerous-view-replace-wo-priority,
+    development-status-allowed,
+    duplicate-id-csv,
+    duplicate-key,
+    duplicate-xml-fields,
+    duplicate-xml-record-id,
+    eval-referenced,
+    eval-used,
+    incoherent-interpreter-exec-perm,
+    license-allowed,
+    manifest-author-string,
+    manifest-deprecated-key,
+    manifest-required-author,
+    manifest-required-key,
+    manifest-version-format,
+    method-compute,
+    method-inverse,
+    method-required-super,
+    method-search,
+    openerp-exception-warning,
+    pointless-statement,
+    pointless-string-statement,
+    print-used,
+    redundant-keyword-arg,
+    redundant-modulename-xml,
+    reimported,
+    relative-import,
+    return-in-init,
+    rst-syntax-error,
+    sql-injection,
+    too-few-format-args,
+    translation-field,
+    translation-required,
+    unreachable,
+    use-vim-comment,
+    wrong-tabs-instead-of-spaces,
+    xml-syntax-error,
+    attribute-string-redundant,
+    character-not-valid-in-resource-link,
+    consider-merging-classes-inherited,
+    context-overridden,
+    create-user-wo-reset-password,
+    dangerous-filter-wo-user,
+    dangerous-qweb-replace-wo-priority,
+    deprecated-data-xml-node,
+    deprecated-openerp-xml-node,
+    duplicate-po-message-definition,
+    except-pass,
+    file-not-used,
+    invalid-commit,
+    manifest-maintainers-list,
+    missing-newline-extrafiles,
+    missing-readme,
+    missing-return,
+    odoo-addons-relative-import,
+    old-api7-method-defined,
+    po-msgstr-variables,
+    po-syntax-error,
+    renamed-field-parameter,
+    resource-not-exist,
+    str-format-used,
+    test-folder-imported,
+    translation-contains-variable,
+    translation-positional-used,
+    unnecessary-utf8-coding-comment,
+    website-manifest-key-not-valid-uri,
+    xml-attribute-translatable,
+    xml-deprecated-qweb-directive,
+    xml-deprecated-tree-attribute,
+    external-request-timeout
+
+[REPORTS]
+msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
+output-format=colorized
+reports=no
diff --git a/.ruff.toml b/.ruff.toml
new file mode 100644
index 0000000000000000000000000000000000000000..0f1e35f24803bf777951495f745cb2f1630cb4cb
--- /dev/null
+++ b/.ruff.toml
@@ -0,0 +1,30 @@
+
+target-version = "py310"
+fix = true
+
+[lint]
+extend-select = [
+    "B",
+    "C90",
+    "E501",  # line too long (default 88)
+    "I",  # isort
+    "UP",  # pyupgrade
+]
+exclude = ["setup/*"]
+
+[format]
+exclude = ["setup/*"]
+
+[lint.per-file-ignores]
+"__init__.py" = ["F401", "I001"]  # ignore unused and unsorted imports in __init__.py
+"__manifest__.py" = ["B018"]  # useless expression
+
+[lint.isort]
+section-order = ["future", "standard-library", "third-party", "odoo", "odoo-addons", "first-party", "local-folder"]
+
+[lint.isort.sections]
+"odoo" = ["odoo"]
+"odoo-addons" = ["odoo.addons"]
+
+[lint.mccabe]
+max-complexity = 16
diff --git a/__manifest__.py b/__manifest__.py
index ff3678e6a5e25485370f1ff22dc25dbd3b0bb2b4..198596b94fba8a11ea47e0b4ed2d2534bf8ec2dd 100644
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -1,12 +1,16 @@
 # © 2022 Le Filament (<http://www.le-filament.com>)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 {
-    'name': 'Partners Geolocation - API Data Gouv',
-    'version': '0.0',
-    'category': 'Hidden/Tools',
-    'depends': ['base_geolocalize'],
-    'data': [
-        'data/data.xml',
+    "name": "Partners Geolocation - API Data Gouv",
+    "summary": "France API Gouv geolocation connection",
+    "author": "Le Filament",
+    "license": "AGPL-3",
+    "website": "https://www.le-filament.com",
+    "version": "18.0.1.0.0",
+    "category": "Hidden/Tools",
+    "depends": ["base_geolocalize"],
+    "data": [
+        "data/data.xml",
     ],
-    'installable': True,
+    "installable": True,
 }
diff --git a/data/data.xml b/data/data.xml
index 85c72cf6511f566ea2084eb7887a9095ba8cabac..7c6e6f1d4e575a310b8e9687d31b435937d90900 100644
--- a/data/data.xml
+++ b/data/data.xml
@@ -1,8 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8" ?>
 <odoo>
     <record id="geoprovider_adresse_data_gouv" model="base.geo_provider">
         <field name="tech_name">adressedatagouv</field>
         <field name="name">Adresse Data Gouv</field>
     </record>
-
 </odoo>
diff --git a/eslint.config.cjs b/eslint.config.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..0d5731f89a8bc2073894c17dfe41e410db0e4cfe
--- /dev/null
+++ b/eslint.config.cjs
@@ -0,0 +1,202 @@
+jsdoc = require("eslint-plugin-jsdoc");
+
+const config = [{
+    plugins: {
+      jsdoc,
+    },
+
+    languageOptions: {
+        globals: {
+            _: "readonly",
+            $: "readonly",
+            fuzzy: "readonly",
+            jQuery: "readonly",
+            moment: "readonly",
+            odoo: "readonly",
+            openerp: "readonly",
+            owl: "readonly",
+            luxon: "readonly",
+        },
+
+        ecmaVersion: 2024,
+        sourceType: "script",
+    },
+
+    rules: {
+        "accessor-pairs": "warn",
+        "array-callback-return": "warn",
+        "callback-return": "warn",
+        "capitalized-comments": ["warn", "always", {
+            ignoreConsecutiveComments: true,
+            ignoreInlineComments: true,
+        }],
+        complexity: ["warn", 15],
+        "constructor-super": "warn",
+        "dot-notation": "warn",
+        eqeqeq: "warn",
+        "global-require": "warn",
+        "handle-callback-err": "warn",
+        "id-blacklist": "warn",
+        "id-match": "warn",
+        "init-declarations": "error",
+        "max-depth": "warn",
+        "max-nested-callbacks": "warn",
+        "max-statements-per-line": "warn",
+        "no-alert": "warn",
+        "no-array-constructor": "warn",
+        "no-caller": "warn",
+        "no-case-declarations": "warn",
+        "no-class-assign": "warn",
+        "no-cond-assign": "error",
+        "no-const-assign": "error",
+        "no-constant-condition": "warn",
+        "no-control-regex": "warn",
+        "no-debugger": "error",
+        "no-delete-var": "warn",
+        "no-div-regex": "warn",
+        "no-dupe-args": "error",
+        "no-dupe-class-members": "error",
+        "no-dupe-keys": "error",
+        "no-duplicate-case": "error",
+        "no-duplicate-imports": "error",
+        "no-else-return": "warn",
+        "no-empty-character-class": "warn",
+        "no-empty-function": "error",
+        "no-empty-pattern": "error",
+        "no-empty": "warn",
+        "no-eq-null": "error",
+        "no-eval": "error",
+        "no-ex-assign": "error",
+        "no-extend-native": "warn",
+        "no-extra-bind": "warn",
+        "no-extra-boolean-cast": "warn",
+        "no-extra-label": "warn",
+        "no-fallthrough": "warn",
+        "no-func-assign": "error",
+        "no-global-assign": "error",
+        "no-implicit-coercion": ["warn", {
+            allow: ["~"],
+        }],
+        "no-implicit-globals": "warn",
+        "no-implied-eval": "warn",
+        "no-inline-comments": "warn",
+        "no-inner-declarations": "warn",
+        "no-invalid-regexp": "warn",
+        "no-irregular-whitespace": "warn",
+        "no-iterator": "warn",
+        "no-label-var": "warn",
+        "no-labels": "warn",
+        "no-lone-blocks": "warn",
+        "no-lonely-if": "error",
+        "no-mixed-requires": "error",
+        "no-multi-str": "warn",
+        "no-native-reassign": "error",
+        "no-negated-condition": "warn",
+        "no-negated-in-lhs": "error",
+        "no-new-func": "warn",
+        "no-new-object": "warn",
+        "no-new-require": "warn",
+        "no-new-symbol": "warn",
+        "no-new-wrappers": "warn",
+        "no-new": "warn",
+        "no-obj-calls": "warn",
+        "no-octal-escape": "warn",
+        "no-octal": "warn",
+        "no-param-reassign": "warn",
+        "no-path-concat": "warn",
+        "no-process-env": "warn",
+        "no-process-exit": "warn",
+        "no-proto": "warn",
+        "no-prototype-builtins": "warn",
+        "no-redeclare": "warn",
+        "no-regex-spaces": "warn",
+        "no-restricted-globals": "warn",
+        "no-restricted-imports": "warn",
+        "no-restricted-modules": "warn",
+        "no-restricted-syntax": "warn",
+        "no-return-assign": "error",
+        "no-script-url": "warn",
+        "no-self-assign": "warn",
+        "no-self-compare": "warn",
+        "no-sequences": "warn",
+        "no-shadow-restricted-names": "warn",
+        "no-shadow": "warn",
+        "no-sparse-arrays": "warn",
+        "no-sync": "warn",
+        "no-this-before-super": "warn",
+        "no-throw-literal": "warn",
+        "no-undef-init": "warn",
+        "no-undef": "error",
+        "no-unmodified-loop-condition": "warn",
+        "no-unneeded-ternary": "error",
+        "no-unreachable": "error",
+        "no-unsafe-finally": "error",
+        "no-unused-expressions": "error",
+        "no-unused-labels": "error",
+        "no-unused-vars": "error",
+        "no-use-before-define": "error",
+        "no-useless-call": "warn",
+        "no-useless-computed-key": "warn",
+        "no-useless-concat": "warn",
+        "no-useless-constructor": "warn",
+        "no-useless-escape": "warn",
+        "no-useless-rename": "warn",
+        "no-void": "warn",
+        "no-with": "warn",
+        "operator-assignment": ["error", "always"],
+        "prefer-const": "warn",
+        radix: "warn",
+        "require-yield": "warn",
+        "sort-imports": "warn",
+        "spaced-comment": ["error", "always"],
+        strict: ["error", "function"],
+        "use-isnan": "error",
+
+        "jsdoc/check-tag-names": "warn",
+        "jsdoc/check-types": "warn",
+        "jsdoc/require-param-description": "off",
+        "jsdoc/require-return": "off",
+        "jsdoc/require-return-description": "off",
+        "jsdoc/require-return-type": "off",
+
+        "valid-typeof": "warn",
+        yoda: "warn",
+    },
+
+    settings: {
+        jsdoc: {
+            tagNamePreference: {
+                arg: "param",
+                argument: "param",
+                augments: "extends",
+                constructor: "class",
+                exception: "throws",
+                func: "function",
+                method: "function",
+                prop: "property",
+                return: "returns",
+                virtual: "abstract",
+                yield: "yields",
+            },
+            preferredTypes: {
+                array: "Array",
+                bool: "Boolean",
+                boolean: "Boolean",
+                number: "Number",
+                object: "Object",
+                str: "String",
+                string: "String",
+            },
+        },
+    },
+
+}, {
+    files: ["**/*.esm.js"],
+
+    languageOptions: {
+        ecmaVersion: 2024,
+        sourceType: "module",
+    },
+}];
+
+module.exports = config
diff --git a/models/base_geocoder.py b/models/base_geocoder.py
index bae895d2d773e23c2dd78269c25f9ae84c931833..4ec3aa2ff550790455caef3f21957fc73b6ee215 100644
--- a/models/base_geocoder.py
+++ b/models/base_geocoder.py
@@ -1,10 +1,11 @@
 # © 2022 Le Filament (<http://www.le-filament.com>)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 
-import requests
 import logging
 
-from odoo import api, models, tools
+import requests
+
+from odoo import models
 
 _logger = logging.getLogger(__name__)
 
@@ -12,12 +13,15 @@ _logger = logging.getLogger(__name__)
 class GeoCoder(models.AbstractModel):
     _inherit = "base.geocoder"
 
-    def _geo_query_address_adressedatagouv(self, street=None, zip=None, city=None, state=None, country=None):
-        address = ("%s %s %s %s" % (street or '', zip or '', city or '', 'France')).strip()
-        addr = address.replace(' ', '+')
+    def _geo_query_address_adressedatagouv(
+        self, street=None, zip=None, city=None, state=None, country=None
+    ):
+        country = country or "France"
+        address_list = list(filter(bool, [street, zip, city, country]))
+        address = "+".join(address_list)
         if zip:
-            addr += '&postcode=%s' % zip
-        return addr
+            address = f"{address}&postcode={zip}"
+        return address
 
     def _call_adressedatagouv(self, addr, **kw):
         """
@@ -25,21 +29,24 @@ class GeoCoder(models.AbstractModel):
         :return: (latitude, longitude) or None if not found
         """
         if not addr:
-            _logger.info('invalid address given')
+            _logger.info("invalid address given")
             return None
 
-        url = 'https://api-adresse.data.gouv.fr/search/?q='
+        url = "https://api-adresse.data.gouv.fr/search/?q="
 
         try:
-            response = requests.get(url + addr)
-            _logger.info('Address API Gouv service called')
+            response = requests.get(url + addr, timeout=15)
+            _logger.info("Address API Gouv service called")
             if response.status_code != 200:
-                _logger.error('Request to Address API Gouv failed.\nCode: %s\nContent: %s'
-                              % (response.status_code, response.content))
+                _logger.error(
+                    f"Request to Address API Gouv failed."
+                    f"\nCode:{response.status_code} \n"
+                    f"Content: {response.content}"
+                )
             result = response.json()
         except Exception as e:
             self._raise_query_error(e)
 
-        if result.get('features', False):
-            geo = result.get('features')[0].get('geometry').get('coordinates')
+        if result.get("features", False):
+            geo = result.get("features")[0].get("geometry").get("coordinates")
         return float(geo[1]), float(geo[0])
diff --git a/prettier.config.cjs b/prettier.config.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..e66cd82cc1e4dfeded9749598144f820c8491fab
--- /dev/null
+++ b/prettier.config.cjs
@@ -0,0 +1,14 @@
+/** @type {import('prettier').Config} */
+
+const config = {
+  // https://github.com/prettier/prettier/issues/15388#issuecomment-1717746872
+  plugins: [require.resolve("@prettier/plugin-xml")],
+  bracketSpacing: false,
+  printWidth: 88,
+  proseWrap: "always",
+  semi: true,
+  trailingComma: "es5",
+  xmlWhitespaceSensitivity: "preserve",
+};
+
+module.exports = config;
diff --git a/tests/__init__.py b/tests/__init__.py
index 44d3f6100bc8715e2218de7de22b41b6e186c8a2..b5d04a7b9f0422deaffeeedc0364a520edd7a495 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,3 +1,2 @@
-# -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 from . import test_geolocalize
diff --git a/tests/test_geolocalize.py b/tests/test_geolocalize.py
index a9403a9d251ef9c557d491ea2c3e4467d05b304f..0098e65eb87542cf9bc4ea0972ff85a34b3c6755 100644
--- a/tests/test_geolocalize.py
+++ b/tests/test_geolocalize.py
@@ -1,17 +1,14 @@
 # © 2022 Le Filament (<http://www.le-filament.com>)
 # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-from odoo.tests import TransactionCase
-from odoo.exceptions import UserError
-
 import odoo.tests
+from odoo.tests import TransactionCase
 
 
-@odoo.tests.tagged('external', '-standard')
+@odoo.tests.tagged("external", "-standard")
 class TestGeoLocalize(TransactionCase):
-
     def test_default_adressedatagouv(self):
-        """ Test that adressedatagouv localize service works. """
-        test_partner = self.env.ref('base.res_partner_2')
+        """Test that adressedatagouv localize service works."""
+        test_partner = self.env.ref("base.res_partner_2")
         test_partner.geo_localize()
         self.assertTrue(test_partner.partner_longitude)
         self.assertTrue(test_partner.partner_latitude)