Move parse_dsn to extensions, add tests

This commit is contained in:
Oleksandr Shulgin 2015-06-01 15:11:12 +02:00
parent 6c57e4a648
commit 6a2f21aa14
6 changed files with 70 additions and 28 deletions

View File

@ -12,6 +12,17 @@
The module contains a few objects and function extending the minimum set of The module contains a few objects and function extending the minimum set of
functionalities defined by the |DBAPI|_. functionalities defined by the |DBAPI|_.
.. function:: parse_dsn(dsn)
Parse connection string into a dictionary of keywords and values.
Uses libpq's ``PQconninfoParse`` to parse the string according to
accepted format(s) and check for supported keywords.
Example::
>>> psycopg2.extensions.parse_dsn('dbname=test user=postgres password=secret')
{'password': 'secret', 'user': 'postgres', 'dbname': 'test'}
.. class:: connection(dsn, async=False) .. class:: connection(dsn, async=False)

View File

@ -78,7 +78,7 @@ The module interface respects the standard defined in the |DBAPI|_.
.. seealso:: .. seealso::
- `parse_dsn` - `~psycopg2.extensions.parse_dsn`
- libpq `connection string syntax`__ - libpq `connection string syntax`__
- libpq supported `connection parameters`__ - libpq supported `connection parameters`__
- libpq supported `environment variables`__ - libpq supported `environment variables`__
@ -92,18 +92,6 @@ The module interface respects the standard defined in the |DBAPI|_.
The parameters *connection_factory* and *async* are Psycopg extensions The parameters *connection_factory* and *async* are Psycopg extensions
to the |DBAPI|. to the |DBAPI|.
.. function:: parse_dsn(dsn)
Parse connection string into a dictionary of keywords and values.
Uses libpq's ``PQconninfoParse`` to parse the string according to
accepted format(s) and check for supported keywords.
Example::
>>> psycopg2.parse_dsn('dbname=test user=postgres password=secret')
{'password': 'secret', 'user': 'postgres', 'dbname': 'test'}
.. data:: apilevel .. data:: apilevel
String constant stating the supported DB API level. For `psycopg2` is String constant stating the supported DB API level. For `psycopg2` is

View File

@ -56,7 +56,7 @@ from psycopg2._psycopg import Error, Warning, DataError, DatabaseError, Programm
from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError
from psycopg2._psycopg import NotSupportedError, OperationalError from psycopg2._psycopg import NotSupportedError, OperationalError
from psycopg2._psycopg import _connect, parse_dsn, apilevel, threadsafety, paramstyle from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle
from psycopg2._psycopg import __version__ from psycopg2._psycopg import __version__
from psycopg2 import tz from psycopg2 import tz

View File

@ -56,7 +56,7 @@ try:
except ImportError: except ImportError:
pass pass
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid, parse_dsn
from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type
from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics, Column from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics, Column

View File

@ -112,13 +112,12 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
return conn; return conn;
} }
#define psyco_parse_dsn_doc \ #define psyco_parse_dsn_doc "parse_dsn(dsn) -> dict"
"parse_dsn(dsn) -- Parse database connection string.\n\n"
static PyObject * static PyObject *
psyco_parse_dsn(PyObject *self, PyObject *args) psyco_parse_dsn(PyObject *self, PyObject *args)
{ {
char *dsn, *err; char *dsn, *err = NULL;
PQconninfoOption *options = NULL, *o; PQconninfoOption *options = NULL, *o;
PyObject *res = NULL, *value; PyObject *res = NULL, *value;
@ -127,21 +126,34 @@ psyco_parse_dsn(PyObject *self, PyObject *args)
} }
options = PQconninfoParse(dsn, &err); options = PQconninfoParse(dsn, &err);
if (!options) { if (options == NULL) {
PyErr_Format(PyExc_RuntimeError, "PQconninfoParse: %s: %s", dsn, err); if (err != NULL) {
PyErr_Format(ProgrammingError, "error parsing the dsn: %s", err);
PQfreemem(err); PQfreemem(err);
} else {
PyErr_SetString(OperationalError, "PQconninfoParse() failed");
}
return NULL; return NULL;
} }
res = PyDict_New(); res = PyDict_New();
if (res != NULL) {
for (o = options; o->keyword != NULL; o++) { for (o = options; o->keyword != NULL; o++) {
if (o->val != NULL) { if (o->val != NULL) {
value = PyString_FromString(o->val); value = Text_FromUTF8(o->val);
if (value == NULL || PyDict_SetItemString(res, o->keyword, value) != 0) { if (value == NULL) {
Py_DECREF(res); Py_DECREF(res);
res = NULL; res = NULL;
break; break;
} }
if (PyDict_SetItemString(res, o->keyword, value) != 0) {
Py_DECREF(value);
Py_DECREF(res);
res = NULL;
break;
}
Py_DECREF(value);
}
} }
} }

View File

@ -270,6 +270,37 @@ class ConnectionTests(ConnectingTestCase):
self.assert_(c.closed, "connection failed so it must be closed") self.assert_(c.closed, "connection failed so it must be closed")
self.assert_('foobar' not in c.dsn, "password was not obscured") self.assert_('foobar' not in c.dsn, "password was not obscured")
def test_parse_dsn(self):
from psycopg2 import ProgrammingError
from psycopg2.extensions import parse_dsn
self.assertEqual(parse_dsn('dbname=test user=tester password=secret'),
dict(user='tester', password='secret', dbname='test'),
"simple DSN parsed")
self.assertEqual(parse_dsn("dbname='test 2' user=tester password=secret"),
dict(user='tester', password='secret', dbname='test 2'),
"DSN with quoting parsed")
self.assertEqual(parse_dsn('postgresql://tester:secret@/test'),
dict(user='tester', password='secret', dbname='test'),
"simple URI dsn parsed")
# Can't really use assertRaisesRegexp() here since we need to
# make sure that secret is *not* exposed in the error messgage
# (and it also requires python >= 2.7).
raised = False
try:
# unterminated quote after dbname:
parse_dsn("dbname='test 2 user=tester password=secret")
except ProgrammingError, e:
raised = True
self.assertTrue(e.message.find('secret') < 0,
"DSN was not exposed in error message")
except e:
self.fail("unexpected error condition: " + repr(e))
self.assertTrue(raised, "ProgrammingError raised due to invalid DSN")
class IsolationLevelsTestCase(ConnectingTestCase): class IsolationLevelsTestCase(ConnectingTestCase):