Skip to content

Commit

Permalink
Add QgsConnectionRegistry
Browse files Browse the repository at this point in the history
This registry provides a convenient place to add API relating
to generic connection fetching and handling methods

Currently contains a single method, which allows for retrieving
matching connections using a "provider://name" format (e.g.
"postgres://my connection")
  • Loading branch information
nyalldawson committed Mar 16, 2020
1 parent 124f9b1 commit 40d5efc
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 0 deletions.
7 changes: 7 additions & 0 deletions python/core/auto_generated/qgsapplication.sip.in
Expand Up @@ -810,6 +810,13 @@ Returns the application's page size registry, used for managing layout page size
Returns the action scope registry.

.. versionadded:: 3.0
%End

static QgsConnectionRegistry *connectionRegistry();
%Docstring
Returns the application's connection registry, used for managing saved data provider connections.

.. versionadded:: 3.14
%End

static QgsRuntimeProfiler *profiler();
Expand Down
61 changes: 61 additions & 0 deletions python/core/auto_generated/qgsconnectionregistry.sip.in
@@ -0,0 +1,61 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsconnectionregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/






class QgsConnectionRegistry : QObject
{
%Docstring
A registry for saved data provider connections, allowing retrieval of
saved connections by name and provider type.

QgsConnectionRegistry is not usually directly created, but rather accessed through
:py:func:`QgsApplication.connectionRegistry()`

.. versionadded:: 3.14
%End

%TypeHeaderCode
#include "qgsconnectionregistry.h"
%End
public:

QgsConnectionRegistry( QObject *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsConnectionRegistry.
%End


QgsAbstractProviderConnection *createConnection( const QString &name ) throw( QgsProviderConnectionException ) /Factory/;
%Docstring
Creates a new connection by loading the connection with the given ``id`` from the settings.

The ``id`` string must be of the format "provider://connection_name", e.g. "postgres://my_connection" for
the PostgreSQL connection saved as "my_connection".

Ownership is transferred to the caller.

:raises :: py:class:`QgsProviderConnectionException`
%End

private:
QgsConnectionRegistry( const QgsConnectionRegistry &other );
};



/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsconnectionregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -30,6 +30,7 @@
%Include auto_generated/qgscolorscheme.sip
%Include auto_generated/qgscolorschemeregistry.sip
%Include auto_generated/qgsconditionalstyle.sip
%Include auto_generated/qgsconnectionregistry.sip
%Include auto_generated/qgscoordinateformatter.sip
%Include auto_generated/qgscoordinatereferencesystem.sip
%Include auto_generated/qgscoordinatetransform.sip
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -219,6 +219,7 @@ SET(QGIS_CORE_SRCS
qgscolorscheme.cpp
qgscolorschemeregistry.cpp
qgsconditionalstyle.cpp
qgsconnectionregistry.cpp
qgscoordinateformatter.cpp
qgscoordinatereferencesystem.cpp
qgscoordinatetransform.cpp
Expand Down Expand Up @@ -726,6 +727,7 @@ SET(QGIS_CORE_HDRS
qgscolorschemeregistry.h
qgsconditionalstyle.h
qgsconnectionpool.h
qgsconnectionregistry.h
qgscoordinateformatter.h
qgscoordinatereferencesystem.h
qgscoordinatetransform.h
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsapplication.cpp
Expand Up @@ -59,6 +59,7 @@
#include "qgsnewsfeedparser.h"
#include "qgsbookmarkmanager.h"
#include "qgsstylemodel.h"
#include "qgsconnectionregistry.h"

#include "gps/qgsgpsconnectionregistry.h"
#include "processing/qgsprocessingregistry.h"
Expand Down Expand Up @@ -2178,6 +2179,11 @@ QgsProcessingRegistry *QgsApplication::processingRegistry()
return members()->mProcessingRegistry;
}

QgsConnectionRegistry *QgsApplication::connectionRegistry()
{
return members()->mConnectionRegistry;
}

QgsPageSizeRegistry *QgsApplication::pageSizeRegistry()
{
return members()->mPageSizeRegistry;
Expand Down Expand Up @@ -2214,6 +2220,7 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
// will need to be careful with the order of creation/destruction
mMessageLog = new QgsMessageLog();
mProfiler = new QgsRuntimeProfiler();
mConnectionRegistry = new QgsConnectionRegistry();
mTaskManager = new QgsTaskManager();
mActionScopeRegistry = new QgsActionScopeRegistry();
mNumericFormatRegistry = new QgsNumericFormatRegistry();
Expand Down Expand Up @@ -2270,6 +2277,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
delete mClassificationMethodRegistry;
delete mNumericFormatRegistry;
delete mBookmarkManager;
delete mConnectionRegistry;
}

QgsApplication::ApplicationMembers *QgsApplication::members()
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsapplication.h
Expand Up @@ -56,6 +56,7 @@ class QgsCalloutRegistry;
class QgsBookmarkManager;
class QgsStyleModel;
class QgsNumericFormatRegistry;
class QgsConnectionRegistry;

/**
* \ingroup core
Expand Down Expand Up @@ -744,6 +745,12 @@ class CORE_EXPORT QgsApplication : public QApplication
*/
static QgsActionScopeRegistry *actionScopeRegistry() SIP_KEEPREFERENCE;

/**
* Returns the application's connection registry, used for managing saved data provider connections.
* \since QGIS 3.14
*/
static QgsConnectionRegistry *connectionRegistry();

/**
* Returns the application runtime profiler.
* \since QGIS 3.0
Expand Down Expand Up @@ -908,6 +915,7 @@ class CORE_EXPORT QgsApplication : public QApplication
QgsPluginLayerRegistry *mPluginLayerRegistry = nullptr;
QgsClassificationMethodRegistry *mClassificationMethodRegistry = nullptr;
QgsProcessingRegistry *mProcessingRegistry = nullptr;
QgsConnectionRegistry *mConnectionRegistry = nullptr;
QgsProjectStorageRegistry *mProjectStorageRegistry = nullptr;
QgsPageSizeRegistry *mPageSizeRegistry = nullptr;
QgsRasterRendererRegistry *mRasterRendererRegistry = nullptr;
Expand Down
42 changes: 42 additions & 0 deletions src/core/qgsconnectionregistry.cpp
@@ -0,0 +1,42 @@
/***************************************************************************
qgsconnectionregistry.cpp
--------------------------
begin : March 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsconnectionregistry.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"

QgsConnectionRegistry::QgsConnectionRegistry( QObject *parent SIP_TRANSFERTHIS )
: QObject( parent )
{
}

QgsAbstractProviderConnection *QgsConnectionRegistry::createConnection( const QString &id )
{
QRegularExpressionMatch m = QRegularExpression( QStringLiteral( "(.*?)\\://(.*)" ) ).match( id );
if ( !m.hasMatch() )
throw QgsProviderConnectionException( QObject::tr( "Invalid connection id" ) );

const QString providerKey = m.captured( 1 );
const QString name = m.captured( 2 );

QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( providerKey );

if ( !md )
throw QgsProviderConnectionException( QObject::tr( "Invalid provider key: %1" ).arg( providerKey ) );

return md->createConnection( name );
}
75 changes: 75 additions & 0 deletions src/core/qgsconnectionregistry.h
@@ -0,0 +1,75 @@
/***************************************************************************
qgsconnectionregistry.h
------------------------
begin : March 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSCONNECTIONREGISTRY_H
#define QGSCONNECTIONREGISTRY_H

#include "qgis_core.h"
#include "qgis.h"
#include <QObject>

class QgsAbstractProviderConnection;


/**
* \class QgsConnectionRegistry
* \ingroup core
* A registry for saved data provider connections, allowing retrieval of
* saved connections by name and provider type.
*
* QgsConnectionRegistry is not usually directly created, but rather accessed through
* QgsApplication::connectionRegistry().
* \since QGIS 3.14
*/
class CORE_EXPORT QgsConnectionRegistry : public QObject
{
Q_OBJECT

public:

/**
* Constructor for QgsConnectionRegistry.
*/
QgsConnectionRegistry( QObject *parent SIP_TRANSFERTHIS = nullptr );

//! Registry cannot be copied
QgsConnectionRegistry( const QgsConnectionRegistry &other ) = delete;
//! Registry cannot be copied
QgsConnectionRegistry &operator=( const QgsConnectionRegistry &other ) = delete;

/**
* Creates a new connection by loading the connection with the given \a id from the settings.
*
* The \a id string must be of the format "provider://connection_name", e.g. "postgres://my_connection" for
* the PostgreSQL connection saved as "my_connection".
*
* Ownership is transferred to the caller.
*
* \throws QgsProviderConnectionException
*/
QgsAbstractProviderConnection *createConnection( const QString &name ) SIP_THROW( QgsProviderConnectionException ) SIP_FACTORY;

private:

#ifdef SIP_RUN
QgsConnectionRegistry( const QgsConnectionRegistry &other );
#endif
};

#endif // QGSCONNECTIONREGISTRY_H


1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -40,6 +40,7 @@ ADD_PYTHON_TEST(PyQgsCoordinateFormatter test_qgscoordinateformatter.py)
ADD_PYTHON_TEST(PyQgsCoordinateOperationWidget test_qgscoordinateoperationwidget.py)
ADD_PYTHON_TEST(PyQgsConditionalFormatWidgets test_qgsconditionalformatwidgets.py)
ADD_PYTHON_TEST(PyQgsConditionalStyle test_qgsconditionalstyle.py)
ADD_PYTHON_TEST(PyQgsConnectionRegistry test_qgsconnectionregistry.py)
ADD_PYTHON_TEST(PyQgsCoordinateTransformContext test_qgscoordinatetransformcontext.py)
ADD_PYTHON_TEST(PyQgsDefaultValue test_qgsdefaultvalue.py)
ADD_PYTHON_TEST(PyQgsXmlUtils test_qgsxmlutils.py)
Expand Down
94 changes: 94 additions & 0 deletions tests/src/python/test_qgsconnectionregistry.py
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsConnectionRegistry.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '16/03/2020'
__copyright__ = 'Copyright 2020, The QGIS Project'

import qgis # NOQA

import shutil
import os
import tempfile
from qgis.core import (
QgsApplication,
QgsSettings,
QgsProviderConnectionException,
QgsVectorLayer,
QgsProviderRegistry
)
from qgis.PyQt.QtCore import QCoreApplication
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath

# Convenience instances in case you may need them
# to find the srs.db
start_app()
TEST_DATA_DIR = unitTestDataPath()


class TestQgsConnectionRegistry(unittest.TestCase):

@classmethod
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(cls.__name__)
QCoreApplication.setApplicationName(cls.__name__)
start_app()
QgsSettings().clear()

gpkg_original_path = '{}/qgis_server/test_project_wms_grouped_layers.gpkg'.format(TEST_DATA_DIR)
cls.basetestpath = tempfile.mkdtemp()
cls.gpkg_path = '{}/test_gpkg.gpkg'.format(cls.basetestpath)
shutil.copy(gpkg_original_path, cls.gpkg_path)
vl = QgsVectorLayer('{}|layername=cdb_lines'.format(cls.gpkg_path), 'test', 'ogr')
assert vl.isValid()

@classmethod
def tearDownClass(cls):
"""Run after all tests"""
os.unlink(cls.gpkg_path)

def testCreateConnectionBad(self):
"""
Test creating connection with bad parameters
"""
with self.assertRaises(QgsProviderConnectionException):
QgsApplication.connectionRegistry().createConnection('invalid')

with self.assertRaises(QgsProviderConnectionException):
QgsApplication.connectionRegistry().createConnection('invalid://')

with self.assertRaises(QgsProviderConnectionException):
QgsApplication.connectionRegistry().createConnection('invalid://aa')

def testCreateConnectionGood(self):
# make a valid connection
md = QgsProviderRegistry.instance().providerMetadata('ogr')
conn = md.createConnection(self.gpkg_path, {})
md.saveConnection(conn, 'qgis_test1')

conn = QgsApplication.connectionRegistry().createConnection('ogr://adasdas')
self.assertFalse(conn.uri())

conn = QgsApplication.connectionRegistry().createConnection('ogr://qgis_test1')
self.assertEqual(conn.uri(), self.gpkg_path)

# case insensitive provider name
conn = QgsApplication.connectionRegistry().createConnection('OGR://qgis_test1')
self.assertEqual(conn.uri(), self.gpkg_path)

# connection name with spaces
md.saveConnection(conn, 'qgis Test 2')
conn = QgsApplication.connectionRegistry().createConnection('OGR://qgis Test 2')
self.assertEqual(conn.uri(), self.gpkg_path)


if __name__ == '__main__':
unittest.main()

0 comments on commit 40d5efc

Please sign in to comment.