diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13b5297..1b3b197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,22 +21,14 @@ jobs: fail-fast: false matrix: include: - - python-version: "2.7" - toxenv: "py27-8.0" - - python-version: "2.7" - toxenv: "py27-9.0" - - python-version: "2.7" - toxenv: "py27-10.0" - - python-version: "3.5" - toxenv: "py35-11.0" + - python-version: "3.6" + toxenv: "py36-11.0" - python-version: "3.6" toxenv: "py36-12.0" - python-version: "3.6" toxenv: "py36-13.0" - python-version: "3.6" toxenv: "py36-14.0" - - python-version: "3.6" - toxenv: "py36-15.0" - python-version: "3.8" toxenv: "py38-15.0" # - python-version: "3.8" @@ -71,6 +63,7 @@ jobs: key: ${{ runner.os }}-pip-${{ matrix.toxenv }} - name: Install system dependencies run: | + sudo apt-get update -qq sudo apt-get install -qq --no-install-recommends \ libxml2-dev libxslt1-dev \ libldap2-dev libsasl2-dev \ diff --git a/.isort.cfg b/.isort.cfg index 2fd0b63..f238bf7 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,9 +1,2 @@ [settings] -; see https://github.com/ambv/black -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -combine_as_imports=True -use_parentheses=True -line_length=88 -known_third_party = click,psycopg2,pytest,setuptools +profile = black diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cff61da..12fe222 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,33 +1,26 @@ repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: debug-statements - repo: https://github.com/pycqa/flake8 - rev: 3.8.3 + rev: 4.0.1 hooks: - id: flake8 additional_dependencies: ["flake8-bugbear==20.1.4"] - repo: https://github.com/asottile/pyupgrade - rev: v2.7.2 + rev: v2.32.1 hooks: - id: pyupgrade - language_version: python3 -- repo: https://github.com/asottile/seed-isort-config - rev: v2.2.0 - hooks: - - id: seed-isort-config - language_version: python3 + args: ["--py36-plus"] - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.5.4 + rev: v5.10.1 hooks: - id: isort - language_version: python3 diff --git a/MANIFEST.in b/MANIFEST.in index 72fa2e5..4af2885 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ exclude .gitignore -exclude .travis.yml exclude .pre-commit-config.yaml +exclude .github diff --git a/README.rst b/README.rst index 424c9e4..243d2bc 100644 --- a/README.rst +++ b/README.rst @@ -21,8 +21,7 @@ Useful community-managed scripts can be found in click-odoo-contrib_. Quick start ~~~~~~~~~~~ -Check Odoo is correctly installed: ``python -c "import odoo"`` (for Odoo 10 -and later) or ``python -c "import openerp"`` (for Odoo 8 and 9) must +Check Odoo is correctly installed: ``python -c "import odoo"`` must work when run from another directory than the Odoo root directory. Install ``click-odoo``:: @@ -115,17 +114,16 @@ as a global variable. Supported Odoo versions ~~~~~~~~~~~~~~~~~~~~~~~ -Odoo version 8, 9, 10, 11, 12, 13 and 14 are supported. +Odoo version 12, 13, 14 and 15 are supported. An important design goal is to provide a consistent behaviour across Odoo versions. .. note:: - ``click-odoo`` does not mandate any particular method of installing odoo. - The only prerequisiste is that ``import odoo`` (>= 10) or ``import openerp`` - (< 10) must work when run from another directory than the Odoo root - directory. + ``click-odoo`` does not mandate any particular method of installing odoo. The only + prerequisiste is that ``import odoo`` must work when run from another directory than + the Odoo root directory. You may also rely on the fact that python adds the current directory to ``sys.path``, so ``import odoo`` works from the Odoo root directory. @@ -148,9 +146,6 @@ commit changes made before or during an interactive session, use ``env.cr.commit Logging ~~~~~~~ -In version 8, Odoo logs to stdout by default. On other versions -it is stderr. ``click-odoo`` attempts to use stderr for Odoo 8 too. - Logging is controlled by the usual Odoo logging options (``--log-level``, ``--logfile``) or the Odoo configuration file. @@ -290,8 +285,7 @@ click_odoo.odoo namespace ------------------------- As a convenience ``click_odoo`` exports the ``odoo`` namespace, so -``from click_odoo import odoo`` is an alias for ``import odoo`` (>= 10) -or ``import openerp as odoo`` (< 10). +``from click_odoo import odoo`` is an alias for ``import odoo``. OdooEnvironment context manager (experimental) ---------------------------------------------- diff --git a/click_odoo/compat.py b/click_odoo/compat.py index 3aea5c6..32bf9ba 100644 --- a/click_odoo/compat.py +++ b/click_odoo/compat.py @@ -8,32 +8,11 @@ "odoo_version_info", ] -try: - import odoo - from odoo.api import Environment -except ImportError as e: - if hasattr(e, "name") and e.name != "odoo": - raise - # Odoo < 10 - try: - import openerp as odoo - from openerp.api import Environment - except ImportError: - if hasattr(e, "name") and e.name != "openerp": - raise - raise ImportError("No module named odoo nor openerp") +import odoo +from odoo.api import Environment +from odoo.release import version_info as odoo_version_info - -try: - from odoo.release import version_info as odoo_version_info -except ImportError: - from openerp.release import version_info as odoo_version_info - - -if odoo_version_info < (10, 0): - odoo_bin = "openerp-server" -else: - odoo_bin = "odoo" +odoo_bin = "odoo" if odoo_version_info < (15, 0): diff --git a/click_odoo/console.py b/click_odoo/console.py index eca6fa0..73785fd 100644 --- a/click_odoo/console.py +++ b/click_odoo/console.py @@ -8,7 +8,7 @@ _logger = logging.getLogger(__name__) -class Shell(object): +class Shell: shells = ["ipython", "ptpython", "bpython", "python"] diff --git a/click_odoo/env.py b/click_odoo/env.py index 08d0691..f85aefe 100644 --- a/click_odoo/env.py +++ b/click_odoo/env.py @@ -4,7 +4,7 @@ import logging from contextlib import contextmanager -from .compat import Environment, environment_manage, odoo, odoo_version_info +from .compat import Environment, environment_manage, odoo _logger = logging.getLogger(__name__) @@ -36,8 +36,5 @@ def OdooEnvironment(database, rollback=False, **kwargs): else: cr.commit() finally: - if odoo_version_info < (10, 0): - odoo.modules.registry.RegistryManager.delete(database) - else: - odoo.modules.registry.Registry.delete(database) + odoo.modules.registry.Registry.delete(database) odoo.sql_db.close_db(database) diff --git a/click_odoo/env_options.py b/click_odoo/env_options.py index 28cd102..6b60d56 100644 --- a/click_odoo/env_options.py +++ b/click_odoo/env_options.py @@ -2,19 +2,18 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). import logging -import sys from contextlib import closing import click from click.decorators import _param_memo # XXX undocumented click internal -from .compat import environment_manage, odoo_version_info +from .compat import environment_manage from .env import OdooEnvironment, odoo _logger = logging.getLogger(__name__) -class env_options(object): +class env_options: def __init__( self, default_log_level="info", @@ -112,19 +111,6 @@ def _register(self, ctx, param, value): ctx.command.invoke = self._invoke return value - def _fix_odoo_logging(self): - if odoo_version_info < (9, 0): - handlers = logging.getLogger().handlers - if handlers and len(handlers) == 1: - handler = handlers[0] - if isinstance(handler, logging.StreamHandler): - if handler.stream is sys.stdout: - handler.stream = sys.stderr - - def _fix_disable_wsgi_module_handlers(self): - if odoo_version_info < (9, 0): - odoo.service.wsgi_server.module_handlers[:] = [] - def get_odoo_args(self, ctx): """Return a list of Odoo command line arguments from the Click context.""" config = ctx.params.get("config") @@ -157,8 +143,6 @@ def _configure_odoo(self, ctx): # see https://github.com/odoo/odoo/commit/b122217f74 odoo.tools.config["load_language"] = None odoo.tools.config.parse_config(odoo_args) - self._fix_odoo_logging() - self._fix_disable_wsgi_module_handlers() odoo.cli.server.report_configuration() def _db_exists(self, dbname): diff --git a/newsfragments/48.removal b/newsfragments/48.removal new file mode 100644 index 0000000..8028a56 --- /dev/null +++ b/newsfragments/48.removal @@ -0,0 +1 @@ +Drop support for python < 3.6 and Odoo 8, 9, 10. diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2a9acf1..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py index 75f0db5..5e16cce 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ include_package_data=True, setup_requires=["setuptools_scm"], install_requires=["click>=7"], + python_requires=">=3.6", license="LGPLv3+", author="ACSONE SA/NV", author_email="info@acsone.eu", @@ -32,8 +33,6 @@ "Intended Audience :: Developers", "License :: OSI Approved :: " "GNU Lesser General Public License v3 or later (LGPLv3+)", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", diff --git a/tests/scripts/install_odoo.py b/tests/scripts/install_odoo.py index d4bc2ca..eddf306 100755 --- a/tests/scripts/install_odoo.py +++ b/tests/scripts/install_odoo.py @@ -10,17 +10,10 @@ def odoo_installed(): try: import odoo # noqa - - return True except ImportError: - # odoo < 10 - try: - import openerp # noqa - - return True - except ImportError: - # odoo not installed - return False + return False + else: + return True def odoo_cloned(): @@ -39,15 +32,10 @@ def clone_odoo(): odoo_dir, ] ) - if "PGHOST" in os.environ and odoo_branch == "8.0": - # Patch postgres connection mechanism to support connection with PGHOST - # environment variable. This patch is a backport from 9.0. - patch_path = os.path.join(os.path.dirname(__file__), "sql_db-8.patch") - subprocess.check_call(["patch", "-p1", "-i", patch_path], cwd=odoo_dir) def install_odoo(): - if odoo_branch in ["8.0", "9.0", "10.0", "11.0", "12.0", "13.0"]: + if odoo_branch in ["11.0", "12.0", "13.0"]: # setuptools 58 dropped support for 2to3, which is required # for dependencies of older Odoo versions subprocess.check_call( diff --git a/tests/scripts/script1.py b/tests/scripts/script1.py index 440dcab..f62d94a 100755 --- a/tests/scripts/script1.py +++ b/tests/scripts/script1.py @@ -1,5 +1,4 @@ #!/usr/bin/env click-odoo -from __future__ import print_function env = env # noqa diff --git a/tests/scripts/script2.py b/tests/scripts/script2.py index be2fd75..39dc1aa 100755 --- a/tests/scripts/script2.py +++ b/tests/scripts/script2.py @@ -1,5 +1,4 @@ #!/usr/bin/env click-odoo -from __future__ import print_function import sys diff --git a/tests/scripts/script5.py b/tests/scripts/script5.py index 55a5634..3f38874 100644 --- a/tests/scripts/script5.py +++ b/tests/scripts/script5.py @@ -1,10 +1,2 @@ -# noqa -try: - import odoo.release as release -except (AttributeError, ImportError): - import openerp.release as release - -if release.version_info < (10, 0): - import openerp.addons.addon1 # noqa -else: - import odoo.addons.addon1 # noqa +import odoo.addons.addon1 # noqa +import odoo.release as release # noqa diff --git a/tests/scripts/sql_db-8.patch b/tests/scripts/sql_db-8.patch deleted file mode 100644 index 8b36e6c..0000000 --- a/tests/scripts/sql_db-8.patch +++ /dev/null @@ -1,127 +0,0 @@ -diff --git a/openerp/sql_db.py b/openerp/sql_db.py -index 22bed4eabbc..03f9f96e082 100644 ---- a/openerp/sql_db.py -+++ b/openerp/sql_db.py -@@ -527,7 +527,11 @@ class ConnectionPool(object): - _logger.debug(('%r ' + msg), self, *args) - - @locked -- def borrow(self, dsn): -+ def borrow(self, connection_info): -+ """ -+ :param dict connection_info: dict of psql connection keywords -+ :rtype: PsycoConnection -+ """ - # free dead and leaked connections - for i, (cnx, _) in tools.reverse_enumerate(self._connections): - if cnx.closed: -@@ -541,7 +545,7 @@ class ConnectionPool(object): - _logger.warning('%r: Free leaked connection to %r', self, cnx.dsn) - - for i, (cnx, used) in enumerate(self._connections): -- if not used and cnx._original_dsn == dsn: -+ if not used and cnx._original_dsn == connection_info: - try: - cnx.reset() - except psycopg2.OperationalError: -@@ -570,11 +574,13 @@ class ConnectionPool(object): - raise PoolError('The Connection Pool Is Full') - - try: -- result = psycopg2.connect(dsn=dsn, connection_factory=PsycoConnection) -+ result = psycopg2.connect( -+ connection_factory=PsycoConnection, -+ **connection_info) - except psycopg2.Error: - _logger.exception('Connection to the database failed') - raise -- result._original_dsn = dsn -+ result._original_dsn = connection_info - self._connections.append((result, True)) - self._debug('Create new connection') - return result -@@ -639,8 +645,17 @@ class Connection(object): - except Exception: - return False - --def dsn(db_or_uri): -- """parse the given `db_or_uri` and return a 2-tuple (dbname, uri)""" -+def connection_info_for(db_or_uri): -+ """ parse the given `db_or_uri` and return a 2-tuple (dbname, connection_params) -+ -+ Connection params are either a dictionary with a single key ``dsn`` -+ containing a connection URI, or a dictionary containing connection -+ parameter keywords which psycopg2 can build a key/value connection string -+ (dsn) from -+ -+ :param str db_or_uri: database name or postgres dsn -+ :rtype: (str, dict) -+ """ - if db_or_uri.startswith(('postgresql://', 'postgres://')): - # extract db from uri - us = urlparse.urlsplit(db_or_uri) -@@ -650,15 +665,15 @@ def dsn(db_or_uri): - db_name = us.username - else: - db_name = us.hostname -- return db_name, db_or_uri -+ return db_name, {'dsn': db_or_uri} - -- _dsn = '' -+ connection_info = {'database': db_or_uri} - for p in ('host', 'port', 'user', 'password'): - cfg = tools.config['db_' + p] - if cfg: -- _dsn += '%s=%s ' % (p, cfg) -+ connection_info[p] = cfg - -- return db_or_uri, '%sdbname=%s' % (_dsn, db_or_uri) -+ return db_or_uri, connection_info - - _Pool = None - -@@ -667,16 +682,16 @@ def db_connect(to, allow_uri=False): - if _Pool is None: - _Pool = ConnectionPool(int(tools.config['db_maxconn'])) - -- db, uri = dsn(to) -+ db, info = connection_info_for(to) - if not allow_uri and db != to: - raise ValueError('URI connections not allowed') -- return Connection(_Pool, db, uri) -+ return Connection(_Pool, db, info) - - def close_db(db_name): - """ You might want to call openerp.modules.registry.RegistryManager.delete(db_name) along this function.""" - global _Pool - if _Pool: -- _Pool.close_all(dsn(db_name)[1]) -+ _Pool.close_all(connection_info_for(db_name)[1]) - - def close_all(): - global _Pool -diff --git a/openerp/tools/config.py b/openerp/tools/config.py -index 15ae40a1dce..f1031fbb34d 100644 ---- a/openerp/tools/config.py -+++ b/openerp/tools/config.py -@@ -518,21 +518,6 @@ class configmanager(object): - if len(self.options['language']) > 5: - raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE') - -- if not self.options['db_user']: -- try: -- import getpass -- self.options['db_user'] = getpass.getuser() -- except: -- self.options['db_user'] = None -- -- die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database') -- -- if self.options['db_password']: -- if sys.platform == 'win32' and not self.options['db_host']: -- self.options['db_host'] = 'localhost' -- #if self.options['db_host']: -- # self._generate_pgpassfile() -- - if opt.save: - self.save() diff --git a/tests/test_click_odoo.py b/tests/test_click_odoo.py index 0b7605b..392dac8 100644 --- a/tests/test_click_odoo.py +++ b/tests/test_click_odoo.py @@ -1,7 +1,6 @@ # Copyright 2018 ACSONE SA/NV () # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from __future__ import print_function import os import subprocess @@ -54,7 +53,7 @@ def test_odoo_env(odoodb): def test_click_odoo(odoodb): - """ Test simple access to env in script """ + """Test simple access to env in script""" script = os.path.join(here, "scripts", "script1.py") cmd = ["click-odoo", "-d", odoodb, script] result = subprocess.check_output(cmd, universal_newlines=True) @@ -62,7 +61,7 @@ def test_click_odoo(odoodb): def test_click_odoo_minus_m(odoodb): - """ Test simple access to env in script """ + """Test simple access to env in script""" script = os.path.join(here, "scripts", "script1.py") cmd = [sys.executable, "-m", "click_odoo", "-d", odoodb, script] result = subprocess.check_output(cmd, universal_newlines=True) @@ -70,7 +69,7 @@ def test_click_odoo_minus_m(odoodb): def test_click_odoo_minus_m_cli(odoodb): - """ Test simple access to env in script """ + """Test simple access to env in script""" script = os.path.join(here, "scripts", "script1.py") cmd = [sys.executable, "-m", "click_odoo.cli", "-d", odoodb, script] result = subprocess.check_output(cmd, universal_newlines=True) @@ -78,7 +77,7 @@ def test_click_odoo_minus_m_cli(odoodb): def test_cli_runner(odoodb): - """ Test simple access to env in script (through click CliRunner) """ + """Test simple access to env in script (through click CliRunner)""" script = os.path.join(here, "scripts", "script1.py") runner = CliRunner() result = runner.invoke(main, ["-d", odoodb, script]) @@ -87,7 +86,7 @@ def test_cli_runner(odoodb): def test_click_odoo_args(odoodb): - """ Test sys.argv in script """ + """Test sys.argv in script""" script = os.path.join(here, "scripts", "script2.py") cmd = ["click-odoo", "-d", odoodb, "--", script, "a", "-b", "-d"] result = subprocess.check_output(cmd, universal_newlines=True) @@ -102,7 +101,7 @@ def test_click_odoo_args(odoodb): def test_click_odoo_shebang(odoodb): - """ Test simple access to env in script with click-odoo shebang """ + """Test simple access to env in script with click-odoo shebang""" script = os.path.join(here, "scripts", "script1.py") cmd = [script, "-d", odoodb] result = subprocess.check_output(cmd, universal_newlines=True) @@ -110,7 +109,7 @@ def test_click_odoo_shebang(odoodb): def test_click_odoo_shebang_args(odoodb): - """ Test script arguments (with click-odoo shebang) """ + """Test script arguments (with click-odoo shebang)""" script = os.path.join(here, "scripts", "script2.py") cmd = [script, "-d", odoodb, "--", "a", "-b", "-d"] result = subprocess.check_output(cmd, universal_newlines=True) @@ -175,7 +174,7 @@ def test_env_options_withdb(odoodb, tmpdir): @click_odoo.env_options() def testcmd(env): login = env["res.users"].search([("login", "=", "admin")]).login - click.echo("login={}".format(login)) + click.echo(f"login={login}") # database from command line runner = CliRunner() @@ -350,7 +349,7 @@ def _assert_testparam_absent(dbname): def test_write_commit(odoodb): - """ test commit in script """ + """test commit in script""" _cleanup_testparam(odoodb) script = os.path.join(here, "scripts", "script4.py") cmd = ["click-odoo", "-d", odoodb, "--", script, "commit"] @@ -359,7 +358,7 @@ def test_write_commit(odoodb): def test_write_rollback(odoodb): - """ test rollback in script """ + """test rollback in script""" _cleanup_testparam(odoodb) script = os.path.join(here, "scripts", "script4.py") cmd = ["click-odoo", "-d", odoodb, "--", script, "rollback"] @@ -368,7 +367,7 @@ def test_write_rollback(odoodb): def test_write_defaulttx(odoodb): - """ test click-odoo commits itself """ + """test click-odoo commits itself""" _cleanup_testparam(odoodb) script = os.path.join(here, "scripts", "script4.py") cmd = ["click-odoo", "-d", odoodb, "--", script] @@ -377,7 +376,7 @@ def test_write_defaulttx(odoodb): def test_write_interactive_defaulttx(mocker, odoodb): - """ test click-odoo rollbacks in interactive mode """ + """test click-odoo rollbacks in interactive mode""" mocker.patch.object(console.Shell, "python") mocker.patch.object(console, "_isatty", return_value=True) @@ -399,7 +398,7 @@ def test_write_stdin_defaulttx(odoodb): def test_write_raise(tmpdir, capfd, odoodb): - """ test nothing is committed if the script raises """ + """test nothing is committed if the script raises""" _cleanup_testparam(odoodb) script = os.path.join(here, "scripts", "script4.py") logfile = tmpdir.join("mylogfile") @@ -414,7 +413,7 @@ def test_write_raise(tmpdir, capfd, odoodb): def test_env_cache(odoodb): - """ test a new environment does not reuse cache """ + """test a new environment does not reuse cache""" _cleanup_testparam(odoodb) with OdooEnvironment(database=odoodb) as env: env["ir.config_parameter"].set_param("testparam", "testvalue") diff --git a/tox.ini b/tox.ini index f1e511b..032df34 100644 --- a/tox.ini +++ b/tox.ini @@ -6,18 +6,13 @@ [tox] envlist = py38-{15.0,master} - py36-{12.0,13.0,14.0,15.0} - py35-{11.0} - py27-{8.0,9.0,10.0} + py36-{11.0,12.0,13.0,14.0,15.0} twine_check pre_commit skip_missing_interpreters = True [testenv] commands = - 8.0: {toxinidir}/tests/scripts/install_odoo.py 8.0 {envdir}/src/odoo - 9.0: {toxinidir}/tests/scripts/install_odoo.py 9.0 {envdir}/src/odoo - 10.0: {toxinidir}/tests/scripts/install_odoo.py 10.0 {envdir}/src/odoo 11.0: {toxinidir}/tests/scripts/install_odoo.py 11.0 {envdir}/src/odoo 12.0: {toxinidir}/tests/scripts/install_odoo.py 12.0 {envdir}/src/odoo 13.0: {toxinidir}/tests/scripts/install_odoo.py 13.0 {envdir}/src/odoo