changeset dd0bb6655e6d in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=dd0bb6655e6d
description:
        Add support for database connection parameters to configuration URI

        issue10101
        review339891002
diffstat:

 CHANGELOG                              |   1 +
 doc/topics/configuration.rst           |  21 ++++++++++++++++--
 trytond/backend/postgresql/database.py |  14 +++---------
 trytond/backend/sqlite/database.py     |  37 +++++++++++++++++++++++++--------
 4 files changed, 51 insertions(+), 22 deletions(-)

diffs (159 lines):

diff -r 36ce819e336a -r dd0bb6655e6d CHANGELOG
--- a/CHANGELOG Mon Apr 12 20:16:41 2021 +0200
+++ b/CHANGELOG Mon Apr 12 20:39:23 2021 +0200
@@ -1,3 +1,4 @@
+* Add support for database connection parameters to configuration URI
 * Use immutable datastructures for Dict and MultiSelection fields
 * Skip warnings for non-interactive operations
 * Check rule only if _check_access is set
diff -r 36ce819e336a -r dd0bb6655e6d doc/topics/configuration.rst
--- a/doc/topics/configuration.rst      Mon Apr 12 20:16:41 2021 +0200
+++ b/doc/topics/configuration.rst      Mon Apr 12 20:39:23 2021 +0200
@@ -106,7 +106,10 @@
 Contains the URI to connect to the SQL database. The URI follows the RFC-3986_.
 The typical form is:
 
-    database://username:password@host:port/
+    database://username:password@host:port/?param1=value1&param2=value2
+
+The parameters are database dependent, check the database documentation for a
+list of valid parameters.
 
 Default: The value of the environment variable ``TRYTOND_DATABASE_URI`` or
 ``sqlite://`` if not set.
@@ -116,15 +119,27 @@
 PostgreSQL
 **********
 
-``pyscopg2`` supports two type of connections:
+``psycopg2`` supports two type of connections:
 
     - TCP/IP connection: ``postgresql://user:password@localhost:5432/``
     - Unix domain connection: ``postgresql://username:password@/``
 
+Please refer to `psycopg2 for the complete specification of the URI
+<https://www.psycopg.org/docs/module.html#psycopg2.connect>`_.
+
+A list of parameters supported by PostgreSQL can be found in the
+`documentation 
<https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS>`_.
+
 SQLite
 ******
 
-The only possible URI is: ``sqlite://``
+The URI is defined as ``sqlite://``
+
+If the name of the database is ``:memory:``, the parameter ``mode`` will be set
+to ``memory`` thus using a pure in-memory database.
+
+The recognized query parameters can be found in SQLite's
+`documentation <https://www.sqlite.org/uri.html#recognized_query_parameters>`_.
 
 path
 ~~~~
diff -r 36ce819e336a -r dd0bb6655e6d trytond/backend/postgresql/database.py
--- a/trytond/backend/postgresql/database.py    Mon Apr 12 20:16:41 2021 +0200
+++ b/trytond/backend/postgresql/database.py    Mon Apr 12 20:39:23 2021 +0200
@@ -4,8 +4,8 @@
 import time
 import logging
 import os
-import urllib.parse
 import json
+import warnings
 from datetime import datetime
 from decimal import Decimal
 from itertools import chain, repeat
@@ -243,17 +243,11 @@
     @classmethod
     def _connection_params(cls, name):
         uri = parse_uri(config.get('database', 'uri'))
+        if uri.path:
+            warnings.warn("The path specified in the URI will be overridden")
         params = {
-            'dbname': name,
+            'dsn': uri._replace(path=name).geturl(),
             }
-        if uri.username:
-            params['user'] = uri.username
-        if uri.password:
-            params['password'] = urllib.parse.unquote_plus(uri.password)
-        if uri.hostname:
-            params['host'] = uri.hostname
-        if uri.port:
-            params['port'] = uri.port
         return params
 
     def connect(self):
diff -r 36ce819e336a -r dd0bb6655e6d trytond/backend/sqlite/database.py
--- a/trytond/backend/sqlite/database.py        Mon Apr 12 20:16:41 2021 +0200
+++ b/trytond/backend/sqlite/database.py        Mon Apr 12 20:39:23 2021 +0200
@@ -7,8 +7,11 @@
 import random
 import threading
 import time
+import urllib.parse
+import warnings
 from decimal import Decimal
 from weakref import WeakKeyDictionary
+from werkzeug.security import safe_join
 
 try:
     from pysqlite2 import dbapi2 as sqlite
@@ -24,7 +27,7 @@
     Overlay, CharLength, CurrentTimestamp, Trim)
 
 from trytond.backend.database import DatabaseInterface, SQLType
-from trytond.config import config
+from trytond.config import config, parse_uri
 from trytond.transaction import Transaction
 
 __all__ = ['Database', 'DatabaseIntegrityError', 'DatabaseOperationalError']
@@ -336,16 +339,10 @@
             Database._local.memory_database = self
 
     def connect(self):
-        if self.name == ':memory:':
-            path = ':memory:'
-        else:
-            db_filename = self.name + '.sqlite'
-            path = os.path.join(config.get('database', 'path'), db_filename)
-            if not os.path.isfile(path):
-                raise IOError('Database "%s" doesn\'t exist!' % path)
         if self._conn is not None:
             return self
-        self._conn = sqlite.connect(path,
+        self._conn = sqlite.connect(
+            self._make_uri(), uri=True,
             detect_types=sqlite.PARSE_DECLTYPES | sqlite.PARSE_COLNAMES,
             factory=SQLiteConnection)
         self._conn.create_function('extract', 2, SQLiteExtract.extract)
@@ -402,6 +399,28 @@
         self._conn.execute('PRAGMA foreign_keys = ON')
         return self
 
+    def _make_uri(self):
+        uri = config.get('database', 'uri')
+        base_uri = parse_uri(uri)
+        if base_uri.path:
+            warnings.warn("The path specified in the URI will be overridden")
+
+        if self.name == ':memory:':
+            query_string = urllib.parse.parse_qs(base_uri.query)
+            query_string['mode'] = 'memory'
+            query = urllib.parse.urlencode(query_string, doseq=True)
+            db_uri = base_uri._replace(netloc='', path='/', query=query)
+        else:
+            db_path = safe_join(
+                config.get('database', 'path'), self.name + '.sqlite')
+            db_uri = base_uri._replace(path=db_path)
+
+        # Use unparse before replacing sqlite with file because SQLite accepts
+        # a relative path URI like file:db/test.sqlite which doesn't conform to
+        # RFC8089 which urllib follows and enforces when the scheme is 'file'
+        db_uri = urllib.parse.urlunparse(db_uri)
+        return db_uri.replace('sqlite', 'file', 1)
+
     def get_connection(self, autocommit=False, readonly=False):
         if self._conn is None:
             self.connect()

Reply via email to