├── MANIFEST.in
├── tests
├── __init__.py
└── test_main.py
├── testing
└── resources
│ ├── test_repo
│ ├── app_module
│ │ ├── __init__.py
│ │ ├── static
│ │ │ └── description
│ │ │ │ └── index.html
│ │ ├── README.rst
│ │ └── __manifest__.py
│ ├── broken_module
│ │ ├── xml_empty.xml
│ │ ├── xml_semi_empty.po
│ │ ├── coding_latin.py
│ │ ├── encoding_utf8.py
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── xml_not_important_0.xml
│ │ │ ├── xml_not_important_1.xml
│ │ │ ├── xml_not_important_4.xml
│ │ │ ├── xml_not_important_3.xml
│ │ │ └── test_model.py
│ │ ├── xml_syntax_error.XML
│ │ ├── report
│ │ │ └── test_report.xml
│ │ ├── doc
│ │ │ └── index.rst
│ │ ├── rst_syntax.rst
│ │ ├── wointerpreter_wx.py
│ │ ├── skip_file_not_used.xml
│ │ ├── interpreter_wx.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── model_inhe1.py
│ │ │ └── model_inhe2.py
│ │ ├── xml_special_char.xml
│ │ ├── __init__.py
│ │ ├── lib
│ │ │ ├── tab_no_check.js
│ │ │ └── broken_example.js
│ │ ├── interpreter_wox.py
│ │ ├── skip_xml_check_2.xml
│ │ ├── broken_example2.js
│ │ ├── broken_example.js
│ │ ├── report.xml
│ │ ├── model_view.xml
│ │ ├── demo
│ │ │ └── duplicated_id_demo.xml
│ │ ├── skip_xml_check.xml
│ │ ├── skip_xml_check_3.xml
│ │ ├── i18n
│ │ │ ├── broken_module.pot
│ │ │ └── es.po
│ │ ├── ir.model.access.csv
│ │ ├── template1.xml
│ │ ├── __openerp__.py
│ │ ├── model_view_odoo.xml
│ │ ├── pylint_oca_broken.py
│ │ ├── model_view2.xml
│ │ └── model_view_odoo2.xml
│ ├── broken_module3
│ │ ├── __init__.py
│ │ ├── README.rst
│ │ ├── __manifest__.py
│ │ ├── migrations
│ │ │ └── 8.0.1.0.2
│ │ │ │ └── pre-migration.py
│ │ ├── ir.model.access.csv
│ │ └── __openerp__.py
│ ├── pylint_deprecated_modules
│ │ ├── ipdb.py
│ │ ├── pdb.py
│ │ ├── pudb.py
│ │ ├── __init__.py
│ │ ├── openerp
│ │ │ ├── osv
│ │ │ │ └── __init__.py
│ │ │ └── __init__.py
│ │ └── README.md
│ ├── test_module
│ │ ├── samples
│ │ │ └── my_no_odoo_file.csv
│ │ ├── migrations
│ │ │ ├── 11.0.1.0.0
│ │ │ │ └── pre-migration.py
│ │ │ └── 10.0.1.0.0
│ │ │ │ └── pre-migration.py
│ │ ├── __init__.py
│ │ ├── static
│ │ │ └── src
│ │ │ │ └── xml
│ │ │ │ └── widget.xml
│ │ ├── absolute_import.py
│ │ ├── test_example.js
│ │ ├── res_partner_unlink.py
│ │ ├── README.rst
│ │ ├── sale_order_unlink.py
│ │ ├── __openerp__.py
│ │ ├── osv_expression.py
│ │ ├── res_users.xml
│ │ ├── security
│ │ │ └── ir.model.access.csv
│ │ ├── model_view.xml
│ │ ├── website_templates.xml
│ │ ├── except_pass.py
│ │ └── i18n
│ │ │ └── fr.po
│ ├── womanifest_module
│ │ ├── ir.model.access.csv
│ │ ├── __init__.py
│ │ └── doc
│ │ │ └── index.rst
│ ├── broken_module2
│ │ ├── i18n
│ │ │ └── en.po
│ │ ├── migrations
│ │ │ └── 2.0
│ │ │ │ └── post-migration.py
│ │ ├── __init__.py
│ │ ├── tests
│ │ │ └── data
│ │ │ │ ├── help_test_data.xml
│ │ │ │ ├── odoo_data_noupdate_0.xml
│ │ │ │ └── odoo_data_noupdate_1.xml
│ │ ├── __openerp__.py
│ │ ├── README.rst
│ │ └── ir.model.access.csv
│ ├── eleven_module
│ │ ├── migrations
│ │ │ ├── 11.0.1.0.1
│ │ │ │ └── pre-migration.py
│ │ │ └── 11.0.1.0.0
│ │ │ │ └── not_used_from_manifest.xml
│ │ ├── README.rst
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ └── test_model1.py
│ │ ├── __init__.py
│ │ ├── utf8_models.py
│ │ ├── __manifest__.py
│ │ ├── models.py
│ │ └── security
│ │ │ └── ir.model.access.csv
│ ├── twelve_module
│ │ ├── migrations
│ │ │ └── 12.0.0.0.0
│ │ │ │ └── pre-migration.py
│ │ ├── README.rst
│ │ ├── __init__.py
│ │ ├── utf8_models.py
│ │ ├── models.py
│ │ ├── security
│ │ │ └── ir.model.access.csv
│ │ └── __manifest__.py
│ └── no_odoo_module
│ │ ├── __init__.py
│ │ ├── myfile.py
│ │ └── eval_used.py
│ ├── test_repo_odoo_namespace
│ └── odoo
│ │ ├── addons
│ │ ├── test_namespace_package_module
│ │ │ ├── __init__.py
│ │ │ ├── __manifest__.py
│ │ │ └── i18n
│ │ │ │ └── fr.po
│ │ └── __init__.py
│ │ └── __init__.py
│ └── .pylintrc-odoo-deprecated-model-methods
├── requirements.txt
├── src
└── pylint_odoo
│ ├── augmentations
│ ├── __init__.py
│ └── main.py
│ ├── __init__.py
│ ├── checkers
│ ├── __init__.py
│ ├── vim_comment.py
│ ├── odoo_base_checker.py
│ └── custom_logging.py
│ ├── plugin.py
│ └── misc.py
├── pyproject.toml
├── .pre-commit-hooks.yaml
├── .prettierrc.yml
├── test-requirements.txt
├── .isort.cfg
├── .flake8
├── .coveragerc
├── .editorconfig
├── .bumpversion.cfg
├── .gitignore
├── setup.cfg
├── setup.py
├── pytest.ini
├── .pylintrc
├── tox.ini
├── .github
└── workflows
│ ├── test.yml
│ └── stale.yml
├── .pre-commit-config.yaml
└── README.md
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include requirements.txt
2 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from . import test_main
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/app_module/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/xml_empty.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module3/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/ipdb.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/pdb.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/pudb.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pylint-plugin-utils==0.8.*
2 | pylint==3.3.*
3 |
--------------------------------------------------------------------------------
/src/pylint_odoo/augmentations/__init__.py:
--------------------------------------------------------------------------------
1 | from . import main
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/xml_semi_empty.po:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/samples/my_no_odoo_file.csv:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/womanifest_module/ir.model.access.csv:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/i18n/en.po:
--------------------------------------------------------------------------------
1 | PO syntax error
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/migrations/2.0/post-migration.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/openerp/osv/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/migrations/11.0.1.0.0/pre-migration.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/migrations/11.0.1.0.1/pre-migration.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/migrations/12.0.0.0.0/pre-migration.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/coding_latin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: latin-1 -*-
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/encoding_utf8.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from . import test_model
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/__init__.py:
--------------------------------------------------------------------------------
1 | from . import osv_expression
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/womanifest_module/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
--------------------------------------------------------------------------------
/src/pylint_odoo/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "9.3.22"
2 |
3 | from .plugin import register
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/README.rst:
--------------------------------------------------------------------------------
1 | # Eleven module module for tests
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/README.rst:
--------------------------------------------------------------------------------
1 | # Eleven module module for tests
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from . import test_model1
3 |
--------------------------------------------------------------------------------
/testing/resources/test_repo_odoo_namespace/odoo/addons/test_namespace_package_module/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/app_module/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 | Testing
3 |
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/no_odoo_module/__init__.py:
--------------------------------------------------------------------------------
1 | from . import myfile
2 | from . import eval_used
3 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/__init__.py:
--------------------------------------------------------------------------------
1 | from . import models
2 | from . import utf8_models
3 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import tests
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/xml_syntax_error.XML:
--------------------------------------------------------------------------------
1 |
2 | <{xml-syntax-error}>
--------------------------------------------------------------------------------
/src/pylint_odoo/checkers/__init__.py:
--------------------------------------------------------------------------------
1 | from . import odoo_addons
2 | from . import vim_comment
3 | from . import custom_logging
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/report/test_report.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/womanifest_module/doc/index.rst:
--------------------------------------------------------------------------------
1 | Module broken
2 | ===============
3 | ``````````
4 | syntax error
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/app_module/README.rst:
--------------------------------------------------------------------------------
1 | app module
2 | ==========
3 |
4 | This module was written to check the test lint
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/__init__.py:
--------------------------------------------------------------------------------
1 | from . import models
2 | from . import utf8_models
3 | from .tests import test_model1
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/static/src/xml/widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/doc/index.rst:
--------------------------------------------------------------------------------
1 | Module broken
2 | ======================
3 |
4 |
5 | ``````````
6 | syntax error
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/rst_syntax.rst:
--------------------------------------------------------------------------------
1 | Module broken
2 | ======================
3 |
4 |
5 | ``````````
6 | syntax error
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module3/README.rst:
--------------------------------------------------------------------------------
1 | Test module 3
2 | =============
3 |
4 | This module was written to check the test lint
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/tests/xml_not_important_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/tests/xml_not_important_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/openerp/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import osv # pylint: disable=W0402
4 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length=119
3 |
4 | [build-system]
5 | requires = ["setuptools >=42"]
6 | build-backend = "setuptools.build_meta"
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/tests/xml_not_important_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/wointerpreter_wx.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | "Module python without interpreter but with execute permission."
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/skip_file_not_used.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/tests/data/help_test_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/no_odoo_module/myfile.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import other_package
3 |
4 | if __name__ == '__main__':
5 | var = other_package
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/interpreter_wx.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # !/usr/bin/python
3 |
4 | "Module python with interpreter and execute permission."
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/models/__init__.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from . import broken_model
4 | from . import model_inhe1
5 | from . import model_inhe2
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/xml_special_char.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OCA/pylint-odoo/HEAD/testing/resources/test_repo/broken_module/xml_special_char.xml
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from . import models
4 | from .models import broken_model
5 | from .tests import test_model
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module3/__manifest__.py:
--------------------------------------------------------------------------------
1 | # Verify a dictionary parsed correctly as node but raising error as literal_eval
2 | {
3 | "key": "" or "",
4 | }
5 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module3/migrations/8.0.1.0.2/pre-migration.py:
--------------------------------------------------------------------------------
1 | # Should raise manifest-behind-migrations but since manifest version is not parseable, it won't
2 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/absolute_import.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | try:
3 | import uninstalled_module
4 | except ImportError:
5 | uninstalled_module = None
6 |
--------------------------------------------------------------------------------
/.pre-commit-hooks.yaml:
--------------------------------------------------------------------------------
1 | - id: pylint_odoo
2 | name: Check for Odoo modules using pylint
3 | entry: pylint
4 | language: python
5 | types: [python]
6 | require_serial: true
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/lib/tab_no_check.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | $('.example').each(function () {
3 | var oe_website_sale = this;
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/tests/data/odoo_data_noupdate_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/utf8_models.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from odoo import models
4 |
5 |
6 | class EleveModel(models.Model):
7 | _name = 'eleve.model'
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/utf8_models.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from odoo import models
4 |
5 |
6 | class TwelveModel(models.Model):
7 | _name = 'twelve.model'
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/interpreter_wox.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | "Module python with interpreter but without execute permission."
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/test_example.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | $('.example').each(function () {
3 | var oe_website_sale = this;
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/app_module/__manifest__.py:
--------------------------------------------------------------------------------
1 | {
2 | "price": 1,
3 | "support": "myemail@mydomain.com",
4 | "author": "Odoo Community Association (OCA)",
5 | "license": "OEEL-1",
6 | }
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/skip_xml_check_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/tests/data/odoo_data_noupdate_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/lib/broken_example.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | $('.example').each(function () {
3 | var oe_website_sale = this;
4 | }) /*missing semicolon*/
5 | }) /*missing semicolon*/
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/tests/xml_not_important_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/testing/resources/.pylintrc-odoo-deprecated-model-methods:
--------------------------------------------------------------------------------
1 | [ODOOLINT]
2 | deprecated-odoo-model-methods={
3 | '15.0': {'custom_deprecated_method_just_because', 'another_deprecated_model_method'},
4 | '16.0': {'fields_view_get'},
5 | }
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/pylint_deprecated_modules/README.md:
--------------------------------------------------------------------------------
1 | Pylint has next issue:
2 |
3 | https://bitbucket.org/logilab/pylint/issues/362/bug-w0402-not-show-fails-if-module-not-is
4 |
5 | This folder add all packages deprecated to show the error.
6 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/broken_example2.js:
--------------------------------------------------------------------------------
1 | /*Use of "+function" instead of "Number(function" */
2 | +function ($) {
3 | 'use strict';
4 | var var_1 = "value1";
5 | var var_2 = "value2";
6 | };
7 | /* Newline required at end of file but not found */
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | # Defaults for all prettier-supported languages.
2 | # Prettier will complete this with settings from .editorconfig file.
3 | bracketSpacing: false
4 | printWidth: 119
5 | proseWrap: always
6 | semi: true
7 | trailingComma: "es5"
8 | xmlWhitespaceSensitivity: "strict"
9 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/migrations/11.0.1.0.0/not_used_from_manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/broken_example.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 | $('.example').each(function () {
3 | var oe_website_sale = this;
4 | }) /*missing semicolon*/
5 | /*Use of console log*/
6 | console.log("This is similar to a print");
7 | }) /*missing semicolon*/
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/res_partner_unlink.py:
--------------------------------------------------------------------------------
1 | from odoo import models
2 |
3 |
4 | class ResPartner(models.Model):
5 | _inherit = 'res.partner'
6 |
7 | def unlink(self):
8 | if self.name == 'explode':
9 | raise RuntimeError()
10 |
11 | return super().unlink()
12 |
--------------------------------------------------------------------------------
/testing/resources/test_repo_odoo_namespace/odoo/__init__.py:
--------------------------------------------------------------------------------
1 | # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
2 | try:
3 | __import__("pkg_resources").declare_namespace(__name__)
4 | except ImportError:
5 | from pkgutil import extend_path
6 |
7 | __path__ = extend_path(__path__, __name__)
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/report.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/testing/resources/test_repo_odoo_namespace/odoo/addons/__init__.py:
--------------------------------------------------------------------------------
1 | # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
2 | try:
3 | __import__("pkg_resources").declare_namespace(__name__)
4 | except ImportError:
5 | from pkgutil import extend_path
6 |
7 | __path__ = extend_path(__path__, __name__)
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo_odoo_namespace/odoo/addons/test_namespace_package_module/__manifest__.py:
--------------------------------------------------------------------------------
1 | {
2 | 'name': 'Namespace package module for tests',
3 | 'license': 'AGPL-3',
4 | 'author': u'Vauxoo,Odoo Community Association (OCA)',
5 | 'version': '12.0.1.0.0',
6 | 'depends': [
7 | 'base',
8 | ],
9 | }
10 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/models.py:
--------------------------------------------------------------------------------
1 | from odoo import models
2 |
3 |
4 | class TwelveModel(models.Model):
5 | _name = "twelve.model"
6 |
7 | def name_get(self):
8 | # do staff
9 | return super().name_get()
10 |
11 | def name_get2(self):
12 | return super().name_get() # Should be super().name_get2()
13 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/models/model_inhe1.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from openerp import models
4 | from .no_exists import package
5 |
6 |
7 | class TestModel(models.Model):
8 | _inherit = 'res.company'
9 |
10 | def method(self):
11 | return package
12 |
13 |
14 | class TestModel2(models.Model):
15 | _inherit = 'model.no.duplicated'
16 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | build
2 | bump2version
3 | coverage
4 | pbr
5 | pre-commit
6 | pytest ; python_version < '3.13'
7 | pytest<8.3.5 ; python_version >= '3.13' # Latest pytest==8.3.5 and py3.13 raises "ResourceWarning: unclosed database in sqlite3.Connection" and "pytest.PytestUnraisableExceptionWarning"
8 | pytest-cov
9 | pytest-xdist
10 | setuptools >=42
11 | tox
12 | twine
13 | wheel
14 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
2 | :alt: License: AGPL-3
3 |
4 | Test module
5 | ===========
6 |
7 | This module was written to check the test of rst syntax.
8 | This is a rst file without syntax error.
9 |
10 | Pygments test
11 |
12 | .. code-block:: python
13 |
14 | if True:
15 | pass
16 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/__manifest__.py:
--------------------------------------------------------------------------------
1 | {
2 | 'name': 'Eleven module for tests',
3 | 'license': 'AGPL-3',
4 | 'author': u'Jesus, Odoo Community Association (OCA)',
5 | 'category': 'Category 01',
6 | 'version': '11.0.1.0.0',
7 | 'depends': [
8 | 'base',
9 | ],
10 | 'data': [
11 | 'security/ir.model.access.csv',
12 | ],
13 | }
14 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/sale_order_unlink.py:
--------------------------------------------------------------------------------
1 | import platform
2 | from odoo.models import Model
3 |
4 | if platform.system() == 'Windows':
5 | raise OSError
6 |
7 |
8 | class SaleOrder(Model):
9 | _name = 'sale.order'
10 |
11 | def unlink(self):
12 | if self.name == 'maybe':
13 | if self.status == 'explosive':
14 | raise Exception()
15 |
16 | return super().unlink()
17 |
--------------------------------------------------------------------------------
/.isort.cfg:
--------------------------------------------------------------------------------
1 | [settings]
2 | ; see https://github.com/psf/black
3 | multi_line_output=3
4 | include_trailing_comma=True
5 | force_grid_wrap=0
6 | combine_as_imports=True
7 | use_parentheses=True
8 | line_length=119
9 | known_odoo=odoo
10 | known_odoo_addons=odoo.addons
11 | sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER
12 | default_section=THIRDPARTY
13 | ensure_newline_before_comments = True
14 | known_local_folder = pylint_odoo
15 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/migrations/10.0.1.0.0/pre-migration.py:
--------------------------------------------------------------------------------
1 | from odoo import SUPERUSER_ID, api
2 | from odoo.addons.test_module import random_stuff
3 |
4 |
5 | def method(cr, unused):
6 | # invalid-name cr and unused-argument unused
7 | return cr
8 |
9 |
10 | def migrate(cr, version):
11 | # suppressed invalid-name cr and unused-argument version
12 | with api.Environment.manage():
13 | env = api.Environment(cr, SUPERUSER_ID, {})
14 | env.ref('xmlid').unlink()
15 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 119
3 | max-complexity = 16
4 | # B = bugbear
5 | # B9 = bugbear opinionated (incl line length)
6 | select = C,E,F,W,B,B9
7 | # E203: whitespace before ':' (black behaviour)
8 | # E501: flake8 line length (covered by bugbear B950)
9 | # W503: line break before binary operator (black behaviour)
10 | # W504: line break after binary operator (black behaviour)
11 | # C901: too complex is enabled from pylint
12 | ignore = E203,E501,W503,W504,C901
13 | per-file-ignores=
14 | __init__.py:F401
15 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module3/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | product.product.account.user,product.model_product_product,group_account_user,1,0,0,0
3 | account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0
4 | account.account.type,model_account_account_type,account.group_account_user,1,0,0,0
5 | account.tax internal user,model_account_tax,base.group_user,1,0,0,0
6 | account.account,model_account_account,account.group_account_user,1,0,0,0
7 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module3/__openerp__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | 'name': 'Broken module 3 for tests',
4 | 'license': 'AGPL-3',
5 | 'author': ['Other', 'Odoo Community Association (OCA)'], # expected string
6 | 'maintainers': 'Others, Many people', # expected a list of strings
7 | 'website': 'htt://odoo-community.com',
8 | 'version': '8.0.1.0.0foo',
9 | 'depends': ['base'],
10 | 'data': [],
11 | 'test': [],
12 | 'installable': False,
13 | 'support': 'valid@email.com',
14 | }
15 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [paths]
2 | source = src
3 |
4 | [run]
5 | source = src
6 | parallel = true
7 | context = ${{COVERAGE_CONTEXT}}
8 |
9 | [report]
10 | show_missing = true
11 | precision = 2
12 | # fail_under = 98
13 | omit =
14 | *__init__.py
15 | */tests/*
16 | *__main__.py
17 |
18 | # Regexes for lines to exclude from consideration
19 | exclude_lines =
20 | # Have to re-enable the standard pragma
21 | pragma: no cover
22 | # tests import the package instead
23 | if __name__ == "__main__":
24 |
25 | [html]
26 | show_contexts=True
27 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/model_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form
7 | test.model
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/no_odoo_module/eval_used.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | def eval_from_param(param):
5 | """eval used from param"""
6 | param("c = 2")
7 |
8 |
9 | def eval_from_other():
10 | """eval used from many ways"""
11 | my_dict = {
12 | 'my_eval': eval, # [eval-used]
13 | }
14 | my_list = [eval] # [eval-used]
15 |
16 | my_var = eval # [eval-used]
17 | # inferred case
18 | my_var('d = 3') # [eval-used]
19 | eval_from_param(eval) # [eval-used]
20 | return my_dict, my_list
21 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/__openerp__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | 'name': 'Broken module 2 for tests',
4 | 'license': 'unknow license', # unknow license
5 | 'author': 'Other,Odoo Community Association (OCA)', # Missing oca author
6 | 'development_status': 'InvalidDevStatus',
7 | 'website': 'https://odoo-community.org,https://odoo.com',
8 | 'version': '1.0',
9 | 'depends': ['base'],
10 | 'data': [],
11 | 'test': [],
12 | 'installable': False,
13 | 'support': 'invalidmail.com', # invalid email
14 | }
15 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/models.py:
--------------------------------------------------------------------------------
1 | from odoo import models
2 |
3 | # astroid is not set as an external Python dependency in the manifest,
4 | # so all of the following imports should fail
5 | import astroid
6 | from astroid import Const
7 | from astroid import BinOp as bo
8 |
9 |
10 | class EleveModel(models.Model):
11 | _name = 'eleve.model'
12 |
13 | def method1(self):
14 | self.const = isinstance(astroid.Const, Const)
15 | self.bo = isinstance(astroid.BinOp, bo)
16 |
17 | def fields_view_get(self):
18 | return self.bo
19 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/demo/duplicated_id_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form
7 | test.model
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/skip_xml_check.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form80
7 | test.model
8 |
9 |
10 | view.model.form80
11 | test.model
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Configuration for known file extensions
2 | [*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}]
3 | charset = utf-8
4 | end_of_line = lf
5 | indent_size = 4
6 | indent_style = space
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.{json,yml,yaml,rst,md}]
11 | indent_size = 2
12 |
13 | # Do not configure editor for libs and autogenerated content
14 | [{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
15 | charset = unset
16 | end_of_line = unset
17 | indent_size = unset
18 | indent_style = unset
19 | insert_final_newline = false
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/skip_xml_check_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | view.model.form80
8 | test.model
9 |
10 |
11 | view.model.form80
12 | test.model
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/__openerp__.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | {
3 | 'name': 'Empty module for tests',
4 | 'license': 'AGPL-3',
5 | 'author': u'Moisés, Odoo Community Association (OCA), author2',
6 | 'version': '10.0.1.0.0',
7 | 'depends': [
8 | 'base',
9 | ],
10 | 'data': [
11 | 'security/ir.model.access.csv',
12 | 'res_users.xml',
13 | 'model_view.xml',
14 | 'website_templates.xml',
15 | ],
16 | 'external_dependencies': {
17 | 'bin': [
18 | 'sh',
19 | ],
20 | 'python': [
21 | 'os',
22 | 'manifest_lib',
23 | ],
24 | },
25 | }
26 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/README.rst:
--------------------------------------------------------------------------------
1 | Test module 2
2 | =============
3 |
4 | This module was written to check the test lint
5 |
6 |
7 | *******
8 | Project
9 | *******
10 |
11 | .. toctree::
12 | :maxdepth: 1
13 |
14 | project/contribute
15 | project/contributors
16 | project/license
17 | project/changes
18 | project/roadmap
19 |
20 | *****************
21 | Developer's guide
22 | *****************
23 |
24 | .. toctree::
25 | :maxdepth: 2
26 |
27 | guides/concepts.rst
28 | guides/code_overview.rst
29 |
30 | ******************
31 | Indices and tables
32 | ******************
33 |
34 | * :ref:`genindex`
35 | * :ref:`modindex`
36 | * :ref:`search`
37 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/osv_expression.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from __future__ import absolute_import
4 | import expression
5 | import expression as expr4
6 | import manifest_lib
7 | import openerp.osv
8 | import openerp.osv.expression
9 |
10 | from openerp.osv import expression as expr2
11 | from openerp.osv import osv as osv2
12 | from openerp.osv import osv, expression # noqa
13 | from openerp.osv import osv, expression as expr3 # noqa
14 | from openerp.osv.expression import is_operator # noqa
15 |
16 |
17 | def dummy():
18 | return (expression, osv, osv2, expr2, openerp.osv.expression, openerp.osv,
19 | expr4, expr3, absolute_import, manifest_lib)
20 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/res_users.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Moy6
7 | moylop260
8 |
9 |
10 |
11 | Admin 2
12 | admin_2
13 |
14 |
15 |
16 |
17 |
18 |
19 | change_password
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/i18n/broken_module.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Odoo Server\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 1985-04-14 17:12+0000\n"
6 | "PO-Revision-Date: 1985-04-14 02:03+0000\n"
7 | "Last-Translator: Moisés López \n"
8 | "Language-Team: \n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: \n"
12 | "Plural-Forms: \n"
13 |
14 | # Missing module: comment
15 | #: model:ir.model.fields,field_description:broken_module.field_description
16 | #: model:ir.model.fields,field_description:broken_module.field_wizard_description
17 | #, python-format
18 | msgid "Branch"
19 | msgstr ""
20 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/tests/test_model1.py:
--------------------------------------------------------------------------------
1 |
2 | from odoo.tests.common import TransactionCase
3 | from odoo.addons.eleven_module.models import EleveModel
4 | from .no_exists import package
5 |
6 | # Even though astroid is not set as an external dependency, it should not fail,
7 | # because this is a test file
8 | import astroid
9 | from astroid import Const
10 | from astroid import BinOp as bo
11 |
12 |
13 | class TestModel(TransactionCase):
14 | def setUp(self):
15 | super(TestModel, self).setUp()
16 | self.const = isinstance(astroid.Const, Const)
17 | self.bo = isinstance(astroid.BinOp, bo)
18 |
19 | def method(self):
20 | return package
21 |
22 | def methodModel(self):
23 | return EleveModel
24 |
--------------------------------------------------------------------------------
/testing/resources/test_repo_odoo_namespace/odoo/addons/test_namespace_package_module/i18n/fr.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Odoo Server\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 1985-04-14 17:12+0000\n"
6 | "PO-Revision-Date: 1985-04-14 02:03+0000\n"
7 | "Last-Translator: Moisés López \n"
8 | "Language-Team: \n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: \n"
12 | "Plural-Forms: \n"
13 |
14 | #. module: test_module
15 | #: model:ir.model.fields,field_description2:test_module.field_description2
16 | #: model:ir.model.fields,field_description2:test_module.field_description2
17 | #, python-format
18 | msgid "Correct variables %s"
19 | msgstr "Correct variables"
20 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0
3 | access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0
4 | access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0
5 | access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0
6 | access_account_account_type,account.tax internal user,model_account_tax,base.group_user,1,0,0,0
7 | access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module2/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0
3 | access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0
4 | access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0
5 | access_account_account_tax,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0
6 | access_account_account_tax,account.tax internal user,model_account_tax,base.group_user,1,0,0,0
7 | access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/eleven_module/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0
3 | access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0
4 | access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0
5 | access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0
6 | access_account_account_tax,account.tax internal user,model_account_tax,base.group_user,1,0,0,0
7 | access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0
3 | access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0
4 | access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0
5 | access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0
6 | access_account_account_tax,account.tax internal user,model_account_tax,base.group_user,1,0,0,0
7 | access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0
8 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 | access_product_product_account_user,product.product.account.user,product.model_product_product,group_account_user,1,0,0,0
3 | access_account_payment_term,account.payment.term,model_account_payment_term,account.group_account_user,1,0,0,0
4 | access_account_payment_term_line,account.payment.term.line,model_account_payment_term_line,account.group_account_user,1,0,0,0
5 | access_account_account_type,account.account.type,model_account_account_type,account.group_account_user,1,0,0,0
6 | access_account_account_tax,account.tax internal user,model_account_tax,base.group_user,1,0,0,0
7 | access_account_account,account.account,model_account_account,account.group_account_user,1,0,0,0
8 |
--------------------------------------------------------------------------------
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 9.3.22
3 | commit = True
4 | tag = True
5 | sign_tags = True
6 |
7 | [bumpversion:file:setup.cfg]
8 | search = version = {current_version}
9 | replace = version = {new_version}
10 |
11 | [bumpversion:file (badge):README.md]
12 | search = /v{current_version}.svg
13 | replace = /v{new_version}.svg
14 |
15 | [bumpversion:file (link):README.md]
16 | search = /v{current_version}...main
17 | replace = /v{new_version}...main
18 |
19 | [bumpversion:file (github link):README.md]
20 | search = /blob/v{current_version}/
21 | replace = /blob/v{new_version}/
22 |
23 | [bumpversion:file:README.md]
24 | search = rev: v{current_version}
25 | replace = rev: v{new_version}
26 |
27 | [bumpversion:file:src/pylint_odoo/__init__.py]
28 | search = __version__ = "{current_version}"
29 | replace = __version__ = "{new_version}"
30 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/twelve_module/__manifest__.py:
--------------------------------------------------------------------------------
1 | {
2 | 'name': 'Twelve module for tests',
3 | 'license': 'AGPL-3',
4 | 'author': u'Jesus, Odoo Community Association (OCA)',
5 | 'version': '12.0.1.0.0',
6 | 'depends': [
7 | 'base',
8 | ],
9 | 'data': [
10 | 'security/ir.model.access.csv',
11 | ],
12 | "assets": {
13 | "web.assets_common": [
14 | "twelve_module/static/nonexistent.js",
15 | "https://shady.cdn.com/somefile.js"
16 | ],
17 | "web.assets_frontend": [
18 | "/twelve_module/hypothetically/good/file.css",
19 | ("before", "/web/static/src/css/random.css", "https://bad.idea.com/cool.css"),
20 | ["prepend", "/web/static/src/js/hello.js", "http://insecure.and.bad.idea.com/kiwi.js"]
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/model_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form
7 | test.model
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 | By name
18 | test.model
19 | {'group_by': ['name']}
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/pylint_odoo/checkers/vim_comment.py:
--------------------------------------------------------------------------------
1 | import tokenize
2 |
3 | from pylint.checkers import BaseTokenChecker
4 |
5 | from .odoo_base_checker import OdooBaseChecker
6 |
7 | ODOO_MSGS = {
8 | # C->convention R->refactor W->warning E->error F->fatal
9 | "W8202": ("Use of vim comment", "use-vim-comment", "Better using local vim configuration file"),
10 | }
11 |
12 |
13 | class VimComment(OdooBaseChecker, BaseTokenChecker):
14 | name = "odoolint"
15 | msgs = ODOO_MSGS
16 |
17 | def is_vim_comment(self, comment):
18 | return comment.strip("# ").lower().startswith("vim:")
19 |
20 | def process_tokens(self, tokens):
21 | for tok_type, token_content, start_line_col, _end_line_col, _line_content in tokens:
22 | if tokenize.COMMENT == tok_type:
23 | line_num = start_line_col[0]
24 | if self.is_vim_comment(token_content):
25 | self.add_message("use-vim-comment", line=line_num)
26 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/template1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/pylint_odoo/plugin.py:
--------------------------------------------------------------------------------
1 | from . import checkers
2 | from .augmentations.main import apply_augmentations
3 |
4 |
5 | def register(linter):
6 | """Required method to auto register this checker"""
7 | linter.register_checker(checkers.odoo_addons.OdooAddons(linter))
8 | linter.register_checker(checkers.vim_comment.VimComment(linter))
9 | linter.register_checker(checkers.custom_logging.CustomLoggingChecker(linter))
10 |
11 | # register any checking fiddlers
12 | apply_augmentations(linter)
13 |
14 |
15 | def get_all_messages():
16 | """Get all messages of this plugin"""
17 | all_msgs = {}
18 | all_msgs.update(checkers.odoo_addons.ODOO_MSGS)
19 | all_msgs.update(checkers.vim_comment.ODOO_MSGS)
20 | all_msgs.update(checkers.custom_logging.ODOO_MSGS)
21 | return all_msgs
22 |
23 |
24 | def messages2md():
25 | all_msgs = get_all_messages()
26 | md_msgs = "Short Name | Description | Code\n--- | --- | ---"
27 | for msg_code, (title, name_key, _description) in sorted(all_msgs.items(), key=lambda v: v[1][1]):
28 | md_msgs += f"\n{name_key} | {title} | {msg_code}"
29 | md_msgs += "\n"
30 | return md_msgs
31 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/__openerp__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | 'name': 'Broken module for tests',
4 | # missing license
5 | 'author': 'Vauxoo, Many People', # Missing oca author
6 | 'category': 'No valid for odoo.com/apps', # raised category-allowed
7 | 'development_status': 'Alpha',
8 | 'description': 'Should be a README.rst file',
9 | 'version': '8_0.1.0.0',
10 | 'website': 'https://odoo-community.org',
11 | 'depends': ['base'],
12 | 'data': [
13 | 'model_view.xml', 'model_view2.xml', 'model_view_odoo.xml',
14 | 'model_view_odoo2.xml',
15 | 'file_no_exist.xml',
16 | 'skip_xml_check.xml',
17 | 'skip_xml_check_2.xml',
18 | 'skip_xml_check_3.xml',
19 | 'duplicated.xml',
20 | 'duplicated.xml',
21 | 'report.xml',
22 | 'template1.xml',
23 | ],
24 | 'demo': ['demo/duplicated_id_demo.xml', 'file_no_exist.xml'],
25 | 'test': ['file_no_exist.yml'],
26 | 'installable': True,
27 | 'name': 'Duplicated value',
28 | 'active': True, # Deprecated active key
29 | 'auto_install': False,
30 | 'price': 800,
31 | }
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | /.venv
5 | /.pytest_cache
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | env/
13 | bin/
14 | build/
15 | develop-eggs/
16 | dist/
17 | dist_wo_pbr/
18 | eggs/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | *.eggs
27 |
28 | # Installer logs
29 | pip-log.txt
30 | pip-delete-this-directory.txt
31 |
32 | # Unit test / coverage reports
33 | htmlcov/
34 | .tox/
35 | .coverage
36 | .coverage\.*
37 | .coverage\_*
38 | .cache
39 | nosetests.xml
40 | coverage.xml
41 |
42 | # Translations
43 | *.mo
44 |
45 | # Pycharm
46 | .idea
47 |
48 | # Eclipse
49 | .settings
50 |
51 | # Visual Studio cache/options directory
52 | .vs/
53 | .vscode
54 |
55 | # OSX Files
56 | .DS_Store
57 |
58 | # Django stuff:
59 | *.log
60 |
61 | # Mr Developer
62 | .mr.developer.cfg
63 | .project
64 | .pydevproject
65 |
66 | # Rope
67 | .ropeproject
68 |
69 | # Sphinx documentation
70 | docs/_build/
71 |
72 | # Backup files
73 | *~
74 | *.swp
75 |
76 | # OCA rules
77 | !static/lib/
78 |
79 | # Auto-generated
80 | ChangeLog
81 | .pre-commit-config-local.yaml
82 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = pylint-odoo
3 | version = 9.3.22
4 | author = Odoo Community Association (OCA)
5 | author_email = support@odoo-community.org
6 | summary = Pylint plugin for Odoo
7 | long_description_content_type = text/markdown
8 | license = APGL3
9 | home_page = https://github.com/OCA/pylint-odoo
10 | requires_python = >=3.10
11 | classifier =
12 | Development Status :: 6 - Mature
13 | Environment :: Console
14 | Intended Audience :: Developers
15 | Intended Audience :: Information Technology
16 | License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
17 | Operating System :: OS Independent
18 | Programming Language :: Python
19 | Programming Language :: Python :: 3
20 | Programming Language :: Python :: 3 :: Only
21 | Programming Language :: Python :: 3.10
22 | Programming Language :: Python :: 3.11
23 | Programming Language :: Python :: 3.12
24 | Programming Language :: Python :: 3.13
25 | Programming Language :: Python :: 3.14
26 | Topic :: Software Development :: Debuggers
27 | Topic :: Software Development :: Quality Assurance
28 | Topic :: Software Development :: Testing
29 |
30 | [options]
31 | package_dir =
32 | =src
33 | packages = find:
34 |
35 | [options.packages.find]
36 | where = src
37 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/models/model_inhe2.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from openerp import models
4 |
5 |
6 | class TestModel(models.Model):
7 | _inherit = 'res.company'
8 |
9 |
10 | class TestModel2(models.Model):
11 | _inherit = 'res.company'
12 |
13 |
14 | class TestModel3(models.Model):
15 | _inherit = 'res.partner'
16 |
17 |
18 | class TestModel4(models.Model):
19 | _inherit = 'res.partner'
20 |
21 |
22 | class TestModel41(models.Model):
23 | _name = 'res.partner'
24 | _inherit = 'res.partner'
25 |
26 |
27 | class TestModel5(models.Model):
28 | _inherit = 'valid.duplicated' # pylint: disable=consider-merging-classes-inherited
29 |
30 |
31 | class TestModel6(models.Model):
32 | _inherit = 'valid.duplicated'
33 |
34 |
35 | class TestModel7(models.Model):
36 | _name = 'valid.duplicated.2'
37 | _inherit = 'valid.duplicated'
38 |
39 | class TestModel75(models.Model):
40 | _inherit = 'valid.duplicated'
41 | _name = 'valid.duplicated.2'
42 |
43 | class TestModel8(models.Model):
44 | _inherit = ['valid.duplicated', 'valid.duplicated2']
45 |
46 | def method_1(self):
47 | _inherit = 'not-class-attribute'
48 | return _inherit
49 |
50 | def method_2(self):
51 | _inherit = 'not-class-attribute'
52 | return _inherit
53 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import re
2 | from os.path import dirname, join
3 |
4 | from setuptools import setup
5 |
6 | try:
7 | from pbr import git
8 | except ImportError:
9 | git = None
10 |
11 |
12 | def generate_changelog():
13 | fname = "ChangeLog"
14 | if not git:
15 | changelog_str = '# ChangeLog was not generated. You need to install "pbr"'
16 | with open(fname, "w", encoding="UTF-8") as fchg:
17 | fchg.write(changelog_str)
18 | return changelog_str
19 | changelog = git._iter_log_oneline()
20 | changelog = git._iter_changelog(changelog)
21 | git.write_git_changelog(changelog=filter(lambda log: not log[1].startswith("* Bump version"), changelog))
22 | return read(fname)
23 |
24 |
25 | def generate_dependencies():
26 | return read("requirements.txt").splitlines()
27 |
28 |
29 | def read(*names, **kwargs):
30 | with open(join(dirname(__file__), *names), encoding=kwargs.get("encoding", "utf8")) as file_obj:
31 | return file_obj.read()
32 |
33 |
34 | def generage_long_description():
35 | long_description = "{}\n{}".format(
36 | # re.compile(".*\(start-badges\).*^.*\(end-badges\)", re.M | re.S).sub("", read("README.md")),
37 | read("README.md"),
38 | re.sub(":[a-z]+:`~?(.*?)`", r"``\1``", generate_changelog()),
39 | )
40 | return long_description
41 |
42 |
43 | setup(
44 | long_description=generage_long_description(),
45 | install_requires=generate_dependencies(),
46 | )
47 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/website_templates.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | norecursedirs =
3 | .git
4 | .tox
5 | .env
6 | dist
7 | build
8 | migrations
9 | test_repo
10 |
11 | python_files =
12 | test_*.py
13 | *_test.py
14 | tests.py
15 | addopts =
16 | -ra
17 | --strict-markers
18 | --ignore=docs/conf.py
19 | --ignore=setup.py
20 | --ignore=ci
21 | --ignore=.eggs
22 | --ignore=test_repo\*
23 | --doctest-modules
24 | --doctest-glob=\*.rst
25 | --tb=short
26 | --pyargs
27 | # The order of these options matters. testpaths comes after addopts so that
28 | # pylint_odoo in testpaths is interpreted as
29 | # --pyargs pylint_odoo.
30 | # Any tests in the src/ directory (that is, tests installed with the package)
31 | # can be run by any user with pytest --pyargs pylint_odoo.
32 | # Packages that are sensitive to the host machine, most famously NumPy,
33 | # include tests with the installed package so that any user can check
34 | # at any time that everything is working properly.
35 | # If you do choose to make installable tests, this will run the installed
36 | # tests as they are actually installed (same principle as when we ensure that
37 | # we always test the installed version of the package).
38 | # If you have no need for this (and your src/ directory is very large),
39 | # you can save a few milliseconds on testing by telling pytest not to search
40 | # the src/ directory by removing
41 | # --pyargs and pylint_odoo from the options here.
42 | testpaths =
43 | ./src/pylint_odoo
44 | ./tests
45 |
46 | # Idea from: https://til.simonwillison.net/pytest/treat-warnings-as-errors
47 | filterwarnings =
48 | error
49 | # You can add exclusions, some examples:
50 | # ignore:'pylint_odoo' defines default_app_config:PendingDeprecationWarning::
51 | # ignore:The {{% if:::
52 | # ignore:Coverage disabled via --no-cov switch!
53 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/except_pass.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Test Except Pass usage"""
3 |
4 |
5 | class TestExceptPass(object):
6 | """Test Except Pass class """
7 |
8 | def test_method(self):
9 | try:
10 | raise Exception('Exception')
11 | except Exception: # except-pass
12 | pass
13 |
14 | def test_2_method(self):
15 | """This pass is skip for body of except has more than one line """
16 | try:
17 | raise Exception('Exception')
18 | except Exception:
19 | pass
20 | print('Exception')
21 |
22 | def test_3_method(self):
23 | """This pass is skip for the exception is assigned"""
24 | exception = False
25 | try:
26 | raise Exception('Exception')
27 | except Exception as exception:
28 | pass
29 | if exception:
30 | pass
31 |
32 | def test_4_method(self):
33 | userError = False
34 | try:
35 | raise Exception('Exception')
36 | except Exception as userError:
37 | pass
38 | if userError:
39 | pass
40 |
41 | def test_5_method(self):
42 | exception = False
43 | try:
44 | raise Exception('Exception')
45 | except (Exception, IndexError) as exception:
46 | pass
47 | if exception:
48 | pass
49 |
50 | def test_6_method(self):
51 | try:
52 | raise Exception('Exception')
53 | except (Exception, IndexError): # except-pass
54 | pass
55 |
56 | def test_7_method(self):
57 | exception = False
58 | try:
59 | raise Exception('Exception')
60 | except (Exception, IndexError, NameError) as exception:
61 | pass
62 | except Exception: # except-pass
63 | pass
64 | if exception:
65 | pass
66 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/tests/test_model.py:
--------------------------------------------------------------------------------
1 | from odoo.tests.common import TransactionCase
2 |
3 |
4 | class TestModel(TransactionCase):
5 | def setUp(self):
6 | super(TestModel, self).setUp()
7 |
8 | def method1(self, example_var):
9 | return example_var
10 |
11 | def test_1(self):
12 | self.partner.message_post(body="Test", subtype="mail.mt_comment")
13 | self.partner.message_post("Test", subtype="mail.mt_comment")
14 |
15 | def test_base_method_1(self):
16 | # Override prohibited, should fail
17 | super().test_base_method_1()
18 | # No override applied, should not fail
19 | super().test_base_method_2()
20 | return super().test_base_method_3()
21 |
22 | def test_base_method_2(self):
23 | # No override applied, should not fail
24 | super(TestModel, self).test_base_method_1()
25 | # Override allowed, should not fail
26 | super(TestModel, self).test_base_method_2()
27 | # No override applied, should not fail
28 | return super(TestModel, self).test_base_method_3()
29 |
30 | def test_base_method_3(self):
31 | some_obj = TransactionCase()
32 | # No override applied, should not fail
33 | super(TestModel, self).test_base_method_1()
34 | super(TestModel, self).test_base_method_2()
35 | some_obj.test_base_method_3()
36 | # Override prohibited, should fail
37 | return super(TestModel, self).test_base_method_3()
38 |
39 | def message_post_queued(self, *args, **kwargs):
40 | # super-method-mismatch valid since that it is enqueued the method
41 | # not calling the same super method
42 | return super().message_post(*args, **kwargs)
43 |
44 | def get_meth_cached(self):
45 | # super-method-mismatch valid since that it is caching the method
46 | # not calling the same super method
47 | return super().get_meth().ids
48 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | [MASTER]
4 | load-plugins=pylint.extensions.docstyle, pylint.extensions.mccabe
5 | score=n
6 |
7 | [MESSAGES CONTROL]
8 | enable=all
9 | # Enable all then relax disable instead of disable all and enable one-by-one
10 | disable=c-extension-no-member,
11 | fixme,
12 | import-error,
13 | inconsistent-return-statements,
14 | line-too-long,
15 | locally-disabled,
16 | logging-too-many-args,
17 | missing-class-docstring,
18 | missing-function-docstring,
19 | missing-module-docstring,
20 | protected-access,
21 | suppressed-message,
22 | too-few-public-methods,
23 | too-many-ancestors,
24 | too-many-arguments,
25 | too-many-boolean-expressions,
26 | too-many-branches,
27 | too-many-branches,
28 | too-many-format-args,
29 | too-many-function-args,
30 | too-many-instance-attributes,
31 | too-many-instance-attributes,
32 | too-many-lines,
33 | too-many-locals,
34 | too-many-locals,
35 | too-many-nested-blocks,
36 | too-many-public-methods,
37 | too-many-return-statements,
38 | too-many-star-expressions,
39 | too-many-statements,
40 | unused-argument,
41 | # TODO: Re-enable the following one
42 | attribute-defined-outside-init,
43 | consider-using-f-string,
44 | implicit-str-concat,
45 | too-complex,
46 |
47 | [REPORTS]
48 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
49 | output-format=colorized
50 | reports=no
51 |
52 | [SIMILARITIES]
53 | min-similarity-lines=14
54 | ignore-comments=yes
55 | ignore-docstrings=yes
56 |
57 | [DESIGN]
58 | # McCabe complexity cyclomatic threshold for too-complex check
59 | # Value definied from https://en.wikipedia.org/wiki/Cyclomatic_complexity
60 | # - The figure of 10 had received substantial corroborating evidence,
61 | # but that in some circumstances it may be appropriate to relax the restriction
62 | # and permit modules with a complexity as high as 15
63 | max-complexity=15
64 |
65 | [BASIC]
66 | good-names=maxDiff
67 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | lint,
4 | update-readme,
5 | build,
6 | py39,
7 | py310,
8 | py311,
9 | py312,
10 | py313,
11 | py314,
12 |
13 | [testenv]
14 | parallel_show_output=true
15 | setenv =
16 | PYTHONPATH={toxinidir}/tests
17 | PYTHONUNBUFFERED=yes
18 | # Compatible with "tox --parallel" to avoid concurrency
19 | COVERAGE_FILE={toxinidir}/.coverage.{envname}
20 | COVERAGE_CONTEXT={envname}
21 | passenv =
22 | *
23 | deps = -r{toxinidir}/test-requirements.txt
24 | usedevelop = true
25 | commands =
26 | pytest -s --cov --cov-report=term-missing --cov-report=html -vv {posargs:}
27 |
28 | [testenv:update-readme]
29 | basepython = {env:TOXPYTHON:python3.13}
30 | setenv =
31 | {[testenv]setenv}
32 | BUILD_README=true
33 | usedevelop = true
34 | commands =
35 | {posargs:pytest -svvk test_build_docstring}
36 | deps =
37 | {[testenv]deps}
38 |
39 | [testenv:lint]
40 | skip_install = true
41 | commands =
42 | pre-commit run --all-files --show-diff-on-failure --color=always
43 |
44 | [testenv:build]
45 | skip_install = true
46 | deps =
47 | {[testenv]deps}
48 | commands =
49 | python -m build --sdist --wheel --outdir dist_wo_pbr/
50 | python -c "import shutil;shutil.rmtree('dist/', ignore_errors=True)"
51 | python -m build --no-isolation --sdist --wheel --outdir dist/ # Generate ChangeLog with pbr
52 | python -m twine check --strict dist/*
53 | bump2version patch --allow-dirty --no-commit --no-tag --dry-run --verbose
54 | # Install packages from binaries to test if all files were already included in the compressed file
55 | python -c '''import sys,pip,os,glob;os.chdir("dist");print("Current dir: %s" % os.getcwd());sys.argv = ["", "install", "-U", "--force-reinstall", glob.glob("*.tar.gz")[-1], "--use-feature=no-binary-enable-wheel-cache"];pip.main()'''
56 | # Testing the package is importing the dependencies well
57 | python -c '''from pylint.lint import Run;import os;os.chdir("dist");res = Run(["--load-plugins=pylint_odoo", "-c", "non-exists.cfg", "--disable=all", "--enable=odoolint", "--list-msgs-enabled"])'''
58 |
59 | [testenv:clean]
60 | commands = coverage erase
61 | skip_install = true
62 | deps = coverage
63 |
--------------------------------------------------------------------------------
/src/pylint_odoo/checkers/odoo_base_checker.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import warnings
4 |
5 | import pylint
6 | from pylint.checkers import BaseChecker
7 |
8 | from .. import misc
9 |
10 |
11 | class OdooBaseChecker(BaseChecker):
12 | # checks_maxmin_odoo_version = {
13 | # check-code: {
14 | # "odoo_minversion": "14.0",
15 | # "odoo_maxversion": "15.0",
16 | # }
17 | checks_maxmin_odoo_version: dict[str, str] = {}
18 |
19 | def msgid_or_symbol2symbol(self, msgid_or_symbol):
20 | try:
21 | msgid = self.linter.msgs_store.message_id_store.get_active_msgids(msgid_or_symbol)[0]
22 | return self.linter.msgs_store.message_id_store.get_symbol(msgid)
23 | except (pylint.exceptions.UnknownMessageError, IndexError):
24 | return None
25 |
26 | def is_odoo_message_enabled(self, msgid):
27 | valid_odoo_versions = self.linter.config.valid_odoo_versions
28 | if len(valid_odoo_versions) != 1:
29 | # It should be defined only one version
30 | return True
31 | msg_symbol = self.msgid_or_symbol2symbol(msgid)
32 | if msg_symbol is None:
33 | return True
34 | odoo_version = valid_odoo_versions[0]
35 | odoo_version_tuple = misc.version_parse(odoo_version)
36 | if not odoo_version_tuple:
37 | warnings.warn(
38 | f"Invalid manifest versions format {odoo_version}. "
39 | "It was not possible to supress checks based on particular odoo version",
40 | UserWarning,
41 | stacklevel=2,
42 | )
43 | return True
44 | required_odoo_versions = self.checks_maxmin_odoo_version.get(msg_symbol) or {}
45 | odoo_minversion = required_odoo_versions.get("odoo_minversion") or misc.DFTL_VALID_ODOO_VERSIONS[0]
46 | odoo_maxversion = required_odoo_versions.get("odoo_maxversion") or misc.DFTL_VALID_ODOO_VERSIONS[-1]
47 | return (
48 | misc.version_parse(odoo_minversion)
49 | <= misc.version_parse(odoo_version)
50 | <= misc.version_parse(odoo_maxversion)
51 | )
52 |
53 | def add_message(self, msgid, *args, **kwargs):
54 | """Emit translation-not-lazy instead of logging-not-lazy"""
55 | if not self.is_odoo_message_enabled(msgid):
56 | return
57 | return super().add_message(msgid, *args, **kwargs)
58 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/model_view_odoo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form
7 | test.model
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | view.model.form
18 | test.model
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | test.model.tree
29 | test.model
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | test.model.tree
40 | test.model
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | test.model.tree
51 | test.model
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | By name
62 | test.model
63 | {'group_by': ['name']}
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/pylint_odoo/augmentations/main.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from astroid import nodes
4 | from pylint.checkers.base import BasicChecker, NameChecker
5 | from pylint.checkers.variables import VariablesChecker
6 | from pylint_plugin_utils import suppress_message
7 |
8 | from .. import misc
9 |
10 |
11 | def is_manifest_file(node):
12 | """Verify if the node file is a manifest file
13 | :return: Boolean `True` if is manifest file else `False`"""
14 | filename = os.path.basename(node.root().file)
15 | is_manifest = filename in misc.MANIFEST_FILES
16 | return is_manifest
17 |
18 |
19 | def is_migration_path(node):
20 | """module/x.y.z/migrations/pre-migration.py path has a few false negatives
21 |
22 | Considering that standard method is:
23 | def migrate(cr, version):
24 |
25 | - invalid-name (C0103) for module and argument name
26 | e.g. "pre-migration.py" instead of pre_migration.py
27 | e.g. "cr" for cursor
28 |
29 | - unused-argument (W0613) for argument
30 | e.g. "version" for version of Odoo
31 |
32 | node: can be module or functiondef
33 | """
34 |
35 | # get 'migrations' from 'module/migrations/x.y.z/pre-migration.py'
36 | if os.path.basename(os.path.dirname(os.path.dirname(node.root().file))) != "migrations":
37 | return False
38 |
39 | # pre-migration.py
40 | # def migrate(cr, version):
41 | if (
42 | isinstance(node, nodes.Module)
43 | and "-" in node.name
44 | or isinstance(node, nodes.FunctionDef)
45 | and node.name == "migrate"
46 | ):
47 | return True
48 | return False
49 |
50 |
51 | def is_executable(node):
52 | """Name of executable files could be executable-file
53 | instead of executable_file.py
54 | So, excluding this check if it is the case
55 | """
56 | if isinstance(node, nodes.Module) and os.path.splitext(node.file)[1] != ".py" and os.access(node.file, os.X_OK):
57 | with open(node.file, encoding="UTF-8") as file_obj:
58 | shebang = file_obj.readline().startswith("#!")
59 | return shebang
60 | return False
61 |
62 |
63 | def apply_augmentations(linter):
64 | """Apply suppression rules."""
65 |
66 | # W0104 - pointless-statement
67 | # manifest file have a valid pointless-statement dict
68 | suppress_message(linter, BasicChecker.visit_expr, "pointless-statement", is_manifest_file)
69 |
70 | # C0103 - invalid-name and W0613 - unused-argument for migrations/
71 | suppress_message(linter, NameChecker.visit_module, "invalid-name", is_migration_path)
72 | suppress_message(linter, NameChecker.visit_module, "invalid-name", is_executable)
73 | suppress_message(linter, NameChecker.visit_functiondef, "invalid-name", is_migration_path)
74 | suppress_message(linter, VariablesChecker.leave_functiondef, "unused-argument", is_migration_path)
75 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/pylint_oca_broken.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | import openerp
4 |
5 | from openerp import api
6 | from openerp.api import one, multi
7 |
8 | from odoo.exceptions import Warning as UserError # pylint: disable=W0622
9 | from odoo.exceptions import Warning as OtherName # pylint: disable=W0404
10 | from odoo.exceptions import Warning # pylint: disable=W0404,W0622
11 | from odoo.exceptions import (AccessError as AE, # pylint: disable=W0404
12 | ValidationError,
13 | Warning as UserError2)
14 |
15 |
16 | class snake_case(object):
17 | pass
18 |
19 |
20 | class UseUnusedImport(object):
21 | def method1(self):
22 | return UserError, OtherName, Warning, AE, ValidationError, UserError2
23 |
24 | def inherited_method(self):
25 | # Missing return()
26 | super(UseUnusedImport, self).inherited_method()
27 |
28 | def inherited_method2(self):
29 | # Missing return(), however this is a generator.
30 | for i in super(UseUnusedImport, self).inherited_method2():
31 | yield i
32 | yield 1
33 |
34 | def write(self):
35 | return super(UseUnusedImport, self).write()
36 |
37 |
38 | class ApiOne(object):
39 | @api.one
40 | def copy(self):
41 | # Missing super()
42 | pass
43 |
44 | def create(self):
45 | # Missing super()
46 | pass
47 |
48 | def write(self):
49 | # Missing super()
50 | pass
51 |
52 | def unlink(self):
53 | # Missing super()
54 | pass
55 |
56 | def read(self):
57 | # Missing super()
58 | pass
59 |
60 | def setUp(self):
61 | # Missing super()
62 | pass
63 |
64 | def tearDown(self):
65 | # Missing super()
66 | pass
67 |
68 | def default_get(self):
69 | # Missing super()
70 | pass
71 |
72 |
73 | class One(object):
74 | @one
75 | def copy(self):
76 | return super(One, self).copy()
77 |
78 |
79 | class OpenerpApiOne(object):
80 | @openerp.api.one
81 | def copy(self):
82 | return super(OpenerpApiOne, self).copy()
83 |
84 |
85 | class WOApiOne(object):
86 | # copy without api.one decorator
87 | def copy(self):
88 | return super(WOApiOne, self).copy()
89 |
90 |
91 | class ApiOneMultiTogether(object):
92 |
93 | @api.multi
94 | @api.one
95 | def copy(self):
96 | return super(ApiOneMultiTogether, self).copy()
97 |
98 | @multi
99 | @one
100 | def copy2(self):
101 | return super(ApiOneMultiTogether, self).copy2()
102 |
103 | @openerp.api.multi
104 | @openerp.api.one
105 | def copy3(self):
106 | return super(ApiOneMultiTogether, self).copy3()
107 |
108 | # vim:comment vim
109 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | push:
4 | branches:
5 | - main
6 | tags:
7 | - 'v*'
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | env:
13 | CACHE_VERSION: 1
14 | PRE_COMMIT_CACHE: ~/.cache/pre-commit
15 |
16 | jobs:
17 | test:
18 | runs-on: ${{ matrix.os }}
19 | timeout-minutes: 30
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | python: ['3.10', '3.11', '3.12', '3.13', '3.14-dev']
24 | os: [ubuntu-latest, windows-latest, macos-13, macos-latest]
25 | tox_env: [py]
26 | include:
27 | - python: '3.13'
28 | os: ubuntu-latest
29 | tox_env: 'lint'
30 | - python: '3.13'
31 | os: ubuntu-latest
32 | tox_env: 'update-readme'
33 | - python: '3.13'
34 | os: ubuntu-latest
35 | tox_env: 'build'
36 | exclude:
37 | # macos-14 AKA macos-latest has switched to being an ARM runner, only supporting newer versions of Python
38 | # https://github.com/actions/setup-python/issues/825#issuecomment-2096792396
39 | - python: '3.10'
40 | os: macos-latest
41 |
42 | steps:
43 | - name: Set git to not change EoL
44 | if: runner.os == 'Windows'
45 | run: |
46 | git config --global core.autocrlf false
47 | - name: Cache pre-commit packages
48 | id: cache-pre-commit
49 | uses: actions/cache@v4
50 | with:
51 | path: ${{ env.PRE_COMMIT_CACHE }}
52 | key: ${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python }}-pre-commit
53 | - uses: actions/checkout@v4
54 | with:
55 | fetch-depth: 0
56 | - uses: actions/setup-python@v5
57 | with:
58 | python-version: ${{ matrix.python }}
59 | architecture: 'x64'
60 | cache: 'pip'
61 | - name: install dependencies
62 | run: |
63 | python -mpip install --progress-bar=off -r test-requirements.txt
64 | pip --version
65 | pip list --format=freeze
66 | - name: Test
67 | run: tox -e ${{ matrix.tox_env }} -v
68 | # TODO: Publish package only for signed tags
69 | - name: Publish package
70 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && contains(matrix.tox_env, 'build')
71 | run: |
72 | ls -lah dist/*
73 | python -m twine upload --verbose -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} --repository-url https://upload.pypi.org/legacy/ dist/*
74 | #TODO: Add GITHUB_RUN_ID.GITHUB_RUN_ATTEMPT.GITHUB_RUN_NUMBER to bumpversion to avoid duplicating upload versions or even the git sha
75 | - name: codecov
76 | if: startsWith(matrix.tox_env, 'py') # only coveralls in python tests
77 | uses: codecov/codecov-action@v5
78 | with:
79 | fail_ci_if_error: true
80 | verbose: true
81 | token: ${{ secrets.CODECOV_TOKEN }}
82 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues and pull requests
2 |
3 | on:
4 | schedule:
5 | - cron: "0 12 * * 0"
6 |
7 | jobs:
8 | stale:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Stale PRs and issues policy
12 | uses: actions/stale@v4
13 | with:
14 | repo-token: ${{ secrets.GITHUB_TOKEN }}
15 | # General settings.
16 | ascending: true
17 | remove-stale-when-updated: true
18 | # Pull Requests settings.
19 | # 120+30 day stale policy for PRs
20 | # * Except PRs marked as "no stale"
21 | days-before-pr-stale: 120
22 | days-before-pr-close: 30
23 | exempt-pr-labels: "no stale"
24 | stale-pr-label: "stale"
25 | stale-pr-message: >
26 | There hasn't been any activity on this pull request in the past 4 months, so
27 | it has been marked as stale and it will be closed automatically if no
28 | further activity occurs in the next 30 days.
29 |
30 | If you want this PR to never become stale, please ask a PSC member to apply
31 | the "no stale" label.
32 | # Issues settings.
33 | # 180+30 day stale policy for open issues
34 | # * Except Issues marked as "no stale"
35 | days-before-issue-stale: 180
36 | days-before-issue-close: 30
37 | exempt-issue-labels: "no stale,needs more information"
38 | stale-issue-label: "stale"
39 | stale-issue-message: >
40 | There hasn't been any activity on this issue in the past 6 months, so it has
41 | been marked as stale and it will be closed automatically if no further
42 | activity occurs in the next 30 days.
43 |
44 | If you want this issue to never become stale, please ask a PSC member to
45 | apply the "no stale" label.
46 |
47 | # 15+30 day stale policy for issues pending more information
48 | # * Issues that are pending more information
49 | # * Except Issues marked as "no stale"
50 | - name: Needs more information stale issues policy
51 | uses: actions/stale@v4
52 | with:
53 | repo-token: ${{ secrets.GITHUB_TOKEN }}
54 | ascending: true
55 | only-labels: "needs more information"
56 | exempt-issue-labels: "no stale"
57 | days-before-stale: 15
58 | days-before-close: 30
59 | days-before-pr-stale: -1
60 | days-before-pr-close: -1
61 | remove-stale-when-updated: true
62 | stale-issue-label: "stale"
63 | stale-issue-message: >
64 | This issue needs more information and there hasn't been any activity
65 | recently, so it has been marked as stale and it will be closed automatically
66 | if no further activity occurs in the next 30 days.
67 |
68 | If you think this is a mistake, please ask a PSC member to remove the "needs
69 | more information" label.
70 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/test_module/i18n/fr.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Odoo Server\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 1985-04-14 17:12+0000\n"
6 | "PO-Revision-Date: 1985-04-14 02:03+0000\n"
7 | "Last-Translator: Moisés López \n"
8 | "Language-Team: \n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: \n"
12 | "Plural-Forms: \n"
13 |
14 | #. module: test_module
15 | #: model:ir.model.fields,field_description:test_module.field_description
16 | #, python-format
17 | msgid "Branch"
18 | msgstr "Branche"
19 |
20 | #. module: test_module
21 | #: model:ir.model.fields,field_description2:test_module.field_description2
22 | #: model:ir.model.fields,field_description2:test_module.field_description2
23 | #, python-format
24 | msgid "Correct variables %s"
25 | msgstr "Correct variables %s"
26 |
27 | #. module: test_module
28 | #: model:ir.model.fields,field_description5:test_module.field_description5
29 | #: model:ir.model.fields,field_description5:test_module.field_description5
30 | #, python-format
31 | msgid "Correct variables %(variable)s"
32 | msgstr "Correct variables %(variable)s"
33 |
34 | #. module: test_module
35 | #: model:ir.model.fields,field_description3:test_module.field_description3
36 | #: model:ir.model.fields,field_description3:test_module.field_description3
37 | #, python-format
38 | msgid "Correct variables {}"
39 | msgstr "Correct variables {}"
40 |
41 | #. module: test_module
42 | #: model:ir.model.fields,field_description4:test_module.field_description4
43 | #: model:ir.model.fields,field_description4:test_module.field_description4
44 | #, python-format
45 | msgid "Correct variables {variable}"
46 | msgstr "Correct variables {variable}"
47 |
48 | #. module: test_module
49 | #: code:test_module/__init__.py:0
50 | #, python-format
51 | msgid ""
52 | "Multi line valid message!\n"
53 | "{message: %s,\n"
54 | "error code: %s,\n"
55 | "error type: %s,\n"
56 | "request id: %s}"
57 | msgstr ""
58 | "Multi line valid message!\n"
59 | "{message2: %s,\n"
60 | "error code2: %s,\n"
61 | "error type2: %s,\n"
62 | "request id2: %s}"
63 |
64 | #. module: test_module
65 | #: code:test_module/__init__.py:0
66 | #, python-format
67 | msgid ""
68 | "Multi line valid message!\n"
69 | "{message: %s,\n"
70 | msgstr ""
71 | "Multi line valid message!\n"
72 | "{message2: %s,\n"
73 |
74 |
75 | #. module: test_module
76 | #: code:test_module/__init__.py:2
77 | #, python-format
78 | msgid ""
79 | "it must be a literal python dictionary definition e.g. "
80 | "\"{'field': 'value'}\""
81 | msgstr ""
82 | "it must be a literal python dictionary definition e.g. "
83 | "\"{'field_translated': 'value_translated'}\""
84 |
85 | #. module: test_module
86 | #: code:addons/test_module/__init__.py:8
87 | #, python-format
88 | msgid "%s%% discount"
89 | msgstr "%s%%discount without space"
90 |
91 | #. module: test_module
92 | #: code:addons/test_module/__init__.py:10
93 | #, python-format
94 | #~ msgid "Obsolet msgid"
95 | #~ msgstr "Obsolet msgstr"
96 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | exclude: |
2 | (?x)
3 | # NOT INSTALLABLE ADDONS
4 | # END NOT INSTALLABLE ADDONS
5 | # Files and folders generated by bots, to avoid loops
6 | ^setup/|/static/description/index\.html$|
7 | # We don't want to mess with tool-generated files
8 | .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
9 | # Maybe reactivate this when all README files include prettier ignore tags?
10 | ^README\.md$|
11 | # Library files can have extraneous formatting (even minimized)
12 | /static/(src/)?lib/|
13 | # test_repo is expected to break lints
14 | testing/resources/|
15 | # Repos using Sphinx to generate docs don't need prettying
16 | ^docs/_templates/.*\.html$|
17 | # You don't usually want a bot to modify your legal texts
18 | (LICENSE.*|COPYING.*)
19 | default_language_version:
20 | python: python3
21 | node: "16.17.0"
22 | repos:
23 | - repo: https://github.com/myint/autoflake
24 | rev: v2.2.1
25 | hooks:
26 | - id: autoflake
27 | args:
28 | - --expand-star-imports
29 | - --ignore-init-module-imports
30 | - --in-place
31 | - --remove-all-unused-imports
32 | - --remove-duplicate-keys
33 | - --remove-unused-variables
34 | - repo: https://github.com/psf/black
35 | rev: 23.10.1
36 | hooks:
37 | - id: black
38 | - repo: https://github.com/pre-commit/mirrors-prettier
39 | rev: v3.0.3
40 | hooks:
41 | - id: prettier
42 | name: prettier (with plugin-xml)
43 | additional_dependencies:
44 | - "prettier@2.7.1"
45 | - "@prettier/plugin-xml@2.2.0"
46 | args:
47 | - --plugin=@prettier/plugin-xml
48 | files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
49 | - repo: https://github.com/pre-commit/pre-commit-hooks
50 | rev: v4.5.0
51 | hooks:
52 | - id: trailing-whitespace
53 | # exclude autogenerated files
54 | exclude: /README\.rst$|\.pot?$
55 | - id: end-of-file-fixer
56 | # exclude autogenerated files
57 | exclude: /README\.rst$|\.pot?$
58 | - id: debug-statements
59 | - id: fix-encoding-pragma
60 | args: ["--remove"]
61 | - id: check-case-conflict
62 | - id: check-docstring-first
63 | - id: check-executables-have-shebangs
64 | - id: check-merge-conflict
65 | # exclude files where underlines are not distinguishable from merge conflicts
66 | exclude: /README\.rst$|^docs/.*\.rst$
67 | - id: check-symlinks
68 | - id: check-xml
69 | - id: mixed-line-ending
70 | args: ["--fix=lf"]
71 | - repo: https://github.com/asottile/pyupgrade
72 | rev: v3.15.0
73 | hooks:
74 | - id: pyupgrade
75 | args: ["--keep-percent-format"]
76 | - repo: https://github.com/PyCQA/isort
77 | rev: 5.12.0
78 | hooks:
79 | - id: isort
80 | name: isort except __init__.py
81 | args:
82 | - --settings=.
83 | exclude: /__init__\.py$
84 | - repo: https://github.com/PyCQA/flake8
85 | rev: 6.1.0
86 | hooks:
87 | - id: flake8
88 | name: flake8
89 | additional_dependencies: ["flake8-bugbear==21.9.2"]
90 | - repo: https://github.com/PyCQA/pylint
91 | rev: v3.0.1
92 | hooks:
93 | - id: pylint
94 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/i18n/es.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Odoo Server\n"
4 | "Report-Msgid-Bugs-To: \n"
5 | "POT-Creation-Date: 1985-04-14 17:12+0000\n"
6 | "PO-Revision-Date: 1985-04-14 02:03+0000\n"
7 | "Last-Translator: Moisés López \n"
8 | "Language-Team: \n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: \n"
12 | "Plural-Forms: \n"
13 |
14 | #. module: broken_module
15 | #: model:ir.model.fields,field_description:broken_module.field_wizard_description
16 | #, python-format
17 | msgid "Branch"
18 | msgstr ""
19 |
20 | #. module: broken_module
21 | #: model:ir.model.fields,field_description:broken_module.field_description
22 | #, python-format
23 | msgid "Branch"
24 | msgstr ""
25 |
26 | #. module: broken_module
27 | #: model:ir.model.fields,field_description:broken_module.field_wizard_description
28 | #, python-format
29 | msgid "Branch"
30 | msgstr ""
31 |
32 | #. module: broken_module
33 | #: model:ir.model.fields,field_description2:broken_module.field_wizard_description2
34 | #, python-format
35 | msgid "Message id toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarge"
36 | msgstr ""
37 |
38 | #. module: broken_module
39 | #: model:ir.model.fields,field_description2:broken_module.field_wizard_description2
40 | #, python-format
41 | msgid "Message id toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo laaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarge"
42 | msgstr ""
43 |
44 | #. module: broken_module
45 | #: model:ir.model.fields,field_description3:broken_module.field_wizard_description3
46 | #, python-format
47 | msgid "Two variables %s %s"
48 | msgstr "Translated just one %s"
49 |
50 | #. module: broken_module
51 | #: model:ir.model.fields,field_description4:broken_module.field_wizard_description4
52 | #, python-format
53 | msgid "One variable {}"
54 | msgstr "Translated 2 {} {}"
55 |
56 | #. module: broken_module
57 | #: model:ir.model.fields,field_description5:broken_module.field_wizard_description5
58 | #, python-format
59 | msgid "One variable {0}"
60 | msgstr "Translated 2 {0} {1}"
61 |
62 | #. module: broken_module
63 | #: model:ir.model.fields,field_description5:broken_module.field_wizard_description5
64 | #, python-format
65 | msgid "One variable {variable1}"
66 | msgstr "Translated 2 {variable1} {variable2}"
67 |
68 | #. module: broken_module
69 | #: model:ir.model.fields,field_description10:broken_module.field_wizard_description10
70 | #, python-format
71 | msgid "One variable {variable1}"
72 | msgstr "Translated other one {variable2}"
73 |
74 | #. module: broken_module
75 | #: model:ir.model.fields,field_description6:broken_module.field_wizard_description6
76 | #, python-format
77 | msgid "It is a mistake"
78 | msgstr "but without raising an error %s"
79 |
80 | #. module: broken_module
81 | #: model:ir.model.fields,field_description7:broken_module.field_wizard_description7
82 | #, python-format
83 | msgid "Same quantity of variables but different variable %s"
84 | msgstr "Same quantity of variables but different variable %d"
85 |
86 | #. module: broken_module
87 | #: model:ir.model.fields,field_description8:broken_module.field_wizard_description8
88 | #, no-python-format
89 | msgid "String variables error but it is not a python format %s"
90 | msgstr "String variables error but it is not a python format"
91 |
--------------------------------------------------------------------------------
/src/pylint_odoo/misc.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import subprocess
4 | import sys
5 | from contextlib import contextmanager
6 | from functools import lru_cache
7 | from pathlib import Path
8 | from urllib.parse import urlsplit
9 |
10 | MANIFEST_DATA_KEYS = ["data", "demo", "demo_xml", "init_xml", "test", "update_xml"]
11 |
12 | README_FILES = ["README.rst", "README.md", "README.txt"]
13 |
14 | MANIFEST_FILES = [
15 | "__manifest__.py",
16 | "__odoo__.py",
17 | "__openerp__.py",
18 | "__terp__.py",
19 | ]
20 | DFTL_README_TMPL_URL = "https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
21 | DFTL_VALID_ODOO_VERSIONS = [
22 | "4.2",
23 | "5.0",
24 | "6.0",
25 | "6.1",
26 | "7.0",
27 | "8.0",
28 | "9.0",
29 | "10.0",
30 | "11.0",
31 | "12.0",
32 | "13.0",
33 | "14.0",
34 | "15.0",
35 | "16.0",
36 | "17.0",
37 | "18.0",
38 | "19.0",
39 | ]
40 | DFTL_MANIFEST_VERSION_FORMAT = r"({valid_odoo_versions})\.\d+\.\d+\.\d+$"
41 | TRANSLATION_METHODS = ("_", "_lt")
42 | EMAIL_RE = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
43 |
44 |
45 | class StringParseError(TypeError):
46 | pass
47 |
48 |
49 | def version_parse(version_str):
50 | try:
51 | return tuple(map(int, version_str.split(".")))
52 | except (ValueError, TypeError):
53 | return tuple()
54 |
55 |
56 | def get_plugin_msgs(pylint_run_res):
57 | """Get all message of this pylint plugin.
58 | :param pylint_run_res: Object returned by pylint.run method.
59 | :return: List of strings with message name.
60 | """
61 |
62 | all_plugin_msgs = []
63 | for key, message in pylint_run_res.linter.msgs_store._messages_definitions.items():
64 | checker_name = message.msgid
65 | if checker_name == "odoolint":
66 | all_plugin_msgs.append(key)
67 | return all_plugin_msgs
68 |
69 |
70 | @contextmanager
71 | def chdir(directory):
72 | """Change the current directory similar to command 'cd directory'
73 | but remembering the previous value to be revert at final
74 | Similar to run 'original_dir=$(pwd) && cd odoo && cd ${original_dir}'
75 | """
76 | original_dir = os.getcwd()
77 | os.chdir(directory)
78 | try:
79 | yield
80 | finally:
81 | os.chdir(original_dir)
82 |
83 |
84 | @lru_cache(maxsize=256)
85 | def top_path(path):
86 | """Get the top level path based on git
87 | But if it is not a git repository so the top is the drive name
88 | e.g. / or C:\\
89 | It is using lru_cache in order to re-use top level path values
90 | if multiple files are sharing the same path
91 | """
92 | try:
93 | with chdir(path):
94 | return subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode(sys.stdout.encoding).strip()
95 | except (FileNotFoundError, subprocess.CalledProcessError):
96 | path = Path(path)
97 | return path.root or path.drive
98 |
99 |
100 | def full_norm_path(path):
101 | """Expand paths in all possible ways"""
102 | return os.path.normpath(os.path.realpath(os.path.abspath(os.path.expanduser(os.path.expandvars(path.strip())))))
103 |
104 |
105 | @lru_cache(maxsize=256)
106 | def walk_up(path, filenames, top):
107 | """Look for "filenames" walking up in parent paths of "path"
108 | but limited only to "top" path
109 | """
110 | if full_norm_path(path) == full_norm_path(top):
111 | return None
112 | for filename in filenames:
113 | path_filename = os.path.join(path, filename)
114 | if os.path.isfile(full_norm_path(path_filename)):
115 | return path_filename
116 | return walk_up(os.path.dirname(path), filenames, top)
117 |
118 |
119 | class InvalidVersion(Exception):
120 | pass
121 |
122 |
123 | def version2tuple(version):
124 | try:
125 | return tuple(int(i) for i in version.split("."))
126 | except (ValueError, AttributeError) as exc:
127 | raise InvalidVersion(
128 | f"Invalid Version only integers separated by dot was expected. e.g. 19.0.1.0.0 but received {[version]}"
129 | ) from exc
130 |
131 |
132 | class InvalidURL(Exception):
133 | pass
134 |
135 |
136 | # Based on https://github.com/python-validators/validators/blob/c9585e91f8b409029/src/validators/domain.py#L87-L99
137 | DOMAIN_RE = re.compile(r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-_]{0,61}[a-z]$", re.IGNORECASE)
138 |
139 |
140 | def validate_url(url):
141 | if not url:
142 | raise InvalidURL("Empty URL")
143 | if re.search(r"\s", url):
144 | raise InvalidURL("URL must not contain white spaces, they must be encoded")
145 | try:
146 | scheme, netloc, _path, _query, _fragment = urlsplit(url)
147 | except ValueError as ve_exc:
148 | raise InvalidURL(f"URL invalid: {str(ve_exc)}") from ve_exc
149 |
150 | if scheme not in ("https", "http"):
151 | raise InvalidURL("URL needs to start with 'http[s]://'")
152 | if not netloc:
153 | raise InvalidURL("Invalid URL domain not identified")
154 |
155 | # Based on https://github.com/python-validators/validators/blob/c9585e91f8b409029/src/validators/domain.py#L98
156 | if re.search(r"__+", netloc):
157 | raise InvalidURL(f"Domain section must not contain double underscore '__' because of security issues {netloc}")
158 | try:
159 | netloc = netloc.encode("idna").decode("utf-8")
160 | except UnicodeError as err:
161 | raise InvalidURL(f"Unable to encode/decode domain section {netloc}") from err
162 | if not DOMAIN_RE.match(netloc):
163 | raise InvalidURL(f"Domain '{netloc}' contains invalid characters")
164 | return True
165 |
166 |
167 | def validate_email(email):
168 | return EMAIL_RE.match(email) is not None
169 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/model_view2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form2
7 | test.model
8 |
9 |
12 |
13 |
14 |
15 |
16 | view.model.form2
17 | test.model
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | view.model.form10
27 | test.model
28 |
29 |
34 |
35 |
36 |
37 |
38 | view.model.form20
39 | test.model
40 |
41 |
44 |
45 |
46 |
47 |
48 | view.model.form30
49 | test.model
50 |
51 |
56 |
57 |
58 |
59 |
60 | view.model.form40
61 | test.model
62 | 100
63 |
64 |
67 |
68 |
69 |
70 |
71 | view.model.form50
72 | test.model
73 | 10
74 |
75 |
78 |
79 |
80 |
81 |
82 | view.model.form60
83 | test.model
84 |
85 |
86 |
89 |
90 |
91 |
92 |
93 | view.model.form70
94 | test.model
95 |
96 |
97 |
100 |
101 |
102 |
103 |
104 | view.model.form80
105 | test.model
106 |
107 |
108 |
109 | view.model.form90
110 | test.model
111 | 10
112 |
113 |
114 |
115 |
116 |
117 |
118 | view.model.form100
119 | test.model
120 | 10
121 |
122 |
123 |
124 |
125 |
126 |
127 | red
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/pylint_odoo/checkers/custom_logging.py:
--------------------------------------------------------------------------------
1 | import re
2 | from contextlib import contextmanager
3 | from typing import Literal
4 | from unittest.mock import patch
5 |
6 | from astroid import builder, exceptions as astroid_exceptions, nodes
7 | from pylint.checkers import logging
8 |
9 | from .. import misc
10 | from .odoo_addons import OdooAddons
11 | from .odoo_base_checker import OdooBaseChecker
12 |
13 |
14 | @contextmanager
15 | def config_logging_modules(linter, modules):
16 | original_logging_modules = linter.config.logging_modules
17 | linter.config.logging_modules = modules
18 | try:
19 | yield
20 | finally:
21 | linter.config.logging_modules = original_logging_modules
22 |
23 |
24 | def transform_msgs(msgs):
25 | """Transform all the 'logging' messages and code to 'translation'
26 | from:
27 | - {'W1201': ('logging-*', 'logging ...', ...)}
28 | to:
29 | - {'W8301': ('translation-*', 'translation ...', ...)}
30 | """
31 | new_msgs = {}
32 | for msgid, msgattrs in msgs.items():
33 | msg_short, msg_code, msg_desc = msgattrs[:3]
34 | if "logging" not in msg_code.lower(): # pragma: no cover
35 | continue
36 | msg_short = msg_short.replace("logging", "odoo._")
37 | msgid = re.sub("12", BASE_CHECKS_ID, msgid, count=1)
38 | msg_code = msg_code.replace("logging", "translation")
39 | new_msgs[msgid] = (msg_short, msg_code, msg_desc) + msgattrs[3:]
40 | return new_msgs
41 |
42 |
43 | BASE_CHECKS_ID = "83"
44 |
45 | ODOO_MSGS = transform_msgs(logging.MSGS)
46 |
47 |
48 | class CustomLoggingChecker(OdooBaseChecker, logging.LoggingChecker):
49 | name = "odoolint"
50 | msgs = ODOO_MSGS
51 |
52 | def __init__(self, *args, **kwargs):
53 | super().__init__(*args, **kwargs)
54 | for msg_attrs in self.msgs.values():
55 | # checks_maxmin_odoo_version = {
56 | # check-code: {
57 | # "odoo_minversion": tuple(int, int),
58 | # "odoo_maxversion": tuple(int, int),
59 | # }
60 | self.checks_maxmin_odoo_version[msg_attrs[1]] = {
61 | "odoo_minversion": "14.0",
62 | }
63 |
64 | def add_message(self, msgid, *args, **kwargs):
65 | """Emit translation-* instead of logging-*
66 | e.g. translation-not-lazy instead of logging-not-lazy
67 | """
68 | msgid = msgid.replace("logging", "translation")
69 | return super().add_message(msgid, *args, **kwargs)
70 |
71 | def visit_call(self, node):
72 | name = OdooAddons.get_func_name(node.func)
73 | if name == "format" and (new_node := self.transform_formatcall2tlcall(node)):
74 | node = new_node
75 | name = OdooAddons.get_func_name(node.func)
76 | if name not in misc.TRANSLATION_METHODS:
77 | return
78 | with config_logging_modules(self.linter, ("odoo",)):
79 | self._check_log_method(node, name)
80 |
81 | def transform_formatcall2tlcall(self, node):
82 | """Transform no detectable node:
83 | _("lazy not detectable: {}").format("var")
84 | To detectable one:
85 | _("lazy detectable: {}".format("vat"))
86 | """
87 | if not (
88 | isinstance(node, nodes.Call) and isinstance(node.func, nodes.Attribute) and node.func.attrname == "format"
89 | ):
90 | return
91 |
92 | format_expr = node.func.expr.as_string()
93 |
94 | args_str = ", ".join(arg.as_string() for arg in node.args)
95 | kwargs_str = ", ".join(f"{kw.arg}={kw.value.as_string()}" for kw in node.keywords)
96 | all_args = ", ".join(item for item in [args_str, kwargs_str] if item)
97 | new_code = f"{format_expr[:-1]}.format({all_args}))"
98 | try:
99 | new_node = builder.extract_node(new_code)
100 | except astroid_exceptions.AstroidSyntaxError:
101 | return
102 | # node = self.env._('{param1}').format(param1='hello')
103 | # new_node = self.env._('{param1}'.format(param1='hello'))
104 | # new_node.args[0] = '{param1}'.format(param1='hello')
105 | if not new_node.args:
106 | return
107 | node_attrs = ["lineno", "col_offset", "parent", "end_lineno", "end_col_offset", "position", "fromlineno"]
108 | for node_attr in node_attrs:
109 | setattr(new_node, node_attr, getattr(node, node_attr, None))
110 | # Help to preserve the lineno of the first arg used from pylint/checkers/logging.py::_check_log_method
111 | setattr(new_node.args[0], node_attr, getattr(node, node_attr, None))
112 | return new_node
113 |
114 | def transform_binop2call(self, node):
115 | """Transform no detectable node:
116 | _("lazy not detectable: %s") % es_err.error
117 | To detectable one:
118 | _("lazy detectable: %s" % es_err.error)
119 | """
120 | new_code = f"{node.left.as_string()[:-1]} {node.op} {node.right.as_string()})"
121 | try:
122 | new_node = builder.extract_node(new_code)
123 | except astroid_exceptions.AstroidSyntaxError:
124 | return
125 | node_attrs = ["lineno", "col_offset", "parent", "end_lineno", "end_col_offset", "position", "fromlineno"]
126 | for node_attr in node_attrs:
127 | setattr(new_node, node_attr, getattr(node, node_attr, None))
128 | return new_node
129 |
130 | def visit_binop(self, node):
131 | if not isinstance(node.left, nodes.Call) or node.op != "%" or OdooAddons.get_func_name(node.left.func) != "_":
132 | return
133 | new_node = self.transform_binop2call(node)
134 | if new_node:
135 | self.visit_call(new_node)
136 |
137 | def _check_log_method(self, *args, **kwargs):
138 | with patch("pylint.checkers.logging.CHECKED_CONVENIENCE_FUNCTIONS", {"_"}):
139 | super()._check_log_method(*args, **kwargs)
140 |
141 | def _check_format_string(self, node: nodes.Call, format_arg: Literal[0, 1]) -> None:
142 | num_args = logging._count_supplied_tokens(node.args[format_arg + 1 :])
143 | # Custom revert for https://github.com/pylint-dev/pylint/commit/c23674554a7fac2fbb390cb67
144 | # since translation returns a string so it is a valid case for translation but not for logging
145 | if not num_args:
146 | # If no args were supplied the string is not interpolated and can contain
147 | # formatting characters - it's used verbatim. Don't check any further.
148 | return
149 | return super()._check_format_string(node=node, format_arg=format_arg)
150 |
--------------------------------------------------------------------------------
/testing/resources/test_repo/broken_module/model_view_odoo2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | view.model.form
7 | test.model
8 |
9 |
10 |
11 |
12 |
13 | view.model.form4
14 |
15 |
16 |
17 |
18 | view.model.form
19 | test.model
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | red
30 | red
31 |
32 |
33 |
34 |
35 |
36 |
37 | ir.config_parameter
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | view.model.form
50 | test.model
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Access rule
63 |
64 |
65 |
66 |
67 |
68 | [('user_ids','in',user.id)]
69 |
70 |
71 |
72 |
73 | test.model5
74 | view.model.form
75 | test.model
76 |
77 |
78 |
101 |
102 |
103 |
104 |
105 |
106 | view.model.form
107 | test.model
108 |
109 |
121 |
122 |
123 |
124 |
125 |
126 | view.model.form
127 | test.model
128 |
129 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/tests/test_main.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import os
4 | import re
5 | import stat
6 | import sys
7 | import unittest
8 | from collections import Counter, defaultdict
9 | from glob import glob
10 | from io import StringIO
11 | from tempfile import NamedTemporaryFile
12 |
13 | import pytest
14 | from pylint.reporters.text import TextReporter
15 | from pylint.testutils._run import _Run as Run
16 | from pylint.testutils.utils import _patch_streams
17 |
18 | from pylint_odoo import __version__ as version, plugin
19 |
20 | RE_CHECK_OUTPUT = re.compile(r"\- \[(?P[\w|-]+)\]")
21 |
22 | EXPECTED_ERRORS = {
23 | "attribute-deprecated": 3,
24 | "attribute-string-redundant": 31,
25 | "bad-builtin-groupby": 2,
26 | "category-allowed-app": 1,
27 | "consider-merging-classes-inherited": 2,
28 | "context-overridden": 3,
29 | "deprecated-name-get": 1,
30 | "deprecated-odoo-model-method": 2,
31 | "development-status-allowed": 1,
32 | "except-pass": 3,
33 | "external-request-timeout": 51,
34 | "inheritable-method-lambda": 2,
35 | "inheritable-method-string": 4,
36 | "invalid-commit": 4,
37 | "invalid-email": 1,
38 | "license-allowed": 1,
39 | "manifest-author-string": 1,
40 | "manifest-behind-migrations": 3,
41 | "manifest-data-duplicated": 1,
42 | "manifest-deprecated-key": 1,
43 | "manifest-external-assets": 3,
44 | "manifest-maintainers-list": 1,
45 | "manifest-required-author": 1,
46 | "manifest-required-key-app": 5,
47 | "manifest-required-key": 1,
48 | "manifest-superfluous-key": 7,
49 | "manifest-version-format": 3,
50 | "method-compute": 2,
51 | "method-inverse": 2,
52 | "method-required-super": 8,
53 | "method-search": 2,
54 | "missing-odoo-file-app": 1,
55 | "missing-readme": 1,
56 | "missing-return": 1,
57 | "no-raise-unlink": 2,
58 | "no-search-all": 12,
59 | "no-wizard-in-models": 1,
60 | "no-write-in-compute": 16,
61 | "odoo-addons-relative-import": 4,
62 | "odoo-exception-warning": 4,
63 | "prefer-env-translation": 112,
64 | "print-used": 1,
65 | "renamed-field-parameter": 2,
66 | "resource-not-exist": 4,
67 | "sql-injection": 21,
68 | "super-method-mismatch": 7,
69 | "test-folder-imported": 3,
70 | "translation-contains-variable": 33,
71 | "translation-field": 3,
72 | "translation-format-interpolation": 22,
73 | "translation-format-truncated": 2,
74 | "translation-fstring-interpolation": 3,
75 | "translation-not-lazy": 42,
76 | "translation-positional-used": 30,
77 | "translation-required": 16,
78 | "translation-too-few-args": 2,
79 | "translation-too-many-args": 2,
80 | "translation-unsupported-format": 2,
81 | "use-vim-comment": 1,
82 | "website-manifest-key-not-valid-uri": 2,
83 | }
84 |
85 |
86 | class MainTest(unittest.TestCase):
87 | def setUp(self):
88 | self.default_options = [
89 | "--load-plugins=pylint_odoo",
90 | "--reports=no",
91 | "--score=no",
92 | "--msg-template={path}:{line} {msg} - [{symbol}]",
93 | "--persistent=no",
94 | ]
95 | self.root_path_modules = os.path.join(
96 | os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "testing", "resources", "test_repo"
97 | )
98 | # Similar to pre-commit way
99 | self.paths_modules = glob(os.path.join(self.root_path_modules, "**", "*.py"), recursive=True)
100 | self.odoo_namespace_addons_path = os.path.join(
101 | os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
102 | "testing",
103 | "resources",
104 | "test_repo_odoo_namespace",
105 | "odoo",
106 | )
107 | self.default_extra_params = [
108 | "--disable=all",
109 | "--enable=odoolint,pointless-statement,trailing-newlines",
110 | ]
111 | self.sys_path_origin = list(sys.path)
112 | self.maxDiff = None
113 | self.expected_errors = EXPECTED_ERRORS.copy()
114 |
115 | def tearDown(self):
116 | sys.path = list(self.sys_path_origin)
117 |
118 | def run_pylint(self, paths, extra_params: list | None = None, verbose=False, rcfile: str = ""):
119 | for path in paths:
120 | if not os.path.exists(path):
121 | raise OSError('Path "{path}" not found.'.format(path=path))
122 |
123 | if extra_params is None:
124 | extra_params = self.default_extra_params
125 | if rcfile:
126 | extra_params.append(f"--rcfile={rcfile}")
127 |
128 | sys.path.extend(paths)
129 | cmd = self.default_options + extra_params + paths
130 | reporter = TextReporter(StringIO())
131 | with open(os.devnull, "w", encoding="UTF-8") as f_dummy:
132 | self._run_pylint(cmd, f_dummy, reporter=reporter)
133 | if verbose:
134 | reporter.out.seek(0)
135 | print(reporter.out.read())
136 | return reporter
137 |
138 | @staticmethod
139 | def _run_pylint(args, out, reporter):
140 | with pytest.raises(SystemExit) as ctx_mgr:
141 | if sys.gettrace() is None: # No pdb enabled
142 | with _patch_streams(out):
143 | Run(args, reporter=reporter)
144 | else: # pdb enabled
145 | Run(args, reporter=reporter)
146 | return int(ctx_mgr.value.code)
147 |
148 | def test_10_path_dont_exist(self):
149 | """test if path don't exist"""
150 | path_unexist = "/tmp/____unexist______"
151 | with self.assertRaisesRegex(OSError, r'Path "{path}" not found.$'.format(path=path_unexist)):
152 | self.run_pylint([path_unexist])
153 |
154 | def test_20_expected_errors(self):
155 | """Expected vs found errors"""
156 | pylint_res = self.run_pylint(self.paths_modules, verbose=True)
157 | real_errors = pylint_res.linter.stats.by_msg
158 | self.assertEqual(self.expected_errors, real_errors)
159 |
160 | def test_25_checks_excluding_by_odoo_version(self):
161 | """All odoolint errors vs found but excluding based on Odoo version"""
162 | excluded_msgs = {
163 | "deprecated-odoo-model-method",
164 | "no-raise-unlink",
165 | "prefer-env-translation",
166 | "translation-format-interpolation",
167 | "translation-format-truncated",
168 | "translation-fstring-interpolation",
169 | "translation-not-lazy",
170 | "translation-too-few-args",
171 | "translation-too-many-args",
172 | "translation-unsupported-format",
173 | "deprecated-name-get",
174 | }
175 | self.default_extra_params += ["--valid-odoo-versions=13.0"]
176 | pylint_res = self.run_pylint(self.paths_modules)
177 | real_errors = pylint_res.linter.stats.by_msg
178 | expected_errors = self.expected_errors.copy()
179 | for excluded_msg in excluded_msgs:
180 | expected_errors.pop(excluded_msg)
181 | expected_errors.update({"manifest-version-format": 6})
182 | self.assertEqual(expected_errors, real_errors)
183 |
184 | def test_35_checks_emiting_by_odoo_version(self):
185 | """All odoolint errors vs found but see if were not excluded for valid odoo version"""
186 | self.default_extra_params += ["--valid-odoo-versions=14.0"]
187 | pylint_res = self.run_pylint(self.paths_modules)
188 | real_errors = pylint_res.linter.stats.by_msg
189 | expected_errors = self.expected_errors.copy()
190 | expected_errors.update({"manifest-version-format": 6})
191 | excluded_msgs = {
192 | "deprecated-name-get",
193 | "deprecated-odoo-model-method",
194 | "no-raise-unlink",
195 | "prefer-env-translation",
196 | "translation-contains-variable",
197 | }
198 | for excluded_msg in excluded_msgs:
199 | expected_errors.pop(excluded_msg)
200 | self.assertEqual(expected_errors, real_errors)
201 |
202 | def test_85_valid_odoo_version_format(self):
203 | """Test --manifest-version-format parameter"""
204 | # First, run Pylint for version 8.0
205 | extra_params = [
206 | r'--manifest-version-format="8\.0\.\d+\.\d+.\d+$"',
207 | "--valid-odoo-versions=8.0",
208 | "--disable=all",
209 | "--enable=manifest-version-format",
210 | ]
211 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
212 | real_errors = pylint_res.linter.stats.by_msg
213 | expected_errors = {
214 | "manifest-version-format": 6,
215 | }
216 | self.assertDictEqual(real_errors, expected_errors)
217 |
218 | # Now for version 11.0
219 | extra_params[0] = r'--manifest-version-format="11\.0\.\d+\.\d+.\d+$"'
220 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
221 | real_errors = pylint_res.linter.stats.by_msg
222 | expected_errors = {
223 | "manifest-version-format": 5,
224 | }
225 | self.assertDictEqual(real_errors, expected_errors)
226 |
227 | def test_90_valid_odoo_versions(self):
228 | """Test --valid-odoo-versions parameter when it's '8.0' & '11.0'"""
229 | # First, run Pylint for version 8.0
230 | extra_params = [
231 | "--valid-odoo-versions=8.0",
232 | "--disable=all",
233 | "--enable=manifest-version-format",
234 | ]
235 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
236 | real_errors = pylint_res.linter.stats.by_msg
237 | expected_errors = {
238 | "manifest-version-format": 6,
239 | }
240 | self.assertDictEqual(real_errors, expected_errors)
241 |
242 | # Now for version 11.0
243 | extra_params[0] = "--valid-odoo-versions=11.0"
244 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
245 | real_errors = pylint_res.linter.stats.by_msg
246 | expected_errors = {
247 | "manifest-version-format": 5,
248 | }
249 | self.assertDictEqual(real_errors, expected_errors)
250 |
251 | def test_110_manifest_required_authors(self):
252 | """Test --manifest-required-authors using a different author and
253 | multiple authors separated by commas
254 | """
255 | # First, run Pylint using a different author
256 | extra_params = [
257 | "--manifest-required-authors=Vauxoo",
258 | "--disable=all",
259 | "--enable=manifest-required-author",
260 | ]
261 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
262 | real_errors = pylint_res.linter.stats.by_msg
263 | expected_errors = {
264 | "manifest-required-author": 5,
265 | }
266 | self.assertDictEqual(real_errors, expected_errors)
267 |
268 | # Then, run it using multiple authors
269 | extra_params[0] = "--manifest-required-authors=Vauxoo,Other"
270 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
271 | real_errors = pylint_res.linter.stats.by_msg
272 | expected_errors["manifest-required-author"] -= 1
273 | self.assertDictEqual(real_errors, expected_errors)
274 |
275 | # Testing deprecated attribute
276 | extra_params[0] = "--manifest-required-author=Odoo Community Association (OCA)"
277 | pylint_res = self.run_pylint(self.paths_modules, extra_params)
278 | real_errors = pylint_res.linter.stats.by_msg
279 | expected_errors_deprecated = {
280 | "manifest-required-author": (EXPECTED_ERRORS["manifest-required-author"]),
281 | }
282 | self.assertDictEqual(real_errors, expected_errors_deprecated)
283 |
284 | def test_140_check_suppress_migrations(self):
285 | """Test migrations path supress checks"""
286 | extra_params = [
287 | "--disable=all",
288 | "--enable=invalid-name,unused-argument",
289 | ]
290 | path_modules = [
291 | os.path.join(
292 | os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
293 | "testing",
294 | "resources",
295 | "test_repo",
296 | "test_module",
297 | "migrations",
298 | "10.0.1.0.0",
299 | "pre-migration.py",
300 | )
301 | ]
302 |
303 | # Messages suppressed with plugin for migration
304 | pylint_res = self.run_pylint(path_modules, extra_params)
305 | real_errors = pylint_res.linter.stats.by_msg
306 | expected_errors = {
307 | "unused-argument": 1,
308 | }
309 | self.assertDictEqual(real_errors, expected_errors)
310 |
311 | # Messages raised without plugin
312 | self.default_options.remove("--load-plugins=pylint_odoo")
313 | pylint_res = self.run_pylint(path_modules, extra_params)
314 | real_errors = pylint_res.linter.stats.by_msg
315 | expected_errors = {
316 | "invalid-name": 1,
317 | "unused-argument": 2,
318 | }
319 | self.assertDictEqual(real_errors, expected_errors)
320 |
321 | def test_140_check_migrations_is_not_odoo_module(self):
322 | """Checking that migrations folder is not considered a odoo module
323 | Related to https://github.com/OCA/pylint-odoo/issues/357"""
324 | extra_params = [
325 | "--disable=all",
326 | "--enable=missing-readme",
327 | ]
328 | test_module = os.path.join(
329 | os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
330 | "testing",
331 | "resources",
332 | "test_repo",
333 | "test_module",
334 | )
335 | path_modules = [
336 | os.path.join(test_module, "__init__.py"),
337 | os.path.join(test_module, "migrations", "10.0.1.0.0", "pre-migration.py"),
338 | ]
339 | pylint_res = self.run_pylint(path_modules, extra_params)
340 | real_errors = pylint_res.linter.stats.by_msg
341 | expected_errors = {}
342 | self.assertDictEqual(real_errors, expected_errors)
343 |
344 | def test_gettext_env(self):
345 | """prefer-env-translation is only valid for odoo v18.0+ but not for older odoo versions"""
346 | pylint_res = self.run_pylint(
347 | self.paths_modules, ["--valid-odoo-versions=18.0", "--disable=all", "--enable=prefer-env-translation"]
348 | )
349 | real_errors = pylint_res.linter.stats.by_msg
350 | expected_errors = {"prefer-env-translation": self.expected_errors.get("prefer-env-translation")}
351 | self.assertEqual(expected_errors, real_errors)
352 |
353 | pylint_res = self.run_pylint(
354 | self.paths_modules, ["--valid-odoo-versions=17.0", "--disable=all", "--enable=prefer-env-translation"]
355 | )
356 | real_errors = pylint_res.linter.stats.by_msg
357 | expected_errors = {}
358 | self.assertEqual(expected_errors, real_errors)
359 |
360 | @unittest.skipUnless(not sys.platform.startswith("win"), "TOOD: Fix with windows") # TODO: Fix it
361 | def test_145_check_fstring_sqli(self):
362 | """Verify the linter is capable of finding SQL Injection vulnerabilities
363 | when using fstrings.
364 | Related to https://github.com/OCA/pylint-odoo/issues/363"""
365 | extra_params = ["--disable=all", "--enable=sql-injection"]
366 | queries = """
367 | def fstring_sqli(self):
368 | self.env.cr.execute(f"SELECT * FROM TABLE WHERE SQLI = {self.table}")
369 | self.env.cr.execute(
370 | f"SELECT * FROM TABLE WHERE SQLI = {'hello' + self.name}"
371 | )
372 | self.env.cr.execute(f"SELECT * FROM {self.name} WHERE SQLI = {'hello'}")
373 | death_wish = f"SELECT * FROM TABLE WHERE SQLI = {self.name}"
374 | self.env.cr.execute(death_wish)
375 | def fstring_no_sqli(self):
376 | self.env.cr.execute(f"SELECT * FROM TABLE WHERE SQLI = {'hello'}")
377 | self.env.cr.execute(
378 | f"CREATE VIEW {self._table} AS (SELECT * FROM res_partner)"
379 | )
380 | self.env.cr.execute(f"SELECT NAME FROM res_partner LIMIT 10")
381 | """
382 | with NamedTemporaryFile(mode="w") as tmp_f:
383 | tmp_f.write(queries)
384 | tmp_f.flush()
385 | pylint_res = self.run_pylint([tmp_f.name], extra_params)
386 |
387 | real_errors = pylint_res.linter.stats.by_msg
388 | self.assertDictEqual(real_errors, {"sql-injection": 4})
389 |
390 | def test_150_check_only_enabled_one_check(self):
391 | """Checking -d all -e ONLY-ONE-CHECK"""
392 | disable = "--disable=all"
393 | for expected_error_name, expected_error_value in EXPECTED_ERRORS.items():
394 | enable = "--enable=%s" % expected_error_name
395 | pylint_res = self.run_pylint(self.paths_modules, [disable, enable])
396 | real_errors = pylint_res.linter.stats.by_msg
397 | expected_errors = {expected_error_name: expected_error_value}
398 | self.assertDictEqual(real_errors, expected_errors)
399 |
400 | def test_160_check_only_disabled_one_check(self):
401 | """Checking -d all -e odoolint -d ONLY-ONE-CHECK"""
402 | for disable_error in EXPECTED_ERRORS:
403 | expected_errors = self.expected_errors.copy()
404 | enable = "--disable=%s" % disable_error
405 | pylint_res = self.run_pylint(self.paths_modules, self.default_extra_params + [enable])
406 | real_errors = pylint_res.linter.stats.by_msg
407 | expected_errors.pop(disable_error)
408 | self.assertDictEqual(real_errors, expected_errors)
409 |
410 | def test_165_no_raises_unlink(self):
411 | extra_params = ["--disable=all", "--enable=no-raise-unlink"]
412 | test_repo = os.path.join(self.root_path_modules, "test_module")
413 |
414 | self.assertDictEqual(
415 | self.run_pylint([test_repo], extra_params).linter.stats.by_msg,
416 | {"no-raise-unlink": 2},
417 | )
418 |
419 | # This check is only valid for Odoo 15.0 and upwards
420 | extra_params.append("--valid-odoo-versions=14.0")
421 | self.assertFalse(self.run_pylint([test_repo], extra_params).linter.stats.by_msg)
422 |
423 | # Test category-allowed with and without error
424 | def test_170_category_allowed(self):
425 | extra_params = ["--disable=all", "--enable=category-allowed-app", "--category-allowed-app=Category 00"]
426 | pylint_res = self.run_pylint(self.paths_modules, extra_params, verbose=True)
427 | real_errors = pylint_res.linter.stats.by_msg
428 | expected_errors = {
429 | "category-allowed-app": 1,
430 | }
431 | self.assertDictEqual(real_errors, expected_errors)
432 |
433 | expected_errors = {
434 | "category-allowed-app": 1,
435 | }
436 | extra_params = ["--disable=all", "--enable=category-allowed-app", "--category-allowed-app=Category 01"]
437 | pylint_res = self.run_pylint(self.paths_modules, extra_params, verbose=True)
438 | real_errors = pylint_res.linter.stats.by_msg
439 | self.assertDictEqual(real_errors, expected_errors)
440 |
441 | extra_params = ["--disable=all", "--enable=category-allowed", "--category-allowed=Category 00"]
442 | pylint_res = self.run_pylint(self.paths_modules, extra_params, verbose=True)
443 | real_errors = pylint_res.linter.stats.by_msg
444 | expected_errors = {
445 | "category-allowed": 1,
446 | }
447 | self.assertDictEqual(real_errors, expected_errors)
448 |
449 | expected_errors = {}
450 | extra_params = ["--disable=all", "--enable=category-allowed", "--category-allowed-app=Category 01"]
451 | pylint_res = self.run_pylint(self.paths_modules, extra_params, verbose=True)
452 | real_errors = pylint_res.linter.stats.by_msg
453 | self.assertDictEqual(real_errors, expected_errors)
454 |
455 | def test_option_odoo_deprecated_model_method(self):
456 | pylint_res = self.run_pylint(
457 | self.paths_modules,
458 | rcfile=os.path.abspath(
459 | os.path.join(__file__, "..", "..", "testing", "resources", ".pylintrc-odoo-deprecated-model-methods")
460 | ),
461 | )
462 |
463 | self.assertEqual(
464 | 4,
465 | pylint_res.linter.stats.by_msg["deprecated-odoo-model-method"],
466 | )
467 |
468 | def test_175_prohibited_method_override(self):
469 | """Test --prohibited_override_methods parameter"""
470 | extra_params = [
471 | "--disable=all",
472 | "--enable=prohibited-method-override",
473 | "--prohibited-method-override=test_base_method_1,test_base_method_3",
474 | ]
475 | pylint_res = self.run_pylint(self.paths_modules, extra_params, verbose=True)
476 | real_errors = pylint_res.linter.stats.by_msg
477 | expected_errors = {
478 | "prohibited-method-override": 2,
479 | }
480 | self.assertDictEqual(real_errors, expected_errors)
481 |
482 | def test_180_jobs(self):
483 | """Using jobs could raise new errors"""
484 | self.default_extra_params += ["--jobs=2"]
485 | pylint_res = self.run_pylint(self.paths_modules, verbose=True)
486 | # pylint_res.linter.stats.by_msg has a issue generating the stats wrong with --jobs
487 | res = self._get_messages_from_output(pylint_res)
488 | real_errors = {key: len(set(lines)) for key, lines in res.items()}
489 | self.assertEqual(self.expected_errors, real_errors)
490 |
491 | for key, lines in res.items():
492 | lines_counter = Counter(tuple(lines))
493 | for line, count in lines_counter.items():
494 | self.assertLessEqual(count, 2, "%s duplicated more than 2 times. Line %s" % (key, line))
495 |
496 | def test_format_version_value_error(self):
497 | """Test --valid-odoo-versions to force a value error exception"""
498 | extra_params = [
499 | "--valid-odoo-versions=8.0saas",
500 | "--disable=all",
501 | "--enable=manifest-version-format",
502 | ]
503 | with self.assertWarns(UserWarning) as warn:
504 | pylint_res = self.run_pylint(self.paths_modules, extra_params, verbose=True)
505 | real_errors = pylint_res.linter.stats.by_msg
506 | expected_errors = {"manifest-version-format": 6}
507 | self.assertDictEqual(real_errors, expected_errors)
508 | self.assertIn("Invalid manifest versions format ['8.0saas']", str(warn.warning))
509 |
510 | @unittest.skipUnless(not sys.platform.startswith("win"), "Windows works a little different with executable files")
511 | def test_invalid_name_executable(self):
512 | """Test valid case for file name of executable-file instead of executable_file.py"""
513 | extra_params = [
514 | "--disable=all",
515 | "--enable=invalid-name",
516 | ]
517 | with NamedTemporaryFile(mode="w", prefix="executable-", suffix=".py") as tmp_f:
518 | tmp_f.write("#!/usr/bin/env python3")
519 | tmp_f.flush()
520 | pylint_res = self.run_pylint([tmp_f.name], extra_params)
521 | real_errors = pylint_res.linter.stats.by_msg
522 | self.assertDictEqual(real_errors, {"invalid-name": 1}, "The file extension is .py should raise the check")
523 |
524 | with NamedTemporaryFile(mode="w", prefix="executable-") as tmp_f:
525 | tmp_f.write("#!/usr/bin/env python3")
526 | tmp_f.flush()
527 | pylint_res = self.run_pylint([tmp_f.name], extra_params)
528 | real_errors = pylint_res.linter.stats.by_msg
529 | self.assertDictEqual(
530 | real_errors,
531 | {"invalid-name": 1},
532 | "The file doens't have executable permissions so should raise the check",
533 | )
534 |
535 | with NamedTemporaryFile(mode="w", prefix="executable-") as tmp_f:
536 | os.chmod(tmp_f.name, os.stat(tmp_f.name).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
537 | tmp_f.write("#!/usr/bin/env python3")
538 | tmp_f.flush()
539 | pylint_res = self.run_pylint([tmp_f.name], extra_params)
540 | real_errors = pylint_res.linter.stats.by_msg
541 | self.assertDictEqual(
542 | real_errors,
543 | {},
544 | "The file is a valid executable with executable permissions, "
545 | "name with -, without py extension and with shebang. It should not be raise the check",
546 | )
547 |
548 | @staticmethod
549 | def re_replace(sub_start, sub_end, substitution, content):
550 | re_sub = re.compile(rf"^{re.escape(sub_start)}$.*^{re.escape(sub_end)}$", re.M | re.S)
551 | if not re_sub.findall(content):
552 | raise UserWarning("No matched content")
553 | substitution = substitution.replace("\\", "\\\\")
554 | new_content = re_sub.sub(f"{sub_start}\n\n{substitution}\n\n{sub_end}", content)
555 | return new_content
556 |
557 | def _get_messages_from_output(self, pylint_res):
558 | pylint_res.out.seek(0)
559 | all_check_errors_merged = defaultdict(list)
560 | for line in pylint_res.out:
561 | checks_found = RE_CHECK_OUTPUT.findall(line)
562 | if not checks_found:
563 | continue
564 | line = RE_CHECK_OUTPUT.sub("", line).strip()
565 | all_check_errors_merged[checks_found[0]].append(line)
566 | return all_check_errors_merged
567 |
568 | @unittest.skipIf(not os.environ.get("BUILD_README"), "BUILD_README environment variable not enabled")
569 | def test_build_docstring(self):
570 | messages_content = plugin.messages2md()
571 | readme_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "README.md")
572 | with open(readme_path, encoding="UTF-8") as f_readme:
573 | readme_content = f_readme.read()
574 |
575 | new_readme = self.re_replace(
576 | "[//]: # (start-checks)", "[//]: # (end-checks)", messages_content, readme_content
577 | )
578 |
579 | pylint_res = self.run_pylint(self.paths_modules, verbose=True)
580 | all_check_errors_merged = self._get_messages_from_output(pylint_res)
581 | check_example_content = ""
582 | for check_error, msgs in sorted(all_check_errors_merged.items(), key=lambda a: a[0]):
583 | check_example_content += f"\n\n * {check_error}\n"
584 | for msg in sorted(msgs)[:3]:
585 | msg = msg.replace(":", "#L", 1)
586 | check_example_content += f"\n - https://github.com/OCA/pylint-odoo/blob/v{version}/{msg}"
587 | check_example_content = f"# Examples\n{check_example_content}"
588 | new_readme = self.re_replace(
589 | "[//]: # (start-example)", "[//]: # (end-example)", check_example_content, new_readme
590 | )
591 |
592 | with open(readme_path, "w", encoding="UTF-8") as f_readme:
593 | f_readme.write(new_readme)
594 | self.assertEqual(
595 | readme_content,
596 | new_readme,
597 | "The README was updated! Don't panic only failing for CI purposes. Run the same test again.",
598 | )
599 |
600 |
601 | if __name__ == "__main__":
602 | unittest.main()
603 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [//]: # (start-badges)
2 |
3 | [](https://github.com/OCA/pylint-odoo/actions/workflows/test.yml?query=branch%3Amain)
4 | [](https://codecov.io/gh/OCA/pylint-odoo)
5 | [](https://github.com/psf/black)
6 | [](https://pypi.org/project/pylint-odoo)
7 | [](https://pypi.python.org/pypi/pylint-odoo)
8 | [](https://pypi.org/project/pylint-odoo)
9 | [](https://pypi.org/project/pylint-odoo)
10 | [](https://github.com/OCA/pylint-odoo/compare/v9.3.22...main)
11 |
12 | [//]: # (end-badges)
13 |
14 |
15 | # Pylint Odoo plugin
16 |
17 | Enable custom checks for Odoo modules.
18 |
19 | [//]: # (start-checks)
20 |
21 | Short Name | Description | Code
22 | --- | --- | ---
23 | attribute-deprecated | attribute "%s" deprecated | W8105
24 | attribute-string-redundant | The attribute string is redundant. String parameter equal to name of variable | W8113
25 | bad-builtin-groupby | Used builtin function `itertools.groupby`. Prefer `odoo.tools.groupby` instead. More info about https://github.com/odoo/odoo/issues/105376 | W8155
26 | category-allowed | Category "%s" not allowed in manifest file. | C8114
27 | category-allowed-app | Category "%s" not allowed in manifest file for modules with price. | C8117
28 | consider-merging-classes-inherited | Consider merging classes inherited to "%s" from %s. | R8180
29 | context-overridden | Context overridden using dict. Better using kwargs `with_context(**%s)` or `with_context(key=value)` | W8121
30 | deprecated-name-get | 'name_get' is deprecated. Use '_compute_display_name' instead. More info at https://github.com/odoo/odoo/pull/122085. | E8146
31 | deprecated-odoo-model-method | %s has been deprecated by Odoo. Please look for alternatives. | W8160
32 | development-status-allowed | Manifest key development_status "%s" not allowed. Use one of: %s. | C8111
33 | except-pass | pass into block except. If you really need to use the pass consider logging that exception | W8138
34 | external-request-timeout | Use of external request method `%s` without timeout. It could wait for a long time | E8106
35 | inheritable-method-lambda | Use `%s=lambda self: self.%s()` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126 | E8148
36 | inheritable-method-string | Use string method name `"%s"` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126 | E8147
37 | invalid-commit | Use of cr.commit() directly - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#never-commit-the-transaction | E8102
38 | invalid-email | Invalid email "%s" | R8181
39 | license-allowed | License "%s" not allowed in manifest file. | C8105
40 | manifest-author-string | The author key in the manifest file must be a string (with comma separated values) | E8101
41 | manifest-behind-migrations | Manifest version (%s) is lower than migration scripts (%s) | E8145
42 | manifest-data-duplicated | The file "%s" is duplicated in lines %s from manifest key "%s" | W8125
43 | manifest-deprecated-key | Deprecated key "%s" in manifest file | C8103
44 | manifest-external-assets | Asset %s should be distributed with module's source code. More info at https://httptoolkit.com/blog/public-cdn-risks/ | W8162
45 | manifest-maintainers-list | The maintainers key in the manifest file must be a list of strings | E8104
46 | manifest-required-author | One of the following authors must be present in manifest: %s | C8101
47 | manifest-required-key | Missing required key "%s" in manifest file | C8102
48 | manifest-required-key-app | Missing required key "%s" in manifest file for modules with price. | C8119
49 | manifest-superfluous-key | Manifest superfluous key "%s". It is the same as the default value: %s. Better remove it | C8116
50 | manifest-version-format | Wrong Version Format "%s" in manifest file. Regex to match: "%s" | C8106
51 | method-compute | Name of compute method should start with "_compute_" | C8108
52 | method-inverse | Name of inverse method should start with "_inverse_" | C8110
53 | method-required-super | Missing `super` call in "%s" method. | W8106
54 | method-search | Name of search method should start with "_search_" | C8109
55 | missing-odoo-file | Missing %s file | C8115
56 | missing-odoo-file-app | Missing %s file for modules with price | C8118
57 | missing-readme | Missing ./README.rst file. Template here: %s | C8112
58 | missing-return | Missing `return` (`super` is used) in method %s. | W8110
59 | no-raise-unlink | No exceptions should be raised inside unlink() functions | E8140
60 | no-search-all | Using an empty domain `%s([])` without a `limit` will load all records, may impact performance. | W8163
61 | no-wizard-in-models | No wizard class for model directory. See the complete structure https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#complete-structure | C8113
62 | no-write-in-compute | Compute method calling `write`. Use `update` instead. | E8135
63 | odoo-addons-relative-import | Same Odoo module absolute import. You should use relative import with "." instead of "odoo.addons.%s" | W8150
64 | odoo-exception-warning | `odoo.exceptions.Warning` is a deprecated alias to `odoo.exceptions.UserError` use `from odoo.exceptions import UserError` | R8101
65 | prefer-env-translation | Better using self.env._ More info at https://github.com/odoo/odoo/pull/174844 | W8161
66 | print-used | Print used. Use `logger` instead. | W8116
67 | prohibited-method-override | Prohibited override of "%s" method. | W8107
68 | renamed-field-parameter | Field parameter "%s" is no longer supported. Use "%s" instead. | W8111
69 | resource-not-exist | File "%s": "%s" not found. | F8101
70 | sql-injection | SQL injection risk. Use parameters if you can. - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#no-sql-injection | E8103
71 | super-method-mismatch | `super().%s` mismatch but defined method is `%s` | W8164
72 | test-folder-imported | Test folder imported in module %s | E8130
73 | translation-contains-variable | Translatable term in "%s" contains variables. Use %s instead | W8115
74 | translation-field | Translation method _("string") in fields is not necessary. | W8103
75 | translation-format-interpolation | Use %s formatting in odoo._ functions | W8302
76 | translation-format-truncated | Logging format string ends in middle of conversion specifier | E8301
77 | translation-fstring-interpolation | Use %s formatting in odoo._ functions | W8303
78 | translation-not-lazy | Use %s formatting in odoo._ functions | W8301
79 | translation-positional-used | Translation method _(%s) is using positional string printf formatting with multiple arguments. Use named placeholder `_("%%(placeholder)s")` instead. | W8120
80 | translation-required | String parameter on "%s" requires translation. Use %s%s(%s) | C8107
81 | translation-too-few-args | Not enough arguments for odoo._ format string | E8306
82 | translation-too-many-args | Too many arguments for odoo._ format string | E8305
83 | translation-unsupported-format | Unsupported odoo._ format character %r (%#02x) at index %d | E8300
84 | use-vim-comment | Use of vim comment | W8202
85 | website-manifest-key-not-valid-uri | Website "%s" in manifest key is not a valid URI. %s | W8114
86 |
87 |
88 | [//]: # (end-checks)
89 |
90 |
91 | # Install
92 |
93 | You do not need to install manually if you use pre-commit-config
94 |
95 | But if you even need to install it
96 |
97 | pip install pylint-odoo
98 |
99 | # Usage pre-commit-config.yaml
100 |
101 | Add to your ".pre-commit-config.yaml" configuration file the following input
102 |
103 |
104 | ```yaml
105 | - repo: https://github.com/OCA/pylint-odoo
106 | rev: v9.3.22 # may be a tag or commit hash
107 | hooks:
108 | # Add to your .pylintrc file:
109 | # [MASTER]
110 | # load-plugins=pylint_odoo
111 | - id: pylint_odoo
112 | ```
113 |
114 | # Usage
115 |
116 | pylint --load-plugins=pylint_odoo -e odoolint path/to/test
117 |
118 | or use configuration file you can generate the OCA one using the following template repository:
119 |
120 | https://github.com/OCA/oca-addons-repo-template
121 |
122 | Then running
123 |
124 | pylint --rcfile=.pylintrc path/to/test
125 |
126 |
127 | Example to test only pylint_odoo checks:
128 |
129 | pylint --load-plugins=pylint_odoo -d all -e odoolint {ADDONS-PATH}/*
130 |
131 | There are checks only valid for a particular Odoo version
132 | To know what version of odoo are you running pylint needs the parameter
133 |
134 | pylint --load-plugins=pylint_odoo --valid-odoo-versions={YOUR_ODOO_VERSION}
135 |
136 | with particular odoo version e.g. `"16.0"`
137 |
138 | Check valid only for odoo >= 18.0
139 |
140 | prefer-env-translation
141 |
142 | Checks valid only for odoo >= 14.0
143 |
144 | translation-format-interpolation
145 | translation-format-truncated
146 | translation-fstring-interpolation
147 | translation-not-lazy
148 | translation-too-few-args
149 | translation-too-many-args
150 | translation-unsupported-format
151 |
152 | Checks valid only for odoo <= 13.0
153 |
154 | translation-contains-variable
155 |
156 |
157 | [//]: # (start-example)
158 |
159 | # Examples
160 |
161 |
162 | * attribute-deprecated
163 |
164 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L200 attribute "_columns" deprecated
165 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L201 attribute "_defaults" deprecated
166 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L202 attribute "length" deprecated
167 |
168 | * attribute-string-redundant
169 |
170 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L276 The attribute string is redundant. String parameter equal to name of variable
171 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L328 The attribute string is redundant. String parameter equal to name of variable
172 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L338 The attribute string is redundant. String parameter equal to name of variable
173 |
174 | * bad-builtin-groupby
175 |
176 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L205 Used builtin function `itertools.groupby`. Prefer `odoo.tools.groupby` instead. More info about https://github.com/odoo/odoo/issues/105376
177 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L206 Used builtin function `itertools.groupby`. Prefer `odoo.tools.groupby` instead. More info about https://github.com/odoo/odoo/issues/105376
178 |
179 | * category-allowed-app
180 |
181 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L6 Category "No valid for odoo.com/apps" not allowed in manifest file for modules with price.
182 |
183 | * consider-merging-classes-inherited
184 |
185 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/model_inhe2.py#L11 Consider merging classes inherited to "res.company" from testing/resources/test_repo/broken_module/models/model_inhe1.py:8:4, testing/resources/test_repo/broken_module/models/model_inhe2.py:7:4.
186 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/model_inhe2.py#L19 Consider merging classes inherited to "res.partner" from testing/resources/test_repo/broken_module/models/model_inhe2.py:15:4.
187 |
188 | * context-overridden
189 |
190 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L307 Context overridden using dict. Better using kwargs `with_context(**{'overwrite_context': True})` or `with_context(key=value)`
191 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L309 Context overridden using dict. Better using kwargs `with_context(**ctx)` or `with_context(key=value)`
192 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L311 Context overridden using dict. Better using kwargs `with_context(**ctx2)` or `with_context(key=value)`
193 |
194 | * deprecated-name-get
195 |
196 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/twelve_module/models.py#L7 'name_get' is deprecated. Use '_compute_display_name' instead. More info at https://github.com/odoo/odoo/pull/122085.
197 |
198 | * deprecated-odoo-model-method
199 |
200 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L186 fields_view_get has been deprecated by Odoo. Please look for alternatives.
201 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/eleven_module/models.py#L17 fields_view_get has been deprecated by Odoo. Please look for alternatives.
202 |
203 | * development-status-allowed
204 |
205 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__openerp__.py#L6 Manifest key development_status "InvalidDevStatus" not allowed. Use one of: Alpha, Beta, Mature, Production/Stable.
206 |
207 | * except-pass
208 |
209 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/except_pass.py#L11 pass into block except. If you really need to use the pass consider logging that exception
210 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/except_pass.py#L53 pass into block except. If you really need to use the pass consider logging that exception
211 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/except_pass.py#L62 pass into block except. If you really need to use the pass consider logging that exception
212 |
213 | * external-request-timeout
214 |
215 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L1003 Use of external request method `requests.delete` without timeout. It could wait for a long time
216 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L1004 Use of external request method `requests.get` without timeout. It could wait for a long time
217 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L1005 Use of external request method `requests.head` without timeout. It could wait for a long time
218 |
219 | * inheritable-method-lambda
220 |
221 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L176 Use `default=lambda self: self._default()` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126
222 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L179 Use `domain=lambda self: self._domain()` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126
223 |
224 | * inheritable-method-string
225 |
226 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L253 Use string method name `"_compute_name"` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126
227 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L254 Use string method name `"_search_name"` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126
228 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L255 Use string method name `"_inverse_name"` to preserve inheritability. More info at https://github.com/OCA/odoo-pre-commit-hooks/issues/126
229 |
230 | * invalid-commit
231 |
232 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L656 Use of cr.commit() directly - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#never-commit-the-transaction
233 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L657 Use of cr.commit() directly - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#never-commit-the-transaction
234 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L658 Use of cr.commit() directly - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#never-commit-the-transaction
235 |
236 | * invalid-email
237 |
238 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__openerp__.py#L13 Invalid email "invalidmail.com"
239 |
240 | * license-allowed
241 |
242 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__openerp__.py#L4 License "unknow license" not allowed in manifest file.
243 |
244 | * manifest-author-string
245 |
246 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module3/__openerp__.py#L5 The author key in the manifest file must be a string (with comma separated values)
247 |
248 | * manifest-behind-migrations
249 |
250 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__openerp__.py#L2 Manifest version (1.0) is lower than migration scripts (2.0)
251 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/eleven_module/__manifest__.py#L1 Manifest version (11.0.1.0.0) is lower than migration scripts (11.0.1.0.1)
252 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/__openerp__.py#L2 Manifest version (10.0.1.0.0) is lower than migration scripts (11.0.1.0.0)
253 |
254 | * manifest-data-duplicated
255 |
256 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L19 The file "duplicated.xml" is duplicated in lines 20 from manifest key "data"
257 |
258 | * manifest-deprecated-key
259 |
260 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L8 Deprecated key "description" in manifest file
261 |
262 | * manifest-external-assets
263 |
264 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/twelve_module/__manifest__.py#L15 Asset https://shady.cdn.com/somefile.js should be distributed with module's source code. More info at https://httptoolkit.com/blog/public-cdn-risks/
265 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/twelve_module/__manifest__.py#L19 Asset https://bad.idea.com/cool.css should be distributed with module's source code. More info at https://httptoolkit.com/blog/public-cdn-risks/
266 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/twelve_module/__manifest__.py#L20 Asset http://insecure.and.bad.idea.com/kiwi.js should be distributed with module's source code. More info at https://httptoolkit.com/blog/public-cdn-risks/
267 |
268 | * manifest-maintainers-list
269 |
270 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module3/__openerp__.py#L6 The maintainers key in the manifest file must be a list of strings
271 |
272 | * manifest-required-author
273 |
274 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L5 One of the following authors must be present in manifest: 'Odoo Community Association (OCA)'
275 |
276 | * manifest-required-key
277 |
278 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L2 Missing required key "license" in manifest file
279 |
280 | * manifest-required-key-app
281 |
282 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/app_module/__manifest__.py#L1 Missing required key "currency" in manifest file for modules with price.
283 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/app_module/__manifest__.py#L1 Missing required key "images" in manifest file for modules with price.
284 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L2 Missing required key "currency" in manifest file for modules with price.
285 |
286 | * manifest-superfluous-key
287 |
288 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L26 Manifest superfluous key "installable". It is the same as the default value: True. Better remove it
289 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L28 Manifest superfluous key "active". It is the same as the default value: True. Better remove it
290 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L29 Manifest superfluous key "auto_install". It is the same as the default value: False. Better remove it
291 |
292 | * manifest-version-format
293 |
294 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L9 Wrong Version Format "8_0.1.0.0" in manifest file. Regex to match: "(4\.2|5\.0|6\.0|6\.1|7\.0|8\.0|9\.0|10\.0|11\.0|12\.0|13\.0|14\.0|15\.0|16\.0|17\.0|18\.0|19\.0)\.\d+\.\d+\.\d+$"
295 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__openerp__.py#L8 Wrong Version Format "1.0" in manifest file. Regex to match: "(4\.2|5\.0|6\.0|6\.1|7\.0|8\.0|9\.0|10\.0|11\.0|12\.0|13\.0|14\.0|15\.0|16\.0|17\.0|18\.0|19\.0)\.\d+\.\d+\.\d+$"
296 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module3/__openerp__.py#L8 Wrong Version Format "8.0.1.0.0foo" in manifest file. Regex to match: "(4\.2|5\.0|6\.0|6\.1|7\.0|8\.0|9\.0|10\.0|11\.0|12\.0|13\.0|14\.0|15\.0|16\.0|17\.0|18\.0|19\.0)\.\d+\.\d+\.\d+$"
297 |
298 | * method-compute
299 |
300 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L262 Name of compute method should start with "_compute_"
301 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L270 Name of compute method should start with "_compute_"
302 |
303 | * method-inverse
304 |
305 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L264 Name of inverse method should start with "_inverse_"
306 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L272 Name of inverse method should start with "_inverse_"
307 |
308 | * method-required-super
309 |
310 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L40 Missing `super` call in "copy" method.
311 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L44 Missing `super` call in "create" method.
312 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L48 Missing `super` call in "write" method.
313 |
314 | * method-search
315 |
316 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L263 Name of search method should start with "_search_"
317 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L271 Name of search method should start with "_search_"
318 |
319 | * missing-odoo-file-app
320 |
321 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L2 Missing broken_module/static/description/index.html file for modules with price
322 |
323 | * missing-readme
324 |
325 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L2 Missing ./README.rst file. Template here: https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst
326 |
327 | * missing-return
328 |
329 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L24 Missing `return` (`super` is used) in method inherited_method.
330 |
331 | * no-raise-unlink
332 |
333 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/res_partner_unlink.py#L9 No exceptions should be raised inside unlink() functions
334 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/sale_order_unlink.py#L14 No exceptions should be raised inside unlink() functions
335 |
336 | * no-search-all
337 |
338 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L100 Using an empty domain `search([])` without a `limit` will load all records, may impact performance.
339 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L102 Using an empty domain `search([])` without a `limit` will load all records, may impact performance.
340 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L103 Using an empty domain `search([])` without a `limit` will load all records, may impact performance.
341 |
342 | * no-wizard-in-models
343 |
344 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L1142 No wizard class for model directory. See the complete structure https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#complete-structure
345 |
346 | * no-write-in-compute
347 |
348 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L209 Compute method calling `write`. Use `update` instead.
349 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L211 Compute method calling `write`. Use `update` instead.
350 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L214 Compute method calling `write`. Use `update` instead.
351 |
352 | * odoo-addons-relative-import
353 |
354 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L56 Same Odoo module absolute import. You should use relative import with "." instead of "odoo.addons.broken_module"
355 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L57 Same Odoo module absolute import. You should use relative import with "." instead of "odoo.addons.broken_module"
356 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L58 Same Odoo module absolute import. You should use relative import with "." instead of "odoo.addons.broken_module"
357 |
358 | * odoo-exception-warning
359 |
360 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L10 `odoo.exceptions.Warning` is a deprecated alias to `odoo.exceptions.UserError` use `from odoo.exceptions import UserError`
361 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L11 `odoo.exceptions.Warning` is a deprecated alias to `odoo.exceptions.UserError` use `from odoo.exceptions import UserError`
362 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L8 `odoo.exceptions.Warning` is a deprecated alias to `odoo.exceptions.UserError` use `from odoo.exceptions import UserError`
363 |
364 | * prefer-env-translation
365 |
366 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L244 Better using self.env._ More info at https://github.com/odoo/odoo/pull/174844
367 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L260 Better using self.env._ More info at https://github.com/odoo/odoo/pull/174844
368 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L268 Better using self.env._ More info at https://github.com/odoo/odoo/pull/174844
369 |
370 | * print-used
371 |
372 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/test_module/except_pass.py#L20 Print used. Use `logger` instead.
373 |
374 | * renamed-field-parameter
375 |
376 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L328 Field parameter "digits_compute" is no longer supported. Use "digits" instead.
377 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L328 Field parameter "select" is no longer supported. Use "index" instead.
378 |
379 | * resource-not-exist
380 |
381 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L15 File "data": "file_no_exist.xml" not found.
382 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L19 File "data": "duplicated.xml" not found.
383 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__openerp__.py#L24 File "demo": "file_no_exist.xml" not found.
384 |
385 | * sql-injection
386 |
387 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L889 SQL injection risk. Use parameters if you can. - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#no-sql-injection
388 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L891 SQL injection risk. Use parameters if you can. - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#no-sql-injection
389 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L893 SQL injection risk. Use parameters if you can. - More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#no-sql-injection
390 |
391 | * super-method-mismatch
392 |
393 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/tests/test_model.py#L19 `super().test_base_method_2` mismatch but defined method is `test_base_method_1`
394 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/tests/test_model.py#L20 `super().test_base_method_3` mismatch but defined method is `test_base_method_1`
395 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/tests/test_model.py#L24 `super().test_base_method_1` mismatch but defined method is `test_base_method_2`
396 |
397 | * test-folder-imported
398 |
399 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/__init__.py#L5 Test folder imported in module broken_module
400 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__init__.py#L3 Test folder imported in module broken_module2
401 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/eleven_module/__init__.py#L3 Test folder imported in module eleven_module
402 |
403 | * translation-contains-variable
404 |
405 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L490 Translatable term in "'Variable not translatable: %s' % variable1" contains variables. Use _('Variable not translatable: %s') % variable1 instead
406 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L491 Translatable term in "'Variables not translatable: %s, %s' % (variable1, variable2)" contains variables. Use _('Variables not translatable: %s, %s') % (variable1, variable2) instead
407 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L493 Translatable term in "'Variable not translatable: %s' % variable1" contains variables. Use _('Variable not translatable: %s') % variable1 instead
408 |
409 | * translation-field
410 |
411 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L244 Translation method _("string") in fields is not necessary.
412 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L260 Translation method _("string") in fields is not necessary.
413 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L268 Translation method _("string") in fields is not necessary.
414 |
415 | * translation-format-interpolation
416 |
417 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L466 Use lazy % or .format() or % formatting in odoo._ functions
418 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L474 Use lazy % or .format() or % formatting in odoo._ functions
419 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L499 Use lazy % or .format() or % formatting in odoo._ functions
420 |
421 | * translation-format-truncated
422 |
423 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L767 Logging format string ends in middle of conversion specifier
424 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L782 Logging format string ends in middle of conversion specifier
425 |
426 | * translation-fstring-interpolation
427 |
428 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L268 Use lazy % or .format() or % formatting in odoo._ functions
429 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L765 Use lazy % or .format() or % formatting in odoo._ functions
430 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L780 Use lazy % or .format() or % formatting in odoo._ functions
431 |
432 | * translation-not-lazy
433 |
434 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L464 Use lazy % or .format() or % formatting in odoo._ functions
435 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L465 Use lazy % or .format() or % formatting in odoo._ functions
436 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L467 Use lazy % or .format() or % formatting in odoo._ functions
437 |
438 | * translation-positional-used
439 |
440 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L491 Translation method _('Variables not translatable: %s, %s' % (variable1, variable2)) is using positional string printf formatting with multiple arguments. Use named placeholder `_("%(placeholder)s")` instead.
441 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L494 Translation method _('Variables not translatable: %s %s' % (variable1, variable2)) is using positional string printf formatting with multiple arguments. Use named placeholder `_("%(placeholder)s")` instead.
442 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L497 Translation method _('Variables not translatable: %s, %s' % (variable1, variable2)) is using positional string printf formatting with multiple arguments. Use named placeholder `_("%(placeholder)s")` instead.
443 |
444 | * translation-required
445 |
446 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L441 String parameter on "message_post" requires translation. Use body=self.env._('Body not translatable %s')
447 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L441 String parameter on "message_post" requires translation. Use subject=self.env._('Subject not translatable')
448 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L443 String parameter on "message_post" requires translation. Use body=self.env._('Body not translatable {}')
449 |
450 | * translation-too-few-args
451 |
452 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L763 Not enough arguments for odoo._ format string
453 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L778 Not enough arguments for odoo._ format string
454 |
455 | * translation-too-many-args
456 |
457 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L768 Too many arguments for odoo._ format string
458 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L783 Too many arguments for odoo._ format string
459 |
460 | * translation-unsupported-format
461 |
462 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L766 Unsupported odoo._ format character 'y' (0x79) at index 30
463 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/models/broken_model.py#L781 Unsupported odoo._ format character 'y' (0x79) at index 30
464 |
465 | * use-vim-comment
466 |
467 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module/pylint_oca_broken.py#L108 Use of vim comment
468 |
469 | * website-manifest-key-not-valid-uri
470 |
471 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module2/__openerp__.py#L7 Website "https://odoo-community.org,https://odoo.com" in manifest key is not a valid URI. Domain 'odoo-community.org,https:' contains invalid characters
472 | - https://github.com/OCA/pylint-odoo/blob/v9.3.22/testing/resources/test_repo/broken_module3/__openerp__.py#L7 Website "htt://odoo-community.com" in manifest key is not a valid URI. URL needs to start with 'http[s]://'
473 |
474 | [//]: # (end-example)
475 |
476 | # Development
477 |
478 | To run all the tests run:
479 |
480 | tox
481 |
482 | Use extra parameters to change the test behaviour
483 |
484 | e.g. particular python version
485 |
486 | tox -e py310
487 |
488 | e.g. particular unittest method
489 |
490 | tox -e py310 -- -k test_20_expected_errors
491 |
492 | e.g. all the tests at the same time in parallel
493 |
494 | tox -p auto
495 |
496 | ## Licenses
497 |
498 | This repository is licensed under [AGPL-3.0](LICENSE).
499 |
500 | ----
501 | OCA, or the [Odoo Community Association](http://odoo-community.org/), is a nonprofit
502 | organization whose mission is to support the collaborative development of Odoo features
503 | and promote its widespread use.
504 |
--------------------------------------------------------------------------------