Add support for database connection parameters to configuration URI

issue10101
review339891002
This commit is contained in:
Nicolas Évrard
2021-04-12 20:39:23 +02:00
parent 9ae1fb1694
commit a5e3d0b0f8
4 changed files with 51 additions and 22 deletions
+1
View File
@@ -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
+18 -3
View File
@@ -106,7 +106,10 @@ uri
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 @@ The available databases are:
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
~~~~
+4 -10
View File
@@ -4,8 +4,8 @@ from collections import defaultdict
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 @@ class Database(DatabaseInterface):
@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):
+28 -9
View File
@@ -7,8 +7,11 @@ import os
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 @@ from sql.functions import (Function, Extract, Position, Substring,
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 @@ class Database(DatabaseInterface):
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 @@ class Database(DatabaseInterface):
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()