Browse Source

[IMP] base_external_dbsource: Refactor & Split by source

* Heavily refactor code for reusability
* Split all sources into independent modules
* Add more test coverage
* Add CRUD methods
* Add iterator execute return to roadmap
pull/659/head
Dave Lasley 8 years ago
parent
commit
ba5330b8ae
No known key found for this signature in database GPG Key ID: 7DDBA4BA81B934CF
  1. 2
      .travis.yml
  2. 33
      base_external_dbsource/README.rst
  3. 1
      base_external_dbsource/__init__.py
  4. 6
      base_external_dbsource/__manifest__.py
  5. 13
      base_external_dbsource/exceptions.py
  6. 1
      base_external_dbsource/models/__init__.py
  7. 425
      base_external_dbsource/models/base_external_dbsource.py
  8. 2
      base_external_dbsource/tests/__init__.py
  9. 247
      base_external_dbsource/tests/test_base_external_dbsource.py
  10. 66
      base_external_dbsource/tests/test_create_dbsource.py
  11. 77
      base_external_dbsource_firebird/README.rst
  12. 2
      base_external_dbsource_firebird/__init__.py
  13. 28
      base_external_dbsource_firebird/__manifest__.py
  14. 9
      base_external_dbsource_firebird/demo/base_external_dbsource.xml
  15. 3
      base_external_dbsource_firebird/models/__init__.py
  16. 56
      base_external_dbsource_firebird/models/base_external_dbsource.py
  17. 3
      base_external_dbsource_firebird/tests/__init__.py
  18. 41
      base_external_dbsource_firebird/tests/test_base_external_dbsource.py
  19. 80
      base_external_dbsource_mssql/README.rst
  20. 2
      base_external_dbsource_mssql/__init__.py
  21. 29
      base_external_dbsource_mssql/__manifest__.py
  22. 9
      base_external_dbsource_mssql/demo/base_external_dbsource.xml
  23. 3
      base_external_dbsource_mssql/models/__init__.py
  24. 45
      base_external_dbsource_mssql/models/base_external_dbsource.py
  25. 3
      base_external_dbsource_mssql/tests/__init__.py
  26. 42
      base_external_dbsource_mssql/tests/test_base_external_dbsource.py
  27. 80
      base_external_dbsource_mysql/README.rst
  28. 2
      base_external_dbsource_mysql/__init__.py
  29. 29
      base_external_dbsource_mysql/__manifest__.py
  30. 9
      base_external_dbsource_mysql/demo/base_external_dbsource.xml
  31. 3
      base_external_dbsource_mysql/models/__init__.py
  32. 43
      base_external_dbsource_mysql/models/base_external_dbsource.py
  33. 3
      base_external_dbsource_mysql/tests/__init__.py
  34. 42
      base_external_dbsource_mysql/tests/test_base_external_dbsource.py
  35. 79
      base_external_dbsource_odbc/README.rst
  36. 2
      base_external_dbsource_odbc/__init__.py
  37. 29
      base_external_dbsource_odbc/__manifest__.py
  38. 9
      base_external_dbsource_odbc/demo/base_external_dbsource.xml
  39. 3
      base_external_dbsource_odbc/models/__init__.py
  40. 42
      base_external_dbsource_odbc/models/base_external_dbsource.py
  41. 3
      base_external_dbsource_odbc/tests/__init__.py
  42. 46
      base_external_dbsource_odbc/tests/test_base_external_dbsource.py
  43. 79
      base_external_dbsource_oracle/README.rst
  44. 2
      base_external_dbsource_oracle/__init__.py
  45. 25
      base_external_dbsource_oracle/__manifest__.py
  46. 3
      base_external_dbsource_oracle/models/__init__.py
  47. 47
      base_external_dbsource_oracle/models/base_external_dbsource.py
  48. 77
      base_external_dbsource_sqlite/README.rst
  49. 2
      base_external_dbsource_sqlite/__init__.py
  50. 28
      base_external_dbsource_sqlite/__manifest__.py
  51. 9
      base_external_dbsource_sqlite/demo/base_external_dbsource.xml
  52. 3
      base_external_dbsource_sqlite/models/__init__.py
  53. 59
      base_external_dbsource_sqlite/models/base_external_dbsource.py
  54. 3
      base_external_dbsource_sqlite/tests/__init__.py
  55. 42
      base_external_dbsource_sqlite/tests/test_base_external_dbsource.py
  56. 5
      requirements.txt

2
.travis.yml

@ -11,6 +11,8 @@ addons:
packages: packages:
- expect-dev # provides unbuffer utility - expect-dev # provides unbuffer utility
- python-lxml # because pip installation is slow - python-lxml # because pip installation is slow
- unixodbc-dev
- python-mysqldb
env: env:
global: global:

33
base_external_dbsource/README.rst

@ -19,18 +19,11 @@ Configuration
Database sources can be configured in Settings > Configuration -> Data sources. Database sources can be configured in Settings > Configuration -> Data sources.
Depending on the database, you need:
* to install unixodbc and python-pyodbc packages to use ODBC connections.
* to install FreeTDS driver (tdsodbc package) and configure it through ODBC to connect to Microsoft SQL Server.
* to install and configure Oracle Instant Client and cx_Oracle python library to connect to Oracle.
* to install fdb package to connect in Firebird.
Usage Usage
===== =====
To use this module: To use this module:
-------------------
* Go to Settings > Database Structure > Database Sources * Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information: * Click on Create to enter the following information:
@ -38,24 +31,35 @@ To use this module:
* Data source name  * Data source name 
* Password * Password
* Connector: Choose the database to which you want to connect * Connector: Choose the database to which you want to connect
* Connection string : Specify how to connect to database
* Connection string: Specify how to connect to database
To extend this module:
----------------------
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/9.0 for server-tools
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap Known issues / Roadmap
====================== ======================
* Find a way to remove or default the CA certs dir
* Add concept of multiple connection strings for one source (multiple nodes)
* Add a ConnectionEnvironment that allows for the reuse of connections
* Message box should be displayed instead of error in ``connection_test``
* Remove old api compatibility layers (v11)
* Instead of returning list of results, we should return iterators. This will support
larger datasets in a more efficient manner.
* Implement better CRUD handling
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback `here <https://github.com/OCA/
server-tools/issues/new?body=module:%20
base_external_dbsource%0Aversion:%20
9.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits Credits
======= =======
@ -67,6 +71,7 @@ Contributors
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com> * Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com> * Gervais Naoussi <gervaisnaoussi@gmail.com>
* Michell Stuttgart <michellstut@gmail.com> * Michell Stuttgart <michellstut@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer Maintainer
---------- ----------

1
base_external_dbsource/__init__.py

@ -1,2 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import models from . import models

6
base_external_dbsource/__manifest__.py

@ -4,9 +4,11 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
'name': 'External Database Sources', 'name': 'External Database Sources',
'version': '10.0.1.0.1',
'version': '10.0.2.0.0',
'category': 'Tools', 'category': 'Tools',
'author': "Daniel Reis,Odoo Community Association (OCA)",
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools', 'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3', 'license': 'LGPL-3',
'images': [ 'images': [

13
base_external_dbsource/exceptions.py

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.exceptions import UserError
class ConnectionFailedError(UserError):
pass
class ConnectionSuccessError(UserError):
pass

1
base_external_dbsource/models/__init__.py

@ -1,2 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import base_external_dbsource from . import base_external_dbsource

425
base_external_dbsource/models/base_external_dbsource.py

@ -1,70 +1,50 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis # Copyright 2011 Daniel Reis
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import os
import logging import logging
import psycopg2 import psycopg2
from odoo import models, fields, api, _
from odoo.exceptions import Warning as UserError
import odoo.tools as tools
from contextlib import contextmanager
_logger = logging.getLogger(__name__)
from odoo import _, api, fields, models, tools
CONNECTORS = []
from ..exceptions import ConnectionFailedError, ConnectionSuccessError
try:
import sqlalchemy
CONNECTORS.append(('sqlite', 'SQLite'))
try:
import pymssql
CONNECTORS.append(('mssql', 'Microsoft SQL Server'))
assert pymssql
except (ImportError, AssertionError):
_logger.info('MS SQL Server not available. Please install "pymssql"\
python package.')
try:
import MySQLdb
CONNECTORS.append(('mysql', 'MySQL'))
assert MySQLdb
except (ImportError, AssertionError):
_logger.info('MySQL not available. Please install "mysqldb"\
python package.')
except:
_logger.info('SQL Alchemy not available. Please install "slqalchemy"\
python package.')
try:
import pyodbc
CONNECTORS.append(('pyodbc', 'ODBC'))
except:
_logger.info('ODBC libraries not available. Please install "unixodbc"\
and "python-pyodbc" packages.')
try:
import cx_Oracle
CONNECTORS.append(('cx_Oracle', 'Oracle'))
except:
_logger.info('Oracle libraries not available. Please install "cx_Oracle"\
python package.')
try:
import fdb
CONNECTORS.append(('fdb', 'Firebird'))
except:
_logger.info('Firebird libraries not available. Please install "fdb"\
python package.')
CONNECTORS.append(('postgresql', 'PostgreSQL'))
_logger = logging.getLogger(__name__)
class BaseExternalDbsource(models.Model): class BaseExternalDbsource(models.Model):
""" It provides logic for connection to an external data source
Classes implementing this interface must provide the following methods
suffixed with the adapter type. See the method definitions and examples
for more information:
* ``connection_open_*``
* ``connection_close_*``
* ``execute_*``
Optional methods for adapters to implement:
* ``remote_browse_*``
* ``remote_create_*``
* ``remote_delete_*``
* ``remote_search_*``
* ``remote_update_*``
"""
_name = "base.external.dbsource" _name = "base.external.dbsource"
_description = 'External Database Sources' _description = 'External Database Sources'
name = fields.Char('Datasource name', required=True, size=64)
CONNECTORS = [
('postgresql', 'PostgreSQL'),
]
# This is appended to the conn string if pass declared but not detected.
# Children should declare PWD_STRING_CONNECTOR (such as PWD_STRING_FBD)
# to allow for override.
PWD_STRING = 'PWD=%s;'
name = fields.Char('Datasource name', required=True, size=64)
conn_string = fields.Text('Connection string', help=""" conn_string = fields.Text('Connection string', help="""
Sample connection strings: Sample connection strings:
- Microsoft SQL Server: - Microsoft SQL Server:
@ -72,64 +52,99 @@ class BaseExternalDbsource(models.Model):
- MySQL: mysql://user:%s@server:port/dbname - MySQL: mysql://user:%s@server:port/dbname
- ODBC: DRIVER={FreeTDS};SERVER=server.address;Database=mydb;UID=sa - ODBC: DRIVER={FreeTDS};SERVER=server.address;Database=mydb;UID=sa
- ORACLE: username/%s@//server.address:port/instance - ORACLE: username/%s@//server.address:port/instance
- FireBird: host=localhost;database=mydatabase.gdb;user=sysdba;password=%s;
port=3050;charset=utf8
- PostgreSQL: - PostgreSQL:
dbname='template1' user='dbuser' host='localhost' port='5432' \ dbname='template1' user='dbuser' host='localhost' port='5432' \
password=%s password=%s
- SQLite: sqlite:///test.db - SQLite: sqlite:///test.db
- Elasticsearch: https://user:%s@localhost:9200
""") """)
conn_string_full = fields.Text(
readonly=True,
compute='_compute_conn_string_full',
)
password = fields.Char('Password', size=40) password = fields.Char('Password', size=40)
client_cert = fields.Text()
client_key = fields.Text()
ca_certs = fields.Char(
help='Path to CA Certs file on server.',
)
connector = fields.Selection(
CONNECTORS, 'Connector', required=True,
help="If a connector is missing from the list, check the server "
"log to confirm that the required components were detected.",
)
connector = fields.Selection(CONNECTORS, 'Connector', required=True,
help="If a connector is missing from the\
list, check the server log to confirm\
that the required components were\
detected.")
current_table = None
@api.multi @api.multi
def conn_open(self):
"""The connection is open here."""
@api.depends('conn_string', 'password')
def _compute_conn_string_full(self):
for record in self:
if record.password:
if '%s' not in record.conn_string:
pwd_string = getattr(
record,
'PWD_STRING_%s' % record.connector.upper(),
record.PWD_STRING,
)
record.conn_string += pwd_string
record.conn_string_full = record.conn_string % record.password
else:
record.conn_string_full = record.conn_string
self.ensure_one()
# Get dbsource record
# Build the full connection string
connStr = self.conn_string
if self.password:
if '%s' not in self.conn_string:
connStr += ';PWD=%s'
connStr = connStr % self.password
# Try to connect
if self.connector == 'cx_Oracle':
os.environ['NLS_LANG'] = 'AMERICAN_AMERICA.UTF8'
conn = cx_Oracle.connect(connStr)
elif self.connector == 'pyodbc':
conn = pyodbc.connect(connStr)
elif self.connector in ('sqlite', 'mysql', 'mssql'):
conn = sqlalchemy.create_engine(connStr).connect()
elif self.connector == 'fdb':
kwargs = dict([x.split('=') for x in connStr.split(';')])
conn = fdb.connect(**kwargs)
elif self.connector == 'postgresql':
conn = psycopg2.connect(connStr)
return conn
# Interface
@api.multi
def change_table(self, name):
""" Change the table that is used for CRUD operations """
self.current_table = name
@api.multi
def connection_close(self, connection):
""" It closes the connection to the data source.
This method calls adapter method of this same name, suffixed with
the adapter type.
"""
method = self._get_adapter_method('connection_close')
return method(connection)
@api.multi
@contextmanager
def connection_open(self):
""" It provides a context manager for the data source.
This method calls adapter method of this same name, suffixed with
the adapter type.
"""
method = self._get_adapter_method('connection_open')
try:
connection = method()
yield connection
finally:
try:
self.connection_close(connection)
except:
_logger.exception('Connection close failure.')
@api.multi @api.multi
def execute(self, sqlquery, sqlparams=None, metadata=False,
context=None):
"""Executes SQL and returns a list of rows.
def execute(
self, query=None, execute_params=None, metadata=False, **kwargs
):
""" Executes a query and returns a list of rows.
"sqlparams" can be a dict of values, that can be referenced in
the SQL statement using "%(key)s" or, in the case of Oracle,
"execute_params" can be a dict of values, that can be referenced
in the SQL statement using "%(key)s" or, in the case of Oracle,
":key". ":key".
Example: Example:
sqlquery = "select * from mytable where city = %(city)s and
query = "SELECT * FROM mytable WHERE city = %(city)s AND
date > %(dt)s" date > %(dt)s"
params = {'city': 'Lisbon',
'dt': datetime.datetime(2000, 12, 31)}
execute_params = {
'city': 'Lisbon',
'dt': datetime.datetime(2000, 12, 31),
}
If metadata=True, it will instead return a dict containing the If metadata=True, it will instead return a dict containing the
rows list and the columns list, in the format: rows list and the columns list, in the format:
@ -137,52 +152,202 @@ class BaseExternalDbsource(models.Model):
, 'rows': [ (a0, b0, ...), (a1, b1, ...), ...] } , 'rows': [ (a0, b0, ...), (a1, b1, ...), ...] }
""" """
rows, cols = list(), list()
for obj in self:
conn = obj.conn_open()
if obj.connector in ["sqlite", "mysql", "mssql"]:
# using sqlalchemy
cur = conn.execute(sqlquery, sqlparams)
if metadata:
cols = cur.keys()
rows = [r for r in cur]
# Old API compatibility
if not query:
try:
query = kwargs['sqlquery']
except KeyError:
raise TypeError(_('query is a required argument'))
if not execute_params:
try:
execute_params = kwargs['sqlparams']
except KeyError:
pass
elif obj.connector in ["fdb"]:
# using other db connectors
cur = conn.cursor()
for key in sqlparams:
sqlquery = sqlquery.replace('%%(%s)s' % key,
str(sqlparams[key]))
method = self._get_adapter_method('execute')
rows, cols = method(query, execute_params, metadata)
cur.execute(sqlquery)
rows = cur.fetchall()
else:
# using other db connectors
cur = conn.cursor()
cur.execute(sqlquery, sqlparams)
if metadata:
cols = [d[0] for d in cur.description]
rows = cur.fetchall()
conn.close()
if metadata: if metadata:
return{'cols': cols, 'rows': rows}
return {'cols': cols, 'rows': rows}
else: else:
return rows return rows
@api.multi @api.multi
def connection_test(self): def connection_test(self):
"""Test of connection."""
self.ensure_one()
conn = False
""" It tests the connection
Raises:
ConnectionSuccessError: On connection success
ConnectionFailedError: On connection failed
"""
for obj in self:
try: try:
conn = self.conn_open()
with self.connection_open():
pass
except Exception as e: except Exception as e:
raise UserError(_("Connection test failed: \
Here is what we got instead:\n %s") % tools.ustr(e))
finally:
if conn:
conn.close()
raise ConnectionFailedError(_(
"Connection test failed:\n"
"Here is what we got instead:\n%s"
) % tools.ustr(e))
raise ConnectionSuccessError(_(
"Connection test succeeded:\n"
"Everything seems properly set up!",
))
@api.multi
def remote_browse(self, record_ids, *args, **kwargs):
""" It browses for and returns the records from remote by ID
This method calls adapter method of this same name, suffixed with
the adapter type.
Args:
record_ids: (list) List of remote IDs to browse.
*args: Positional arguments to be passed to adapter method.
**kwargs: Keyword arguments to be passed to adapter method.
Returns:
(iter) Iterator of record mappings that match the ID.
"""
# TODO: if OK a (wizard) message box should be displayed
raise UserError(_("Connection test succeeded: \
Everything seems properly set up!"))
assert self.current_table
method = self._get_adapter_method('remote_browse')
return method(record_ids, *args, **kwargs)
@api.multi
def remote_create(self, vals, *args, **kwargs):
""" It creates a record on the remote data source.
This method calls adapter method of this same name, suffixed with
the adapter type.
Args:
vals: (dict) Values to use for creation.
*args: Positional arguments to be passed to adapter method.
**kwargs: Keyword arguments to be passed to adapter method.
Returns:
(mapping) A mapping of the record that was created.
"""
assert self.current_table
method = self._get_adapter_method('remote_create')
return method(vals, *args, **kwargs)
@api.multi
def remote_delete(self, record_ids, *args, **kwargs):
""" It deletes records by ID on remote
This method calls adapter method of this same name, suffixed with
the adapter type.
Args:
record_ids: (list) List of remote IDs to delete.
*args: Positional arguments to be passed to adapter method.
**kwargs: Keyword arguments to be passed to adapter method.
Returns:
(iter) Iterator of bools indicating delete status.
"""
assert self.current_table
method = self._get_adapter_method('remote_delete')
return method(record_ids, *args, **kwargs)
@api.multi
def remote_search(self, query, *args, **kwargs):
""" It searches the remote for the query.
This method calls adapter method of this same name, suffixed with
the adapter type.
Args:
query: (mixed) Query domain as required by the adapter.
*args: Positional arguments to be passed to adapter method.
**kwargs: Keyword arguments to be passed to adapter method.
Returns:
(iter) Iterator of record mappings that match query.
"""
assert self.current_table
method = self._get_adapter_method('remote_search')
return method(query, *args, **kwargs)
@api.multi
def remote_update(self, record_ids, vals, *args, **kwargs):
""" It updates the remote records with the vals
This method calls adapter method of this same name, suffixed with
the adapter type.
Args:
record_ids: (list) List of remote IDs to delete.
*args: Positional arguments to be passed to adapter method.
**kwargs: Keyword arguments to be passed to adapter method.
Returns:
(iter) Iterator of record mappings that were updated.
"""
assert self.current_table
method = self._get_adapter_method('remote_update')
return method(record_ids, vals, *args, **kwargs)
# Adapters
def connection_close_postgresql(self, connection):
return connection.close()
def connection_open_postgresql(self):
return psycopg2.connect(self.conn_string)
def execute_postgresql(self, query, params, metadata):
return self._execute_generic(query, params, metadata)
def _execute_generic(self, query, params, metadata):
with self.connection_open() as connection:
cur = connection.cursor()
cur.execute(query, params)
cols = []
if metadata:
cols = [d[0] for d in cur.description]
rows = cur.fetchall()
return rows, cols
# Compatibility & Private
@api.multi
def conn_open(self):
""" It opens and returns a connection to the remote data source.
This method calls adapter method of this same name, suffixed with
the adapter type.
Deprecate:
This method has been replaced with ``connection_open``.
"""
with self.connection_open() as connection:
return connection
def _get_adapter_method(self, method_prefix):
""" It returns the connector adapter method for ``method_prefix``.
Args:
method_prefix: (str) Prefix of adapter method (such as
``connection_open``).
Raises:
NotImplementedError: When the method is not found
Returns:
(instancemethod)
"""
self.ensure_one()
method = '%s_%s' % (method_prefix, self.connector)
try:
return getattr(self, method)
except AttributeError:
raise NotImplementedError(_(
'"%s" method not found, check that all assets are installed '
'for the %s connector type.'
)) % (
method, self.connector,
)

2
base_external_dbsource/tests/__init__.py

@ -1,3 +1,3 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from . import test_create_dbsource
from . import test_base_external_dbsource

247
base_external_dbsource/tests/test_base_external_dbsource.py

@ -0,0 +1,247 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
import mock
from odoo.tests import common
from ..exceptions import ConnectionFailedError, ConnectionSuccessError
class TestBaseExternalDbsource(common.TransactionCase):
def setUp(self):
super(TestBaseExternalDbsource, self).setUp()
self.dbsource = self.env.ref('base_external_dbsource.demo_postgre')
def _test_adapter_method(
self, method_name, side_effect=None, return_value=None,
create=False, args=None, kwargs=None,
):
if args is None:
args = []
if kwargs is None:
kwargs = {}
adapter = '%s_postgresql' % method_name
with mock.patch.object(self.dbsource,
adapter, create=create) as adapter:
if side_effect is not None:
adapter.side_effect = side_effect
elif return_value is not None:
adapter.return_value = return_value
res = getattr(self.dbsource, method_name)(*args, **kwargs)
return res, adapter
def test_conn_string_full(self):
""" It should add password if string interpolation not detected """
self.dbsource.conn_string = 'User=Derp;'
self.dbsource.password = 'password'
expect = self.dbsource.conn_string + 'PWD=%s;' % self.dbsource.password
self.assertEqual(
self.dbsource.conn_string_full, expect,
)
# Interface
def test_connection_success(self):
""" It should raise for successful connection """
with self.assertRaises(ConnectionSuccessError):
self.dbsource.connection_test()
def test_connection_fail(self):
""" It should raise for failed/invalid connection """
with mock.patch.object(self.dbsource, 'connection_open') as conn:
conn.side_effect = Exception
with self.assertRaises(ConnectionFailedError):
self.dbsource.connection_test()
def test_connection_open_calls_close(self):
""" It should close connection after context ends """
with mock.patch.object(
self.dbsource, 'connection_close',
) as close:
with self.dbsource.connection_open():
pass
close.assert_called_once()
def test_connection_close(self):
""" It should call adapter's close method """
args = [mock.MagicMock()]
res, adapter = self._test_adapter_method(
'connection_close', args=args,
)
adapter.assert_called_once_with(args[0])
def test_execute_asserts_query_arg(self):
""" It should raise a TypeError if query and sqlquery not in args """
with self.assertRaises(TypeError):
self.dbsource.execute()
def test_execute_calls_adapter(self):
""" It should call the adapter methods with proper args """
expect = ('query', 'execute', 'metadata')
return_value = 'rows', 'cols'
res, adapter = self._test_adapter_method(
'execute', args=expect, return_value=return_value,
)
adapter.assert_called_once_with(*expect)
def test_execute_return(self):
""" It should return rows if not metadata """
expect = (True, True, False)
return_value = 'rows', 'cols'
res, adapter = self._test_adapter_method(
'execute', args=expect, return_value=return_value,
)
self.assertEqual(res, return_value[0])
def test_execute_return_metadata(self):
""" It should return rows and cols if metadata """
expect = (True, True, True)
return_value = 'rows', 'cols'
res, adapter = self._test_adapter_method(
'execute', args=expect, return_value=return_value,
)
self.assertEqual(
res,
{'rows': return_value[0],
'cols': return_value[1]},
)
def test_remote_browse(self):
""" It should call the adapter method with proper args """
args = [1], 'args'
kwargs = {'kwargs': True}
self.dbsource.current_table = 'table'
res, adapter = self._test_adapter_method(
'remote_browse', create=True, args=args, kwargs=kwargs,
)
adapter.assert_called_once_with(*args, **kwargs)
self.assertEqual(res, adapter())
def test_remote_browse_asserts_current_table(self):
""" It should raise AssertionError if a table not selected """
args = [1], 'args'
kwargs = {'kwargs': True}
with self.assertRaises(AssertionError):
res, adapter = self._test_adapter_method(
'remote_browse', create=True, args=args, kwargs=kwargs,
)
def test_remote_create(self):
""" It should call the adapter method with proper args """
args = {'val': 'Value'}, 'args'
kwargs = {'kwargs': True}
self.dbsource.current_table = 'table'
res, adapter = self._test_adapter_method(
'remote_create', create=True, args=args, kwargs=kwargs,
)
adapter.assert_called_once_with(*args, **kwargs)
self.assertEqual(res, adapter())
def test_remote_create_asserts_current_table(self):
""" It should raise AssertionError if a table not selected """
args = [1], 'args'
kwargs = {'kwargs': True}
with self.assertRaises(AssertionError):
res, adapter = self._test_adapter_method(
'remote_create', create=True, args=args, kwargs=kwargs,
)
def test_remote_delete(self):
""" It should call the adapter method with proper args """
args = [1], 'args'
kwargs = {'kwargs': True}
self.dbsource.current_table = 'table'
res, adapter = self._test_adapter_method(
'remote_delete', create=True, args=args, kwargs=kwargs,
)
adapter.assert_called_once_with(*args, **kwargs)
self.assertEqual(res, adapter())
def test_remote_delete_asserts_current_table(self):
""" It should raise AssertionError if a table not selected """
args = [1], 'args'
kwargs = {'kwargs': True}
with self.assertRaises(AssertionError):
res, adapter = self._test_adapter_method(
'remote_delete', create=True, args=args, kwargs=kwargs,
)
def test_remote_search(self):
""" It should call the adapter method with proper args """
args = {'search': 'query'}, 'args'
kwargs = {'kwargs': True}
self.dbsource.current_table = 'table'
res, adapter = self._test_adapter_method(
'remote_search', create=True, args=args, kwargs=kwargs,
)
adapter.assert_called_once_with(*args, **kwargs)
self.assertEqual(res, adapter())
def test_remote_search_asserts_current_table(self):
""" It should raise AssertionError if a table not selected """
args = [1], 'args'
kwargs = {'kwargs': True}
with self.assertRaises(AssertionError):
res, adapter = self._test_adapter_method(
'remote_search', create=True, args=args, kwargs=kwargs,
)
def test_remote_update(self):
""" It should call the adapter method with proper args """
args = [1], {'vals': 'Value'}, 'args'
kwargs = {'kwargs': True}
self.dbsource.current_table = 'table'
res, adapter = self._test_adapter_method(
'remote_update', create=True, args=args, kwargs=kwargs,
)
adapter.assert_called_once_with(*args, **kwargs)
self.assertEqual(res, adapter())
def test_remote_update_asserts_current_table(self):
""" It should raise AssertionError if a table not selected """
args = [1], 'args'
kwargs = {'kwargs': True}
with self.assertRaises(AssertionError):
res, adapter = self._test_adapter_method(
'remote_update', create=True, args=args, kwargs=kwargs,
)
# Postgres
def test_execute_postgresql(self):
""" It should call generic executor with proper args """
expect = ('query', 'execute', 'metadata')
with mock.patch.object(
self.dbsource, '_execute_generic', autospec=True,
) as execute:
execute.return_value = 'rows', 'cols'
self.dbsource.execute(*expect)
execute.assert_called_once_with(*expect)
# Old API Compat
def test_execute_calls_adapter_old_api(self):
""" It should call the adapter correctly if old kwargs provided """
expect = [None, None, 'metadata']
with mock.patch.object(
self.dbsource, 'execute_postgresql', autospec=True,
) as psql:
psql.return_value = 'rows', 'cols'
self.dbsource.execute(
*expect, sqlparams='params', sqlquery='query'
)
expect[0], expect[1] = 'query', 'params'
psql.assert_called_once_with(*expect)
def test_conn_open(self):
""" It should return open connection for use """
with mock.patch.object(
self.dbsource, 'connection_open', autospec=True,
) as connection:
res = self.dbsource.conn_open()
self.assertEqual(
res,
connection().__enter__(),
)

66
base_external_dbsource/tests/test_create_dbsource.py

@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
from odoo.exceptions import Warning as UserError
from odoo.tests import common
import logging
class TestCreateDbsource(common.TransactionCase):
"""Test class for base_external_dbsource."""
def test_create_dbsource(self):
"""source creation should succeed."""
dbsource = self.env.ref('base_external_dbsource.demo_postgre')
try:
dbsource.connection_test()
except UserError as e:
logging.warning("Log = " + str(e))
self.assertTrue(u'Everything seems properly set up!' in str(e))
def test_create_dbsource_failed(self):
"""source creation without connection string should failed."""
dbsource = self.env.ref('base_external_dbsource.demo_postgre')
# Connection without connection_string
dbsource.conn_string = ""
try:
dbsource.connection_test()
except UserError as e:
logging.warning("Log = " + str(e))
self.assertTrue(u'Here is what we got instead:' in str(e))
def test_create_dbsource_without_connector_failed(self):
"""source creation with other connector should failed."""
dbsource = self.env.ref('base_external_dbsource.demo_postgre')
# Connection to mysql
try:
dbsource.connector = "mysql"
dbsource.connection_test()
except ValueError as e:
logging.warning("Log = " + str(e))
self.assertTrue(u'Wrong value for' in str(e))
# Connection to mysql
try:
dbsource.connector = "pyodbc"
dbsource.connection_test()
except ValueError as e:
logging.warning("Log = " + str(e))
self.assertTrue(u'Wrong value for' in str(e))
# Connection to oracle
try:
dbsource.connector = "cx_Oracle"
dbsource.connection_test()
except ValueError as e:
logging.warning("Log = " + str(e))
self.assertTrue(u'Wrong value for' in str(e))
# Connection to firebird
try:
dbsource.connector = "fdb"
dbsource.connection_test()
except Exception as e:
logging.warning("Log = " + str(e))
self.assertTrue(u'Wrong value for' in str(e))

77
base_external_dbsource_firebird/README.rst

@ -0,0 +1,77 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
===================================
External Database Source - Firebird
===================================
This module extends ``base_external_dbsource``, allowing you to connect to
foreign Firebird databases.
Installation
============
* Install ``fdb`` python library
Configuration
=============
Database sources can be configured in Settings > Configuration -> Data sources.
Usage
=====
To use this module:
* Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information:
* Datasource name 
* Pasword
* Connector: Choose the database to which you want to connect
* Connection string : Specify how to connect to database
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap
======================
* Setting ``metadata`` to ``True`` in ``execute_fdb`` will do nothing.
* ``execute`` is susceptible to SQL injection.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Daniel Reis <dreis.pt@hotmail.com>
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
base_external_dbsource_firebird/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

28
base_external_dbsource_firebird/__manifest__.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux>
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': 'External Database Source - Firebird',
'version': '10.0.1.0.0',
'category': 'Tools',
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3',
'depends': [
'base_external_dbsource',
],
# Uncomment this for v11
# 'external_dependencies': {
# 'python': [
# 'fdb',
# ]
# },
'demo': [
'demo/base_external_dbsource.xml',
],
'installable': True,
'auto_install': True, # Remove this key for v11
}

9
base_external_dbsource_firebird/demo/base_external_dbsource.xml

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record model="base.external.dbsource" id="demo_firebird">
<field name="name">Firebird Demo</field>
<field name="conn_string">User=firebird;Database=SampleDatabase.fdb;DataSource=localhost;Port=3050;Dialect=3;Charset=NONE;Role=;Connection lifetime=15;Pooling=true;MinPoolSize=0;MaxPoolSize=50;Packet Size=8192;ServerType=0;</field>
<field name="password">password</field>
<field name="connector">fdb</field>
</record>
</odoo>

3
base_external_dbsource_firebird/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import base_external_dbsource

56
base_external_dbsource_firebird/models/base_external_dbsource.py

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import api
from odoo import models
_logger = logging.getLogger(__name__)
try:
from odoo.addons.base_external_dbsource.models import (
base_external_dbsource,
)
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS
try:
import fdb
CONNECTORS.append(('fdb', 'Firebird'))
except:
_logger.info('Firebird library not available. Please install "fdb" '
'python package.')
except ImportError:
_logger.info('base_external_dbsource Odoo module not found.')
class BaseExternalDbsource(models.Model):
""" It provides logic for connection to an Firebird data source. """
_inherit = "base.external.dbsource"
PWD_STRING_FDB = 'Password=%s;'
@api.multi
def connection_close_fdb(self, connection):
return connection.close()
@api.multi
def connection_open_fdb(self):
kwargs = {}
for option in self.conn_string_full.split(';'):
try:
key, value = option.split('=')
except ValueError:
continue
kwargs[key.lower()] = value
return fdb.connect(**kwargs)
@api.multi
def execute_fdb(self, sqlquery, sqlparams, metadata):
with self.connection_open_fdb() as conn:
cur = conn.cursor()
cur.execute(sqlquery % sqlparams)
rows = cur.fetchall()
return rows, []

3
base_external_dbsource_firebird/tests/__init__.py

@ -0,0 +1,3 @@
# -*- encoding: utf-8 -*-
from . import test_base_external_dbsource

41
base_external_dbsource_firebird/tests/test_base_external_dbsource.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
import mock
from odoo.tests import common
ADAPTER = ('odoo.addons.base_external_dbsource_firebird.models'
'.base_external_dbsource.fdb')
class TestBaseExternalDbsource(common.TransactionCase):
def setUp(self):
super(TestBaseExternalDbsource, self).setUp()
self.dbsource = self.env.ref(
'base_external_dbsource_firebird.demo_firebird',
)
def test_connection_close_fdb(self):
""" It should close the connection """
connection = mock.MagicMock()
res = self.dbsource.connection_close_fdb(connection)
self.assertEqual(res, connection.close())
@mock.patch(ADAPTER)
def test_connection_open_fdb(self, fdb):
""" It should open the connection with the split conn string """
self.dbsource.conn_string = 'User=User;'
self.dbsource.connection_open_fdb()
fdb.connect.assert_called_once_with(**{
'user': 'User',
'password': 'password',
})
@mock.patch(ADAPTER)
def test_connection_open_fdb_return(self, fdb):
""" It should return the newly opened connection """
res = self.dbsource.connection_open_fdb()
self.assertEqual(res, fdb.connect())

80
base_external_dbsource_mssql/README.rst

@ -0,0 +1,80 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
================================
External Database Source - MSSQL
================================
This module extends ``base_external_dbsource``, allowing you to connect to
foreign MSSQL databases using SQLAlchemy.
Installation
============
* Install & configure FreeTDS driver (tdsodbc package)
* Install ``sqlalchemy`` and ``pymssql`` python libraries
* Install ``base_external_dbsource_sqlite`` Odoo module
Configuration
=============
Database sources can be configured in Settings > Configuration -> Data sources.
Usage
=====
To use this module:
* Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information:
* Datasource name 
* Pasword
* Connector: Choose the database to which you want to connect
* Connection string: Specify how to connect to database
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap
======================
* Add X.509 authentication
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Daniel Reis <dreis.pt@hotmail.com>
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
base_external_dbsource_mssql/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

29
base_external_dbsource_mssql/__manifest__.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux>
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': 'External Database Source - MSSQL',
'version': '10.0.1.0.0',
'category': 'Tools',
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3',
'depends': [
'base_external_dbsource_sqlite',
],
# Uncomment this for v11
# 'external_dependencies': [
# 'python': [
# 'sqlalchemy',
# 'pymssql',
# ]
# ],
'demo': [
'demo/base_external_dbsource.xml',
],
'installable': True,
'auto_install': True, # Remove this key for v11
}

9
base_external_dbsource_mssql/demo/base_external_dbsource.xml

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record model="base.external.dbsource" id="demo_mssql">
<field name="name">MSSQL Demo</field>
<field name="conn_string">Server=myServerAddress;Database=myDataBase;User Id=myUsername;</field>
<field name="password">password</field>
<field name="connector">mssql</field>
</record>
</odoo>

3
base_external_dbsource_mssql/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import base_external_dbsource

45
base_external_dbsource_mssql/models/base_external_dbsource.py

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
try:
from odoo.addons.base_external_dbsource.models import (
base_external_dbsource,
)
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS
try:
import pymssql
CONNECTORS.append(('mssql', 'Microsoft SQL Server'))
assert pymssql
except (ImportError, AssertionError):
_logger.info('MS SQL Server not available. Please install "pymssql" '
'python package.')
except ImportError:
_logger.info('base_external_dbsource Odoo module not found.')
class BaseExternalDbsource(models.Model):
""" It provides logic for connection to a MSSQL data source. """
_inherit = "base.external.dbsource"
PWD_STRING_MSSQL = 'Password=%s;'
@api.multi
def connection_close_mssql(self, connection):
return connection.close()
@api.multi
def connection_open_mssql(self):
return self._connection_open_sqlalchemy()
@api.multi
def execute_mssql(self, sqlquery, sqlparams, metadata):
return self._execute_sqlalchemy(sqlquery, sqlparams, metadata)

3
base_external_dbsource_mssql/tests/__init__.py

@ -0,0 +1,3 @@
# -*- encoding: utf-8 -*-
from . import test_base_external_dbsource

42
base_external_dbsource_mssql/tests/test_base_external_dbsource.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
import mock
from odoo.tests import common
ADAPTER = ('odoo.addons.base_external_dbsource_mssql.models'
'.base_external_dbsource.pymssql')
class TestBaseExternalDbsource(common.TransactionCase):
def setUp(self):
super(TestBaseExternalDbsource, self).setUp()
self.dbsource = self.env.ref(
'base_external_dbsource_mssql.demo_mssql',
)
def test_connection_close_mssql(self):
""" It should close the connection """
connection = mock.MagicMock()
res = self.dbsource.connection_close_mssql(connection)
self.assertEqual(res, connection.close())
def test_connection_open_mssql(self):
""" It should call SQLAlchemy open """
with mock.patch.object(
self.dbsource, '_connection_open_sqlalchemy'
) as parent_method:
self.dbsource.connection_open_mssql()
parent_method.assert_called_once_with()
def test_excecute_mssql(self):
""" It should pass args to SQLAlchemy execute """
expect = 'sqlquery', 'sqlparams', 'metadata'
with mock.patch.object(
self.dbsource, '_execute_sqlalchemy'
) as parent_method:
self.dbsource.execute_mssql(*expect)
parent_method.assert_called_once_with(*expect)

80
base_external_dbsource_mysql/README.rst

@ -0,0 +1,80 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
================================
External Database Source - MySQL
================================
This module extends ``base_external_dbsource``, allowing you to connect to
foreign MySQL databases using SQLAlchemy.
Installation
============
* Install ``sqlalchemy`` and ``MySQLdb`` python libraries
* Install ``base_external_dbsource_sqlite`` Odoo module
Configuration
=============
Database sources can be configured in Settings > Configuration -> Data sources.
Usage
=====
To use this module:
* Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information:
* Datasource name 
* Pasword
* Connector: Choose the database to which you want to connect
* Connection string: Specify how to connect to database
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap
======================
* Add X.509 authentication
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Daniel Reis <dreis.pt@hotmail.com>
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
base_external_dbsource_mysql/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

29
base_external_dbsource_mysql/__manifest__.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux>
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': 'External Database Source - MySQL',
'version': '10.0.1.0.0',
'category': 'Tools',
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3',
'depends': [
'base_external_dbsource_sqlite',
],
# Uncomment this for v11
# 'external_dependencies': [
# 'python': [
# 'sqlalchemy',
# 'MySQLdb',
# ]
# ],
'demo': [
'demo/base_external_dbsource.xml',
],
'installable': True,
'auto_install': True, # Remove this key for v11
}

9
base_external_dbsource_mysql/demo/base_external_dbsource.xml

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record model="base.external.dbsource" id="demo_mysql">
<field name="name">MySQL Demo</field>
<field name="conn_string">Server=myServerAddress;Database=myDataBase;Uid=myUsername;</field>
<field name="password">password</field>
<field name="connector">mysql</field>
</record>
</odoo>

3
base_external_dbsource_mysql/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import base_external_dbsource

43
base_external_dbsource_mysql/models/base_external_dbsource.py

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
try:
from odoo.addons.base_external_dbsource.models import (
base_external_dbsource,
)
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS
try:
import MySQLdb
CONNECTORS.append(('mysql', 'MySQL'))
assert MySQLdb
except (ImportError, AssertionError):
_logger.info('MySQL not available. Please install "mysqldb" '
'python package.')
except ImportError:
_logger.info('base_external_dbsource Odoo module not found.')
class BaseExternalDbsource(models.Model):
""" It provides logic for connection to a MySQL data source. """
_inherit = "base.external.dbsource"
@api.multi
def connection_close_mysql(self, connection):
return connection.close()
@api.multi
def connection_open_mysql(self):
return self._connection_open_sqlalchemy()
@api.multi
def execute_mysql(self, sqlquery, sqlparams, metadata):
return self._execute_sqlalchemy(sqlquery, sqlparams, metadata)

3
base_external_dbsource_mysql/tests/__init__.py

@ -0,0 +1,3 @@
# -*- encoding: utf-8 -*-
from . import test_base_external_dbsource

42
base_external_dbsource_mysql/tests/test_base_external_dbsource.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
import mock
from odoo.tests import common
ADAPTER = ('odoo.addons.base_external_dbsource_mysql.models'
'.base_external_dbsource.MySQLdb')
class TestBaseExternalDbsource(common.TransactionCase):
def setUp(self):
super(TestBaseExternalDbsource, self).setUp()
self.dbsource = self.env.ref(
'base_external_dbsource_mysql.demo_mysql',
)
def test_connection_close_mysql(self):
""" It should close the connection """
connection = mock.MagicMock()
res = self.dbsource.connection_close_mysql(connection)
self.assertEqual(res, connection.close())
def test_connection_open_mysql(self):
""" It should call SQLAlchemy open """
with mock.patch.object(
self.dbsource, '_connection_open_sqlalchemy'
) as parent_method:
self.dbsource.connection_open_mysql()
parent_method.assert_called_once_with()
def test_excecute_mysql(self):
""" It should pass args to SQLAlchemy execute """
expect = 'sqlquery', 'sqlparams', 'metadata'
with mock.patch.object(
self.dbsource, '_execute_sqlalchemy'
) as parent_method:
self.dbsource.execute_mysql(*expect)
parent_method.assert_called_once_with(*expect)

79
base_external_dbsource_odbc/README.rst

@ -0,0 +1,79 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
===============================
External Database Source - ODBC
===============================
This module extends ``base_external_dbsource``, allowing you to connect to
foreign ODBC databases using PyODBC.
Installation
============
* Install ``unixodbc`` and ``python-pyodbc`` packages
* Install ``base_external_dbsource_sqlite`` Odoo module
Configuration
=============
Database sources can be configured in Settings > Configuration -> Data sources.
Usage
=====
To use this module:
* Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information:
* Datasource name 
* Pasword
* Connector: Choose the database to which you want to connect
* Connection string: Specify how to connect to database
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap
======================
* Add X.509 authentication
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Daniel Reis <dreis.pt@hotmail.com>
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
base_external_dbsource_odbc/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

29
base_external_dbsource_odbc/__manifest__.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux>
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': 'External Database Source - ODBC',
'version': '10.0.1.0.0',
'category': 'Tools',
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3',
'depends': [
'base_external_dbsource_sqlite',
],
# Uncomment this for v11
# 'external_dependencies': [
# 'python': [
# 'sqlalchemy',
# 'pyodbc',
# ]
# ],
'demo': [
'demo/base_external_dbsource.xml',
],
'installable': True,
'auto_install': True, # Remove this key for v11
}

9
base_external_dbsource_odbc/demo/base_external_dbsource.xml

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record model="base.external.dbsource" id="demo_odbc">
<field name="name">ODBC Demo</field>
<field name="conn_string">Driver='ODBC Driver 11 for SQL Server';Server=myServerAddress;Database=myDataBase;Uid=myUsername;</field>
<field name="password">password</field>
<field name="connector">pyodbc</field>
</record>
</odoo>

3
base_external_dbsource_odbc/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import base_external_dbsource

42
base_external_dbsource_odbc/models/base_external_dbsource.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
try:
from odoo.addons.base_external_dbsource.models import (
base_external_dbsource,
)
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS
try:
import pyodbc
CONNECTORS.append(('pyodbc', 'ODBC'))
except ImportError:
_logger.info('ODBC libraries not available. Please install '
'"unixodbc" and "python-pyodbc" packages.')
except ImportError:
_logger.info('base_external_dbsource Odoo module not found.')
class BaseExternalDbsource(models.Model):
""" It provides logic for connection to a ODBC data source. """
_inherit = "base.external.dbsource"
@api.multi
def connection_close_pyodbc(self, connection):
return connection.close()
@api.multi
def connection_open_pyodbc(self):
return pyodbc.connect(self.conn_string_full)
@api.multi
def execute_pyodbc(self, sqlquery, sqlparams, metadata):
return self._execute_generic(sqlquery, sqlparams, metadata)

3
base_external_dbsource_odbc/tests/__init__.py

@ -0,0 +1,3 @@
# -*- encoding: utf-8 -*-
from . import test_base_external_dbsource

46
base_external_dbsource_odbc/tests/test_base_external_dbsource.py

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
import mock
from odoo.tests import common
ADAPTER = ('odoo.addons.base_external_dbsource_odbc.models'
'.base_external_dbsource.pyodbc')
class TestBaseExternalDbsource(common.TransactionCase):
def setUp(self):
super(TestBaseExternalDbsource, self).setUp()
self.dbsource = self.env.ref(
'base_external_dbsource_odbc.demo_odbc',
)
def test_connection_close_pyodbc(self):
""" It should close the connection """
connection = mock.MagicMock()
res = self.dbsource.connection_close_pyodbc(connection)
self.assertEqual(res, connection.close())
@mock.patch(ADAPTER)
def test_connection_open_pyodbc(self, pyodbc):
""" It should open the connection with the full conn string """
self.dbsource.connection_open_pyodbc()
pyodbc.connect.assert_called_once_with(
self.dbsource.conn_string_full,
)
@mock.patch(ADAPTER)
def test_connection_open_pyodbc_return(self, pyodbc):
""" It should return the newly opened connection """
res = self.dbsource.connection_open_pyodbc()
self.assertEqual(res, pyodbc.connect())
def test_execute_pyodbc(self):
""" It should call the generic execute method w/ proper args """
expect = 'sqlquery', 'sqlparams', 'metadata'
with mock.patch.object(self.dbsource, '_execute_generic') as execute:
self.dbsource.execute_pyodbc(*expect)
execute.assert_called_once_with(*expect)

79
base_external_dbsource_oracle/README.rst

@ -0,0 +1,79 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
=================================
External Database Source - Oracle
=================================
This module extends ``base_external_dbsource``, allowing you to connect to
foreign Oracle databases.
Installation
============
* Install and configure Oracle Instant Client
* Install ``cx_Oracle`` python library
Configuration
=============
Database sources can be configured in Settings > Configuration -> Data sources.
Usage
=====
To use this module:
* Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information:
* Datasource name 
* Pasword
* Connector: Choose the database to which you want to connect
* Connection string: Specify how to connect to database
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap
======================
* Find a way to test (cx_Oracle requires InstantClient, which Travis doesn't have)
* Add X.509 authentication
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Daniel Reis <dreis.pt@hotmail.com>
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
base_external_dbsource_oracle/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

25
base_external_dbsource_oracle/__manifest__.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux>
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': 'External Database Source - Oracle',
'version': '10.0.1.0.0',
'category': 'Tools',
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3',
'depends': [
'base_external_dbsource',
],
# Uncomment this for v11
# 'external_dependencies': [
# 'python': [
# 'cx_Oracle',
# ]
# ],
'installable': True,
'auto_install': True, # Remove this key for v11
}

3
base_external_dbsource_oracle/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import base_external_dbsource

47
base_external_dbsource_oracle/models/base_external_dbsource.py

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
import os
from odoo import api
from odoo import models
_logger = logging.getLogger(__name__)
try:
from odoo.addons.base_external_dbsource.models import (
base_external_dbsource,
)
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS
try:
import cx_Oracle
CONNECTORS.append(('cx_Oracle', 'Oracle'))
except ImportError:
_logger.info('Oracle libraries not available. Please install '
'"cx_Oracle" python package.')
except ImportError:
_logger.info('base_external_dbsource Odoo module not found.')
class BaseExternalDbsource(models.Model):
""" It provides logic for connection to an Oracle data source. """
_inherit = "base.external.dbsource"
PWD_STRING_CX_ORACLE = 'Password=%s;'
@api.multi
def connection_close_cx_Oracle(self, connection):
return connection.close()
@api.multi
def connection_open_cx_Oracle(self):
os.environ['NLS_LANG'] = 'AMERICAN_AMERICA.UTF8'
return cx_Oracle.connect(self.conn_string_full)
@api.multi
def execute_cx_Oracle(self, sqlquery, sqlparams, metadata):
return self._execute_generic(sqlquery, sqlparams, metadata)

77
base_external_dbsource_sqlite/README.rst

@ -0,0 +1,77 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
=================================
External Database Source - SQLite
=================================
This module extends ``base_external_dbsource``, allowing you to connect to
foreign SQLite databases using SQLAlchemy.
Installation
============
* Install ``sqlalchemy`` python library
Configuration
=============
Database sources can be configured in Settings > Configuration -> Data sources.
Usage
=====
To use this module:
* Go to Settings > Database Structure > Database Sources
* Click on Create to enter the following information:
* Datasource name 
* Pasword
* Connector: Choose the database to which you want to connect
* Connection string: Specify how to connect to database
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/10.0 for server-tools
Known issues / Roadmap
======================
* Add X.509 authentication
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Daniel Reis <dreis.pt@hotmail.com>
* Maxime Chambreuil <maxime.chambreuil@savoirfairelinux.com>
* Gervais Naoussi <gervaisnaoussi@gmail.com>
* Dave Lasley <dave@laslabs.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
base_external_dbsource_sqlite/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

28
base_external_dbsource_sqlite/__manifest__.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright <2011> <Daniel Reis, Maxime Chambreuil, Savoir-faire Linux>
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
'name': 'External Database Source - SQLite',
'version': '10.0.1.0.0',
'category': 'Tools',
'author': "Daniel Reis, "
"LasLabs, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/server-tools',
'license': 'LGPL-3',
'depends': [
'base_external_dbsource',
],
# Uncomment this for v11
# 'external_dependencies': [
# 'python': [
# 'sqlalchemy',
# ]
# ],
'demo': [
'demo/base_external_dbsource.xml',
],
'installable': True,
'auto_install': True, # Remove this key for v11
}

9
base_external_dbsource_sqlite/demo/base_external_dbsource.xml

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record model="base.external.dbsource" id="demo_sqlite">
<field name="name">SQLite Demo</field>
<field name="conn_string">Data Source=:memory:;Version=3;New=True;</field>
<field name="password">password</field>
<field name="connector">sqlite</field>
</record>
</odoo>

3
base_external_dbsource_sqlite/models/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import base_external_dbsource

59
base_external_dbsource_sqlite/models/base_external_dbsource.py

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Daniel Reis
# Copyright 2016 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
try:
from odoo.addons.base_external_dbsource.models import (
base_external_dbsource,
)
CONNECTORS = base_external_dbsource.BaseExternalDbsource.CONNECTORS
try:
import sqlalchemy
CONNECTORS.append(('sqlite', 'SQLite'))
except ImportError:
_logger.info('SQLAlchemy library not available. Please '
'install "sqlalchemy" python package.')
except ImportError:
_logger.info('base_external_dbsource Odoo module not found.')
class BaseExternalDbsource(models.Model):
""" It provides logic for connection to a SQLite data source. """
_inherit = "base.external.dbsource"
PWD_STRING_SQLITE = 'Password=%s;'
@api.multi
def connection_close_sqlite(self, connection):
return connection.close()
@api.multi
def connection_open_sqlite(self):
return self._connection_open_sqlalchemy()
@api.multi
def execute_sqlite(self, sqlquery, sqlparams, metadata):
return self._execute_sqlalchemy(sqlquery, sqlparams, metadata)
@api.multi
def _connection_open_sqlalchemy(self):
return sqlalchemy.create_engine(self.conn_string_full).connect()
@api.multi
def _execute_sqlalchemy(self, sqlquery, sqlparams, metadata):
rows, cols = list(), list()
for record in self:
with record.connection_open() as connection:
cur = connection.execute(sqlquery, sqlparams)
if metadata:
cols = cur.keys()
rows = [r for r in cur]
return rows, cols

3
base_external_dbsource_sqlite/tests/__init__.py

@ -0,0 +1,3 @@
# -*- encoding: utf-8 -*-
from . import test_base_external_dbsource

42
base_external_dbsource_sqlite/tests/test_base_external_dbsource.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
import mock
from odoo.tests import common
ADAPTER = ('odoo.addons.base_external_dbsource_sqlite.models'
'.base_external_dbsource.sqlalchemy')
class TestBaseExternalDbsource(common.TransactionCase):
def setUp(self):
super(TestBaseExternalDbsource, self).setUp()
self.dbsource = self.env.ref(
'base_external_dbsource_sqlite.demo_sqlite',
)
def test_connection_close_sqlite(self):
""" It should close the connection """
connection = mock.MagicMock()
res = self.dbsource.connection_close_sqlite(connection)
self.assertEqual(res, connection.close())
def test_connection_open_sqlite(self):
""" It should call SQLAlchemy open """
with mock.patch.object(
self.dbsource, '_connection_open_sqlalchemy'
) as parent_method:
self.dbsource.connection_open_sqlite()
parent_method.assert_called_once_with()
def test_excecute_sqlite(self):
""" It should pass args to SQLAlchemy execute """
expect = 'sqlquery', 'sqlparams', 'metadata'
with mock.patch.object(
self.dbsource, '_execute_sqlalchemy'
) as parent_method:
self.dbsource.execute_sqlite(*expect)
parent_method.assert_called_once_with(*expect)

5
requirements.txt

@ -4,3 +4,8 @@ acme_tiny
IPy IPy
validate_email validate_email
pysftp pysftp
fdb
sqlalchemy
pymssql
MySQL-python
pyodbc
Loading…
Cancel
Save