From d2b67364fd2b0b192342281d24a7e3d0a4909980 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Thu, 17 Nov 2011 01:51:25 +0000 Subject: [PATCH] connect() supports generic keyword arguments passed to the dsn --- doc/src/module.rst | 20 +++-- lib/__init__.py | 61 +++++++++++++- psycopg/psycopgmodule.c | 178 +++++----------------------------------- 3 files changed, 97 insertions(+), 162 deletions(-) diff --git a/doc/src/module.rst b/doc/src/module.rst index fb709624..29f4b636 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -20,7 +20,7 @@ The module interface respects the standard defined in the |DBAPI|_. Create a new database session and return a new `connection` object. - You can specify the connection parameters either as a string:: + The connection parameters can be specified either as a string:: conn = psycopg2.connect("dbname=test user=postgres password=secret") @@ -28,17 +28,23 @@ The module interface respects the standard defined in the |DBAPI|_. conn = psycopg2.connect(database="test", user="postgres", password="secret") - The full list of available parameters is: - + The basic connection parameters are: + - `!dbname` -- the database name (only in dsn string) - `!database` -- the database name (only as keyword argument) - `!user` -- user name used to authenticate - `!password` -- password used to authenticate - `!host` -- database host address (defaults to UNIX socket if not provided) - `!port` -- connection port number (defaults to 5432 if not provided) - - `!sslmode` -- `SSL TCP/IP negotiation`__ mode - .. __: http://www.postgresql.org/docs/9.0/static/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS + Any other connection parameter supported by the client library/server can + be passed either in the connection string or as keyword. See the + PostgreSQL documentation for a complete `list of supported parameters`__. + Also note that the same parameters can be passed to the client library + using `environment variables`__. + + .. __: http://www.postgresql.org/docs/9.1/static/libpq-connect.html#LIBPQ-PQCONNECTDBPARAMS + .. __: http://www.postgresql.org/docs/9.1/static/libpq-envars.html Using the *connection_factory* parameter a different class or connections factory can be specified. It should be a callable object @@ -48,6 +54,10 @@ The module interface respects the standard defined in the |DBAPI|_. Using *async*\=1 an asynchronous connection will be created: see :ref:`async-support` to know about advantages and limitations. + .. versionchanged:: 2.4.3 + any keyword argument is passed to the connection. Previously only the + basic parameters (plus `!sslmode`) were supported as keywords. + .. extension:: The parameters *connection_factory* and *async* are Psycopg extensions diff --git a/lib/__init__.py b/lib/__init__.py index e04d35b4..7676f3c3 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -73,7 +73,7 @@ from psycopg2._psycopg import Error, Warning, DataError, DatabaseError, Programm from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError from psycopg2._psycopg import NotSupportedError, OperationalError -from psycopg2._psycopg import connect, apilevel, threadsafety, paramstyle +from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle from psycopg2._psycopg import __version__ from psycopg2 import tz @@ -97,5 +97,64 @@ else: _ext.register_adapter(Decimal, Adapter) del Decimal, Adapter + +def connect(dsn=None, + database=None, user=None, password=None, host=None, port=None, + connection_factory=None, async=False, **kwargs): + """ + Create a new database connection. + + The connection parameters can be specified either as a string: + + conn = psycopg2.connect("dbname=test user=postgres password=secret") + + or using a set of keyword arguments: + + conn = psycopg2.connect(database="test", user="postgres", password="secret") + + The basic connection parameters are: + + - *dbname*: the database name (only in dsn string) + - *database*: the database name (only as keyword argument) + - *user*: user name used to authenticate + - *password*: password used to authenticate + - *host*: database host address (defaults to UNIX socket if not provided) + - *port*: connection port number (defaults to 5432 if not provided) + + Using the *connection_factory* parameter a different class or connections + factory can be specified. It should be a callable object taking a dsn + argument. + + Using *async*=True an asynchronous connection will be created. + + Any other keyword parameter will be passed to the underlying client + library: the list of supported parameter depends on the library version. + + """ + + if dsn is None: + items = [] + if database is not None: + items.append(('dbname', database)) + if user is not None: + items.append(('user', user)) + if password is not None: + items.append(('password', password)) + if host is not None: + items.append(('host', host)) + if port is not None: + items.append(('port', port)) + + items.extend( + [(k, v) for (k, v) in kwargs.iteritems() if v is not None]) + dsn = " ".join(["%s=%s" % item for item in items]) + + if not dsn: + raise InterfaceError('missing dsn and no parameters') + + return _connect(dsn, + connection_factory=connection_factory, async=async) + + __all__ = filter(lambda k: not k.startswith('_'), locals().keys()) diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 2c7e3fbf..3b2b0609 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -75,177 +75,43 @@ HIDDEN PyObject *psyco_DescriptionType = NULL; /** connect module-level function **/ #define psyco_connect_doc \ -"connect(dsn, ...) -- Create a new database connection.\n\n" \ -"This function supports two different but equivalent sets of arguments.\n" \ -"A single data source name or ``dsn`` string can be used to specify the\n" \ -"connection parameters, as follows::\n\n" \ -" psycopg2.connect(\"dbname=xxx user=xxx ...\")\n\n" \ -"If ``dsn`` is not provided it is possible to pass the parameters as\n" \ -"keyword arguments; e.g.::\n\n" \ -" psycopg2.connect(database='xxx', user='xxx', ...)\n\n" \ -"The full list of available parameters is:\n\n" \ -"- ``dbname`` -- database name (only in 'dsn')\n" \ -"- ``database`` -- database name (only as keyword argument)\n" \ -"- ``host`` -- host address (defaults to UNIX socket if not provided)\n" \ -"- ``port`` -- port number (defaults to 5432 if not provided)\n" \ -"- ``user`` -- user name used to authenticate\n" \ -"- ``password`` -- password used to authenticate\n" \ -"- ``sslmode`` -- SSL mode (see PostgreSQL documentation)\n\n" \ -"- ``async`` -- if the connection should provide asynchronous API\n\n" \ -"If the ``connection_factory`` keyword argument is not provided this\n" \ -"function always return an instance of the `connection` class.\n" \ -"Else the given sub-class of `extensions.connection` will be used to\n" \ -"instantiate the connection object.\n\n" \ -":return: New database connection\n" \ -":rtype: `extensions.connection`" - -static size_t -_psyco_connect_fill_dsn(char *dsn, const char *kw, const char *v, size_t i) -{ - strcpy(&dsn[i], kw); i += strlen(kw); - strcpy(&dsn[i], v); i += strlen(v); - return i; -} +"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n" static PyObject * psyco_connect(PyObject *self, PyObject *args, PyObject *keywds) { - PyObject *conn = NULL, *factory = NULL; - PyObject *pyport = NULL; - - size_t idsn=-1; - int iport=-1; - const char *dsn_static = NULL; - char *dsn_dynamic=NULL; - const char *database=NULL, *user=NULL, *password=NULL; - const char *host=NULL, *sslmode=NULL; - char port[16]; + PyObject *conn = NULL; + PyObject *factory = NULL; + const char *dsn = NULL; int async = 0; - static char *kwlist[] = {"dsn", "database", "host", "port", - "user", "password", "sslmode", - "connection_factory", "async", NULL}; + static char *kwlist[] = {"dsn", "connection_factory", "async", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sssOsssOi", kwlist, - &dsn_static, &database, &host, &pyport, - &user, &password, &sslmode, - &factory, &async)) { + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|Oi", kwlist, + &dsn, &factory, &async)) { return NULL; } -#if PY_MAJOR_VERSION < 3 - if (pyport && PyString_Check(pyport)) { - PyObject *pyint = PyInt_FromString(PyString_AsString(pyport), NULL, 10); - if (!pyint) goto fail; - /* Must use PyInt_AsLong rather than PyInt_AS_LONG, because - * PyInt_FromString can return a PyLongObject: */ - iport = PyInt_AsLong(pyint); - Py_DECREF(pyint); - if (iport == -1 && PyErr_Occurred()) - goto fail; - } - else if (pyport && PyInt_Check(pyport)) { - iport = PyInt_AsLong(pyport); - if (iport == -1 && PyErr_Occurred()) - goto fail; - } -#else - if (pyport && PyUnicode_Check(pyport)) { - PyObject *pyint = PyObject_CallFunction((PyObject*)&PyLong_Type, - "Oi", pyport, 10); - if (!pyint) goto fail; - iport = PyLong_AsLong(pyint); - Py_DECREF(pyint); - if (iport == -1 && PyErr_Occurred()) - goto fail; - } - else if (pyport && PyLong_Check(pyport)) { - iport = PyLong_AsLong(pyport); - if (iport == -1 && PyErr_Occurred()) - goto fail; - } -#endif - else if (pyport != NULL) { - PyErr_SetString(PyExc_TypeError, "port must be a string or int"); - goto fail; + Dprintf("psyco_connect: dsn = '%s', async = %d", dsn, async); + + /* allocate connection, fill with errors and return it */ + if (factory == NULL || factory == Py_None) { + factory = (PyObject *)&connectionType; } - if (iport > 0) - PyOS_snprintf(port, 16, "%d", iport); - - if (dsn_static == NULL) { - size_t l = 46; /* len(" dbname= user= password= host= port= sslmode=\0") */ - - if (database) l += strlen(database); - if (host) l += strlen(host); - if (iport > 0) l += strlen(port); - if (user) l += strlen(user); - if (password) l += strlen(password); - if (sslmode) l += strlen(sslmode); - - dsn_dynamic = malloc(l*sizeof(char)); - if (dsn_dynamic == NULL) { - PyErr_SetString(InterfaceError, "dynamic dsn allocation failed"); - goto fail; - } - - idsn = 0; - if (database) - idsn = _psyco_connect_fill_dsn(dsn_dynamic, " dbname=", database, idsn); - if (host) - idsn = _psyco_connect_fill_dsn(dsn_dynamic, " host=", host, idsn); - if (iport > 0) - idsn = _psyco_connect_fill_dsn(dsn_dynamic, " port=", port, idsn); - if (user) - idsn = _psyco_connect_fill_dsn(dsn_dynamic, " user=", user, idsn); - if (password) - idsn = _psyco_connect_fill_dsn(dsn_dynamic, " password=", password, idsn); - if (sslmode) - idsn = _psyco_connect_fill_dsn(dsn_dynamic, " sslmode=", sslmode, idsn); - - if (idsn > 0) { - dsn_dynamic[idsn] = '\0'; - memmove(dsn_dynamic, &dsn_dynamic[1], idsn); - } - else { - PyErr_SetString(InterfaceError, "missing dsn and no parameters"); - goto fail; - } - } - - { - const char *dsn = (dsn_static != NULL ? dsn_static : dsn_dynamic); - Dprintf("psyco_connect: dsn = '%s', async = %d", dsn, async); - - /* allocate connection, fill with errors and return it */ - if (factory == NULL) factory = (PyObject *)&connectionType; - /* Here we are breaking the connection.__init__ interface defined - * by psycopg2. So, if not requiring an async conn, avoid passing - * the async parameter. */ - /* TODO: would it be possible to avoid an additional parameter - * to the conn constructor? A subclass? (but it would require mixins - * to further subclass) Another dsn parameter (but is not really - * a connection parameter that can be configured) */ - if (!async) { + /* Here we are breaking the connection.__init__ interface defined + * by psycopg2. So, if not requiring an async conn, avoid passing + * the async parameter. */ + /* TODO: would it be possible to avoid an additional parameter + * to the conn constructor? A subclass? (but it would require mixins + * to further subclass) Another dsn parameter (but is not really + * a connection parameter that can be configured) */ + if (!async) { conn = PyObject_CallFunction(factory, "s", dsn); - } else { + } else { conn = PyObject_CallFunction(factory, "si", dsn, async); - } } - goto cleanup; - fail: - assert (PyErr_Occurred()); - if (conn != NULL) { - Py_DECREF(conn); - conn = NULL; - } - /* Fall through to cleanup: */ - cleanup: - if (dsn_dynamic != NULL) { - free(dsn_dynamic); - } - return conn; } @@ -754,7 +620,7 @@ exit: /** method table and module initialization **/ static PyMethodDef psycopgMethods[] = { - {"connect", (PyCFunction)psyco_connect, + {"_connect", (PyCFunction)psyco_connect, METH_VARARGS|METH_KEYWORDS, psyco_connect_doc}, {"adapt", (PyCFunction)psyco_microprotocols_adapt, METH_VARARGS, psyco_microprotocols_adapt_doc},